diff --git a/.ci/end2end.groovy b/.ci/end2end.groovy index 2cdc6d1c297cd..025836a90204c 100644 --- a/.ci/end2end.groovy +++ b/.ci/end2end.groovy @@ -37,22 +37,31 @@ pipeline { deleteDir() gitCheckout(basedir: "${BASE_DIR}", githubNotifyFirstTimeContributor: false, shallow: false, reference: "/var/lib/jenkins/.git-references/kibana.git") + + // Filter when to run based on the below reasons: + // - On a PRs when: + // - There are changes related to the APM UI project + // - only when the owners of those changes are members of the given GitHub teams + // - On merges to branches when: + // - There are changes related to the APM UI project + // - FORCE parameter is set to true. script { + def apm_updated = false dir("${BASE_DIR}"){ - def regexps =[ "^x-pack/plugins/apm/.*" ] - env.APM_UPDATED = isGitRegionMatch(patterns: regexps) + apm_updated = isGitRegionMatch(patterns: [ "^x-pack/plugins/apm/.*" ]) + } + if (isPR()) { + def isMember = isMemberOf(user: env.CHANGE_AUTHOR, team: ['apm-ui', 'uptime']) + setEnvVar('RUN_APM_E2E', params.FORCE || (apm_updated && isMember)) + } else { + setEnvVar('RUN_APM_E2E', params.FORCE || apm_updated) } } } } stage('Prepare Kibana') { options { skipDefaultCheckout() } - when { - anyOf { - expression { return params.FORCE } - expression { return env.APM_UPDATED != "false" } - } - } + when { expression { return env.RUN_APM_E2E != "false" } } environment { JENKINS_NODE_COOKIE = 'dontKillMe' } @@ -70,12 +79,7 @@ pipeline { } stage('Smoke Tests'){ options { skipDefaultCheckout() } - when { - anyOf { - expression { return params.FORCE } - expression { return env.APM_UPDATED != "false" } - } - } + when { expression { return env.RUN_APM_E2E != "false" } } steps{ notifyTestStatus('Running smoke tests', 'PENDING') dir("${BASE_DIR}"){ diff --git a/.eslintrc.js b/.eslintrc.js index 9b75c36c95abd..3161a25b70870 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -305,6 +305,8 @@ module.exports = { '!src/core/server/mocks{,.ts}', '!src/core/server/types{,.ts}', '!src/core/server/test_utils{,.ts}', + '!src/core/server/utils', // ts alias + '!src/core/server/utils/**/*', // for absolute imports until fixed in // https://github.com/elastic/kibana/issues/36096 '!src/core/server/*.test.mocks{,.ts}', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1d0f6fc50ee9b..03a4f9520c2ba 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -63,6 +63,13 @@ /src/plugins/apm_oss/ @elastic/apm-ui /src/apm.js @watson @vigneshshanmugam +# Client Side Monitoring (lives in APM directories but owned by Uptime) +/x-pack/plugins/apm/e2e/cypress/support/step_definitions/rum @elastic/uptime +/x-pack/plugins/apm/public/application/csmApp.tsx @elastic/uptime +/x-pack/plugins/apm/public/components/app/RumDashboard @elastic/uptime +/x-pack/plugins/apm/server/lib/rum_client @elastic/uptime +/x-pack/plugins/apm/server/routes/rum_client.ts @elastic/uptime + # Beats /x-pack/legacy/plugins/beats_management/ @elastic/beats @@ -78,6 +85,7 @@ # Exclude tutorial resources folder for now because they are not owned by Kibana app and most will move out soon /src/legacy/core_plugins/kibana/public/home/*.ts @elastic/kibana-core-ui /src/legacy/core_plugins/kibana/public/home/np_ready/ @elastic/kibana-core-ui +/x-pack/plugins/global_search_bar/ @elastic/kibana-core-ui # Observability UIs /x-pack/legacy/plugins/infra/ @elastic/logs-metrics-ui @@ -162,6 +170,7 @@ /x-pack/plugins/encrypted_saved_objects/ @elastic/kibana-security /x-pack/plugins/security/ @elastic/kibana-security /x-pack/test/api_integration/apis/security/ @elastic/kibana-security +/x-pack/test/ui_capabilities/ @elastic/kibana-security /x-pack/test/encrypted_saved_objects_api_integration/ @elastic/kibana-security /x-pack/test/functional/apps/security/ @elastic/kibana-security /x-pack/test/kerberos_api_integration/ @elastic/kibana-security @@ -209,8 +218,19 @@ x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @elastic/kib /x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/ @elastic/kibana-alerting-services # Enterprise Search -/x-pack/plugins/enterprise_search/ @elastic/app-search-frontend @elastic/workplace-search-frontend -/x-pack/test/functional_enterprise_search/ @elastic/app-search-frontend @elastic/workplace-search-frontend +# Shared +/x-pack/plugins/enterprise_search/ @elastic/enterprise-search-frontend +/x-pack/test/functional_enterprise_search/ @elastic/enterprise-search-frontend +# App Search +/x-pack/plugins/enterprise_search/public/applications/app_search @elastic/app-search-frontend +/x-pack/plugins/enterprise_search/server/routes/app_search @elastic/app-search-frontend +/x-pack/plugins/enterprise_search/server/collectors/app_search @elastic/app-search-frontend +/x-pack/plugins/enterprise_search/server/saved_objects/app_search @elastic/app-search-frontend +# Workplace Search +/x-pack/plugins/enterprise_search/public/applications/workplace_search @elastic/workplace-search-frontend +/x-pack/plugins/enterprise_search/server/routes/workplace_search @elastic/workplace-search-frontend +/x-pack/plugins/enterprise_search/server/collectors/workplace_search @elastic/workplace-search-frontend +/x-pack/plugins/enterprise_search/server/saved_objects/workplace_search @elastic/workplace-search-frontend # Elasticsearch UI /src/plugins/dev_tools/ @elastic/es-ui diff --git a/.github/ISSUE_TEMPLATE/APM.md b/.github/ISSUE_TEMPLATE/APM.md index 983806f70bc3f..c3abbdd67269d 100644 --- a/.github/ISSUE_TEMPLATE/APM.md +++ b/.github/ISSUE_TEMPLATE/APM.md @@ -2,7 +2,7 @@ name: APM Issue about: Issues related to the APM solution in Kibana labels: Team:apm -title: [APM] +title: "[APM]" --- **Versions** diff --git a/.gitignore b/.gitignore index 1d12ef2a9cff3..1bbd38debbf0f 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,6 @@ apm.tsconfig.json # release notes script output report.csv report.asciidoc + +# TS incremental build cache +*.tsbuildinfo diff --git a/NOTICE.txt b/NOTICE.txt index e1552852d0349..d689abf4c4e05 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -281,6 +281,13 @@ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +--- +This product includes code in the function applyCubicBezierStyles that was +inspired by a public Codepen, which was available under a "MIT" license. + +Copyright (c) 2020 by Guillaume (https://codepen.io/guillaumethomas/pen/xxbbBKO) +MIT License http://www.opensource.org/licenses/mit-license + --- This product includes code that is adapted from mapbox-gl-js, which is available under a "BSD-3-Clause" license. diff --git a/docs/apm/apm-app-users.asciidoc b/docs/apm/apm-app-users.asciidoc index d766c866f87e4..3f0a42251304c 100644 --- a/docs/apm/apm-app-users.asciidoc +++ b/docs/apm/apm-app-users.asciidoc @@ -84,7 +84,7 @@ Here are two examples: | Allow the use of the the {beat_kib_app} | Spaces -| `Read` or `All` on Dashboards, Visualize, and Discover +| `Read` or `All` on Dashboards and Discover | Allow the user to view, edit, and create dashboards, as well as browse data. |==== diff --git a/docs/apm/troubleshooting.asciidoc b/docs/apm/troubleshooting.asciidoc index 65f7a378ec244..e00a67f6c78a4 100644 --- a/docs/apm/troubleshooting.asciidoc +++ b/docs/apm/troubleshooting.asciidoc @@ -49,7 +49,7 @@ GET /_template/apm-{version} *Using Logstash, Kafka, etc.* If you're not outputting data directly from APM Server to Elasticsearch (perhaps you're using Logstash or Kafka), then the index template will not be set up automatically. Instead, you'll need to -{apm-server-ref}/_manually_loading_template_configuration.html[load the template manually]. +{apm-server-ref}/configuration-template.html[load the template manually]. *Using a custom index names* This problem can also occur if you've customized the index name that you write APM data to. diff --git a/docs/canvas/canvas-elements.asciidoc b/docs/canvas/canvas-elements.asciidoc new file mode 100644 index 0000000000000..782bae061b8c1 --- /dev/null +++ b/docs/canvas/canvas-elements.asciidoc @@ -0,0 +1,167 @@ +[role="xpack"] +[[add-canvas-elements]] +=== Add elements + +Create a story about your data by adding elements to your workpad that include images, text, charts, and more. You can create your own elements and connect them to your data sources, add saved objects, and add your own images. + +[float] +[[create-canvas-element]] +==== Create an element + +Choose the type of element you want to use, then connect it to your own data. + +. Click *Add element*, then select the element you want to use. ++ +[role="screenshot"] +image::images/canvas-element-select.gif[Canvas elements] + +. To familiarize yourself with the element, use the preconfigured data demo data. ++ +By default, most of the elements you create use demo data until you change the data source. The demo data includes a small data set that you can use to experiment with your element. + +. To connect the element to your data, select *Data*, then select one of the following data sources: + +* *{es} SQL* — Access your data in {es} using SQL syntax. For information about SQL syntax, refer to {ref}/sql-spec.html[SQL language]. + +* *{es} documents* — Access your data in {es} without using aggregations. To use, select an index and fields, and optionally enter a query using the <>. Use the *{es} documents* data source when you have low volume datasets, to view raw documents, or to plot exact, non-aggregated values on a chart. + +* *Timelion* — Access your time series data using <> queries. To use Timelion queries, you can enter a query using the <>. + +Each element can display a different data source. Pages and workpads often contain multiple data sources. + +[float] +[[canvas-add-object]] +==== Add a saved object + +Add <> to your workpad, such as maps and visualizations. + +. Click *Add element > Add from Visualize Library*. + +. Select the saved object you want to add. ++ +[role="screenshot"] +image::images/canvas-map-embed.gif[] + +. To use the customization options, click the panel menu, then select one of the following options: + +* *Edit map* — Opens <> or <> so that you can edit the original saved object. + +* *Edit panel title* — Adds a title to the saved object. + +* *Customize time range* — Exposes a time filter dedicated to the saved object. + +* *Inspect* — Allows you to drill down into the element data. + +[float] +[[canvas-add-image]] +==== Add your own image + +To personalize your workpad, add your own logos and graphics. + +. Click *Add element > Manage assets*. + +. On the *Manage workpad assets* window, drag and drop your images. + +. To add the image to the workpad, click the *Create image element* icon. ++ +[role="screenshot"] +image::images/canvas-add-image.gif[] + +[float] +[[move-canvas-elements]] +==== Organize elements + +Move and resize your elements to meet your design needs. + +* To move, click and hold the element, then drag to the new location. + +* To move by 1 pixel, select the element, press and hold Shift, then use your arrow keys. + +* To move by 10 pixels, select the element, then use your arrow keys. + +* To resize, click and drag the resize handles to the new dimensions. + +[float] +[[format-canvas-elements]] +==== Format elements + +For consistency and readability across your workpad pages, align, distribute, and reorder elements. + +To align two or more elements: + +. Press and hold Shift, then select the elements you want to align. + +. Click *Edit > Alignment*, then select the alignment option. + +To distribute three or more elements: + +. Press and hold Shift, then select the elements you want to distribute. + +. Click *Edit > Distribution*, then select the distribution option. + +To reorder elements: + +. Select the element you want to reorder. + +. Click *Edit > Order*, then select the order option. + +[float] +[[data-display]] +==== Change the element display options + +Each element has its own display options to fit your design needs. + +To choose the display options, click *Display*, then make your changes. + +To define the appearance of the container and border: + +. Next to *Element style*, click *+*, then select *Container style*. + +. Expand *Container style*. + +. Change the *Appearance* and *Border* options. + +To apply CSS overrides: + +. Next to *Element style*, click *+*, then select *CSS*. + +. Enter the *CSS*. ++ +For example, to center the Markdown element, enter: ++ +[source,text] +-------------------------------------------------- +.canvasRenderEl h1 { +text.align: center; +} +-------------------------------------------------- + +. Click *Apply stylesheet*. + +[float] +[[save-elements]] +==== Save elements + +To use the elements across all workpads, save the elements. + +When you're ready to save your element, select the element, then click *Edit > Save as new element*. + +[role="screenshot"] +image::images/canvas_save_element.png[] + +To save a group of elements, press and hold Shift, select the elements you want to save, then click *Edit > Save as new element*. + +To access your saved elements, click *Add element > My elements*. + +[float] +[[delete-elements]] +==== Delete elements + +When you no longer need an element, delete it from your workpad. + +. Select the element you want to delete. + +. Click *Edit > Delete*. ++ +[role="screenshot"] +image::images/canvas_element_options.png[] diff --git a/docs/developer/architecture/code-exploration.asciidoc b/docs/developer/architecture/code-exploration.asciidoc new file mode 100644 index 0000000000000..4a390336da34f --- /dev/null +++ b/docs/developer/architecture/code-exploration.asciidoc @@ -0,0 +1,598 @@ +//// + +NOTE: + This is an automatically generated file. Please do not edit directly. Instead, run the + following from within the kibana repository: + + node scripts/build_plugin_list_docs + + You can update the template within packages/kbn-dev-utils/target/plugin_list/generate_plugin_list.js + +//// + +[[code-exploration]] +== Exploring Kibana code + +The goals of our folder heirarchy are: + +- Easy for developers to know where to add new services, plugins and applications. +- Easy for developers to know where to find the code from services, plugins and applications. +- Easy to browse and understand our folder structure. + +To that aim, we strive to: + +- Avoid too many files in any given folder. +- Choose clear, unambigious folder names. +- Organize by domain. +- Every folder should contain a README that describes the contents of that folder. + +[discrete] +[[kibana-services-applications]] +=== Services and Applications + +[discrete] +==== src/plugins + +- {kib-repo}blob/{branch}/src/plugins/advanced_settings[advancedSettings] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/apm_oss[apmOss] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/bfetch/README.md[bfetch] + +bfetch allows to batch HTTP requests and streams responses back. + + +- {kib-repo}blob/{branch}/src/plugins/charts/README.md[charts] + +The Charts plugin is a way to create easier integration of shared colors, themes, types and other utilities across all Kibana charts and visualizations. + + +- {kib-repo}blob/{branch}/src/plugins/console[console] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/dashboard/README.md[dashboard] + +Contains the dashboard application. + + +- {kib-repo}blob/{branch}/src/plugins/data/README.md[data] + +data plugin provides common data access services. + + +- {kib-repo}blob/{branch}/src/plugins/dev_tools/README.md[devTools] + +The ui/registry/dev_tools is removed in favor of the devTools plugin which exposes a register method in the setup contract. +Registering app works mostly the same as registering apps in core.application.register. +Routing will be handled by the id of the dev tool - your dev tool will be mounted when the URL matches /app/dev_tools#/. +This API doesn't support angular, for registering angular dev tools, bootstrap a local module on mount into the given HTML element. + + +- {kib-repo}blob/{branch}/src/plugins/discover/README.md[discover] + +Contains the Discover application and the saved search embeddable. + + +- {kib-repo}blob/{branch}/src/plugins/embeddable/README.md[embeddable] + +Embeddables are re-usable widgets that can be rendered in any environment or plugin. Developers can embed them directly in their plugin. End users can dynamically add them to any embeddable containers. + + +- {kib-repo}blob/{branch}/src/plugins/es_ui_shared/README.md[esUiShared] + +This plugin contains reusable code in the form of self-contained modules (or libraries). Each of these modules exports a set of functionality relevant to the domain of the module. + + +- {kib-repo}blob/{branch}/src/plugins/expressions/README.md[expressions] + +This plugin provides methods which will parse & execute an expression pipeline +string for you, as well as a series of registries for advanced users who might +want to incorporate their own functions, types, and renderers into the service +for use in their own application. + + +- {kib-repo}blob/{branch}/src/plugins/home/README.md[home] + +Moves the legacy ui/registry/feature_catalogue module for registering "features" that should be shown in the home page's feature catalogue to a service within a "home" plugin. The feature catalogue refered to here should not be confused with the "feature" plugin for registering features used to derive UI capabilities for feature controls. + + +- {kib-repo}blob/{branch}/src/plugins/index_pattern_management[indexPatternManagement] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/input_control_vis/README.md[inputControlVis] + +Contains the input control visualization allowing to place custom filter controls on a dashboard. + + +- {kib-repo}blob/{branch}/src/plugins/inspector/README.md[inspector] + +The inspector is a contextual tool to gain insights into different elements +in Kibana, e.g. visualizations. It has the form of a flyout panel. + + +- {kib-repo}blob/{branch}/src/plugins/kibana_legacy/README.md[kibanaLegacy] + +This plugin will contain several helpers and services to integrate pieces of the legacy Kibana app with the new Kibana platform. + + +- {kib-repo}blob/{branch}/src/plugins/kibana_react/README.md[kibanaReact] + +Tools for building React applications in Kibana. + + +- {kib-repo}blob/{branch}/src/plugins/kibana_usage_collection/README.md[kibanaUsageCollection] + +This plugin registers the basic usage collectors from Kibana: + + +- {kib-repo}blob/{branch}/src/plugins/kibana_utils/README.md[kibanaUtils] + +Utilities for building Kibana plugins. + + +- {kib-repo}blob/{branch}/src/plugins/legacy_export[legacyExport] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/management[management] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/maps_legacy[mapsLegacy] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/navigation/README.md[navigation] + +The navigation plugins exports the TopNavMenu component. +It also provides a stateful version of it on the start contract. + + +- {kib-repo}blob/{branch}/src/plugins/newsfeed[newsfeed] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/region_map[regionMap] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/saved_objects[savedObjects] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/saved_objects_management[savedObjectsManagement] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/share/README.md[share] + +Replaces the legacy ui/share module for registering share context menus. + + +- {kib-repo}blob/{branch}/src/plugins/telemetry/README.md[telemetry] + +Telemetry allows Kibana features to have usage tracked in the wild. The general term "telemetry" refers to multiple things: + + +- {kib-repo}blob/{branch}/src/plugins/telemetry_collection_manager/README.md[telemetryCollectionManager] + +Telemetry's collection manager to go through all the telemetry sources when fetching it before reporting. + + +- {kib-repo}blob/{branch}/src/plugins/telemetry_management_section/README.md[telemetryManagementSection] + +This plugin adds the Advanced Settings section for the Usage Data collection (aka Telemetry). + + +- {kib-repo}blob/{branch}/src/plugins/tile_map[tileMap] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/src/plugins/timelion/README.md[timelion] + +Contains the deprecated timelion application. For the timelion visualization, +which also contains the timelion APIs and backend, look at the vis_type_timelion plugin. + + +- {kib-repo}blob/{branch}/src/plugins/ui_actions/README.md[uiActions] + +An API for: + + +- {kib-repo}blob/{branch}/src/plugins/usage_collection/README.md[usageCollection] + +Usage Collection allows collecting usage data for other services to consume (telemetry and monitoring). +To integrate with the telemetry services for usage collection of your feature, there are 2 steps: + + +- {kib-repo}blob/{branch}/src/plugins/vis_type_markdown/README.md[visTypeMarkdown] + +The markdown visualization that can be used to place text panels on dashboards. + + +- {kib-repo}blob/{branch}/src/plugins/vis_type_metric/README.md[visTypeMetric] + +Contains the metric visualization. + + +- {kib-repo}blob/{branch}/src/plugins/vis_type_table/README.md[visTypeTable] + +Contains the data table visualization, that allows presenting data in a simple table format. + + +- {kib-repo}blob/{branch}/src/plugins/vis_type_tagcloud/README.md[visTypeTagcloud] + +Contains the tagcloud visualization. + + +- {kib-repo}blob/{branch}/src/plugins/vis_type_timelion/README.md[visTypeTimelion] + +Contains the timelion visualization and the timelion backend. + + +- {kib-repo}blob/{branch}/src/plugins/vis_type_timeseries/README.md[visTypeTimeseries] + +Contains everything around TSVB (the editor, visualizatin implementations and backends). + + +- {kib-repo}blob/{branch}/src/plugins/vis_type_vega/README.md[visTypeVega] + +Contains the Vega visualization. + + +- {kib-repo}blob/{branch}/src/plugins/vis_type_vislib/README.md[visTypeVislib] + +Contains the vislib visualizations. These are the classical area/line/bar, pie, gauge/goal and +heatmap charts. + + +- {kib-repo}blob/{branch}/src/plugins/vis_type_xy/README.md[visTypeXy] + +Contains the new xy-axis chart using the elastic-charts library, which will eventually +replace the vislib xy-axis (bar, area, line) charts. + + +- {kib-repo}blob/{branch}/src/plugins/visualizations/README.md[visualizations] + +Contains most of the visualization infrastructure, e.g. the visualization type registry or the +visualization embeddable. + + +- {kib-repo}blob/{branch}/src/plugins/visualize/README.md[visualize] + +Contains the visualize application which includes the listing page and the app frame, +which will load the visualization's editor. + + +[discrete] +==== x-pack/plugins + +- {kib-repo}blob/{branch}/x-pack/plugins/actions/README.md[actions] + +The Kibana actions plugin provides a framework to create executable actions. You can: + + +- {kib-repo}blob/{branch}/x-pack/plugins/alerting_builtins/README.md[alertingBuiltins] + +This plugin provides alertTypes shipped with Kibana for use with the +the alerts plugin. When enabled, it will register +the built-in alertTypes with the alerting plugin, register associated HTTP +routes, etc. + + +- {kib-repo}blob/{branch}/x-pack/plugins/alerts/README.md[alerts] + +The Kibana alerting plugin provides a common place to set up alerts. You can: + + +- {kib-repo}blob/{branch}/x-pack/plugins/apm/readme.md[apm] + +To access an elasticsearch instance that has live data you have two options: + + +- {kib-repo}blob/{branch}/x-pack/plugins/audit_trail[auditTrail] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/beats_management/readme.md[beatsManagement] + +Notes: +Failure to have auth enabled in Kibana will make for a broken UI. UI-based errors not yet in place + + +- {kib-repo}blob/{branch}/x-pack/plugins/canvas/README.md[canvas] + +"Never look back. The past is done. The future is a blank canvas." ― Suzy Kassem, Rise Up and Salute the Sun + + +- {kib-repo}blob/{branch}/x-pack/plugins/case/README.md[case] + +Experimental Feature + + +- {kib-repo}blob/{branch}/x-pack/plugins/cloud[cloud] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/code[code] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/console_extensions[consoleExtensions] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/cross_cluster_replication/README.md[crossClusterReplication] + +You can run a local cluster and simulate a remote cluster within a single Kibana directory. + + +- {kib-repo}blob/{branch}/x-pack/plugins/dashboard_enhanced/README.md[dashboardEnhanced] + +Contains the enhancements to the OSS dashboard app. + + +- {kib-repo}blob/{branch}/x-pack/plugins/dashboard_mode/README.md[dashboardMode] + +The deprecated dashboard only mode. + + +- {kib-repo}blob/{branch}/x-pack/plugins/data_enhanced[dataEnhanced] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/discover_enhanced/README.md[discoverEnhanced] + +Contains the enhancements to the OSS discover app. + + +- {kib-repo}blob/{branch}/x-pack/plugins/embeddable_enhanced[embeddableEnhanced] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/encrypted_saved_objects/README.md[encryptedSavedObjects] + +The purpose of this plugin is to provide a way to encrypt/decrypt attributes on the custom Saved Objects that works with +security and spaces filtering as well as performing audit logging. + + +- {kib-repo}blob/{branch}/x-pack/plugins/enterprise_search/README.md[enterpriseSearch] + +This plugin's goal is to provide a Kibana user interface to the Enterprise Search solution's products (App Search and Workplace Search). In it's current MVP state, the plugin provides the following with the goal of gathering user feedback and raising product awareness: + + +- {kib-repo}blob/{branch}/x-pack/plugins/event_log/README.md[eventLog] + +The purpose of this plugin is to provide a way to persist a history of events +occuring in Kibana, initially just for the Make It Action project - alerts +and actions. + + +- {kib-repo}blob/{branch}/x-pack/plugins/features[features] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/file_upload[fileUpload] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/global_search/README.md[globalSearch] + +The GlobalSearch plugin provides an easy way to search for various objects, such as applications +or dashboards from the Kibana instance, from both server and client-side plugins + + +- {kib-repo}blob/{branch}/x-pack/plugins/global_search_bar/README.md[globalSearchBar] + +The GlobalSearchBar plugin provides a search interface for navigating Kibana. (It is the UI to the GlobalSearch plugin.) + + +- {kib-repo}blob/{branch}/x-pack/plugins/global_search_providers[globalSearchProviders] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/graph/README.md[graph] + +This is the main source folder of the Graph plugin. It contains all of the Kibana server and client source code. x-pack/test/functional/apps/graph contains additional functional tests. + + +- {kib-repo}blob/{branch}/x-pack/plugins/grokdebugger/README.md[grokdebugger] + +- {kib-repo}blob/{branch}/x-pack/plugins/index_lifecycle_management/README.md[indexLifecycleManagement] + +You can test that the Frozen badge, phase filtering, and lifecycle information is surfaced in +Index Management by running this series of requests in Console: + + +- {kib-repo}blob/{branch}/x-pack/plugins/index_management[indexManagement] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/infra/README.md[infra] + +This is the home of the infra plugin, which aims to provide a solution for +the infrastructure monitoring use-case within Kibana. + + +- {kib-repo}blob/{branch}/x-pack/plugins/ingest_manager/README.md[ingestManager] + +Fleet needs to have Elasticsearch API keys enabled, and also to have TLS enabled on kibana, (if you want to run Kibana without TLS you can provide the following config flag --xpack.ingestManager.fleet.tlsCheckDisabled=false) + + +- {kib-repo}blob/{branch}/x-pack/plugins/ingest_pipelines/README.md[ingestPipelines] + +The ingest_pipelines plugin provides Kibana support for Elasticsearch's ingest nodes. Please refer to the Elasticsearch documentation for more details. + + +- {kib-repo}blob/{branch}/x-pack/plugins/lens/readme.md[lens] + +Run all tests from the x-pack root directory + + +- {kib-repo}blob/{branch}/x-pack/plugins/license_management[licenseManagement] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/licensing/README.md[licensing] + +The licensing plugin retrieves license data from Elasticsearch at regular configurable intervals. + + +- {kib-repo}blob/{branch}/x-pack/plugins/lists/README.md[lists] + +README.md for developers working on the backend lists on how to get started +using the CURL scripts in the scripts folder. + + +- {kib-repo}blob/{branch}/x-pack/plugins/logstash[logstash] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/maps/README.md[maps] + +Visualize geo data from Elasticsearch or 3rd party geo-services. + + +- {kib-repo}blob/{branch}/x-pack/plugins/maps_legacy_licensing/README.md[mapsLegacyLicensing] + +This plugin provides access to the detailed tile map services from Elastic. + + +- {kib-repo}blob/{branch}/x-pack/plugins/ml[ml] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/monitoring[monitoring] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/observability/README.md[observability] + +This plugin provides shared components and services for use across observability solutions, as well as the observability landing page UI. + + +- {kib-repo}blob/{branch}/x-pack/plugins/oss_telemetry[ossTelemetry] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/painless_lab[painlessLab] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/remote_clusters[remoteClusters] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/reporting/README.md[reporting] + +An awesome Kibana reporting plugin + + +- {kib-repo}blob/{branch}/x-pack/plugins/rollup/README.md[rollup] + +Welcome to the Kibana rollup plugin! This plugin provides Kibana support for Elasticsearch's rollup feature. Please refer to the Elasticsearch documentation to understand rollup indices and how to create rollup jobs. + + +- {kib-repo}blob/{branch}/x-pack/plugins/searchprofiler[searchprofiler] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/security/README.md[security] + +See Configuring security in Kibana. + + +- {kib-repo}blob/{branch}/x-pack/plugins/security_solution/README.md[securitySolution] + +Welcome to the Kibana Security Solution plugin! This README will go over getting started with development and testing. + + +- {kib-repo}blob/{branch}/x-pack/plugins/snapshot_restore/README.md[snapshotRestore] + +or + + +- {kib-repo}blob/{branch}/x-pack/plugins/spaces[spaces] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/task_manager[taskManager] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/telemetry_collection_xpack/README.md[telemetryCollectionXpack] + +Gathers all usage collection, retrieving them from both: OSS and X-Pack plugins. + + +- {kib-repo}blob/{branch}/x-pack/plugins/transform[transform] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/translations[translations] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/triggers_actions_ui/README.md[triggers_actions_ui] + +The Kibana alerts and actions UI plugin provides a user interface for managing alerts and actions. +As a developer you can reuse and extend built-in alerts and actions UI functionality: + + +- {kib-repo}blob/{branch}/x-pack/plugins/ui_actions_enhanced/README.md[uiActionsEnhanced] + +- {kib-repo}blob/{branch}/x-pack/plugins/upgrade_assistant[upgradeAssistant] + +WARNING: Missing README. + + +- {kib-repo}blob/{branch}/x-pack/plugins/uptime/README.md[uptime] + +The purpose of this plugin is to provide users of Heartbeat more visibility of what's happening +in their infrastructure. + + +- {kib-repo}blob/{branch}/x-pack/plugins/watcher/README.md[watcher] + +This plugins adopts some conventions in addition to or in place of conventions in Kibana (at the time of the plugin's creation): + diff --git a/docs/developer/architecture/security/feature-registration.asciidoc b/docs/developer/architecture/security/feature-registration.asciidoc index 3724624dbb917..3ff83e9db8c43 100644 --- a/docs/developer/architecture/security/feature-registration.asciidoc +++ b/docs/developer/architecture/security/feature-registration.asciidoc @@ -9,13 +9,12 @@ Registering features also gives your plugin access to “UI Capabilities”. The === Registering a feature -Feature registration is controlled via the built-in `xpack_main` plugin. To register a feature, call `xpack_main`'s `registerFeature` function from your plugin's `init` function, and provide the appropriate details: +Feature registration is controlled via the built-in `features` plugin. To register a feature, call `features`'s `registerKibanaFeature` function from your plugin's `setup` lifecycle function, and provide the appropriate details: ["source","javascript"] ----------- -init(server) { - const xpackMainPlugin = server.plugins.xpack_main; - xpackMainPlugin.registerFeature({ +setup(core, { features }) { + features.registerKibanaFeature({ // feature details here. }); } @@ -45,12 +44,12 @@ Registering a feature consists of the following fields. For more information, co |An array of applications this feature enables. Typically, all of your plugin's apps (from `uiExports`) will be included here. |`privileges` (required) -|{kib-repo}blob/{branch}/x-pack/plugins/features/common/feature.ts[`FeatureConfig`]. +|{kib-repo}blob/{branch}/x-pack/plugins/features/common/feature.ts[`KibanaFeatureConfig`]. |See <> and <> |The set of privileges this feature requires to function. |`subFeatures` (optional) -|{kib-repo}blob/{branch}/x-pack/plugins/features/common/feature.ts[`FeatureConfig`]. +|{kib-repo}blob/{branch}/x-pack/plugins/features/common/feature.ts[`KibanaFeatureConfig`]. |See <> |The set of subfeatures that enables finer access control than the `all` and `read` feature privileges. These options are only available in the Gold subscription level and higher. @@ -73,15 +72,17 @@ For a full explanation of fields and options, consult the {kib-repo}blob/{branch === Using UI Capabilities UI Capabilities are available to your public (client) plugin code. These capabilities are read-only, and are used to inform the UI. This object is namespaced by feature id. For example, if your feature id is “foo”, then your UI Capabilities are stored at `uiCapabilities.foo`. -To access capabilities, import them from `ui/capabilities`: +Capabilities can be accessed from your plugin's `start` lifecycle from the `core.application` service: ["source","javascript"] ----------- -import { uiCapabilities } from 'ui/capabilities'; +public start(core) { + const { capabilities } = core.application; -const canUserSave = uiCapabilities.foo.save; -if (canUserSave) { - // show save button + const canUserSave = capabilities.foo.save; + if (canUserSave) { + // show save button + } } ----------- @@ -89,9 +90,8 @@ if (canUserSave) { === Example 1: Canvas Application ["source","javascript"] ----------- -init(server) { - const xpackMainPlugin = server.plugins.xpack_main; - xpackMainPlugin.registerFeature({ +public setup(core, { features }) { + features.registerKibanaFeature({ id: 'canvas', name: 'Canvas', icon: 'canvasApp', @@ -130,11 +130,13 @@ The `all` privilege defines a single “save” UI Capability. To access this in ["source","javascript"] ----------- -import { uiCapabilities } from 'ui/capabilities'; +public start(core) { + const { capabilities } = core.application; -const canUserSave = uiCapabilities.canvas.save; -if (canUserSave) { - // show save button + const canUserSave = capabilities.canvas.save; + if (canUserSave) { + // show save button + } } ----------- @@ -145,9 +147,8 @@ Because the `read` privilege does not define the `save` capability, users with r ["source","javascript"] ----------- -init(server) { - const xpackMainPlugin = server.plugins.xpack_main; - xpackMainPlugin.registerFeature({ +public setup(core, { features }) { + features.registerKibanaFeature({ id: 'dev_tools', name: i18n.translate('xpack.features.devToolsFeatureName', { defaultMessage: 'Dev Tools', @@ -206,9 +207,8 @@ a single "Create Short URLs" subfeature privilege is defined, which allows users ["source","javascript"] ----------- -init(server) { - const xpackMainPlugin = server.plugins.xpack_main; - xpackMainPlugin.registerFeature({ +public setup(core, { features }) { + features.registerKibanaFeature({ { id: 'discover', name: i18n.translate('xpack.features.discoverFeatureName', { diff --git a/docs/developer/contributing/development-accessibility-tests.asciidoc b/docs/developer/contributing/development-accessibility-tests.asciidoc index facf7ff14a6c1..584d779bc7de6 100644 --- a/docs/developer/contributing/development-accessibility-tests.asciidoc +++ b/docs/developer/contributing/development-accessibility-tests.asciidoc @@ -1,23 +1,104 @@ [[development-accessibility-tests]] == Automated Accessibility Testing + +To write an accessibility test, use the provided accessibility service `getService('a11y')`. Accessibility tests are fairly straightforward to write as https://github.com/dequelabs/axe-core[axe] does most of the heavy lifting. Navigate to the UI that you need to test, then call `testAppSnapshot();` from the service imported earlier to make sure axe finds no failures. Navigate through every portion of the UI for the best coverage. + +An example test might look like this: +[source,js] +---- +export default function ({ getService, getPageObjects }) { + const { common, home } = getPageObjects(['common', 'home']); + const a11y = getService('a11y'); /* this is the wrapping service around axe */ + + describe('Kibana Home', () => { + before(async () => { + await common.navigateToApp('home'); /* navigates to the page we want to test */ + }); + + it('Kibana Home view', async () => { + await retry.waitFor( + 'home page visible', + async () => await testSubjects.exists('homeApp') + ); /* confirm you're on the correct page and that it's loaded */ + await a11y.testAppSnapshot(); /* this expects that there are no failures found by axe */ + }); + + /** + * If these tests were added by our QA team, tests that fail that require significant app code + * changes to be fixed will be skipped with a corresponding issue label with more info + */ + // Skipped due to https://github.com/elastic/kibana/issues/99999 + it.skip('all plugins view page meets a11y requirements', async () => { + await home.clickAllKibanaPlugins(); + await a11y.testAppSnapshot(); + }); + + /** + * Testing all the versions and different views of of a page is important to get good + * coverage. Things like empty states, different license levels, different permissions, and + * loaded data can all significantly change the UI which necessitates their own test. + */ + it('Add Kibana sample data page', async () => { + await common.navigateToUrl('home', '/tutorial_directory/sampleData', { + useActualUrl: true, + }); + await a11y.testAppSnapshot(); + }); + }); +} +---- + +=== Running tests To run the tests locally: [arabic] -. In one terminal window run -`node scripts/functional_tests_server --config test/accessibility/config.ts` -. In another terminal window run -`node scripts/functional_test_runner.js --config test/accessibility/config.ts` +. In one terminal window run: ++ +[source,shell] +----------- +node scripts/functional_tests_server --config test/accessibility/config.ts +----------- + +. When the server prints that it is ready, in another terminal window run: ++ +[source,shell] +----------- +node scripts/functional_test_runner.js --config test/accessibility/config.ts +----------- To run the x-pack tests, swap the config file out for `x-pack/test/accessibility/config.ts`. -After the server is up, you can go to this instance of {kib} at -`localhost:5620`. - The testing is done using https://github.com/dequelabs/axe-core[axe]. -The same thing that runs in CI, can be run locally using their browser -plugins: +You can run the same thing that runs CI using browser plugins: * https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US[Chrome] -* https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/[Firefox] \ No newline at end of file +* https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/[Firefox] + +=== Anatomy of a failure + +Failures can seem confusing if you've never seen one before. Here is a breakdown of what a failure coming from CI might look like: +[source,bash] +---- +1) Dashboard + create dashboard button: + + Error: a11y report: + + VIOLATION + [aria-hidden-focus]: Ensures aria-hidden elements do not contain focusable elements + Help: https://dequeuniversity.com/rules/axe/3.5/aria-hidden-focus?application=axeAPI + Elements: + - #example + at Accessibility.testAxeReport (test/accessibility/services/a11y/a11y.ts:90:15) + at Accessibility.testAppSnapshot (test/accessibility/services/a11y/a11y.ts:58:18) + at process._tickCallback (internal/process/next_tick.js:68:7) +---- + + +* "Dashboard" and "create dashboard button" are the names of the test suite and specific test that failed. +* Always in brackets, "[aria-hidden-focus]" is the name of the axe rule that failed, followed by a short description. +* "Help: " links to the axe documentation for that rule, including severity, remediation tips, and good and bad code examples. +* "Elements:" points to where in the DOM the failure originated (using CSS selector syntax). In this example, the problem came from an element with the ID `example`. If the selector is too complicated to find the source of the problem, use the browser plugins mentioned earlier to locate it. If you have a general idea where the issue is coming from, you can also try adding unique IDs to the page to narrow down the location. +* The stack trace points to the internals of axe. The stack trace is there in case the test failure is a bug in axe and not in your code, although this is unlikely. diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index b3180a7a03874..7727cd322181f 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -95,7 +95,7 @@ in Kibana, e.g. visualizations. It has the form of a flyout panel. |{kib-repo}blob/{branch}/src/plugins/kibana_legacy/README.md[kibanaLegacy] -|This plugin will contain several helpers and services to integrate pieces of the legacy Kibana app with the new Kibana platform. +|This plugin contains several helpers and services to integrate pieces of the legacy Kibana app with the new Kibana platform. |{kib-repo}blob/{branch}/src/plugins/kibana_react/README.md[kibanaReact] @@ -172,6 +172,10 @@ which also contains the timelion APIs and backend, look at the vis_type_timelion |An API for: +|{kib-repo}blob/{branch}/src/plugins/url_forwarding/README.md[urlForwarding] +|This plugins contains helpers to redirect legacy URLs. It can be used to forward old URLs to their new counterparts. + + |{kib-repo}blob/{branch}/src/plugins/usage_collection/README.md[usageCollection] |Usage Collection allows collecting usage data for other services to consume (telemetry and monitoring). To integrate with the telemetry services for usage collection of your feature, there are 2 steps: @@ -337,6 +341,10 @@ and actions. or dashboards from the Kibana instance, from both server and client-side plugins +|{kib-repo}blob/{branch}/x-pack/plugins/global_search_bar/README.md[globalSearchBar] +|The GlobalSearchBar plugin provides a search interface for navigating Kibana. (It is the UI to the GlobalSearch plugin.) + + |{kib-repo}blob/{branch}/x-pack/plugins/global_search_providers[globalSearchProviders] |WARNING: Missing README. @@ -412,10 +420,6 @@ using the CURL scripts in the scripts folder. |This plugin provides shared components and services for use across observability solutions, as well as the observability landing page UI. -|{kib-repo}blob/{branch}/x-pack/plugins/oss_telemetry[ossTelemetry] -|WARNING: Missing README. - - |{kib-repo}blob/{branch}/x-pack/plugins/painless_lab[painlessLab] |WARNING: Missing README. diff --git a/docs/development/core/public/kibana-plugin-core-public.app.capabilities.md b/docs/development/core/public/kibana-plugin-core-public.app.capabilities.md new file mode 100644 index 0000000000000..4a027a6ab132c --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.app.capabilities.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [App](./kibana-plugin-core-public.app.md) > [capabilities](./kibana-plugin-core-public.app.capabilities.md) + +## App.capabilities property + +Custom capabilities defined by the app. + +Signature: + +```typescript +capabilities?: Partial; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.app.category.md b/docs/development/core/public/kibana-plugin-core-public.app.category.md new file mode 100644 index 0000000000000..a1e74f2bcf5e2 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.app.category.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [App](./kibana-plugin-core-public.app.md) > [category](./kibana-plugin-core-public.app.category.md) + +## App.category property + +The category definition of the product See [AppCategory](./kibana-plugin-core-public.appcategory.md) See DEFAULT\_APP\_CATEGORIES for more reference + +Signature: + +```typescript +category?: AppCategory; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.app.defaultpath.md b/docs/development/core/public/kibana-plugin-core-public.app.defaultpath.md new file mode 100644 index 0000000000000..3c952ec053e62 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.app.defaultpath.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [App](./kibana-plugin-core-public.app.md) > [defaultPath](./kibana-plugin-core-public.app.defaultpath.md) + +## App.defaultPath property + +Allow to define the default path a user should be directed to when navigating to the app. When defined, this value will be used as a default for the `path` option when calling [navigateToApp](./kibana-plugin-core-public.applicationstart.navigatetoapp.md)\`, and will also be appended to the [application navLink](./kibana-plugin-core-public.chromenavlink.md) in the navigation bar. + +Signature: + +```typescript +defaultPath?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.app.euiicontype.md b/docs/development/core/public/kibana-plugin-core-public.app.euiicontype.md new file mode 100644 index 0000000000000..ff79d832f92e2 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.app.euiicontype.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [App](./kibana-plugin-core-public.app.md) > [euiIconType](./kibana-plugin-core-public.app.euiicontype.md) + +## App.euiIconType property + +A EUI iconType that will be used for the app's icon. This icon takes precendence over the `icon` property. + +Signature: + +```typescript +euiIconType?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.app.icon.md b/docs/development/core/public/kibana-plugin-core-public.app.icon.md new file mode 100644 index 0000000000000..98260da5d2021 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.app.icon.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [App](./kibana-plugin-core-public.app.md) > [icon](./kibana-plugin-core-public.app.icon.md) + +## App.icon property + +A URL to an image file used as an icon. Used as a fallback if `euiIconType` is not provided. + +Signature: + +```typescript +icon?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.app.id.md b/docs/development/core/public/kibana-plugin-core-public.app.id.md new file mode 100644 index 0000000000000..9899cfc0cf572 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.app.id.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [App](./kibana-plugin-core-public.app.md) > [id](./kibana-plugin-core-public.app.id.md) + +## App.id property + +The unique identifier of the application + +Signature: + +```typescript +id: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.app.md b/docs/development/core/public/kibana-plugin-core-public.app.md index 8dd60972549f9..7bdee9dc4c53e 100644 --- a/docs/development/core/public/kibana-plugin-core-public.app.md +++ b/docs/development/core/public/kibana-plugin-core-public.app.md @@ -4,12 +4,11 @@ ## App interface -Extension of [common app properties](./kibana-plugin-core-public.appbase.md) with the mount function. Signature: ```typescript -export interface App extends AppBase +export interface App ``` ## Properties @@ -17,7 +16,19 @@ export interface App extends AppBase | Property | Type | Description | | --- | --- | --- | | [appRoute](./kibana-plugin-core-public.app.approute.md) | string | Override the application's routing path from /app/${id}. Must be unique across registered applications. Should not include the base path from HTTP. | +| [capabilities](./kibana-plugin-core-public.app.capabilities.md) | Partial<Capabilities> | Custom capabilities defined by the app. | +| [category](./kibana-plugin-core-public.app.category.md) | AppCategory | The category definition of the product See [AppCategory](./kibana-plugin-core-public.appcategory.md) See DEFAULT\_APP\_CATEGORIES for more reference | | [chromeless](./kibana-plugin-core-public.app.chromeless.md) | boolean | Hide the UI chrome when the application is mounted. Defaults to false. Takes precedence over chrome service visibility settings. | +| [defaultPath](./kibana-plugin-core-public.app.defaultpath.md) | string | Allow to define the default path a user should be directed to when navigating to the app. When defined, this value will be used as a default for the path option when calling [navigateToApp](./kibana-plugin-core-public.applicationstart.navigatetoapp.md)\`, and will also be appended to the [application navLink](./kibana-plugin-core-public.chromenavlink.md) in the navigation bar. | +| [euiIconType](./kibana-plugin-core-public.app.euiicontype.md) | string | A EUI iconType that will be used for the app's icon. This icon takes precendence over the icon property. | | [exactRoute](./kibana-plugin-core-public.app.exactroute.md) | boolean | If set to true, the application's route will only be checked against an exact match. Defaults to false. | +| [icon](./kibana-plugin-core-public.app.icon.md) | string | A URL to an image file used as an icon. Used as a fallback if euiIconType is not provided. | +| [id](./kibana-plugin-core-public.app.id.md) | string | The unique identifier of the application | | [mount](./kibana-plugin-core-public.app.mount.md) | AppMount<HistoryLocationState> | AppMountDeprecated<HistoryLocationState> | A mount function called when the user navigates to this app's route. May have signature of [AppMount](./kibana-plugin-core-public.appmount.md) or [AppMountDeprecated](./kibana-plugin-core-public.appmountdeprecated.md). | +| [navLinkStatus](./kibana-plugin-core-public.app.navlinkstatus.md) | AppNavLinkStatus | The initial status of the application's navLink. Defaulting to visible if status is accessible and hidden if status is inaccessible See [AppNavLinkStatus](./kibana-plugin-core-public.appnavlinkstatus.md) | +| [order](./kibana-plugin-core-public.app.order.md) | number | An ordinal used to sort nav links relative to one another for display. | +| [status](./kibana-plugin-core-public.app.status.md) | AppStatus | The initial status of the application. Defaulting to accessible | +| [title](./kibana-plugin-core-public.app.title.md) | string | The title of the application. | +| [tooltip](./kibana-plugin-core-public.app.tooltip.md) | string | A tooltip shown when hovering over app link. | +| [updater$](./kibana-plugin-core-public.app.updater_.md) | Observable<AppUpdater> | An [AppUpdater](./kibana-plugin-core-public.appupdater.md) observable that can be used to update the application [AppUpdatableFields](./kibana-plugin-core-public.appupdatablefields.md) at runtime. | diff --git a/docs/development/core/public/kibana-plugin-core-public.app.navlinkstatus.md b/docs/development/core/public/kibana-plugin-core-public.app.navlinkstatus.md new file mode 100644 index 0000000000000..c01a26e42e237 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.app.navlinkstatus.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [App](./kibana-plugin-core-public.app.md) > [navLinkStatus](./kibana-plugin-core-public.app.navlinkstatus.md) + +## App.navLinkStatus property + +The initial status of the application's navLink. Defaulting to `visible` if `status` is `accessible` and `hidden` if status is `inaccessible` See [AppNavLinkStatus](./kibana-plugin-core-public.appnavlinkstatus.md) + +Signature: + +```typescript +navLinkStatus?: AppNavLinkStatus; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.app.order.md b/docs/development/core/public/kibana-plugin-core-public.app.order.md new file mode 100644 index 0000000000000..bb6be116b6b58 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.app.order.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [App](./kibana-plugin-core-public.app.md) > [order](./kibana-plugin-core-public.app.order.md) + +## App.order property + +An ordinal used to sort nav links relative to one another for display. + +Signature: + +```typescript +order?: number; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.app.status.md b/docs/development/core/public/kibana-plugin-core-public.app.status.md new file mode 100644 index 0000000000000..caa6ff1dcac9e --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.app.status.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [App](./kibana-plugin-core-public.app.md) > [status](./kibana-plugin-core-public.app.status.md) + +## App.status property + +The initial status of the application. Defaulting to `accessible` + +Signature: + +```typescript +status?: AppStatus; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.app.title.md b/docs/development/core/public/kibana-plugin-core-public.app.title.md new file mode 100644 index 0000000000000..c705e3ab8d2b1 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.app.title.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [App](./kibana-plugin-core-public.app.md) > [title](./kibana-plugin-core-public.app.title.md) + +## App.title property + +The title of the application. + +Signature: + +```typescript +title: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.app.tooltip.md b/docs/development/core/public/kibana-plugin-core-public.app.tooltip.md new file mode 100644 index 0000000000000..e901de0fdccc9 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.app.tooltip.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [App](./kibana-plugin-core-public.app.md) > [tooltip](./kibana-plugin-core-public.app.tooltip.md) + +## App.tooltip property + +A tooltip shown when hovering over app link. + +Signature: + +```typescript +tooltip?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.app.updater_.md b/docs/development/core/public/kibana-plugin-core-public.app.updater_.md new file mode 100644 index 0000000000000..67acccbd02965 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.app.updater_.md @@ -0,0 +1,44 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [App](./kibana-plugin-core-public.app.md) > [updater$](./kibana-plugin-core-public.app.updater_.md) + +## App.updater$ property + +An [AppUpdater](./kibana-plugin-core-public.appupdater.md) observable that can be used to update the application [AppUpdatableFields](./kibana-plugin-core-public.appupdatablefields.md) at runtime. + +Signature: + +```typescript +updater$?: Observable; +``` + +## Example + +How to update an application navLink at runtime + +```ts +// inside your plugin's setup function +export class MyPlugin implements Plugin { + private appUpdater = new BehaviorSubject(() => ({})); + + setup({ application }) { + application.register({ + id: 'my-app', + title: 'My App', + updater$: this.appUpdater, + async mount(params) { + const { renderApp } = await import('./application'); + return renderApp(params); + }, + }); + } + + start() { + // later, when the navlink needs to be updated + appUpdater.next(() => { + navLinkStatus: AppNavLinkStatus.disabled, + }) + } + +``` + diff --git a/docs/development/core/public/kibana-plugin-core-public.appbase.capabilities.md b/docs/development/core/public/kibana-plugin-core-public.appbase.capabilities.md deleted file mode 100644 index 3dd440c4253b3..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.appbase.capabilities.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppBase](./kibana-plugin-core-public.appbase.md) > [capabilities](./kibana-plugin-core-public.appbase.capabilities.md) - -## AppBase.capabilities property - -Custom capabilities defined by the app. - -Signature: - -```typescript -capabilities?: Partial; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.appbase.category.md b/docs/development/core/public/kibana-plugin-core-public.appbase.category.md deleted file mode 100644 index 29532a15747e1..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.appbase.category.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppBase](./kibana-plugin-core-public.appbase.md) > [category](./kibana-plugin-core-public.appbase.category.md) - -## AppBase.category property - -The category definition of the product See [AppCategory](./kibana-plugin-core-public.appcategory.md) See DEFAULT\_APP\_CATEGORIES for more reference - -Signature: - -```typescript -category?: AppCategory; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.appbase.chromeless.md b/docs/development/core/public/kibana-plugin-core-public.appbase.chromeless.md deleted file mode 100644 index 793eab4b5bdfa..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.appbase.chromeless.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppBase](./kibana-plugin-core-public.appbase.md) > [chromeless](./kibana-plugin-core-public.appbase.chromeless.md) - -## AppBase.chromeless property - -Hide the UI chrome when the application is mounted. Defaults to `false`. Takes precedence over chrome service visibility settings. - -Signature: - -```typescript -chromeless?: boolean; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.appbase.defaultpath.md b/docs/development/core/public/kibana-plugin-core-public.appbase.defaultpath.md deleted file mode 100644 index 51492756ef232..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.appbase.defaultpath.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppBase](./kibana-plugin-core-public.appbase.md) > [defaultPath](./kibana-plugin-core-public.appbase.defaultpath.md) - -## AppBase.defaultPath property - -Allow to define the default path a user should be directed to when navigating to the app. When defined, this value will be used as a default for the `path` option when calling [navigateToApp](./kibana-plugin-core-public.applicationstart.navigatetoapp.md)\`, and will also be appended to the [application navLink](./kibana-plugin-core-public.chromenavlink.md) in the navigation bar. - -Signature: - -```typescript -defaultPath?: string; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.appbase.euiicontype.md b/docs/development/core/public/kibana-plugin-core-public.appbase.euiicontype.md deleted file mode 100644 index e5bfa38097361..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.appbase.euiicontype.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppBase](./kibana-plugin-core-public.appbase.md) > [euiIconType](./kibana-plugin-core-public.appbase.euiicontype.md) - -## AppBase.euiIconType property - -A EUI iconType that will be used for the app's icon. This icon takes precendence over the `icon` property. - -Signature: - -```typescript -euiIconType?: string; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.appbase.icon.md b/docs/development/core/public/kibana-plugin-core-public.appbase.icon.md deleted file mode 100644 index 0bd67922dc39c..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.appbase.icon.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppBase](./kibana-plugin-core-public.appbase.md) > [icon](./kibana-plugin-core-public.appbase.icon.md) - -## AppBase.icon property - -A URL to an image file used as an icon. Used as a fallback if `euiIconType` is not provided. - -Signature: - -```typescript -icon?: string; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.appbase.id.md b/docs/development/core/public/kibana-plugin-core-public.appbase.id.md deleted file mode 100644 index 6c0ec462fa16b..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.appbase.id.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppBase](./kibana-plugin-core-public.appbase.md) > [id](./kibana-plugin-core-public.appbase.id.md) - -## AppBase.id property - -The unique identifier of the application - -Signature: - -```typescript -id: string; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.appbase.md b/docs/development/core/public/kibana-plugin-core-public.appbase.md deleted file mode 100644 index 7b624f12ac1df..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.appbase.md +++ /dev/null @@ -1,31 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppBase](./kibana-plugin-core-public.appbase.md) - -## AppBase interface - - -Signature: - -```typescript -export interface AppBase -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [capabilities](./kibana-plugin-core-public.appbase.capabilities.md) | Partial<Capabilities> | Custom capabilities defined by the app. | -| [category](./kibana-plugin-core-public.appbase.category.md) | AppCategory | The category definition of the product See [AppCategory](./kibana-plugin-core-public.appcategory.md) See DEFAULT\_APP\_CATEGORIES for more reference | -| [chromeless](./kibana-plugin-core-public.appbase.chromeless.md) | boolean | Hide the UI chrome when the application is mounted. Defaults to false. Takes precedence over chrome service visibility settings. | -| [defaultPath](./kibana-plugin-core-public.appbase.defaultpath.md) | string | Allow to define the default path a user should be directed to when navigating to the app. When defined, this value will be used as a default for the path option when calling [navigateToApp](./kibana-plugin-core-public.applicationstart.navigatetoapp.md)\`, and will also be appended to the [application navLink](./kibana-plugin-core-public.chromenavlink.md) in the navigation bar. | -| [euiIconType](./kibana-plugin-core-public.appbase.euiicontype.md) | string | A EUI iconType that will be used for the app's icon. This icon takes precendence over the icon property. | -| [icon](./kibana-plugin-core-public.appbase.icon.md) | string | A URL to an image file used as an icon. Used as a fallback if euiIconType is not provided. | -| [id](./kibana-plugin-core-public.appbase.id.md) | string | The unique identifier of the application | -| [navLinkStatus](./kibana-plugin-core-public.appbase.navlinkstatus.md) | AppNavLinkStatus | The initial status of the application's navLink. Defaulting to visible if status is accessible and hidden if status is inaccessible See [AppNavLinkStatus](./kibana-plugin-core-public.appnavlinkstatus.md) | -| [order](./kibana-plugin-core-public.appbase.order.md) | number | An ordinal used to sort nav links relative to one another for display. | -| [status](./kibana-plugin-core-public.appbase.status.md) | AppStatus | The initial status of the application. Defaulting to accessible | -| [title](./kibana-plugin-core-public.appbase.title.md) | string | The title of the application. | -| [tooltip](./kibana-plugin-core-public.appbase.tooltip.md) | string | A tooltip shown when hovering over app link. | -| [updater$](./kibana-plugin-core-public.appbase.updater_.md) | Observable<AppUpdater> | An [AppUpdater](./kibana-plugin-core-public.appupdater.md) observable that can be used to update the application [AppUpdatableFields](./kibana-plugin-core-public.appupdatablefields.md) at runtime. | - diff --git a/docs/development/core/public/kibana-plugin-core-public.appbase.navlinkstatus.md b/docs/development/core/public/kibana-plugin-core-public.appbase.navlinkstatus.md deleted file mode 100644 index decfb235b2858..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.appbase.navlinkstatus.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppBase](./kibana-plugin-core-public.appbase.md) > [navLinkStatus](./kibana-plugin-core-public.appbase.navlinkstatus.md) - -## AppBase.navLinkStatus property - -The initial status of the application's navLink. Defaulting to `visible` if `status` is `accessible` and `hidden` if status is `inaccessible` See [AppNavLinkStatus](./kibana-plugin-core-public.appnavlinkstatus.md) - -Signature: - -```typescript -navLinkStatus?: AppNavLinkStatus; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.appbase.order.md b/docs/development/core/public/kibana-plugin-core-public.appbase.order.md deleted file mode 100644 index 606a40e72d592..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.appbase.order.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppBase](./kibana-plugin-core-public.appbase.md) > [order](./kibana-plugin-core-public.appbase.order.md) - -## AppBase.order property - -An ordinal used to sort nav links relative to one another for display. - -Signature: - -```typescript -order?: number; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.appbase.status.md b/docs/development/core/public/kibana-plugin-core-public.appbase.status.md deleted file mode 100644 index 4d6ba6ebd955e..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.appbase.status.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppBase](./kibana-plugin-core-public.appbase.md) > [status](./kibana-plugin-core-public.appbase.status.md) - -## AppBase.status property - -The initial status of the application. Defaulting to `accessible` - -Signature: - -```typescript -status?: AppStatus; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.appbase.title.md b/docs/development/core/public/kibana-plugin-core-public.appbase.title.md deleted file mode 100644 index d6058badee8e8..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.appbase.title.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppBase](./kibana-plugin-core-public.appbase.md) > [title](./kibana-plugin-core-public.appbase.title.md) - -## AppBase.title property - -The title of the application. - -Signature: - -```typescript -title: string; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.appbase.tooltip.md b/docs/development/core/public/kibana-plugin-core-public.appbase.tooltip.md deleted file mode 100644 index 0c0b0840eb921..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.appbase.tooltip.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppBase](./kibana-plugin-core-public.appbase.md) > [tooltip](./kibana-plugin-core-public.appbase.tooltip.md) - -## AppBase.tooltip property - -A tooltip shown when hovering over app link. - -Signature: - -```typescript -tooltip?: string; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.appbase.updater_.md b/docs/development/core/public/kibana-plugin-core-public.appbase.updater_.md deleted file mode 100644 index c2c572755f9b2..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.appbase.updater_.md +++ /dev/null @@ -1,44 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppBase](./kibana-plugin-core-public.appbase.md) > [updater$](./kibana-plugin-core-public.appbase.updater_.md) - -## AppBase.updater$ property - -An [AppUpdater](./kibana-plugin-core-public.appupdater.md) observable that can be used to update the application [AppUpdatableFields](./kibana-plugin-core-public.appupdatablefields.md) at runtime. - -Signature: - -```typescript -updater$?: Observable; -``` - -## Example - -How to update an application navLink at runtime - -```ts -// inside your plugin's setup function -export class MyPlugin implements Plugin { - private appUpdater = new BehaviorSubject(() => ({})); - - setup({ application }) { - application.register({ - id: 'my-app', - title: 'My App', - updater$: this.appUpdater, - async mount(params) { - const { renderApp } = await import('./application'); - return renderApp(params); - }, - }); - } - - start() { - // later, when the navlink needs to be updated - appUpdater.next(() => { - navLinkStatus: AppNavLinkStatus.disabled, - }) - } - -``` - diff --git a/docs/development/core/public/kibana-plugin-core-public.applicationstart.applications_.md b/docs/development/core/public/kibana-plugin-core-public.applicationstart.applications_.md index d428faa500faf..bcc5435f35951 100644 --- a/docs/development/core/public/kibana-plugin-core-public.applicationstart.applications_.md +++ b/docs/development/core/public/kibana-plugin-core-public.applicationstart.applications_.md @@ -9,7 +9,7 @@ Observable emitting the list of currently registered apps and their associated s Signature: ```typescript -applications$: Observable>; +applications$: Observable>; ``` ## Remarks diff --git a/docs/development/core/public/kibana-plugin-core-public.applicationstart.md b/docs/development/core/public/kibana-plugin-core-public.applicationstart.md index 896de2de32dd5..00318f32984e9 100644 --- a/docs/development/core/public/kibana-plugin-core-public.applicationstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.applicationstart.md @@ -15,7 +15,7 @@ export interface ApplicationStart | Property | Type | Description | | --- | --- | --- | -| [applications$](./kibana-plugin-core-public.applicationstart.applications_.md) | Observable<ReadonlyMap<string, PublicAppInfo | PublicLegacyAppInfo>> | Observable emitting the list of currently registered apps and their associated status. | +| [applications$](./kibana-plugin-core-public.applicationstart.applications_.md) | Observable<ReadonlyMap<string, PublicAppInfo>> | Observable emitting the list of currently registered apps and their associated status. | | [capabilities](./kibana-plugin-core-public.applicationstart.capabilities.md) | RecursiveReadonly<Capabilities> | Gets the read-only capabilities. | | [currentAppId$](./kibana-plugin-core-public.applicationstart.currentappid_.md) | Observable<string | undefined> | An observable that emits the current application id and each subsequent id update. | diff --git a/docs/development/core/public/kibana-plugin-core-public.appupdatablefields.md b/docs/development/core/public/kibana-plugin-core-public.appupdatablefields.md index 3d8b5d115c8a2..1232b7f940255 100644 --- a/docs/development/core/public/kibana-plugin-core-public.appupdatablefields.md +++ b/docs/development/core/public/kibana-plugin-core-public.appupdatablefields.md @@ -9,5 +9,5 @@ Defines the list of fields that can be updated via an [AppUpdater](./kibana-plug Signature: ```typescript -export declare type AppUpdatableFields = Pick; +export declare type AppUpdatableFields = Pick; ``` diff --git a/docs/development/core/public/kibana-plugin-core-public.appupdater.md b/docs/development/core/public/kibana-plugin-core-public.appupdater.md index a1c1424132da6..744c52f221da7 100644 --- a/docs/development/core/public/kibana-plugin-core-public.appupdater.md +++ b/docs/development/core/public/kibana-plugin-core-public.appupdater.md @@ -9,5 +9,5 @@ Updater for applications. see [ApplicationSetup](./kibana-plugin-core-public.app Signature: ```typescript -export declare type AppUpdater = (app: AppBase) => Partial | undefined; +export declare type AppUpdater = (app: App) => Partial | undefined; ``` diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.md b/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.md index bca69adeef66b..47365782599ed 100644 --- a/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.md +++ b/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.md @@ -30,6 +30,7 @@ chrome.navControls.registerLeft({ | Method | Description | | --- | --- | -| [registerLeft(navControl)](./kibana-plugin-core-public.chromenavcontrols.registerleft.md) | Register a nav control to be presented on the left side of the chrome header. | -| [registerRight(navControl)](./kibana-plugin-core-public.chromenavcontrols.registerright.md) | Register a nav control to be presented on the right side of the chrome header. | +| [registerCenter(navControl)](./kibana-plugin-core-public.chromenavcontrols.registercenter.md) | Register a nav control to be presented on the top-center side of the chrome header. | +| [registerLeft(navControl)](./kibana-plugin-core-public.chromenavcontrols.registerleft.md) | Register a nav control to be presented on the bottom-left side of the chrome header. | +| [registerRight(navControl)](./kibana-plugin-core-public.chromenavcontrols.registerright.md) | Register a nav control to be presented on the top-right side of the chrome header. | diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.registercenter.md b/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.registercenter.md new file mode 100644 index 0000000000000..2f921050e58dd --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.registercenter.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ChromeNavControls](./kibana-plugin-core-public.chromenavcontrols.md) > [registerCenter](./kibana-plugin-core-public.chromenavcontrols.registercenter.md) + +## ChromeNavControls.registerCenter() method + +Register a nav control to be presented on the top-center side of the chrome header. + +Signature: + +```typescript +registerCenter(navControl: ChromeNavControl): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| navControl | ChromeNavControl | | + +Returns: + +`void` + diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.registerleft.md b/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.registerleft.md index c5c78bf9fb1da..514c44bd9d710 100644 --- a/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.registerleft.md +++ b/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.registerleft.md @@ -4,7 +4,7 @@ ## ChromeNavControls.registerLeft() method -Register a nav control to be presented on the left side of the chrome header. +Register a nav control to be presented on the bottom-left side of the chrome header. Signature: diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.registerright.md b/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.registerright.md index 12058f1d16ab9..eb56e0e38c6c9 100644 --- a/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.registerright.md +++ b/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.registerright.md @@ -4,7 +4,7 @@ ## ChromeNavControls.registerRight() method -Register a nav control to be presented on the right side of the chrome header. +Register a nav control to be presented on the top-right side of the chrome header. Signature: diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.active.md b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.active.md deleted file mode 100644 index fb8a6eb691b42..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.active.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ChromeNavLink](./kibana-plugin-core-public.chromenavlink.md) > [active](./kibana-plugin-core-public.chromenavlink.active.md) - -## ChromeNavLink.active property - -> Warning: This API is now obsolete. -> -> - -Indicates whether or not this app is currently on the screen. - -Signature: - -```typescript -readonly active?: boolean; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.disabled.md b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.disabled.md index 9e1aefb79ad39..2b4d22be187f9 100644 --- a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.disabled.md +++ b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.disabled.md @@ -4,10 +4,6 @@ ## ChromeNavLink.disabled property -> Warning: This API is now obsolete. -> -> - Disables a link from being clickable. Signature: diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.disablesuburltracking.md b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.disablesuburltracking.md deleted file mode 100644 index 843fd959d262a..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.disablesuburltracking.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ChromeNavLink](./kibana-plugin-core-public.chromenavlink.md) > [disableSubUrlTracking](./kibana-plugin-core-public.chromenavlink.disablesuburltracking.md) - -## ChromeNavLink.disableSubUrlTracking property - -> Warning: This API is now obsolete. -> -> - -A flag that tells legacy chrome to ignore the link when tracking sub-urls - -Signature: - -```typescript -readonly disableSubUrlTracking?: boolean; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.href.md b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.href.md index a8af0c997ca78..f51fa7e5b1355 100644 --- a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.href.md +++ b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.href.md @@ -9,5 +9,5 @@ Settled state between `url`, `baseUrl`, and `active` Signature: ```typescript -readonly href?: string; +readonly href: string; ``` diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.linktolastsuburl.md b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.linktolastsuburl.md deleted file mode 100644 index 0b6d6ae129744..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.linktolastsuburl.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ChromeNavLink](./kibana-plugin-core-public.chromenavlink.md) > [linkToLastSubUrl](./kibana-plugin-core-public.chromenavlink.linktolastsuburl.md) - -## ChromeNavLink.linkToLastSubUrl property - -> Warning: This API is now obsolete. -> -> - -Whether or not the subUrl feature should be enabled. - -Signature: - -```typescript -readonly linkToLastSubUrl?: boolean; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.md b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.md index 0349e865bff97..dfe8f119505aa 100644 --- a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.md +++ b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.md @@ -15,20 +15,16 @@ export interface ChromeNavLink | Property | Type | Description | | --- | --- | --- | -| [active](./kibana-plugin-core-public.chromenavlink.active.md) | boolean | Indicates whether or not this app is currently on the screen. | | [baseUrl](./kibana-plugin-core-public.chromenavlink.baseurl.md) | string | The base route used to open the root of an application. | | [category](./kibana-plugin-core-public.chromenavlink.category.md) | AppCategory | The category the app lives in | | [disabled](./kibana-plugin-core-public.chromenavlink.disabled.md) | boolean | Disables a link from being clickable. | -| [disableSubUrlTracking](./kibana-plugin-core-public.chromenavlink.disablesuburltracking.md) | boolean | A flag that tells legacy chrome to ignore the link when tracking sub-urls | | [euiIconType](./kibana-plugin-core-public.chromenavlink.euiicontype.md) | string | A EUI iconType that will be used for the app's icon. This icon takes precedence over the icon property. | | [hidden](./kibana-plugin-core-public.chromenavlink.hidden.md) | boolean | Hides a link from the navigation. | | [href](./kibana-plugin-core-public.chromenavlink.href.md) | string | Settled state between url, baseUrl, and active | | [icon](./kibana-plugin-core-public.chromenavlink.icon.md) | string | A URL to an image file used as an icon. Used as a fallback if euiIconType is not provided. | | [id](./kibana-plugin-core-public.chromenavlink.id.md) | string | A unique identifier for looking up links. | -| [linkToLastSubUrl](./kibana-plugin-core-public.chromenavlink.linktolastsuburl.md) | boolean | Whether or not the subUrl feature should be enabled. | | [order](./kibana-plugin-core-public.chromenavlink.order.md) | number | An ordinal used to sort nav links relative to one another for display. | -| [subUrlBase](./kibana-plugin-core-public.chromenavlink.suburlbase.md) | string | A url base that legacy apps can set to match deep URLs to an application. | | [title](./kibana-plugin-core-public.chromenavlink.title.md) | string | The title of the application. | | [tooltip](./kibana-plugin-core-public.chromenavlink.tooltip.md) | string | A tooltip shown when hovering over an app link. | -| [url](./kibana-plugin-core-public.chromenavlink.url.md) | string | The route used to open the [default path](./kibana-plugin-core-public.appbase.defaultpath.md) of an application. If unset, baseUrl will be used instead. | +| [url](./kibana-plugin-core-public.chromenavlink.url.md) | string | The route used to open the of an application. If unset, baseUrl will be used instead. | diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.suburlbase.md b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.suburlbase.md deleted file mode 100644 index 047a1d83b137f..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.suburlbase.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ChromeNavLink](./kibana-plugin-core-public.chromenavlink.md) > [subUrlBase](./kibana-plugin-core-public.chromenavlink.suburlbase.md) - -## ChromeNavLink.subUrlBase property - -> Warning: This API is now obsolete. -> -> - -A url base that legacy apps can set to match deep URLs to an application. - -Signature: - -```typescript -readonly subUrlBase?: string; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.url.md b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.url.md index 1e0b890015993..833930c494786 100644 --- a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.url.md +++ b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.url.md @@ -4,7 +4,7 @@ ## ChromeNavLink.url property -The route used to open the [default path](./kibana-plugin-core-public.appbase.defaultpath.md) of an application. If unset, `baseUrl` will be used instead. +The route used to open the of an application. If unset, `baseUrl` will be used instead. Signature: diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavlinks.update.md b/docs/development/core/public/kibana-plugin-core-public.chromenavlinks.update.md index 5741a4c98f895..7948f2f8543fd 100644 --- a/docs/development/core/public/kibana-plugin-core-public.chromenavlinks.update.md +++ b/docs/development/core/public/kibana-plugin-core-public.chromenavlinks.update.md @@ -6,7 +6,7 @@ > Warning: This API is now obsolete. > -> Uses the [AppBase.updater$](./kibana-plugin-core-public.appbase.updater_.md) property when registering your application with [ApplicationSetup.register()](./kibana-plugin-core-public.applicationsetup.register.md) instead. +> Uses the property when registering your application with [ApplicationSetup.register()](./kibana-plugin-core-public.applicationsetup.register.md) instead. > Update the navlink for the given id with the updated attributes. Returns the updated navlink or `undefined` if it does not exist. diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavlinkupdateablefields.md b/docs/development/core/public/kibana-plugin-core-public.chromenavlinkupdateablefields.md index bd5a1399cded7..0445bb28bb355 100644 --- a/docs/development/core/public/kibana-plugin-core-public.chromenavlinkupdateablefields.md +++ b/docs/development/core/public/kibana-plugin-core-public.chromenavlinkupdateablefields.md @@ -8,5 +8,5 @@ Signature: ```typescript -export declare type ChromeNavLinkUpdateableFields = Partial>; +export declare type ChromeNavLinkUpdateableFields = Partial>; ``` diff --git a/docs/development/core/public/kibana-plugin-core-public.chromestart.getnavtype_.md b/docs/development/core/public/kibana-plugin-core-public.chromestart.getnavtype_.md deleted file mode 100644 index 09864be43996d..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.chromestart.getnavtype_.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ChromeStart](./kibana-plugin-core-public.chromestart.md) > [getNavType$](./kibana-plugin-core-public.chromestart.getnavtype_.md) - -## ChromeStart.getNavType$() method - -Get the navigation type TODO \#64541 Can delete - -Signature: - -```typescript -getNavType$(): Observable; -``` -Returns: - -`Observable` - diff --git a/docs/development/core/public/kibana-plugin-core-public.chromestart.md b/docs/development/core/public/kibana-plugin-core-public.chromestart.md index e983ad50d2afe..2594848ef0847 100644 --- a/docs/development/core/public/kibana-plugin-core-public.chromestart.md +++ b/docs/development/core/public/kibana-plugin-core-public.chromestart.md @@ -59,7 +59,6 @@ core.chrome.setHelpExtension(elem => { | [getHelpExtension$()](./kibana-plugin-core-public.chromestart.gethelpextension_.md) | Get an observable of the current custom help conttent | | [getIsNavDrawerLocked$()](./kibana-plugin-core-public.chromestart.getisnavdrawerlocked_.md) | Get an observable of the current locked state of the nav drawer. | | [getIsVisible$()](./kibana-plugin-core-public.chromestart.getisvisible_.md) | Get an observable of the current visibility state of the chrome. | -| [getNavType$()](./kibana-plugin-core-public.chromestart.getnavtype_.md) | Get the navigation type TODO \#64541 Can delete | | [removeApplicationClass(className)](./kibana-plugin-core-public.chromestart.removeapplicationclass.md) | Remove a className added with addApplicationClass(). If className is unknown it is ignored. | | [setAppTitle(appTitle)](./kibana-plugin-core-public.chromestart.setapptitle.md) | Sets the current app's title | | [setBadge(badge)](./kibana-plugin-core-public.chromestart.setbadge.md) | Override the current badge | diff --git a/docs/development/core/public/kibana-plugin-core-public.coresetup.injectedmetadata.md b/docs/development/core/public/kibana-plugin-core-public.coresetup.injectedmetadata.md index b8f2699b677b0..8c845c621e0d7 100644 --- a/docs/development/core/public/kibana-plugin-core-public.coresetup.injectedmetadata.md +++ b/docs/development/core/public/kibana-plugin-core-public.coresetup.injectedmetadata.md @@ -8,7 +8,7 @@ > > -exposed temporarily until https://github.com/elastic/kibana/issues/41990 done use \*only\* to retrieve config values. There is no way to set injected values in the new platform. Use the legacy platform API instead. +exposed temporarily until https://github.com/elastic/kibana/issues/41990 done use \*only\* to retrieve config values. There is no way to set injected values in the new platform. Signature: diff --git a/docs/development/core/public/kibana-plugin-core-public.coresetup.md b/docs/development/core/public/kibana-plugin-core-public.coresetup.md index 870fa33dce900..b9f97b83af88f 100644 --- a/docs/development/core/public/kibana-plugin-core-public.coresetup.md +++ b/docs/development/core/public/kibana-plugin-core-public.coresetup.md @@ -21,7 +21,7 @@ export interface CoreSetupFatalErrorsSetup | [FatalErrorsSetup](./kibana-plugin-core-public.fatalerrorssetup.md) | | [getStartServices](./kibana-plugin-core-public.coresetup.getstartservices.md) | StartServicesAccessor<TPluginsStart, TStart> | [StartServicesAccessor](./kibana-plugin-core-public.startservicesaccessor.md) | | [http](./kibana-plugin-core-public.coresetup.http.md) | HttpSetup | [HttpSetup](./kibana-plugin-core-public.httpsetup.md) | -| [injectedMetadata](./kibana-plugin-core-public.coresetup.injectedmetadata.md) | {
getInjectedVar: (name: string, defaultValue?: any) => unknown;
} | exposed temporarily until https://github.com/elastic/kibana/issues/41990 done use \*only\* to retrieve config values. There is no way to set injected values in the new platform. Use the legacy platform API instead. | +| [injectedMetadata](./kibana-plugin-core-public.coresetup.injectedmetadata.md) | {
getInjectedVar: (name: string, defaultValue?: any) => unknown;
} | exposed temporarily until https://github.com/elastic/kibana/issues/41990 done use \*only\* to retrieve config values. There is no way to set injected values in the new platform. | | [notifications](./kibana-plugin-core-public.coresetup.notifications.md) | NotificationsSetup | [NotificationsSetup](./kibana-plugin-core-public.notificationssetup.md) | | [uiSettings](./kibana-plugin-core-public.coresetup.uisettings.md) | IUiSettingsClient | [IUiSettingsClient](./kibana-plugin-core-public.iuisettingsclient.md) | diff --git a/docs/development/core/public/kibana-plugin-core-public.corestart.injectedmetadata.md b/docs/development/core/public/kibana-plugin-core-public.corestart.injectedmetadata.md index 45f9349ae8c61..4e9bf7c4bc0d5 100644 --- a/docs/development/core/public/kibana-plugin-core-public.corestart.injectedmetadata.md +++ b/docs/development/core/public/kibana-plugin-core-public.corestart.injectedmetadata.md @@ -8,7 +8,7 @@ > > -exposed temporarily until https://github.com/elastic/kibana/issues/41990 done use \*only\* to retrieve config values. There is no way to set injected values in the new platform. Use the legacy platform API instead. +exposed temporarily until https://github.com/elastic/kibana/issues/41990 done use \*only\* to retrieve config values. There is no way to set injected values in the new platform. Signature: diff --git a/docs/development/core/public/kibana-plugin-core-public.corestart.md b/docs/development/core/public/kibana-plugin-core-public.corestart.md index cb4a825a825b1..a7b45b318d2c9 100644 --- a/docs/development/core/public/kibana-plugin-core-public.corestart.md +++ b/docs/development/core/public/kibana-plugin-core-public.corestart.md @@ -22,7 +22,7 @@ export interface CoreStart | [fatalErrors](./kibana-plugin-core-public.corestart.fatalerrors.md) | FatalErrorsStart | [FatalErrorsStart](./kibana-plugin-core-public.fatalerrorsstart.md) | | [http](./kibana-plugin-core-public.corestart.http.md) | HttpStart | [HttpStart](./kibana-plugin-core-public.httpstart.md) | | [i18n](./kibana-plugin-core-public.corestart.i18n.md) | I18nStart | [I18nStart](./kibana-plugin-core-public.i18nstart.md) | -| [injectedMetadata](./kibana-plugin-core-public.corestart.injectedmetadata.md) | {
getInjectedVar: (name: string, defaultValue?: any) => unknown;
} | exposed temporarily until https://github.com/elastic/kibana/issues/41990 done use \*only\* to retrieve config values. There is no way to set injected values in the new platform. Use the legacy platform API instead. | +| [injectedMetadata](./kibana-plugin-core-public.corestart.injectedmetadata.md) | {
getInjectedVar: (name: string, defaultValue?: any) => unknown;
} | exposed temporarily until https://github.com/elastic/kibana/issues/41990 done use \*only\* to retrieve config values. There is no way to set injected values in the new platform. | | [notifications](./kibana-plugin-core-public.corestart.notifications.md) | NotificationsStart | [NotificationsStart](./kibana-plugin-core-public.notificationsstart.md) | | [overlays](./kibana-plugin-core-public.corestart.overlays.md) | OverlayStart | [OverlayStart](./kibana-plugin-core-public.overlaystart.md) | | [savedObjects](./kibana-plugin-core-public.corestart.savedobjects.md) | SavedObjectsStart | [SavedObjectsStart](./kibana-plugin-core-public.savedobjectsstart.md) | diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index 85e1da08b00af..f7b55b0650d8b 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md @@ -10,6 +10,9 @@ readonly links: { readonly dashboard: { readonly drilldowns: string; + readonly drilldownsTriggerPicker: string; + readonly urlDrilldownTemplateSyntax: string; + readonly urlDrilldownVariables: string; }; readonly filebeat: { readonly base: string; diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md index 4644dc432bc9a..3f58cf08ee6b6 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md @@ -17,5 +17,5 @@ export interface DocLinksStart | --- | --- | --- | | [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string | | | [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string | | -| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly dashboard: {
readonly drilldowns: string;
};
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly date_histogram: string;
readonly date_range: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessSyntax: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly addData: string;
readonly kibana: string;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
readonly visualize: Record<string, string>;
} | | +| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly dashboard: {
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly date_histogram: string;
readonly date_range: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessSyntax: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly addData: string;
readonly kibana: string;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
readonly visualize: Record<string, string>;
} | | diff --git a/docs/development/core/public/kibana-plugin-core-public.legacyapp.appurl.md b/docs/development/core/public/kibana-plugin-core-public.legacyapp.appurl.md deleted file mode 100644 index 292bf29962839..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.legacyapp.appurl.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyApp](./kibana-plugin-core-public.legacyapp.md) > [appUrl](./kibana-plugin-core-public.legacyapp.appurl.md) - -## LegacyApp.appUrl property - -Signature: - -```typescript -appUrl: string; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.legacyapp.disablesuburltracking.md b/docs/development/core/public/kibana-plugin-core-public.legacyapp.disablesuburltracking.md deleted file mode 100644 index af4d0eb7969d3..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.legacyapp.disablesuburltracking.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyApp](./kibana-plugin-core-public.legacyapp.md) > [disableSubUrlTracking](./kibana-plugin-core-public.legacyapp.disablesuburltracking.md) - -## LegacyApp.disableSubUrlTracking property - -Signature: - -```typescript -disableSubUrlTracking?: boolean; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.legacyapp.linktolastsuburl.md b/docs/development/core/public/kibana-plugin-core-public.legacyapp.linktolastsuburl.md deleted file mode 100644 index fa1314b74fd83..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.legacyapp.linktolastsuburl.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyApp](./kibana-plugin-core-public.legacyapp.md) > [linkToLastSubUrl](./kibana-plugin-core-public.legacyapp.linktolastsuburl.md) - -## LegacyApp.linkToLastSubUrl property - -Signature: - -```typescript -linkToLastSubUrl?: boolean; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.legacyapp.md b/docs/development/core/public/kibana-plugin-core-public.legacyapp.md deleted file mode 100644 index 06533aaa99170..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.legacyapp.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyApp](./kibana-plugin-core-public.legacyapp.md) - -## LegacyApp interface - - -Signature: - -```typescript -export interface LegacyApp extends AppBase -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [appUrl](./kibana-plugin-core-public.legacyapp.appurl.md) | string | | -| [disableSubUrlTracking](./kibana-plugin-core-public.legacyapp.disablesuburltracking.md) | boolean | | -| [linkToLastSubUrl](./kibana-plugin-core-public.legacyapp.linktolastsuburl.md) | boolean | | -| [subUrlBase](./kibana-plugin-core-public.legacyapp.suburlbase.md) | string | | - diff --git a/docs/development/core/public/kibana-plugin-core-public.legacyapp.suburlbase.md b/docs/development/core/public/kibana-plugin-core-public.legacyapp.suburlbase.md deleted file mode 100644 index 44a1e52ccd244..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.legacyapp.suburlbase.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyApp](./kibana-plugin-core-public.legacyapp.md) > [subUrlBase](./kibana-plugin-core-public.legacyapp.suburlbase.md) - -## LegacyApp.subUrlBase property - -Signature: - -```typescript -subUrlBase?: string; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.legacycoresetup.injectedmetadata.md b/docs/development/core/public/kibana-plugin-core-public.legacycoresetup.injectedmetadata.md deleted file mode 100644 index 4014d27907e98..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.legacycoresetup.injectedmetadata.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyCoreSetup](./kibana-plugin-core-public.legacycoresetup.md) > [injectedMetadata](./kibana-plugin-core-public.legacycoresetup.injectedmetadata.md) - -## LegacyCoreSetup.injectedMetadata property - -> Warning: This API is now obsolete. -> -> - -Signature: - -```typescript -injectedMetadata: InjectedMetadataSetup; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.legacycoresetup.md b/docs/development/core/public/kibana-plugin-core-public.legacycoresetup.md deleted file mode 100644 index 26220accbfaf3..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.legacycoresetup.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyCoreSetup](./kibana-plugin-core-public.legacycoresetup.md) - -## LegacyCoreSetup interface - -> Warning: This API is now obsolete. -> -> - -Setup interface exposed to the legacy platform via the `ui/new_platform` module. - -Signature: - -```typescript -export interface LegacyCoreSetup extends CoreSetup -``` - -## Remarks - -Some methods are not supported in the legacy platform and while present to make this type compatibile with [CoreSetup](./kibana-plugin-core-public.coresetup.md), unsupported methods will throw exceptions when called. - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [injectedMetadata](./kibana-plugin-core-public.legacycoresetup.injectedmetadata.md) | InjectedMetadataSetup | | - diff --git a/docs/development/core/public/kibana-plugin-core-public.legacycorestart.injectedmetadata.md b/docs/development/core/public/kibana-plugin-core-public.legacycorestart.injectedmetadata.md deleted file mode 100644 index 288b288b1814d..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.legacycorestart.injectedmetadata.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyCoreStart](./kibana-plugin-core-public.legacycorestart.md) > [injectedMetadata](./kibana-plugin-core-public.legacycorestart.injectedmetadata.md) - -## LegacyCoreStart.injectedMetadata property - -> Warning: This API is now obsolete. -> -> - -Signature: - -```typescript -injectedMetadata: InjectedMetadataStart; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.legacycorestart.md b/docs/development/core/public/kibana-plugin-core-public.legacycorestart.md deleted file mode 100644 index 7714d0f325d2c..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.legacycorestart.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyCoreStart](./kibana-plugin-core-public.legacycorestart.md) - -## LegacyCoreStart interface - -> Warning: This API is now obsolete. -> -> - -Start interface exposed to the legacy platform via the `ui/new_platform` module. - -Signature: - -```typescript -export interface LegacyCoreStart extends CoreStart -``` - -## Remarks - -Some methods are not supported in the legacy platform and while present to make this type compatibile with [CoreStart](./kibana-plugin-core-public.corestart.md), unsupported methods will throw exceptions when called. - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [injectedMetadata](./kibana-plugin-core-public.legacycorestart.injectedmetadata.md) | InjectedMetadataStart | | - diff --git a/docs/development/core/public/kibana-plugin-core-public.legacynavlink.category.md b/docs/development/core/public/kibana-plugin-core-public.legacynavlink.category.md deleted file mode 100644 index a70aac70067de..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.legacynavlink.category.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyNavLink](./kibana-plugin-core-public.legacynavlink.md) > [category](./kibana-plugin-core-public.legacynavlink.category.md) - -## LegacyNavLink.category property - -Signature: - -```typescript -category?: AppCategory; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.legacynavlink.euiicontype.md b/docs/development/core/public/kibana-plugin-core-public.legacynavlink.euiicontype.md deleted file mode 100644 index b360578f98cf1..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.legacynavlink.euiicontype.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyNavLink](./kibana-plugin-core-public.legacynavlink.md) > [euiIconType](./kibana-plugin-core-public.legacynavlink.euiicontype.md) - -## LegacyNavLink.euiIconType property - -Signature: - -```typescript -euiIconType?: string; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.legacynavlink.icon.md b/docs/development/core/public/kibana-plugin-core-public.legacynavlink.icon.md deleted file mode 100644 index c2c6f89be0d78..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.legacynavlink.icon.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyNavLink](./kibana-plugin-core-public.legacynavlink.md) > [icon](./kibana-plugin-core-public.legacynavlink.icon.md) - -## LegacyNavLink.icon property - -Signature: - -```typescript -icon?: string; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.legacynavlink.id.md b/docs/development/core/public/kibana-plugin-core-public.legacynavlink.id.md deleted file mode 100644 index fc79b6b4bd6dd..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.legacynavlink.id.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyNavLink](./kibana-plugin-core-public.legacynavlink.md) > [id](./kibana-plugin-core-public.legacynavlink.id.md) - -## LegacyNavLink.id property - -Signature: - -```typescript -id: string; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.legacynavlink.md b/docs/development/core/public/kibana-plugin-core-public.legacynavlink.md deleted file mode 100644 index b6402f991f965..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.legacynavlink.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyNavLink](./kibana-plugin-core-public.legacynavlink.md) - -## LegacyNavLink interface - - -Signature: - -```typescript -export interface LegacyNavLink -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [category](./kibana-plugin-core-public.legacynavlink.category.md) | AppCategory | | -| [euiIconType](./kibana-plugin-core-public.legacynavlink.euiicontype.md) | string | | -| [icon](./kibana-plugin-core-public.legacynavlink.icon.md) | string | | -| [id](./kibana-plugin-core-public.legacynavlink.id.md) | string | | -| [order](./kibana-plugin-core-public.legacynavlink.order.md) | number | | -| [title](./kibana-plugin-core-public.legacynavlink.title.md) | string | | -| [url](./kibana-plugin-core-public.legacynavlink.url.md) | string | | - diff --git a/docs/development/core/public/kibana-plugin-core-public.legacynavlink.order.md b/docs/development/core/public/kibana-plugin-core-public.legacynavlink.order.md deleted file mode 100644 index 6ad3081b81d4b..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.legacynavlink.order.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyNavLink](./kibana-plugin-core-public.legacynavlink.md) > [order](./kibana-plugin-core-public.legacynavlink.order.md) - -## LegacyNavLink.order property - -Signature: - -```typescript -order: number; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.legacynavlink.title.md b/docs/development/core/public/kibana-plugin-core-public.legacynavlink.title.md deleted file mode 100644 index 70b0e37729f26..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.legacynavlink.title.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyNavLink](./kibana-plugin-core-public.legacynavlink.md) > [title](./kibana-plugin-core-public.legacynavlink.title.md) - -## LegacyNavLink.title property - -Signature: - -```typescript -title: string; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.legacynavlink.url.md b/docs/development/core/public/kibana-plugin-core-public.legacynavlink.url.md deleted file mode 100644 index 7e543f4a90c1d..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.legacynavlink.url.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [LegacyNavLink](./kibana-plugin-core-public.legacynavlink.md) > [url](./kibana-plugin-core-public.legacynavlink.url.md) - -## LegacyNavLink.url property - -Signature: - -```typescript -url: string; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.md b/docs/development/core/public/kibana-plugin-core-public.md index c931ce544f5d5..08b12190ef638 100644 --- a/docs/development/core/public/kibana-plugin-core-public.md +++ b/docs/development/core/public/kibana-plugin-core-public.md @@ -41,8 +41,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | Interface | Description | | --- | --- | -| [App](./kibana-plugin-core-public.app.md) | Extension of [common app properties](./kibana-plugin-core-public.appbase.md) with the mount function. | -| [AppBase](./kibana-plugin-core-public.appbase.md) | | +| [App](./kibana-plugin-core-public.app.md) | | | [AppCategory](./kibana-plugin-core-public.appcategory.md) | A category definition for nav links to know where to sort them in the left hand nav | | [AppLeaveConfirmAction](./kibana-plugin-core-public.appleaveconfirmaction.md) | Action to return from a [AppLeaveHandler](./kibana-plugin-core-public.appleavehandler.md) to show a confirmation message when trying to leave an application.See | | [AppLeaveDefaultAction](./kibana-plugin-core-public.appleavedefaultaction.md) | Action to return from a [AppLeaveHandler](./kibana-plugin-core-public.appleavehandler.md) to execute the default behaviour when leaving the application.See | @@ -90,10 +89,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [IHttpResponseInterceptorOverrides](./kibana-plugin-core-public.ihttpresponseinterceptoroverrides.md) | Properties that can be returned by HttpInterceptor.request to override the response. | | [ImageValidation](./kibana-plugin-core-public.imagevalidation.md) | | | [IUiSettingsClient](./kibana-plugin-core-public.iuisettingsclient.md) | Client-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. [IUiSettingsClient](./kibana-plugin-core-public.iuisettingsclient.md) | -| [LegacyApp](./kibana-plugin-core-public.legacyapp.md) | | -| [LegacyCoreSetup](./kibana-plugin-core-public.legacycoresetup.md) | Setup interface exposed to the legacy platform via the ui/new_platform module. | -| [LegacyCoreStart](./kibana-plugin-core-public.legacycorestart.md) | Start interface exposed to the legacy platform via the ui/new_platform module. | -| [LegacyNavLink](./kibana-plugin-core-public.legacynavlink.md) | | | [NavigateToAppOptions](./kibana-plugin-core-public.navigatetoappoptions.md) | Options for the [navigateToApp API](./kibana-plugin-core-public.applicationstart.navigatetoapp.md) | | [NotificationsSetup](./kibana-plugin-core-public.notificationssetup.md) | | | [NotificationsStart](./kibana-plugin-core-public.notificationsstart.md) | | @@ -173,7 +168,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [PluginInitializer](./kibana-plugin-core-public.plugininitializer.md) | The plugin export at the root of a plugin's public directory should conform to this interface. | | [PluginOpaqueId](./kibana-plugin-core-public.pluginopaqueid.md) | | | [PublicAppInfo](./kibana-plugin-core-public.publicappinfo.md) | Public information about a registered [application](./kibana-plugin-core-public.app.md) | -| [PublicLegacyAppInfo](./kibana-plugin-core-public.publiclegacyappinfo.md) | Information about a registered [legacy application](./kibana-plugin-core-public.legacyapp.md) | | [PublicUiSettingsParams](./kibana-plugin-core-public.publicuisettingsparams.md) | A sub-set of [UiSettingsParams](./kibana-plugin-core-public.uisettingsparams.md) exposed to the client-side. | | [SavedObjectAttribute](./kibana-plugin-core-public.savedobjectattribute.md) | Type definition for a Saved Object attribute value | | [SavedObjectAttributeSingle](./kibana-plugin-core-public.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-core-public.savedobjectattribute.md) | diff --git a/docs/development/core/public/kibana-plugin-core-public.navigatetoappoptions.md b/docs/development/core/public/kibana-plugin-core-public.navigatetoappoptions.md index aa51e5706e3d7..b7c01fae4314f 100644 --- a/docs/development/core/public/kibana-plugin-core-public.navigatetoappoptions.md +++ b/docs/development/core/public/kibana-plugin-core-public.navigatetoappoptions.md @@ -16,7 +16,7 @@ export interface NavigateToAppOptions | Property | Type | Description | | --- | --- | --- | -| [path](./kibana-plugin-core-public.navigatetoappoptions.path.md) | string | optional path inside application to deep link to. If undefined, will use [the app's default path](./kibana-plugin-core-public.appbase.defaultpath.md)\` as default. | +| [path](./kibana-plugin-core-public.navigatetoappoptions.path.md) | string | optional path inside application to deep link to. If undefined, will use [the app's default path](./kibana-plugin-core-public.app.defaultpath.md)\` as default. | | [replace](./kibana-plugin-core-public.navigatetoappoptions.replace.md) | boolean | if true, will not create a new history entry when navigating (using replace instead of push) | | [state](./kibana-plugin-core-public.navigatetoappoptions.state.md) | unknown | optional state to forward to the application | diff --git a/docs/development/core/public/kibana-plugin-core-public.navigatetoappoptions.path.md b/docs/development/core/public/kibana-plugin-core-public.navigatetoappoptions.path.md index 58ce7e02d8dd8..095553d05778c 100644 --- a/docs/development/core/public/kibana-plugin-core-public.navigatetoappoptions.path.md +++ b/docs/development/core/public/kibana-plugin-core-public.navigatetoappoptions.path.md @@ -4,7 +4,7 @@ ## NavigateToAppOptions.path property -optional path inside application to deep link to. If undefined, will use [the app's default path](./kibana-plugin-core-public.appbase.defaultpath.md)\` as default. +optional path inside application to deep link to. If undefined, will use [the app's default path](./kibana-plugin-core-public.app.defaultpath.md)\` as default. Signature: diff --git a/docs/development/core/public/kibana-plugin-core-public.navigatetoappoptions.replace.md b/docs/development/core/public/kibana-plugin-core-public.navigatetoappoptions.replace.md index 9530d03486299..8a7440025aedc 100644 --- a/docs/development/core/public/kibana-plugin-core-public.navigatetoappoptions.replace.md +++ b/docs/development/core/public/kibana-plugin-core-public.navigatetoappoptions.replace.md @@ -11,8 +11,3 @@ if true, will not create a new history entry when navigating (using `replace` in ```typescript replace?: boolean; ``` - -## Remarks - -This option not be used when navigating from and/or to legacy applications. - diff --git a/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md b/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md index 4b3b103c92731..3717dc847db25 100644 --- a/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md +++ b/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md @@ -10,7 +10,6 @@ Public information about a registered [application](./kibana-plugin-core-public. ```typescript export declare type PublicAppInfo = Omit & { - legacy: false; status: AppStatus; navLinkStatus: AppNavLinkStatus; appRoute: string; diff --git a/docs/development/core/public/kibana-plugin-core-public.publiclegacyappinfo.md b/docs/development/core/public/kibana-plugin-core-public.publiclegacyappinfo.md deleted file mode 100644 index 051638daabd12..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.publiclegacyappinfo.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [PublicLegacyAppInfo](./kibana-plugin-core-public.publiclegacyappinfo.md) - -## PublicLegacyAppInfo type - -Information about a registered [legacy application](./kibana-plugin-core-public.legacyapp.md) - -Signature: - -```typescript -export declare type PublicLegacyAppInfo = Omit & { - legacy: true; - status: AppStatus; - navLinkStatus: AppNavLinkStatus; -}; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.appenderconfigtype.md b/docs/development/core/server/kibana-plugin-core-server.appenderconfigtype.md index 9c70e658014b3..0838572f26f49 100644 --- a/docs/development/core/server/kibana-plugin-core-server.appenderconfigtype.md +++ b/docs/development/core/server/kibana-plugin-core-server.appenderconfigtype.md @@ -8,5 +8,5 @@ Signature: ```typescript -export declare type AppenderConfigType = TypeOf; +export declare type AppenderConfigType = ConsoleAppenderConfig | FileAppenderConfig | LegacyAppenderConfig; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 89330d2a86f76..c16600d1d0492 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -28,6 +28,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsErrorHelpers](./kibana-plugin-core-server.savedobjectserrorhelpers.md) | | | [SavedObjectsRepository](./kibana-plugin-core-server.savedobjectsrepository.md) | | | [SavedObjectsSerializer](./kibana-plugin-core-server.savedobjectsserializer.md) | A serializer that can be used to manually convert [raw](./kibana-plugin-core-server.savedobjectsrawdoc.md) or [sanitized](./kibana-plugin-core-server.savedobjectsanitizeddoc.md) documents to the other kind. | +| [SavedObjectsUtils](./kibana-plugin-core-server.savedobjectsutils.md) | | | [SavedObjectTypeRegistry](./kibana-plugin-core-server.savedobjecttyperegistry.md) | Registry holding information about all the registered [saved object types](./kibana-plugin-core-server.savedobjectstype.md). | ## Enumerations @@ -123,7 +124,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [LoggerFactory](./kibana-plugin-core-server.loggerfactory.md) | The single purpose of LoggerFactory interface is to define a way to retrieve a context-based logger instance. | | [LoggingServiceSetup](./kibana-plugin-core-server.loggingservicesetup.md) | Provides APIs to plugins for customizing the plugin's logger. | | [LogMeta](./kibana-plugin-core-server.logmeta.md) | Contextual metadata | -| [MetricsServiceSetup](./kibana-plugin-core-server.metricsservicesetup.md) | | +| [MetricsServiceSetup](./kibana-plugin-core-server.metricsservicesetup.md) | APIs to retrieves metrics gathered and exposed by the core platform. | | [NodesVersionCompatibility](./kibana-plugin-core-server.nodesversioncompatibility.md) | | | [OnPostAuthToolkit](./kibana-plugin-core-server.onpostauthtoolkit.md) | A tool set defining an outcome of OnPostAuth interceptor for incoming request. | | [OnPreAuthToolkit](./kibana-plugin-core-server.onpreauthtoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. | diff --git a/docs/development/core/server/kibana-plugin-core-server.metricsservicesetup.collectioninterval.md b/docs/development/core/server/kibana-plugin-core-server.metricsservicesetup.collectioninterval.md new file mode 100644 index 0000000000000..6f05526b66c83 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.metricsservicesetup.collectioninterval.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [MetricsServiceSetup](./kibana-plugin-core-server.metricsservicesetup.md) > [collectionInterval](./kibana-plugin-core-server.metricsservicesetup.collectioninterval.md) + +## MetricsServiceSetup.collectionInterval property + +Interval metrics are collected in milliseconds + +Signature: + +```typescript +readonly collectionInterval: number; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.metricsservicesetup.getopsmetrics_.md b/docs/development/core/server/kibana-plugin-core-server.metricsservicesetup.getopsmetrics_.md new file mode 100644 index 0000000000000..61107fbf20ad9 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.metricsservicesetup.getopsmetrics_.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [MetricsServiceSetup](./kibana-plugin-core-server.metricsservicesetup.md) > [getOpsMetrics$](./kibana-plugin-core-server.metricsservicesetup.getopsmetrics_.md) + +## MetricsServiceSetup.getOpsMetrics$ property + +Retrieve an observable emitting the [OpsMetrics](./kibana-plugin-core-server.opsmetrics.md) gathered. The observable will emit an initial value during core's `start` phase, and a new value every fixed interval of time, based on the `opts.interval` configuration property. + +Signature: + +```typescript +getOpsMetrics$: () => Observable; +``` + +## Example + + +```ts +core.metrics.getOpsMetrics$().subscribe(metrics => { + // do something with the metrics +}) + +``` + diff --git a/docs/development/core/server/kibana-plugin-core-server.metricsservicesetup.md b/docs/development/core/server/kibana-plugin-core-server.metricsservicesetup.md index 0bec919797b6f..5fcb1417dea0e 100644 --- a/docs/development/core/server/kibana-plugin-core-server.metricsservicesetup.md +++ b/docs/development/core/server/kibana-plugin-core-server.metricsservicesetup.md @@ -4,8 +4,18 @@ ## MetricsServiceSetup interface +APIs to retrieves metrics gathered and exposed by the core platform. + Signature: ```typescript export interface MetricsServiceSetup ``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [collectionInterval](./kibana-plugin-core-server.metricsservicesetup.collectioninterval.md) | number | Interval metrics are collected in milliseconds | +| [getOpsMetrics$](./kibana-plugin-core-server.metricsservicesetup.getopsmetrics_.md) | () => Observable<OpsMetrics> | Retrieve an observable emitting the [OpsMetrics](./kibana-plugin-core-server.opsmetrics.md) gathered. The observable will emit an initial value during core's start phase, and a new value every fixed interval of time, based on the opts.interval configuration property. | + diff --git a/docs/development/core/server/kibana-plugin-core-server.opsmetrics.collected_at.md b/docs/development/core/server/kibana-plugin-core-server.opsmetrics.collected_at.md new file mode 100644 index 0000000000000..25125569b7b38 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.opsmetrics.collected_at.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [OpsMetrics](./kibana-plugin-core-server.opsmetrics.md) > [collected\_at](./kibana-plugin-core-server.opsmetrics.collected_at.md) + +## OpsMetrics.collected\_at property + +Time metrics were recorded at. + +Signature: + +```typescript +collected_at: Date; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.opsmetrics.md b/docs/development/core/server/kibana-plugin-core-server.opsmetrics.md index d2d4782385c06..9803c0fbd53cc 100644 --- a/docs/development/core/server/kibana-plugin-core-server.opsmetrics.md +++ b/docs/development/core/server/kibana-plugin-core-server.opsmetrics.md @@ -16,6 +16,7 @@ export interface OpsMetrics | Property | Type | Description | | --- | --- | --- | +| [collected\_at](./kibana-plugin-core-server.opsmetrics.collected_at.md) | Date | Time metrics were recorded at. | | [concurrent\_connections](./kibana-plugin-core-server.opsmetrics.concurrent_connections.md) | OpsServerMetrics['concurrent_connections'] | number of current concurrent connections to the server | | [os](./kibana-plugin-core-server.opsmetrics.os.md) | OpsOsMetrics | OS related metrics | | [process](./kibana-plugin-core-server.opsmetrics.process.md) | OpsProcessMetrics | Process related metrics | diff --git a/docs/development/core/server/kibana-plugin-core-server.opsosmetrics.cpu.md b/docs/development/core/server/kibana-plugin-core-server.opsosmetrics.cpu.md new file mode 100644 index 0000000000000..095c45266a251 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.opsosmetrics.cpu.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [OpsOsMetrics](./kibana-plugin-core-server.opsosmetrics.md) > [cpu](./kibana-plugin-core-server.opsosmetrics.cpu.md) + +## OpsOsMetrics.cpu property + +cpu cgroup metrics, undefined when not running in a cgroup + +Signature: + +```typescript +cpu?: { + control_group: string; + cfs_period_micros: number; + cfs_quota_micros: number; + stat: { + number_of_elapsed_periods: number; + number_of_times_throttled: number; + time_throttled_nanos: number; + }; + }; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.opsosmetrics.cpuacct.md b/docs/development/core/server/kibana-plugin-core-server.opsosmetrics.cpuacct.md new file mode 100644 index 0000000000000..140646a0d1a35 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.opsosmetrics.cpuacct.md @@ -0,0 +1,16 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [OpsOsMetrics](./kibana-plugin-core-server.opsosmetrics.md) > [cpuacct](./kibana-plugin-core-server.opsosmetrics.cpuacct.md) + +## OpsOsMetrics.cpuacct property + +cpu accounting metrics, undefined when not running in a cgroup + +Signature: + +```typescript +cpuacct?: { + control_group: string; + usage_nanos: number; + }; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.opsosmetrics.md b/docs/development/core/server/kibana-plugin-core-server.opsosmetrics.md index 5fedb76a9c8d7..8938608531139 100644 --- a/docs/development/core/server/kibana-plugin-core-server.opsosmetrics.md +++ b/docs/development/core/server/kibana-plugin-core-server.opsosmetrics.md @@ -16,6 +16,8 @@ export interface OpsOsMetrics | Property | Type | Description | | --- | --- | --- | +| [cpu](./kibana-plugin-core-server.opsosmetrics.cpu.md) | {
control_group: string;
cfs_period_micros: number;
cfs_quota_micros: number;
stat: {
number_of_elapsed_periods: number;
number_of_times_throttled: number;
time_throttled_nanos: number;
};
} | cpu cgroup metrics, undefined when not running in a cgroup | +| [cpuacct](./kibana-plugin-core-server.opsosmetrics.cpuacct.md) | {
control_group: string;
usage_nanos: number;
} | cpu accounting metrics, undefined when not running in a cgroup | | [distro](./kibana-plugin-core-server.opsosmetrics.distro.md) | string | The os distrib. Only present for linux platforms | | [distroRelease](./kibana-plugin-core-server.opsosmetrics.distrorelease.md) | string | The os distrib release, prefixed by the os distrib. Only present for linux platforms | | [load](./kibana-plugin-core-server.opsosmetrics.load.md) | {
'1m': number;
'5m': number;
'15m': number;
} | cpu load metrics | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkupdateobject.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkupdateobject.md index e079e0fa51aac..d71eda6009284 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkupdateobject.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkupdateobject.md @@ -17,5 +17,6 @@ export interface SavedObjectsBulkUpdateObject extends PickPartial<T> | The data for a Saved Object is stored as an object in the attributes property. | | [id](./kibana-plugin-core-server.savedobjectsbulkupdateobject.id.md) | string | The ID of this Saved Object, guaranteed to be unique for all objects of the same type | +| [namespace](./kibana-plugin-core-server.savedobjectsbulkupdateobject.namespace.md) | string | Optional namespace string to use when searching for this object. If this is defined, it will supersede the namespace ID that is in [SavedObjectsBulkUpdateOptions](./kibana-plugin-core-server.savedobjectsbulkupdateoptions.md).Note: the default namespace's string representation is 'default', and its ID representation is undefined. | | [type](./kibana-plugin-core-server.savedobjectsbulkupdateobject.type.md) | string | The type of this Saved Object. Each plugin can define it's own custom Saved Object types. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkupdateobject.namespace.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkupdateobject.namespace.md new file mode 100644 index 0000000000000..544efcd3be909 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkupdateobject.namespace.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsBulkUpdateObject](./kibana-plugin-core-server.savedobjectsbulkupdateobject.md) > [namespace](./kibana-plugin-core-server.savedobjectsbulkupdateobject.namespace.md) + +## SavedObjectsBulkUpdateObject.namespace property + +Optional namespace string to use when searching for this object. If this is defined, it will supersede the namespace ID that is in [SavedObjectsBulkUpdateOptions](./kibana-plugin-core-server.savedobjectsbulkupdateoptions.md). + +Note: the default namespace's string representation is `'default'`, and its ID representation is `undefined`. + +Signature: + +```typescript +namespace?: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.md index 6ef7b991bb159..650459bfdb435 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.md @@ -16,8 +16,6 @@ export interface SavedObjectsServiceSetup When plugins access the Saved Objects client, a new client is created using the factory provided to `setClientFactory` and wrapped by all wrappers registered through `addClientWrapper`. -All the setup APIs will throw if called after the service has started, and therefor cannot be used from legacy plugin code. Legacy plugins should use the legacy savedObject service until migrated. - ## Example 1 diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md index 57c9e04966c1b..54e01d3110a2d 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md @@ -14,10 +14,6 @@ See the [mappings format](./kibana-plugin-core-server.savedobjectstypemappingdef registerType: (type: SavedObjectsType) => void; ``` -## Remarks - -The type definition is an aggregation of the legacy savedObjects `schema`, `mappings` and `migration` concepts. This API is the single entry point to register saved object types in the new platform. - ## Example diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsutils.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsutils.md new file mode 100644 index 0000000000000..e365dfbcb5142 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsutils.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsUtils](./kibana-plugin-core-server.savedobjectsutils.md) + +## SavedObjectsUtils class + + +Signature: + +```typescript +export declare class SavedObjectsUtils +``` + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [namespaceIdToString](./kibana-plugin-core-server.savedobjectsutils.namespaceidtostring.md) | static | (namespace?: string | undefined) => string | Converts a given saved object namespace ID to its string representation. All namespace IDs have an identical string representation, with the exception of the undefined namespace ID (which has a namespace string of 'default'). | +| [namespaceStringToId](./kibana-plugin-core-server.savedobjectsutils.namespacestringtoid.md) | static | (namespace: string) => string | undefined | Converts a given saved object namespace string to its ID representation. All namespace strings have an identical ID representation, with the exception of the 'default' namespace string (which has a namespace ID of undefined). | + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsutils.namespaceidtostring.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsutils.namespaceidtostring.md new file mode 100644 index 0000000000000..591505892e64f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsutils.namespaceidtostring.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsUtils](./kibana-plugin-core-server.savedobjectsutils.md) > [namespaceIdToString](./kibana-plugin-core-server.savedobjectsutils.namespaceidtostring.md) + +## SavedObjectsUtils.namespaceIdToString property + +Converts a given saved object namespace ID to its string representation. All namespace IDs have an identical string representation, with the exception of the `undefined` namespace ID (which has a namespace string of `'default'`). + +Signature: + +```typescript +static namespaceIdToString: (namespace?: string | undefined) => string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsutils.namespacestringtoid.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsutils.namespacestringtoid.md new file mode 100644 index 0000000000000..e052fe493b5ea --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsutils.namespacestringtoid.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsUtils](./kibana-plugin-core-server.savedobjectsutils.md) > [namespaceStringToId](./kibana-plugin-core-server.savedobjectsutils.namespacestringtoid.md) + +## SavedObjectsUtils.namespaceStringToId property + +Converts a given saved object namespace string to its ID representation. All namespace strings have an identical ID representation, with the exception of the `'default'` namespace string (which has a namespace ID of `undefined`). + +Signature: + +```typescript +static namespaceStringToId: (namespace: string) => string | undefined; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig._constructor_.md new file mode 100644 index 0000000000000..9287a08ff196b --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig._constructor_.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [(constructor)](./kibana-plugin-plugins-data-public.aggconfig._constructor_.md) + +## AggConfig.(constructor) + +Constructs a new instance of the `AggConfig` class + +Signature: + +```typescript +constructor(aggConfigs: IAggConfigs, opts: AggConfigOptions); +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| aggConfigs | IAggConfigs | | +| opts | AggConfigOptions | | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.aggconfigs.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.aggconfigs.md new file mode 100644 index 0000000000000..f552bbd2d1cfc --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.aggconfigs.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [aggConfigs](./kibana-plugin-plugins-data-public.aggconfig.aggconfigs.md) + +## AggConfig.aggConfigs property + +Signature: + +```typescript +aggConfigs: IAggConfigs; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.brandnew.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.brandnew.md new file mode 100644 index 0000000000000..eb1f3af4c5b01 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.brandnew.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [brandNew](./kibana-plugin-plugins-data-public.aggconfig.brandnew.md) + +## AggConfig.brandNew property + +Signature: + +```typescript +brandNew?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.createfilter.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.createfilter.md new file mode 100644 index 0000000000000..7ec0350f65321 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.createfilter.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [createFilter](./kibana-plugin-plugins-data-public.aggconfig.createfilter.md) + +## AggConfig.createFilter() method + +Signature: + +```typescript +createFilter(key: string, params?: {}): any; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| key | string | | +| params | {} | | + +Returns: + +`any` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.enabled.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.enabled.md new file mode 100644 index 0000000000000..82595ee5f5b63 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.enabled.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [enabled](./kibana-plugin-plugins-data-public.aggconfig.enabled.md) + +## AggConfig.enabled property + +Signature: + +```typescript +enabled: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.ensureids.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.ensureids.md new file mode 100644 index 0000000000000..04e0b82187a5f --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.ensureids.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [ensureIds](./kibana-plugin-plugins-data-public.aggconfig.ensureids.md) + +## AggConfig.ensureIds() method + +Ensure that all of the objects in the list have ids, the objects and list are modified by reference. + +Signature: + +```typescript +static ensureIds(list: any[]): any[]; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| list | any[] | | + +Returns: + +`any[]` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.fieldistimefield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.fieldistimefield.md new file mode 100644 index 0000000000000..a1fde4dec25b1 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.fieldistimefield.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [fieldIsTimeField](./kibana-plugin-plugins-data-public.aggconfig.fieldistimefield.md) + +## AggConfig.fieldIsTimeField() method + +Signature: + +```typescript +fieldIsTimeField(): boolean | "" | undefined; +``` +Returns: + +`boolean | "" | undefined` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.fieldname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.fieldname.md new file mode 100644 index 0000000000000..2d3acb7f026ff --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.fieldname.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [fieldName](./kibana-plugin-plugins-data-public.aggconfig.fieldname.md) + +## AggConfig.fieldName() method + +Signature: + +```typescript +fieldName(): any; +``` +Returns: + +`any` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getaggparams.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getaggparams.md new file mode 100644 index 0000000000000..f898844ff0273 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getaggparams.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [getAggParams](./kibana-plugin-plugins-data-public.aggconfig.getaggparams.md) + +## AggConfig.getAggParams() method + +Signature: + +```typescript +getAggParams(): import("./param_types/agg").AggParamType[]; +``` +Returns: + +`import("./param_types/agg").AggParamType[]` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getfield.md new file mode 100644 index 0000000000000..1fb6f88c43171 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getfield.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [getField](./kibana-plugin-plugins-data-public.aggconfig.getfield.md) + +## AggConfig.getField() method + +Signature: + +```typescript +getField(): any; +``` +Returns: + +`any` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getfielddisplayname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getfielddisplayname.md new file mode 100644 index 0000000000000..710499cee62dd --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getfielddisplayname.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [getFieldDisplayName](./kibana-plugin-plugins-data-public.aggconfig.getfielddisplayname.md) + +## AggConfig.getFieldDisplayName() method + +Signature: + +```typescript +getFieldDisplayName(): any; +``` +Returns: + +`any` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getindexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getindexpattern.md new file mode 100644 index 0000000000000..ed0e9d0fbb5de --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getindexpattern.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [getIndexPattern](./kibana-plugin-plugins-data-public.aggconfig.getindexpattern.md) + +## AggConfig.getIndexPattern() method + +Signature: + +```typescript +getIndexPattern(): import("../../../public").IndexPattern; +``` +Returns: + +`import("../../../public").IndexPattern` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getkey.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getkey.md new file mode 100644 index 0000000000000..a2a59fcf9ae31 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getkey.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [getKey](./kibana-plugin-plugins-data-public.aggconfig.getkey.md) + +## AggConfig.getKey() method + +Signature: + +```typescript +getKey(bucket: any, key?: string): any; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| bucket | any | | +| key | string | | + +Returns: + +`any` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getparam.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getparam.md new file mode 100644 index 0000000000000..ad4cd2fa175f8 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getparam.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [getParam](./kibana-plugin-plugins-data-public.aggconfig.getparam.md) + +## AggConfig.getParam() method + +Signature: + +```typescript +getParam(key: string): any; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| key | string | | + +Returns: + +`any` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getrequestaggs.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getrequestaggs.md new file mode 100644 index 0000000000000..773c2f5a7c0e9 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getrequestaggs.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [getRequestAggs](./kibana-plugin-plugins-data-public.aggconfig.getrequestaggs.md) + +## AggConfig.getRequestAggs() method + +Signature: + +```typescript +getRequestAggs(): AggConfig[]; +``` +Returns: + +`AggConfig[]` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getresponseaggs.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getresponseaggs.md new file mode 100644 index 0000000000000..cf515e68dcc57 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getresponseaggs.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [getResponseAggs](./kibana-plugin-plugins-data-public.aggconfig.getresponseaggs.md) + +## AggConfig.getResponseAggs() method + +Signature: + +```typescript +getResponseAggs(): AggConfig[]; +``` +Returns: + +`AggConfig[]` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.gettimerange.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.gettimerange.md new file mode 100644 index 0000000000000..897a6d8dda3f1 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.gettimerange.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [getTimeRange](./kibana-plugin-plugins-data-public.aggconfig.gettimerange.md) + +## AggConfig.getTimeRange() method + +Signature: + +```typescript +getTimeRange(): import("../../../public").TimeRange | undefined; +``` +Returns: + +`import("../../../public").TimeRange | undefined` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getvalue.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getvalue.md new file mode 100644 index 0000000000000..4fab1af3f6464 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.getvalue.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [getValue](./kibana-plugin-plugins-data-public.aggconfig.getvalue.md) + +## AggConfig.getValue() method + +Signature: + +```typescript +getValue(bucket: any): any; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| bucket | any | | + +Returns: + +`any` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.id.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.id.md new file mode 100644 index 0000000000000..1fa7a5c57e2a8 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.id.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [id](./kibana-plugin-plugins-data-public.aggconfig.id.md) + +## AggConfig.id property + +Signature: + +```typescript +id: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.isfilterable.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.isfilterable.md new file mode 100644 index 0000000000000..a795ab1e91c2c --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.isfilterable.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [isFilterable](./kibana-plugin-plugins-data-public.aggconfig.isfilterable.md) + +## AggConfig.isFilterable() method + +Signature: + +```typescript +isFilterable(): boolean; +``` +Returns: + +`boolean` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.makelabel.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.makelabel.md new file mode 100644 index 0000000000000..65923ed0ae889 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.makelabel.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [makeLabel](./kibana-plugin-plugins-data-public.aggconfig.makelabel.md) + +## AggConfig.makeLabel() method + +Signature: + +```typescript +makeLabel(percentageMode?: boolean): any; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| percentageMode | boolean | | + +Returns: + +`any` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.md new file mode 100644 index 0000000000000..ceb90cffbf6ca --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.md @@ -0,0 +1,62 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) + +## AggConfig class + +Signature: + +```typescript +export declare class AggConfig +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(aggConfigs, opts)](./kibana-plugin-plugins-data-public.aggconfig._constructor_.md) | | Constructs a new instance of the AggConfig class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [aggConfigs](./kibana-plugin-plugins-data-public.aggconfig.aggconfigs.md) | | IAggConfigs | | +| [brandNew](./kibana-plugin-plugins-data-public.aggconfig.brandnew.md) | | boolean | | +| [enabled](./kibana-plugin-plugins-data-public.aggconfig.enabled.md) | | boolean | | +| [id](./kibana-plugin-plugins-data-public.aggconfig.id.md) | | string | | +| [params](./kibana-plugin-plugins-data-public.aggconfig.params.md) | | any | | +| [parent](./kibana-plugin-plugins-data-public.aggconfig.parent.md) | | IAggConfigs | | +| [schema](./kibana-plugin-plugins-data-public.aggconfig.schema.md) | | string | | +| [type](./kibana-plugin-plugins-data-public.aggconfig.type.md) | | IAggType | | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [createFilter(key, params)](./kibana-plugin-plugins-data-public.aggconfig.createfilter.md) | | | +| [ensureIds(list)](./kibana-plugin-plugins-data-public.aggconfig.ensureids.md) | static | Ensure that all of the objects in the list have ids, the objects and list are modified by reference. | +| [fieldIsTimeField()](./kibana-plugin-plugins-data-public.aggconfig.fieldistimefield.md) | | | +| [fieldName()](./kibana-plugin-plugins-data-public.aggconfig.fieldname.md) | | | +| [getAggParams()](./kibana-plugin-plugins-data-public.aggconfig.getaggparams.md) | | | +| [getField()](./kibana-plugin-plugins-data-public.aggconfig.getfield.md) | | | +| [getFieldDisplayName()](./kibana-plugin-plugins-data-public.aggconfig.getfielddisplayname.md) | | | +| [getIndexPattern()](./kibana-plugin-plugins-data-public.aggconfig.getindexpattern.md) | | | +| [getKey(bucket, key)](./kibana-plugin-plugins-data-public.aggconfig.getkey.md) | | | +| [getParam(key)](./kibana-plugin-plugins-data-public.aggconfig.getparam.md) | | | +| [getRequestAggs()](./kibana-plugin-plugins-data-public.aggconfig.getrequestaggs.md) | | | +| [getResponseAggs()](./kibana-plugin-plugins-data-public.aggconfig.getresponseaggs.md) | | | +| [getTimeRange()](./kibana-plugin-plugins-data-public.aggconfig.gettimerange.md) | | | +| [getValue(bucket)](./kibana-plugin-plugins-data-public.aggconfig.getvalue.md) | | | +| [isFilterable()](./kibana-plugin-plugins-data-public.aggconfig.isfilterable.md) | | | +| [makeLabel(percentageMode)](./kibana-plugin-plugins-data-public.aggconfig.makelabel.md) | | | +| [nextId(list)](./kibana-plugin-plugins-data-public.aggconfig.nextid.md) | static | Calculate the next id based on the ids in this list {array} list - a list of objects with id properties | +| [onSearchRequestStart(searchSource, options)](./kibana-plugin-plugins-data-public.aggconfig.onsearchrequeststart.md) | | Hook for pre-flight logic, see AggType\#onSearchRequestStart | +| [serialize()](./kibana-plugin-plugins-data-public.aggconfig.serialize.md) | | | +| [setParams(from)](./kibana-plugin-plugins-data-public.aggconfig.setparams.md) | | Write the current values to this.params, filling in the defaults as we go | +| [setType(type)](./kibana-plugin-plugins-data-public.aggconfig.settype.md) | | | +| [toDsl(aggConfigs)](./kibana-plugin-plugins-data-public.aggconfig.todsl.md) | | Convert this aggConfig to its dsl syntax.Adds params and adhoc subaggs to a pojo, then returns it | +| [toExpressionAst()](./kibana-plugin-plugins-data-public.aggconfig.toexpressionast.md) | | | +| [toJSON()](./kibana-plugin-plugins-data-public.aggconfig.tojson.md) | | | +| [toSerializedFieldFormat()](./kibana-plugin-plugins-data-public.aggconfig.toserializedfieldformat.md) | | Returns a serialized field format for the field used in this agg. This can be passed to fieldFormats.deserialize to get the field format instance. | +| [write(aggs)](./kibana-plugin-plugins-data-public.aggconfig.write.md) | | | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.nextid.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.nextid.md new file mode 100644 index 0000000000000..ab524a6d1c4f1 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.nextid.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [nextId](./kibana-plugin-plugins-data-public.aggconfig.nextid.md) + +## AggConfig.nextId() method + +Calculate the next id based on the ids in this list + + {array} list - a list of objects with id properties + +Signature: + +```typescript +static nextId(list: IAggConfig[]): number; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| list | IAggConfig[] | | + +Returns: + +`number` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.onsearchrequeststart.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.onsearchrequeststart.md new file mode 100644 index 0000000000000..81df7866560e3 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.onsearchrequeststart.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [onSearchRequestStart](./kibana-plugin-plugins-data-public.aggconfig.onsearchrequeststart.md) + +## AggConfig.onSearchRequestStart() method + +Hook for pre-flight logic, see AggType\#onSearchRequestStart + +Signature: + +```typescript +onSearchRequestStart(searchSource: ISearchSource, options?: ISearchOptions): Promise | Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| searchSource | ISearchSource | | +| options | ISearchOptions | | + +Returns: + +`Promise | Promise` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.params.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.params.md new file mode 100644 index 0000000000000..5bdb67f53b519 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.params.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [params](./kibana-plugin-plugins-data-public.aggconfig.params.md) + +## AggConfig.params property + +Signature: + +```typescript +params: any; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.parent.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.parent.md new file mode 100644 index 0000000000000..53d028457a9ae --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.parent.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [parent](./kibana-plugin-plugins-data-public.aggconfig.parent.md) + +## AggConfig.parent property + +Signature: + +```typescript +parent?: IAggConfigs; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.schema.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.schema.md new file mode 100644 index 0000000000000..afbf685951356 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.schema.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [schema](./kibana-plugin-plugins-data-public.aggconfig.schema.md) + +## AggConfig.schema property + +Signature: + +```typescript +schema?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.serialize.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.serialize.md new file mode 100644 index 0000000000000..b0eebdbcc11ec --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.serialize.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [serialize](./kibana-plugin-plugins-data-public.aggconfig.serialize.md) + +## AggConfig.serialize() method + +Signature: + +```typescript +serialize(): AggConfigSerialized; +``` +Returns: + +`AggConfigSerialized` + +Returns a serialized representation of an AggConfig. + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.setparams.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.setparams.md new file mode 100644 index 0000000000000..cb495b7653f8a --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.setparams.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [setParams](./kibana-plugin-plugins-data-public.aggconfig.setparams.md) + +## AggConfig.setParams() method + +Write the current values to this.params, filling in the defaults as we go + +Signature: + +```typescript +setParams(from: any): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| from | any | | + +Returns: + +`void` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.settype.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.settype.md new file mode 100644 index 0000000000000..0b07186a6ca33 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.settype.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [setType](./kibana-plugin-plugins-data-public.aggconfig.settype.md) + +## AggConfig.setType() method + +Signature: + +```typescript +setType(type: IAggType): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | IAggType | | + +Returns: + +`void` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.todsl.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.todsl.md new file mode 100644 index 0000000000000..ac655c2a88a7b --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.todsl.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [toDsl](./kibana-plugin-plugins-data-public.aggconfig.todsl.md) + +## AggConfig.toDsl() method + +Convert this aggConfig to its dsl syntax. + +Adds params and adhoc subaggs to a pojo, then returns it + +Signature: + +```typescript +toDsl(aggConfigs?: IAggConfigs): any; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| aggConfigs | IAggConfigs | | + +Returns: + +`any` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.toexpressionast.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.toexpressionast.md new file mode 100644 index 0000000000000..99001e81fde49 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.toexpressionast.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [toExpressionAst](./kibana-plugin-plugins-data-public.aggconfig.toexpressionast.md) + +## AggConfig.toExpressionAst() method + +Signature: + +```typescript +toExpressionAst(): ExpressionAstFunction | undefined; +``` +Returns: + +`ExpressionAstFunction | undefined` + +Returns an ExpressionAst representing the function for this agg type. + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.tojson.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.tojson.md new file mode 100644 index 0000000000000..aa639aa574076 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.tojson.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [toJSON](./kibana-plugin-plugins-data-public.aggconfig.tojson.md) + +## AggConfig.toJSON() method + +> Warning: This API is now obsolete. +> +> - Use serialize() instead. +> + +Signature: + +```typescript +toJSON(): AggConfigSerialized; +``` +Returns: + +`AggConfigSerialized` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.toserializedfieldformat.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.toserializedfieldformat.md new file mode 100644 index 0000000000000..7a75950f9cc6d --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.toserializedfieldformat.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [toSerializedFieldFormat](./kibana-plugin-plugins-data-public.aggconfig.toserializedfieldformat.md) + +## AggConfig.toSerializedFieldFormat() method + +Returns a serialized field format for the field used in this agg. This can be passed to fieldFormats.deserialize to get the field format instance. + +Signature: + +```typescript +toSerializedFieldFormat(): {} | Ensure, SerializableState>; +``` +Returns: + +`{} | Ensure, SerializableState>` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.type.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.type.md new file mode 100644 index 0000000000000..9dc44caee42e8 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.type.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [type](./kibana-plugin-plugins-data-public.aggconfig.type.md) + +## AggConfig.type property + +Signature: + +```typescript +get type(): IAggType; + +set type(type: IAggType); +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.write.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.write.md new file mode 100644 index 0000000000000..f98394b57cac3 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfig.write.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) > [write](./kibana-plugin-plugins-data-public.aggconfig.write.md) + +## AggConfig.write() method + +Signature: + +```typescript +write(aggs?: IAggConfigs): Record; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| aggs | IAggConfigs | | + +Returns: + +`Record` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs._constructor_.md new file mode 100644 index 0000000000000..c9e08b9712480 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs._constructor_.md @@ -0,0 +1,32 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) > [(constructor)](./kibana-plugin-plugins-data-public.aggconfigs._constructor_.md) + +## AggConfigs.(constructor) + +Constructs a new instance of the `AggConfigs` class + +Signature: + +```typescript +constructor(indexPattern: IndexPattern, configStates: Pick & Pick<{ + type: string | IAggType; + }, "type"> & Pick<{ + type: string | IAggType; + }, never>, "enabled" | "type" | "schema" | "id" | "params">[] | undefined, opts: AggConfigsOptions); +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| indexPattern | IndexPattern | | +| configStates | Pick<Pick<{
type: string;
enabled?: boolean | undefined;
id?: string | undefined;
params?: {} | import("./agg_config").SerializableState | undefined;
schema?: string | undefined;
}, "enabled" | "schema" | "id" | "params"> & Pick<{
type: string | IAggType;
}, "type"> & Pick<{
type: string | IAggType;
}, never>, "enabled" | "type" | "schema" | "id" | "params">[] | undefined | | +| opts | AggConfigsOptions | | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.aggs.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.aggs.md new file mode 100644 index 0000000000000..0d217e037ecb1 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.aggs.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) > [aggs](./kibana-plugin-plugins-data-public.aggconfigs.aggs.md) + +## AggConfigs.aggs property + +Signature: + +```typescript +aggs: IAggConfig[]; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.byid.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.byid.md new file mode 100644 index 0000000000000..14d65ada5e39d --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.byid.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) > [byId](./kibana-plugin-plugins-data-public.aggconfigs.byid.md) + +## AggConfigs.byId() method + +Signature: + +```typescript +byId(id: string): AggConfig | undefined; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| id | string | | + +Returns: + +`AggConfig | undefined` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.byindex.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.byindex.md new file mode 100644 index 0000000000000..5977c81ddaf36 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.byindex.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) > [byIndex](./kibana-plugin-plugins-data-public.aggconfigs.byindex.md) + +## AggConfigs.byIndex() method + +Signature: + +```typescript +byIndex(index: number): AggConfig; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| index | number | | + +Returns: + +`AggConfig` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.byname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.byname.md new file mode 100644 index 0000000000000..772ba1f074d0d --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.byname.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) > [byName](./kibana-plugin-plugins-data-public.aggconfigs.byname.md) + +## AggConfigs.byName() method + +Signature: + +```typescript +byName(name: string): AggConfig[]; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | | + +Returns: + +`AggConfig[]` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.byschemaname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.byschemaname.md new file mode 100644 index 0000000000000..3a7c6a5f89e17 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.byschemaname.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) > [bySchemaName](./kibana-plugin-plugins-data-public.aggconfigs.byschemaname.md) + +## AggConfigs.bySchemaName() method + +Signature: + +```typescript +bySchemaName(schema: string): AggConfig[]; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| schema | string | | + +Returns: + +`AggConfig[]` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.bytype.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.bytype.md new file mode 100644 index 0000000000000..8bbf85ce4f29b --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.bytype.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) > [byType](./kibana-plugin-plugins-data-public.aggconfigs.bytype.md) + +## AggConfigs.byType() method + +Signature: + +```typescript +byType(type: string): AggConfig[]; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | + +Returns: + +`AggConfig[]` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.bytypename.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.bytypename.md new file mode 100644 index 0000000000000..97f05837493f2 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.bytypename.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) > [byTypeName](./kibana-plugin-plugins-data-public.aggconfigs.bytypename.md) + +## AggConfigs.byTypeName() method + +Signature: + +```typescript +byTypeName(type: string): AggConfig[]; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | + +Returns: + +`AggConfig[]` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.clone.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.clone.md new file mode 100644 index 0000000000000..0206f3c6b4751 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.clone.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) > [clone](./kibana-plugin-plugins-data-public.aggconfigs.clone.md) + +## AggConfigs.clone() method + +Signature: + +```typescript +clone({ enabledOnly }?: { + enabledOnly?: boolean | undefined; + }): AggConfigs; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| { enabledOnly } | {
enabledOnly?: boolean | undefined;
} | | + +Returns: + +`AggConfigs` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.createaggconfig.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.createaggconfig.md new file mode 100644 index 0000000000000..2ccded7c74e4c --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.createaggconfig.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) > [createAggConfig](./kibana-plugin-plugins-data-public.aggconfigs.createaggconfig.md) + +## AggConfigs.createAggConfig property + +Signature: + +```typescript +createAggConfig: (params: CreateAggConfigParams, { addToAggConfigs }?: { + addToAggConfigs?: boolean | undefined; + }) => T; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.getall.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.getall.md new file mode 100644 index 0000000000000..091ec1ce416c3 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.getall.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) > [getAll](./kibana-plugin-plugins-data-public.aggconfigs.getall.md) + +## AggConfigs.getAll() method + +Signature: + +```typescript +getAll(): AggConfig[]; +``` +Returns: + +`AggConfig[]` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.getrequestaggbyid.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.getrequestaggbyid.md new file mode 100644 index 0000000000000..f375648ca1cb7 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.getrequestaggbyid.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) > [getRequestAggById](./kibana-plugin-plugins-data-public.aggconfigs.getrequestaggbyid.md) + +## AggConfigs.getRequestAggById() method + +Signature: + +```typescript +getRequestAggById(id: string): AggConfig | undefined; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| id | string | | + +Returns: + +`AggConfig | undefined` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.getrequestaggs.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.getrequestaggs.md new file mode 100644 index 0000000000000..f4db6e373f5c3 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.getrequestaggs.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) > [getRequestAggs](./kibana-plugin-plugins-data-public.aggconfigs.getrequestaggs.md) + +## AggConfigs.getRequestAggs() method + +Signature: + +```typescript +getRequestAggs(): AggConfig[]; +``` +Returns: + +`AggConfig[]` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.getresponseaggbyid.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.getresponseaggbyid.md new file mode 100644 index 0000000000000..ab31c74f6000d --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.getresponseaggbyid.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) > [getResponseAggById](./kibana-plugin-plugins-data-public.aggconfigs.getresponseaggbyid.md) + +## AggConfigs.getResponseAggById() method + +Find a response agg by it's id. This may be an agg in the aggConfigs, or one created specifically for a response value + +Signature: + +```typescript +getResponseAggById(id: string): AggConfig | undefined; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| id | string | | + +Returns: + +`AggConfig | undefined` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.getresponseaggs.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.getresponseaggs.md new file mode 100644 index 0000000000000..47e26bdea9e9c --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.getresponseaggs.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) > [getResponseAggs](./kibana-plugin-plugins-data-public.aggconfigs.getresponseaggs.md) + +## AggConfigs.getResponseAggs() method + +Gets the AggConfigs (and possibly ResponseAggConfigs) that represent the values that will be produced when all aggs are run. + +With multi-value metric aggs it is possible for a single agg request to result in multiple agg values, which is why the length of a vis' responseValuesAggs may be different than the vis' aggs + + {array\[AggConfig\]} + +Signature: + +```typescript +getResponseAggs(): AggConfig[]; +``` +Returns: + +`AggConfig[]` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.indexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.indexpattern.md new file mode 100644 index 0000000000000..9bd91e185df1e --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.indexpattern.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) > [indexPattern](./kibana-plugin-plugins-data-public.aggconfigs.indexpattern.md) + +## AggConfigs.indexPattern property + +Signature: + +```typescript +indexPattern: IndexPattern; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.jsondataequals.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.jsondataequals.md new file mode 100644 index 0000000000000..d94c3959cd6a2 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.jsondataequals.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) > [jsonDataEquals](./kibana-plugin-plugins-data-public.aggconfigs.jsondataequals.md) + +## AggConfigs.jsonDataEquals() method + +Data-by-data comparison of this Aggregation Ignores the non-array indexes + +Signature: + +```typescript +jsonDataEquals(aggConfigs: AggConfig[]): boolean; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| aggConfigs | AggConfig[] | | + +Returns: + +`boolean` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.md new file mode 100644 index 0000000000000..c0ba1bbeea334 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.md @@ -0,0 +1,48 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) + +## AggConfigs class + +Signature: + +```typescript +export declare class AggConfigs +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(indexPattern, configStates, opts)](./kibana-plugin-plugins-data-public.aggconfigs._constructor_.md) | | Constructs a new instance of the AggConfigs class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [aggs](./kibana-plugin-plugins-data-public.aggconfigs.aggs.md) | | IAggConfig[] | | +| [createAggConfig](./kibana-plugin-plugins-data-public.aggconfigs.createaggconfig.md) | | <T extends AggConfig = AggConfig>(params: CreateAggConfigParams, { addToAggConfigs }?: {
addToAggConfigs?: boolean | undefined;
}) => T | | +| [indexPattern](./kibana-plugin-plugins-data-public.aggconfigs.indexpattern.md) | | IndexPattern | | +| [timeRange](./kibana-plugin-plugins-data-public.aggconfigs.timerange.md) | | TimeRange | | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [byId(id)](./kibana-plugin-plugins-data-public.aggconfigs.byid.md) | | | +| [byIndex(index)](./kibana-plugin-plugins-data-public.aggconfigs.byindex.md) | | | +| [byName(name)](./kibana-plugin-plugins-data-public.aggconfigs.byname.md) | | | +| [bySchemaName(schema)](./kibana-plugin-plugins-data-public.aggconfigs.byschemaname.md) | | | +| [byType(type)](./kibana-plugin-plugins-data-public.aggconfigs.bytype.md) | | | +| [byTypeName(type)](./kibana-plugin-plugins-data-public.aggconfigs.bytypename.md) | | | +| [clone({ enabledOnly })](./kibana-plugin-plugins-data-public.aggconfigs.clone.md) | | | +| [getAll()](./kibana-plugin-plugins-data-public.aggconfigs.getall.md) | | | +| [getRequestAggById(id)](./kibana-plugin-plugins-data-public.aggconfigs.getrequestaggbyid.md) | | | +| [getRequestAggs()](./kibana-plugin-plugins-data-public.aggconfigs.getrequestaggs.md) | | | +| [getResponseAggById(id)](./kibana-plugin-plugins-data-public.aggconfigs.getresponseaggbyid.md) | | Find a response agg by it's id. This may be an agg in the aggConfigs, or one created specifically for a response value | +| [getResponseAggs()](./kibana-plugin-plugins-data-public.aggconfigs.getresponseaggs.md) | | Gets the AggConfigs (and possibly ResponseAggConfigs) that represent the values that will be produced when all aggs are run.With multi-value metric aggs it is possible for a single agg request to result in multiple agg values, which is why the length of a vis' responseValuesAggs may be different than the vis' aggs {array\[AggConfig\]} | +| [jsonDataEquals(aggConfigs)](./kibana-plugin-plugins-data-public.aggconfigs.jsondataequals.md) | | Data-by-data comparison of this Aggregation Ignores the non-array indexes | +| [onSearchRequestStart(searchSource, options)](./kibana-plugin-plugins-data-public.aggconfigs.onsearchrequeststart.md) | | | +| [setTimeRange(timeRange)](./kibana-plugin-plugins-data-public.aggconfigs.settimerange.md) | | | +| [toDsl(hierarchical)](./kibana-plugin-plugins-data-public.aggconfigs.todsl.md) | | | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.onsearchrequeststart.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.onsearchrequeststart.md new file mode 100644 index 0000000000000..3ae7af408563c --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.onsearchrequeststart.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) > [onSearchRequestStart](./kibana-plugin-plugins-data-public.aggconfigs.onsearchrequeststart.md) + +## AggConfigs.onSearchRequestStart() method + +Signature: + +```typescript +onSearchRequestStart(searchSource: ISearchSource, options?: ISearchOptions): Promise<[unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown]>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| searchSource | ISearchSource | | +| options | ISearchOptions | | + +Returns: + +`Promise<[unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown]>` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.settimerange.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.settimerange.md new file mode 100644 index 0000000000000..77530f02bc9a3 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.settimerange.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) > [setTimeRange](./kibana-plugin-plugins-data-public.aggconfigs.settimerange.md) + +## AggConfigs.setTimeRange() method + +Signature: + +```typescript +setTimeRange(timeRange: TimeRange): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| timeRange | TimeRange | | + +Returns: + +`void` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.timerange.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.timerange.md new file mode 100644 index 0000000000000..b4caef6c7f6d2 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.timerange.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) > [timeRange](./kibana-plugin-plugins-data-public.aggconfigs.timerange.md) + +## AggConfigs.timeRange property + +Signature: + +```typescript +timeRange?: TimeRange; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.todsl.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.todsl.md new file mode 100644 index 0000000000000..055c4113ca3e4 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.todsl.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) > [toDsl](./kibana-plugin-plugins-data-public.aggconfigs.todsl.md) + +## AggConfigs.toDsl() method + +Signature: + +```typescript +toDsl(hierarchical?: boolean): Record; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| hierarchical | boolean | | + +Returns: + +`Record` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggsstart.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggsstart.md new file mode 100644 index 0000000000000..7bdf9d6501203 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggsstart.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggsStart](./kibana-plugin-plugins-data-public.aggsstart.md) + +## AggsStart type + +AggsStart represents the actual external contract as AggsCommonStart is only used internally. The difference is that AggsStart includes the typings for the registry with initialized agg types. + +Signature: + +```typescript +export declare type AggsStart = Assign; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.autocompletestart.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.autocompletestart.md new file mode 100644 index 0000000000000..44cee8c32421d --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.autocompletestart.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AutocompleteStart](./kibana-plugin-plugins-data-public.autocompletestart.md) + +## AutocompleteStart type + +\* + +Signature: + +```typescript +export declare type AutocompleteStart = ReturnType; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginsetup.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginsetup.md index dba1d79e78682..fc5624aeddce1 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginsetup.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginsetup.md @@ -4,6 +4,8 @@ ## DataPublicPluginSetup interface +Data plugin public Setup contract + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.actions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.actions.md index 25ce6eaa688f8..10997c94fab06 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.actions.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.actions.md @@ -4,11 +4,10 @@ ## DataPublicPluginStart.actions property +filter creation utilities [DataPublicPluginStartActions](./kibana-plugin-plugins-data-public.datapublicpluginstartactions.md) + Signature: ```typescript -actions: { - createFiltersFromValueClickAction: typeof createFiltersFromValueClickAction; - createFiltersFromRangeSelectAction: typeof createFiltersFromRangeSelectAction; - }; +actions: DataPublicPluginStartActions; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.autocomplete.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.autocomplete.md index d2e5aee7d90dd..8a09a10cccb24 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.autocomplete.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.autocomplete.md @@ -4,6 +4,8 @@ ## DataPublicPluginStart.autocomplete property +autocomplete service [AutocompleteStart](./kibana-plugin-plugins-data-public.autocompletestart.md) + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.fieldformats.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.fieldformats.md index dd4b38f64d10b..344044b38f7de 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.fieldformats.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.fieldformats.md @@ -4,6 +4,8 @@ ## DataPublicPluginStart.fieldFormats property +field formats service [FieldFormatsStart](./kibana-plugin-plugins-data-public.fieldformatsstart.md) + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.indexpatterns.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.indexpatterns.md index b3dd6a61760a6..0cf1e3101713d 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.indexpatterns.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.indexpatterns.md @@ -4,6 +4,8 @@ ## DataPublicPluginStart.indexPatterns property +index patterns service [IndexPatternsContract](./kibana-plugin-plugins-data-public.indexpatternscontract.md) + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.md index 4f43f10ce089e..7bae0bca701bf 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.md @@ -4,6 +4,8 @@ ## DataPublicPluginStart interface +Data plugin public Start contract + Signature: ```typescript @@ -14,11 +16,11 @@ export interface DataPublicPluginStart | Property | Type | Description | | --- | --- | --- | -| [actions](./kibana-plugin-plugins-data-public.datapublicpluginstart.actions.md) | {
createFiltersFromValueClickAction: typeof createFiltersFromValueClickAction;
createFiltersFromRangeSelectAction: typeof createFiltersFromRangeSelectAction;
} | | -| [autocomplete](./kibana-plugin-plugins-data-public.datapublicpluginstart.autocomplete.md) | AutocompleteStart | | -| [fieldFormats](./kibana-plugin-plugins-data-public.datapublicpluginstart.fieldformats.md) | FieldFormatsStart | | -| [indexPatterns](./kibana-plugin-plugins-data-public.datapublicpluginstart.indexpatterns.md) | IndexPatternsContract | | -| [query](./kibana-plugin-plugins-data-public.datapublicpluginstart.query.md) | QueryStart | | -| [search](./kibana-plugin-plugins-data-public.datapublicpluginstart.search.md) | ISearchStart | | -| [ui](./kibana-plugin-plugins-data-public.datapublicpluginstart.ui.md) | {
IndexPatternSelect: React.ComponentType<IndexPatternSelectProps>;
SearchBar: React.ComponentType<StatefulSearchBarProps>;
} | | +| [actions](./kibana-plugin-plugins-data-public.datapublicpluginstart.actions.md) | DataPublicPluginStartActions | filter creation utilities [DataPublicPluginStartActions](./kibana-plugin-plugins-data-public.datapublicpluginstartactions.md) | +| [autocomplete](./kibana-plugin-plugins-data-public.datapublicpluginstart.autocomplete.md) | AutocompleteStart | autocomplete service [AutocompleteStart](./kibana-plugin-plugins-data-public.autocompletestart.md) | +| [fieldFormats](./kibana-plugin-plugins-data-public.datapublicpluginstart.fieldformats.md) | FieldFormatsStart | field formats service [FieldFormatsStart](./kibana-plugin-plugins-data-public.fieldformatsstart.md) | +| [indexPatterns](./kibana-plugin-plugins-data-public.datapublicpluginstart.indexpatterns.md) | IndexPatternsContract | index patterns service [IndexPatternsContract](./kibana-plugin-plugins-data-public.indexpatternscontract.md) | +| [query](./kibana-plugin-plugins-data-public.datapublicpluginstart.query.md) | QueryStart | query service [QueryStart](./kibana-plugin-plugins-data-public.querystart.md) | +| [search](./kibana-plugin-plugins-data-public.datapublicpluginstart.search.md) | ISearchStart | search service [ISearchStart](./kibana-plugin-plugins-data-public.isearchstart.md) | +| [ui](./kibana-plugin-plugins-data-public.datapublicpluginstart.ui.md) | DataPublicPluginStartUi | prewired UI components [DataPublicPluginStartUi](./kibana-plugin-plugins-data-public.datapublicpluginstartui.md) | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.query.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.query.md index a44e250077ed4..16ba5dafbb264 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.query.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.query.md @@ -4,6 +4,8 @@ ## DataPublicPluginStart.query property +query service [QueryStart](./kibana-plugin-plugins-data-public.querystart.md) + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.search.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.search.md index eec00e7b13e9d..98832d7ca11d8 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.search.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.search.md @@ -4,6 +4,8 @@ ## DataPublicPluginStart.search property +search service [ISearchStart](./kibana-plugin-plugins-data-public.isearchstart.md) + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.ui.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.ui.md index 9c24216834371..671a1814ac644 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.ui.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.ui.md @@ -4,11 +4,10 @@ ## DataPublicPluginStart.ui property +prewired UI components [DataPublicPluginStartUi](./kibana-plugin-plugins-data-public.datapublicpluginstartui.md) + Signature: ```typescript -ui: { - IndexPatternSelect: React.ComponentType; - SearchBar: React.ComponentType; - }; +ui: DataPublicPluginStartUi; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstartactions.createfiltersfromrangeselectaction.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstartactions.createfiltersfromrangeselectaction.md new file mode 100644 index 0000000000000..c954e0095cbb6 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstartactions.createfiltersfromrangeselectaction.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [DataPublicPluginStartActions](./kibana-plugin-plugins-data-public.datapublicpluginstartactions.md) > [createFiltersFromRangeSelectAction](./kibana-plugin-plugins-data-public.datapublicpluginstartactions.createfiltersfromrangeselectaction.md) + +## DataPublicPluginStartActions.createFiltersFromRangeSelectAction property + +Signature: + +```typescript +createFiltersFromRangeSelectAction: typeof createFiltersFromRangeSelectAction; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstartactions.createfiltersfromvalueclickaction.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstartactions.createfiltersfromvalueclickaction.md new file mode 100644 index 0000000000000..70bd5091f3604 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstartactions.createfiltersfromvalueclickaction.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [DataPublicPluginStartActions](./kibana-plugin-plugins-data-public.datapublicpluginstartactions.md) > [createFiltersFromValueClickAction](./kibana-plugin-plugins-data-public.datapublicpluginstartactions.createfiltersfromvalueclickaction.md) + +## DataPublicPluginStartActions.createFiltersFromValueClickAction property + +Signature: + +```typescript +createFiltersFromValueClickAction: typeof createFiltersFromValueClickAction; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstartactions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstartactions.md new file mode 100644 index 0000000000000..d44c9e892cb80 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstartactions.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [DataPublicPluginStartActions](./kibana-plugin-plugins-data-public.datapublicpluginstartactions.md) + +## DataPublicPluginStartActions interface + +utilities to generate filters from action context + +Signature: + +```typescript +export interface DataPublicPluginStartActions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [createFiltersFromRangeSelectAction](./kibana-plugin-plugins-data-public.datapublicpluginstartactions.createfiltersfromrangeselectaction.md) | typeof createFiltersFromRangeSelectAction | | +| [createFiltersFromValueClickAction](./kibana-plugin-plugins-data-public.datapublicpluginstartactions.createfiltersfromvalueclickaction.md) | typeof createFiltersFromValueClickAction | | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstartui.indexpatternselect.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstartui.indexpatternselect.md new file mode 100644 index 0000000000000..eac29dc5de70d --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstartui.indexpatternselect.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [DataPublicPluginStartUi](./kibana-plugin-plugins-data-public.datapublicpluginstartui.md) > [IndexPatternSelect](./kibana-plugin-plugins-data-public.datapublicpluginstartui.indexpatternselect.md) + +## DataPublicPluginStartUi.IndexPatternSelect property + +Signature: + +```typescript +IndexPatternSelect: React.ComponentType; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstartui.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstartui.md new file mode 100644 index 0000000000000..3d827c0db465b --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstartui.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [DataPublicPluginStartUi](./kibana-plugin-plugins-data-public.datapublicpluginstartui.md) + +## DataPublicPluginStartUi interface + +Data plugin prewired UI components + +Signature: + +```typescript +export interface DataPublicPluginStartUi +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [IndexPatternSelect](./kibana-plugin-plugins-data-public.datapublicpluginstartui.indexpatternselect.md) | React.ComponentType<IndexPatternSelectProps> | | +| [SearchBar](./kibana-plugin-plugins-data-public.datapublicpluginstartui.searchbar.md) | React.ComponentType<StatefulSearchBarProps> | | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstartui.searchbar.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstartui.searchbar.md new file mode 100644 index 0000000000000..06339d14cde24 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstartui.searchbar.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [DataPublicPluginStartUi](./kibana-plugin-plugins-data-public.datapublicpluginstartui.md) > [SearchBar](./kibana-plugin-plugins-data-public.datapublicpluginstartui.searchbar.md) + +## DataPublicPluginStartUi.SearchBar property + +Signature: + +```typescript +SearchBar: React.ComponentType; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fetchoptions.abortsignal.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fetchoptions.abortsignal.md deleted file mode 100644 index 791f1b63e6539..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fetchoptions.abortsignal.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FetchOptions](./kibana-plugin-plugins-data-public.fetchoptions.md) > [abortSignal](./kibana-plugin-plugins-data-public.fetchoptions.abortsignal.md) - -## FetchOptions.abortSignal property - -Signature: - -```typescript -abortSignal?: AbortSignal; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fetchoptions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fetchoptions.md deleted file mode 100644 index f07fdd4280533..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fetchoptions.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FetchOptions](./kibana-plugin-plugins-data-public.fetchoptions.md) - -## FetchOptions interface - -Signature: - -```typescript -export interface FetchOptions -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [abortSignal](./kibana-plugin-plugins-data-public.fetchoptions.abortsignal.md) | AbortSignal | | -| [searchStrategyId](./kibana-plugin-plugins-data-public.fetchoptions.searchstrategyid.md) | string | | - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fetchoptions.searchstrategyid.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fetchoptions.searchstrategyid.md deleted file mode 100644 index 8824529eb4eca..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fetchoptions.searchstrategyid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FetchOptions](./kibana-plugin-plugins-data-public.fetchoptions.md) > [searchStrategyId](./kibana-plugin-plugins-data-public.fetchoptions.searchstrategyid.md) - -## FetchOptions.searchStrategyId property - -Signature: - -```typescript -searchStrategyId?: string; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldformatsstart.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldformatsstart.md new file mode 100644 index 0000000000000..1a0a08f44451a --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldformatsstart.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FieldFormatsStart](./kibana-plugin-plugins-data-public.fieldformatsstart.md) + +## FieldFormatsStart type + + +Signature: + +```typescript +export declare type FieldFormatsStart = Omit & { + deserialize: FormatFactory; +}; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist._constructor_.md deleted file mode 100644 index 9f9613a5a68f7..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist._constructor_.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) > [(constructor)](./kibana-plugin-plugins-data-public.fieldlist._constructor_.md) - -## FieldList.(constructor) - -Constructs a new instance of the `FieldList` class - -Signature: - -```typescript -constructor(indexPattern: IndexPattern, specs?: FieldSpec[], shortDotsEnable?: boolean, onNotification?: OnNotification); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| indexPattern | IndexPattern | | -| specs | FieldSpec[] | | -| shortDotsEnable | boolean | | -| onNotification | OnNotification | | - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.add.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.add.md deleted file mode 100644 index ae3d82f0cc3ea..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.add.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) > [add](./kibana-plugin-plugins-data-public.fieldlist.add.md) - -## FieldList.add property - -Signature: - -```typescript -readonly add: (field: FieldSpec) => void; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.getall.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.getall.md deleted file mode 100644 index da29a4de9acc8..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.getall.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) > [getAll](./kibana-plugin-plugins-data-public.fieldlist.getall.md) - -## FieldList.getAll property - -Signature: - -```typescript -readonly getAll: () => IndexPatternField[]; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.getbyname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.getbyname.md deleted file mode 100644 index af368d003423a..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.getbyname.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) > [getByName](./kibana-plugin-plugins-data-public.fieldlist.getbyname.md) - -## FieldList.getByName property - -Signature: - -```typescript -readonly getByName: (name: IndexPatternField['name']) => IndexPatternField | undefined; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.getbytype.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.getbytype.md deleted file mode 100644 index 16bae3ee7c555..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.getbytype.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) > [getByType](./kibana-plugin-plugins-data-public.fieldlist.getbytype.md) - -## FieldList.getByType property - -Signature: - -```typescript -readonly getByType: (type: IndexPatternField['type']) => any[]; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.md index 012b069430290..79bcaf9700cf0 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.md @@ -1,32 +1,11 @@ -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [fieldList](./kibana-plugin-plugins-data-public.fieldlist.md) -## FieldList class +## fieldList variable Signature: ```typescript -export declare class FieldList extends Array implements IIndexPatternFieldList +fieldList: (specs?: FieldSpec[], shortDotsEnable?: boolean) => IIndexPatternFieldList ``` - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(indexPattern, specs, shortDotsEnable, onNotification)](./kibana-plugin-plugins-data-public.fieldlist._constructor_.md) | | Constructs a new instance of the FieldList class | - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [add](./kibana-plugin-plugins-data-public.fieldlist.add.md) | | (field: FieldSpec) => void | | -| [getAll](./kibana-plugin-plugins-data-public.fieldlist.getall.md) | | () => IndexPatternField[] | | -| [getByName](./kibana-plugin-plugins-data-public.fieldlist.getbyname.md) | | (name: IndexPatternField['name']) => IndexPatternField | undefined | | -| [getByType](./kibana-plugin-plugins-data-public.fieldlist.getbytype.md) | | (type: IndexPatternField['type']) => any[] | | -| [remove](./kibana-plugin-plugins-data-public.fieldlist.remove.md) | | (field: IFieldType) => void | | -| [removeAll](./kibana-plugin-plugins-data-public.fieldlist.removeall.md) | | () => void | | -| [replaceAll](./kibana-plugin-plugins-data-public.fieldlist.replaceall.md) | | (specs: FieldSpec[]) => void | | -| [toSpec](./kibana-plugin-plugins-data-public.fieldlist.tospec.md) | | () => {
count: number;
script: string | undefined;
lang: string | undefined;
conflictDescriptions: Record<string, string[]> | undefined;
name: string;
type: string;
esTypes: string[] | undefined;
scripted: boolean;
searchable: boolean;
aggregatable: boolean;
readFromDocValues: boolean;
subType: import("../types").IFieldSubType | undefined;
format: any;
}[] | | -| [update](./kibana-plugin-plugins-data-public.fieldlist.update.md) | | (field: FieldSpec) => void | | - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.remove.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.remove.md deleted file mode 100644 index 149410adb3550..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.remove.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) > [remove](./kibana-plugin-plugins-data-public.fieldlist.remove.md) - -## FieldList.remove property - -Signature: - -```typescript -readonly remove: (field: IFieldType) => void; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.removeall.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.removeall.md deleted file mode 100644 index 92a45349ad005..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.removeall.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) > [removeAll](./kibana-plugin-plugins-data-public.fieldlist.removeall.md) - -## FieldList.removeAll property - -Signature: - -```typescript -readonly removeAll: () => void; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.replaceall.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.replaceall.md deleted file mode 100644 index 5330440e6b96a..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.replaceall.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) > [replaceAll](./kibana-plugin-plugins-data-public.fieldlist.replaceall.md) - -## FieldList.replaceAll property - -Signature: - -```typescript -readonly replaceAll: (specs: FieldSpec[]) => void; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.tospec.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.tospec.md deleted file mode 100644 index e646339feb495..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.tospec.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) > [toSpec](./kibana-plugin-plugins-data-public.fieldlist.tospec.md) - -## FieldList.toSpec property - -Signature: - -```typescript -readonly toSpec: () => { - count: number; - script: string | undefined; - lang: string | undefined; - conflictDescriptions: Record | undefined; - name: string; - type: string; - esTypes: string[] | undefined; - scripted: boolean; - searchable: boolean; - aggregatable: boolean; - readFromDocValues: boolean; - subType: import("../types").IFieldSubType | undefined; - format: any; - }[]; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.update.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.update.md deleted file mode 100644 index c718e47b31b50..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.update.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) > [update](./kibana-plugin-plugins-data-public.fieldlist.update.md) - -## FieldList.update property - -Signature: - -```typescript -readonly update: (field: FieldSpec) => void; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getsearchparamsfromrequest.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getsearchparamsfromrequest.md index 337b4b3302cc3..d32e9a955f890 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getsearchparamsfromrequest.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getsearchparamsfromrequest.md @@ -9,7 +9,6 @@ ```typescript export declare function getSearchParamsFromRequest(searchRequest: SearchRequest, dependencies: { - esShardTimeout: number; getConfig: GetConfigFn; }): ISearchRequestParams; ``` @@ -19,7 +18,7 @@ export declare function getSearchParamsFromRequest(searchRequest: SearchRequest, | Parameter | Type | Description | | --- | --- | --- | | searchRequest | SearchRequest | | -| dependencies | {
esShardTimeout: number;
getConfig: GetConfigFn;
} | | +| dependencies | {
getConfig: GetConfigFn;
} | | Returns: diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md index 6f42fb32fdb7b..3ff2afafcc514 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md @@ -28,7 +28,7 @@ export interface IFieldType | [searchable](./kibana-plugin-plugins-data-public.ifieldtype.searchable.md) | boolean | | | [sortable](./kibana-plugin-plugins-data-public.ifieldtype.sortable.md) | boolean | | | [subType](./kibana-plugin-plugins-data-public.ifieldtype.subtype.md) | IFieldSubType | | -| [toSpec](./kibana-plugin-plugins-data-public.ifieldtype.tospec.md) | () => FieldSpec | | +| [toSpec](./kibana-plugin-plugins-data-public.ifieldtype.tospec.md) | (options?: {
getFormatterForField?: IndexPattern['getFormatterForField'];
}) => FieldSpec | | | [type](./kibana-plugin-plugins-data-public.ifieldtype.type.md) | string | | | [visualizable](./kibana-plugin-plugins-data-public.ifieldtype.visualizable.md) | boolean | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.tospec.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.tospec.md index 1fb4084c25d34..52238ea2a00ca 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.tospec.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.tospec.md @@ -7,5 +7,7 @@ Signature: ```typescript -toSpec?: () => FieldSpec; +toSpec?: (options?: { + getFormatterForField?: IndexPattern['getFormatterForField']; + }) => FieldSpec; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.md index b068c4804c0dd..b1e13ffaabd07 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.md @@ -21,5 +21,6 @@ export interface IIndexPatternFieldList extends Array | [remove(field)](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.remove.md) | | | [removeAll()](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.removeall.md) | | | [replaceAll(specs)](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.replaceall.md) | | +| [toSpec(options)](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.tospec.md) | | | [update(field)](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.update.md) | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.tospec.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.tospec.md new file mode 100644 index 0000000000000..fd20f2944c5be --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.tospec.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IIndexPatternFieldList](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.md) > [toSpec](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.tospec.md) + +## IIndexPatternFieldList.toSpec() method + +Signature: + +```typescript +toSpec(options?: { + getFormatterForField?: IndexPattern['getFormatterForField']; + }): FieldSpec[]; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | {
getFormatterForField?: IndexPattern['getFormatterForField'];
} | | + +Returns: + +`FieldSpec[]` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._constructor_.md index 2e078e3404fe6..a5bb15c963978 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._constructor_.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._constructor_.md @@ -9,7 +9,7 @@ Constructs a new instance of the `IndexPattern` class Signature: ```typescript -constructor(id: string | undefined, { savedObjectsClient, apiClient, patternCache, fieldFormats, onNotification, onError, shortDotsEnable, metaFields, }: IndexPatternDeps); +constructor(id: string | undefined, { savedObjectsClient, apiClient, patternCache, fieldFormats, indexPatternsService, onNotification, onError, shortDotsEnable, metaFields, }: IndexPatternDeps); ``` ## Parameters @@ -17,5 +17,5 @@ constructor(id: string | undefined, { savedObjectsClient, apiClient, patternCach | Parameter | Type | Description | | --- | --- | --- | | id | string | undefined | | -| { savedObjectsClient, apiClient, patternCache, fieldFormats, onNotification, onError, shortDotsEnable, metaFields, } | IndexPatternDeps | | +| { savedObjectsClient, apiClient, patternCache, fieldFormats, indexPatternsService, onNotification, onError, shortDotsEnable, metaFields, } | IndexPatternDeps | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md index 37db063e284ec..87ce1e258712a 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md @@ -14,7 +14,7 @@ export declare class IndexPattern implements IIndexPattern | Constructor | Modifiers | Description | | --- | --- | --- | -| [(constructor)(id, { savedObjectsClient, apiClient, patternCache, fieldFormats, onNotification, onError, shortDotsEnable, metaFields, })](./kibana-plugin-plugins-data-public.indexpattern._constructor_.md) | | Constructs a new instance of the IndexPattern class | +| [(constructor)(id, { savedObjectsClient, apiClient, patternCache, fieldFormats, indexPatternsService, onNotification, onError, shortDotsEnable, metaFields, })](./kibana-plugin-plugins-data-public.indexpattern._constructor_.md) | | Constructs a new instance of the IndexPattern class | ## Properties @@ -29,11 +29,13 @@ export declare class IndexPattern implements IIndexPattern | [id](./kibana-plugin-plugins-data-public.indexpattern.id.md) | | string | | | [intervalName](./kibana-plugin-plugins-data-public.indexpattern.intervalname.md) | | string | undefined | | | [metaFields](./kibana-plugin-plugins-data-public.indexpattern.metafields.md) | | string[] | | +| [originalBody](./kibana-plugin-plugins-data-public.indexpattern.originalbody.md) | | {
[key: string]: any;
} | | | [sourceFilters](./kibana-plugin-plugins-data-public.indexpattern.sourcefilters.md) | | SourceFilter[] | | | [timeFieldName](./kibana-plugin-plugins-data-public.indexpattern.timefieldname.md) | | string | undefined | | | [title](./kibana-plugin-plugins-data-public.indexpattern.title.md) | | string | | | [type](./kibana-plugin-plugins-data-public.indexpattern.type.md) | | string | undefined | | | [typeMeta](./kibana-plugin-plugins-data-public.indexpattern.typemeta.md) | | TypeMeta | | +| [version](./kibana-plugin-plugins-data-public.indexpattern.version.md) | | string | undefined | | ## Methods @@ -60,8 +62,5 @@ export declare class IndexPattern implements IIndexPattern | [prepBody()](./kibana-plugin-plugins-data-public.indexpattern.prepbody.md) | | | | [refreshFields()](./kibana-plugin-plugins-data-public.indexpattern.refreshfields.md) | | | | [removeScriptedField(fieldName)](./kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md) | | | -| [save(saveAttempts)](./kibana-plugin-plugins-data-public.indexpattern.save.md) | | | -| [toJSON()](./kibana-plugin-plugins-data-public.indexpattern.tojson.md) | | | | [toSpec()](./kibana-plugin-plugins-data-public.indexpattern.tospec.md) | | | -| [toString()](./kibana-plugin-plugins-data-public.indexpattern.tostring.md) | | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.originalbody.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.originalbody.md new file mode 100644 index 0000000000000..4bc3c76afbae9 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.originalbody.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [originalBody](./kibana-plugin-plugins-data-public.indexpattern.originalbody.md) + +## IndexPattern.originalBody property + +Signature: + +```typescript +originalBody: { + [key: string]: any; + }; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md index 42c6dd72b8c4e..e902d9c42b082 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md @@ -7,7 +7,7 @@ Signature: ```typescript -removeScriptedField(fieldName: string): Promise; +removeScriptedField(fieldName: string): void; ``` ## Parameters @@ -18,5 +18,5 @@ removeScriptedField(fieldName: string): Promise; Returns: -`Promise` +`void` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.save.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.save.md deleted file mode 100644 index d0b471cc2bc21..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.save.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [save](./kibana-plugin-plugins-data-public.indexpattern.save.md) - -## IndexPattern.save() method - -Signature: - -```typescript -save(saveAttempts?: number): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| saveAttempts | number | | - -Returns: - -`Promise` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.tojson.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.tojson.md deleted file mode 100644 index 0ae04bb424d44..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.tojson.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [toJSON](./kibana-plugin-plugins-data-public.indexpattern.tojson.md) - -## IndexPattern.toJSON() method - -Signature: - -```typescript -toJSON(): string | undefined; -``` -Returns: - -`string | undefined` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.tostring.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.tostring.md deleted file mode 100644 index a10b549a7b9eb..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.tostring.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [toString](./kibana-plugin-plugins-data-public.indexpattern.tostring.md) - -## IndexPattern.toString() method - -Signature: - -```typescript -toString(): string; -``` -Returns: - -`string` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.version.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.version.md new file mode 100644 index 0000000000000..99d3bc4e7a04d --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.version.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [version](./kibana-plugin-plugins-data-public.indexpattern.version.md) + +## IndexPattern.version property + +Signature: + +```typescript +version: string | undefined; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield._constructor_.md index 10b65bdccdf87..5d467a7a9cbce 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield._constructor_.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield._constructor_.md @@ -9,15 +9,13 @@ Constructs a new instance of the `IndexPatternField` class Signature: ```typescript -constructor(indexPattern: IndexPattern, spec: FieldSpec, displayName: string, onNotification: OnNotification); +constructor(spec: FieldSpec, displayName: string); ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| indexPattern | IndexPattern | | | spec | FieldSpec | | | displayName | string | | -| onNotification | OnNotification | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.format.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.format.md deleted file mode 100644 index f28d5b1bca7e5..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.format.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternField](./kibana-plugin-plugins-data-public.indexpatternfield.md) > [format](./kibana-plugin-plugins-data-public.indexpatternfield.format.md) - -## IndexPatternField.format property - -Signature: - -```typescript -get format(): FieldFormat; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.indexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.indexpattern.md deleted file mode 100644 index 3d145cce9d07d..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.indexpattern.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternField](./kibana-plugin-plugins-data-public.indexpatternfield.md) > [indexPattern](./kibana-plugin-plugins-data-public.indexpatternfield.indexpattern.md) - -## IndexPatternField.indexPattern property - -Signature: - -```typescript -readonly indexPattern: IndexPattern; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md index 713b29ea3a3d3..215188ffa2607 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md @@ -14,7 +14,7 @@ export declare class IndexPatternField implements IFieldType | Constructor | Modifiers | Description | | --- | --- | --- | -| [(constructor)(indexPattern, spec, displayName, onNotification)](./kibana-plugin-plugins-data-public.indexpatternfield._constructor_.md) | | Constructs a new instance of the IndexPatternField class | +| [(constructor)(spec, displayName)](./kibana-plugin-plugins-data-public.indexpatternfield._constructor_.md) | | Constructs a new instance of the IndexPatternField class | ## Properties @@ -26,8 +26,6 @@ export declare class IndexPatternField implements IFieldType | [displayName](./kibana-plugin-plugins-data-public.indexpatternfield.displayname.md) | | string | | | [esTypes](./kibana-plugin-plugins-data-public.indexpatternfield.estypes.md) | | string[] | undefined | | | [filterable](./kibana-plugin-plugins-data-public.indexpatternfield.filterable.md) | | boolean | | -| [format](./kibana-plugin-plugins-data-public.indexpatternfield.format.md) | | FieldFormat | | -| [indexPattern](./kibana-plugin-plugins-data-public.indexpatternfield.indexpattern.md) | | IndexPattern | | | [lang](./kibana-plugin-plugins-data-public.indexpatternfield.lang.md) | | string | undefined | | | [name](./kibana-plugin-plugins-data-public.indexpatternfield.name.md) | | string | | | [readFromDocValues](./kibana-plugin-plugins-data-public.indexpatternfield.readfromdocvalues.md) | | boolean | | @@ -45,5 +43,5 @@ export declare class IndexPatternField implements IFieldType | Method | Modifiers | Description | | --- | --- | --- | | [toJSON()](./kibana-plugin-plugins-data-public.indexpatternfield.tojson.md) | | | -| [toSpec()](./kibana-plugin-plugins-data-public.indexpatternfield.tospec.md) | | | +| [toSpec({ getFormatterForField, })](./kibana-plugin-plugins-data-public.indexpatternfield.tospec.md) | | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tospec.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tospec.md index 5037cb0049e82..1d80c90991f55 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tospec.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tospec.md @@ -7,7 +7,9 @@ Signature: ```typescript -toSpec(): { +toSpec({ getFormatterForField, }?: { + getFormatterForField?: IndexPattern['getFormatterForField']; + }): { count: number; script: string | undefined; lang: string | undefined; @@ -20,9 +22,19 @@ toSpec(): { aggregatable: boolean; readFromDocValues: boolean; subType: import("../types").IFieldSubType | undefined; - format: any; + format: { + id: any; + params: any; + } | undefined; }; ``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| { getFormatterForField, } | {
getFormatterForField?: IndexPattern['getFormatterForField'];
} | | + Returns: `{ @@ -38,6 +50,9 @@ toSpec(): { aggregatable: boolean; readFromDocValues: boolean; subType: import("../types").IFieldSubType | undefined; - format: any; + format: { + id: any; + params: any; + } | undefined; }` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.abortsignal.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.abortsignal.md new file mode 100644 index 0000000000000..fd8d322d54b26 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.abortsignal.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchOptions](./kibana-plugin-plugins-data-public.isearchoptions.md) > [abortSignal](./kibana-plugin-plugins-data-public.isearchoptions.abortsignal.md) + +## ISearchOptions.abortSignal property + +An `AbortSignal` that allows the caller of `search` to abort a search request. + +Signature: + +```typescript +abortSignal?: AbortSignal; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md index 3eb38dc7d52e0..c9018b0048aa3 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md @@ -14,6 +14,6 @@ export interface ISearchOptions | Property | Type | Description | | --- | --- | --- | -| [signal](./kibana-plugin-plugins-data-public.isearchoptions.signal.md) | AbortSignal | | -| [strategy](./kibana-plugin-plugins-data-public.isearchoptions.strategy.md) | string | | +| [abortSignal](./kibana-plugin-plugins-data-public.isearchoptions.abortsignal.md) | AbortSignal | An AbortSignal that allows the caller of search to abort a search request. | +| [strategy](./kibana-plugin-plugins-data-public.isearchoptions.strategy.md) | string | Use this option to force using a specific server side search strategy. Leave empty to use the default strategy. | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.signal.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.signal.md deleted file mode 100644 index 10bd186d55baa..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.signal.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchOptions](./kibana-plugin-plugins-data-public.isearchoptions.md) > [signal](./kibana-plugin-plugins-data-public.isearchoptions.signal.md) - -## ISearchOptions.signal property - -Signature: - -```typescript -signal?: AbortSignal; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.strategy.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.strategy.md index df7e050691a8f..bd2580957f6c1 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.strategy.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.strategy.md @@ -4,6 +4,8 @@ ## ISearchOptions.strategy property +Use this option to force using a specific server side search strategy. Leave empty to use the default strategy. + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.aggs.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.aggs.md new file mode 100644 index 0000000000000..ad97820d4d760 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.aggs.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchSetup](./kibana-plugin-plugins-data-public.isearchsetup.md) > [aggs](./kibana-plugin-plugins-data-public.isearchsetup.aggs.md) + +## ISearchSetup.aggs property + +Signature: + +```typescript +aggs: AggsSetup; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.md new file mode 100644 index 0000000000000..b68c4d61e4e03 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchSetup](./kibana-plugin-plugins-data-public.isearchsetup.md) + +## ISearchSetup interface + +The setup contract exposed by the Search plugin exposes the search strategy extension point. + +Signature: + +```typescript +export interface ISearchSetup +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [aggs](./kibana-plugin-plugins-data-public.isearchsetup.aggs.md) | AggsSetup | | +| [usageCollector](./kibana-plugin-plugins-data-public.isearchsetup.usagecollector.md) | SearchUsageCollector | | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.usagecollector.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.usagecollector.md new file mode 100644 index 0000000000000..908a842974f25 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.usagecollector.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchSetup](./kibana-plugin-plugins-data-public.isearchsetup.md) > [usageCollector](./kibana-plugin-plugins-data-public.isearchsetup.usagecollector.md) + +## ISearchSetup.usageCollector property + +Signature: + +```typescript +usageCollector?: SearchUsageCollector; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsource.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsource.md index 4b9f6e3594dc5..43e10d0bef57a 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsource.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsource.md @@ -4,7 +4,7 @@ ## ISearchSource type -\* +search source interface Signature: diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.aggs.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.aggs.md new file mode 100644 index 0000000000000..993c6bf5a922b --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.aggs.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchStart](./kibana-plugin-plugins-data-public.isearchstart.md) > [aggs](./kibana-plugin-plugins-data-public.isearchstart.aggs.md) + +## ISearchStart.aggs property + +agg config sub service [AggsStart](./kibana-plugin-plugins-data-public.aggsstart.md) + +Signature: + +```typescript +aggs: AggsStart; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.md new file mode 100644 index 0000000000000..cee213fc6e7e3 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchStart](./kibana-plugin-plugins-data-public.isearchstart.md) + +## ISearchStart interface + +search service + +Signature: + +```typescript +export interface ISearchStart +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [aggs](./kibana-plugin-plugins-data-public.isearchstart.aggs.md) | AggsStart | agg config sub service [AggsStart](./kibana-plugin-plugins-data-public.aggsstart.md) | +| [search](./kibana-plugin-plugins-data-public.isearchstart.search.md) | ISearchGeneric | low level search [ISearchGeneric](./kibana-plugin-plugins-data-public.isearchgeneric.md) | +| [searchSource](./kibana-plugin-plugins-data-public.isearchstart.searchsource.md) | ISearchStartSearchSource | high level search [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md) | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.search.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.search.md new file mode 100644 index 0000000000000..80e140e9fdd5c --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.search.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchStart](./kibana-plugin-plugins-data-public.isearchstart.md) > [search](./kibana-plugin-plugins-data-public.isearchstart.search.md) + +## ISearchStart.search property + +low level search [ISearchGeneric](./kibana-plugin-plugins-data-public.isearchgeneric.md) + +Signature: + +```typescript +search: ISearchGeneric; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.searchsource.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.searchsource.md new file mode 100644 index 0000000000000..5d4b884b2c25b --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.searchsource.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchStart](./kibana-plugin-plugins-data-public.isearchstart.md) > [searchSource](./kibana-plugin-plugins-data-public.isearchstart.searchsource.md) + +## ISearchStart.searchSource property + +high level search [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md) + +Signature: + +```typescript +searchSource: ISearchStartSearchSource; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstartsearchsource.create.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstartsearchsource.create.md new file mode 100644 index 0000000000000..7f6344b82d27c --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstartsearchsource.create.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md) > [create](./kibana-plugin-plugins-data-public.isearchstartsearchsource.create.md) + +## ISearchStartSearchSource.create property + +creates [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) based on provided serialized [SearchSourceFields](./kibana-plugin-plugins-data-public.searchsourcefields.md) + +Signature: + +```typescript +create: (fields?: SearchSourceFields) => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstartsearchsource.createempty.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstartsearchsource.createempty.md new file mode 100644 index 0000000000000..b13b5d227c8b4 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstartsearchsource.createempty.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md) > [createEmpty](./kibana-plugin-plugins-data-public.isearchstartsearchsource.createempty.md) + +## ISearchStartSearchSource.createEmpty property + +creates empty [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) + +Signature: + +```typescript +createEmpty: () => ISearchSource; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstartsearchsource.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstartsearchsource.md new file mode 100644 index 0000000000000..f10d5bb002a0f --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstartsearchsource.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md) + +## ISearchStartSearchSource interface + +high level search service + +Signature: + +```typescript +export interface ISearchStartSearchSource +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [create](./kibana-plugin-plugins-data-public.isearchstartsearchsource.create.md) | (fields?: SearchSourceFields) => Promise<ISearchSource> | creates [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) based on provided serialized [SearchSourceFields](./kibana-plugin-plugins-data-public.searchsourcefields.md) | +| [createEmpty](./kibana-plugin-plugins-data-public.isearchstartsearchsource.createempty.md) | () => ISearchSource | creates empty [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index 09702df4fdb54..f51549c81fb62 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -8,9 +8,10 @@ | Class | Description | | --- | --- | +| [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) | | +| [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) | | | [AggParamType](./kibana-plugin-plugins-data-public.aggparamtype.md) | | | [FieldFormat](./kibana-plugin-plugins-data-public.fieldformat.md) | | -| [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) | | | [FilterManager](./kibana-plugin-plugins-data-public.filtermanager.md) | | | [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) | | | [IndexPatternField](./kibana-plugin-plugins-data-public.indexpatternfield.md) | | @@ -19,6 +20,7 @@ | [Plugin](./kibana-plugin-plugins-data-public.plugin.md) | | | [RequestTimeoutError](./kibana-plugin-plugins-data-public.requesttimeouterror.md) | Class used to signify that a request timed out. Useful for applications to conditionally handle this type of error differently than other errors. | | [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) | | +| [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) | \* | | [TimeHistory](./kibana-plugin-plugins-data-public.timehistory.md) | | ## Enumerations @@ -48,10 +50,11 @@ | --- | --- | | [AggParamOption](./kibana-plugin-plugins-data-public.aggparamoption.md) | | | [ApplyGlobalFilterActionContext](./kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.md) | | -| [DataPublicPluginSetup](./kibana-plugin-plugins-data-public.datapublicpluginsetup.md) | | -| [DataPublicPluginStart](./kibana-plugin-plugins-data-public.datapublicpluginstart.md) | | +| [DataPublicPluginSetup](./kibana-plugin-plugins-data-public.datapublicpluginsetup.md) | Data plugin public Setup contract | +| [DataPublicPluginStart](./kibana-plugin-plugins-data-public.datapublicpluginstart.md) | Data plugin public Start contract | +| [DataPublicPluginStartActions](./kibana-plugin-plugins-data-public.datapublicpluginstartactions.md) | utilities to generate filters from action context | +| [DataPublicPluginStartUi](./kibana-plugin-plugins-data-public.datapublicpluginstartui.md) | Data plugin prewired UI components | | [EsQueryConfig](./kibana-plugin-plugins-data-public.esqueryconfig.md) | | -| [FetchOptions](./kibana-plugin-plugins-data-public.fetchoptions.md) | | | [FieldFormatConfig](./kibana-plugin-plugins-data-public.fieldformatconfig.md) | | | [FieldMappingSpec](./kibana-plugin-plugins-data-public.fieldmappingspec.md) | | | [Filter](./kibana-plugin-plugins-data-public.filter.md) | | @@ -67,10 +70,14 @@ | [IndexPatternAttributes](./kibana-plugin-plugins-data-public.indexpatternattributes.md) | Use data plugin interface instead | | [IndexPatternTypeMeta](./kibana-plugin-plugins-data-public.indexpatterntypemeta.md) | | | [ISearchOptions](./kibana-plugin-plugins-data-public.isearchoptions.md) | | +| [ISearchSetup](./kibana-plugin-plugins-data-public.isearchsetup.md) | The setup contract exposed by the Search plugin exposes the search strategy extension point. | +| [ISearchStart](./kibana-plugin-plugins-data-public.isearchstart.md) | search service | +| [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md) | high level search service | | [KueryNode](./kibana-plugin-plugins-data-public.kuerynode.md) | | | [OptionedValueProp](./kibana-plugin-plugins-data-public.optionedvalueprop.md) | | | [Query](./kibana-plugin-plugins-data-public.query.md) | | | [QueryState](./kibana-plugin-plugins-data-public.querystate.md) | All query state service state | +| [QueryStateChange](./kibana-plugin-plugins-data-public.querystatechange.md) | | | [QuerySuggestionBasic](./kibana-plugin-plugins-data-public.querysuggestionbasic.md) | \* | | [QuerySuggestionField](./kibana-plugin-plugins-data-public.querysuggestionfield.md) | \* | | [QuerySuggestionGetFnArgs](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.md) | \* | @@ -80,7 +87,7 @@ | [SavedQueryService](./kibana-plugin-plugins-data-public.savedqueryservice.md) | | | [SearchError](./kibana-plugin-plugins-data-public.searcherror.md) | | | [SearchInterceptorDeps](./kibana-plugin-plugins-data-public.searchinterceptordeps.md) | | -| [SearchSourceFields](./kibana-plugin-plugins-data-public.searchsourcefields.md) | | +| [SearchSourceFields](./kibana-plugin-plugins-data-public.searchsourcefields.md) | search source fields | | [TabbedAggColumn](./kibana-plugin-plugins-data-public.tabbedaggcolumn.md) | \* | | [TabbedTable](./kibana-plugin-plugins-data-public.tabbedtable.md) | \* | | [TimeRange](./kibana-plugin-plugins-data-public.timerange.md) | | @@ -103,6 +110,7 @@ | [expandShorthand](./kibana-plugin-plugins-data-public.expandshorthand.md) | | | [extractSearchSourceReferences](./kibana-plugin-plugins-data-public.extractsearchsourcereferences.md) | | | [fieldFormats](./kibana-plugin-plugins-data-public.fieldformats.md) | | +| [fieldList](./kibana-plugin-plugins-data-public.fieldlist.md) | | | [FilterBar](./kibana-plugin-plugins-data-public.filterbar.md) | | | [getKbnTypeNames](./kibana-plugin-plugins-data-public.getkbntypenames.md) | Get the esTypes known by all kbnFieldTypes {Array} | | [indexPatterns](./kibana-plugin-plugins-data-public.indexpatterns.md) | | @@ -125,6 +133,8 @@ | [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md) | | | [AggGroupName](./kibana-plugin-plugins-data-public.agggroupname.md) | | | [AggParam](./kibana-plugin-plugins-data-public.aggparam.md) | | +| [AggsStart](./kibana-plugin-plugins-data-public.aggsstart.md) | AggsStart represents the actual external contract as AggsCommonStart is only used internally. The difference is that AggsStart includes the typings for the registry with initialized agg types. | +| [AutocompleteStart](./kibana-plugin-plugins-data-public.autocompletestart.md) | \* | | [CustomFilter](./kibana-plugin-plugins-data-public.customfilter.md) | | | [EsaggsExpressionFunctionDefinition](./kibana-plugin-plugins-data-public.esaggsexpressionfunctiondefinition.md) | | | [EsdslExpressionFunctionDefinition](./kibana-plugin-plugins-data-public.esdslexpressionfunctiondefinition.md) | | @@ -134,6 +144,7 @@ | [FieldFormatId](./kibana-plugin-plugins-data-public.fieldformatid.md) | id type is needed for creating custom converters. | | [FieldFormatsContentType](./kibana-plugin-plugins-data-public.fieldformatscontenttype.md) | \* | | [FieldFormatsGetConfigFn](./kibana-plugin-plugins-data-public.fieldformatsgetconfigfn.md) | | +| [FieldFormatsStart](./kibana-plugin-plugins-data-public.fieldformatsstart.md) | | | [IAggConfig](./kibana-plugin-plugins-data-public.iaggconfig.md) | AggConfig This class represents an aggregation, which is displayed in the left-hand nav of the Visualize app. | | [IAggType](./kibana-plugin-plugins-data-public.iaggtype.md) | | | [IFieldFormat](./kibana-plugin-plugins-data-public.ifieldformat.md) | | @@ -145,12 +156,13 @@ | [InputTimeRange](./kibana-plugin-plugins-data-public.inputtimerange.md) | | | [ISearch](./kibana-plugin-plugins-data-public.isearch.md) | | | [ISearchGeneric](./kibana-plugin-plugins-data-public.isearchgeneric.md) | | -| [ISearchSource](./kibana-plugin-plugins-data-public.isearchsource.md) | \* | +| [ISearchSource](./kibana-plugin-plugins-data-public.isearchsource.md) | search source interface | | [MappingObject](./kibana-plugin-plugins-data-public.mappingobject.md) | | | [MatchAllFilter](./kibana-plugin-plugins-data-public.matchallfilter.md) | | | [ParsedInterval](./kibana-plugin-plugins-data-public.parsedinterval.md) | | | [PhraseFilter](./kibana-plugin-plugins-data-public.phrasefilter.md) | | | [PhrasesFilter](./kibana-plugin-plugins-data-public.phrasesfilter.md) | | +| [QueryStart](./kibana-plugin-plugins-data-public.querystart.md) | | | [QuerySuggestion](./kibana-plugin-plugins-data-public.querysuggestion.md) | \* | | [QuerySuggestionGetFn](./kibana-plugin-plugins-data-public.querysuggestiongetfn.md) | | | [RangeFilter](./kibana-plugin-plugins-data-public.rangefilter.md) | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystart.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystart.md new file mode 100644 index 0000000000000..f48a9ee7a79e4 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystart.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStart](./kibana-plugin-plugins-data-public.querystart.md) + +## QueryStart type + +Signature: + +```typescript +export declare type QueryStart = ReturnType; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystatechange.appfilters.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystatechange.appfilters.md new file mode 100644 index 0000000000000..b358e9477e515 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystatechange.appfilters.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStateChange](./kibana-plugin-plugins-data-public.querystatechange.md) > [appFilters](./kibana-plugin-plugins-data-public.querystatechange.appfilters.md) + +## QueryStateChange.appFilters property + +Signature: + +```typescript +appFilters?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystatechange.globalfilters.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystatechange.globalfilters.md new file mode 100644 index 0000000000000..c395f169c35a5 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystatechange.globalfilters.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStateChange](./kibana-plugin-plugins-data-public.querystatechange.md) > [globalFilters](./kibana-plugin-plugins-data-public.querystatechange.globalfilters.md) + +## QueryStateChange.globalFilters property + +Signature: + +```typescript +globalFilters?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystatechange.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystatechange.md new file mode 100644 index 0000000000000..71fb211da11d2 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystatechange.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStateChange](./kibana-plugin-plugins-data-public.querystatechange.md) + +## QueryStateChange interface + +Signature: + +```typescript +export interface QueryStateChange extends QueryStateChangePartial +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [appFilters](./kibana-plugin-plugins-data-public.querystatechange.appfilters.md) | boolean | | +| [globalFilters](./kibana-plugin-plugins-data-public.querystatechange.globalfilters.md) | boolean | | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md index 9f3ed8c1263ba..cf171d9ee9f37 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md @@ -7,5 +7,5 @@ Signature: ```typescript -QueryStringInput: React.FC> +QueryStringInput: React.FC> ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md index 498691c06285d..d1d20291a6799 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md @@ -7,7 +7,7 @@ Signature: ```typescript -SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "dataTestSubj" | "customSubmitButton" | "screenTitle" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "indicateNoData" | "timeHistory" | "onFiltersUpdated">, any> & { - WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; +SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "dataTestSubj" | "timeHistory" | "customSubmitButton" | "screenTitle" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "indicateNoData" | "onFiltersUpdated">, any> & { + WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; } ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor._constructor_.md index 6f5dd1076fb40..4c67639300883 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor._constructor_.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor._constructor_.md @@ -4,12 +4,12 @@ ## SearchInterceptor.(constructor) -This class should be instantiated with a `requestTimeout` corresponding with how many ms after requests are initiated that they should automatically cancel. +Constructs a new instance of the `SearchInterceptor` class Signature: ```typescript -constructor(deps: SearchInterceptorDeps, requestTimeout?: number | undefined); +constructor(deps: SearchInterceptorDeps); ``` ## Parameters @@ -17,5 +17,4 @@ constructor(deps: SearchInterceptorDeps, requestTimeout?: number | undefined); | Parameter | Type | Description | | --- | --- | --- | | deps | SearchInterceptorDeps | | -| requestTimeout | number | undefined | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.getpendingcount_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.getpendingcount_.md deleted file mode 100644 index ef36b3f37b0c7..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.getpendingcount_.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [getPendingCount$](./kibana-plugin-plugins-data-public.searchinterceptor.getpendingcount_.md) - -## SearchInterceptor.getPendingCount$() method - -Returns an `Observable` over the current number of pending searches. This could mean that one of the search requests is still in flight, or that it has only received partial responses. - -Signature: - -```typescript -getPendingCount$(): Observable; -``` -Returns: - -`Observable` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md index 32954927504ae..5cee345db6cd2 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md @@ -14,21 +14,18 @@ export declare class SearchInterceptor | Constructor | Modifiers | Description | | --- | --- | --- | -| [(constructor)(deps, requestTimeout)](./kibana-plugin-plugins-data-public.searchinterceptor._constructor_.md) | | This class should be instantiated with a requestTimeout corresponding with how many ms after requests are initiated that they should automatically cancel. | +| [(constructor)(deps)](./kibana-plugin-plugins-data-public.searchinterceptor._constructor_.md) | | Constructs a new instance of the SearchInterceptor class | ## Properties | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [deps](./kibana-plugin-plugins-data-public.searchinterceptor.deps.md) | | SearchInterceptorDeps | | -| [requestTimeout](./kibana-plugin-plugins-data-public.searchinterceptor.requesttimeout.md) | | number | undefined | | +| [showTimeoutError](./kibana-plugin-plugins-data-public.searchinterceptor.showtimeouterror.md) | | ((e: Error) => void) & import("lodash").Cancelable | | ## Methods | Method | Modifiers | Description | | --- | --- | --- | -| [getPendingCount$()](./kibana-plugin-plugins-data-public.searchinterceptor.getpendingcount_.md) | | Returns an Observable over the current number of pending searches. This could mean that one of the search requests is still in flight, or that it has only received partial responses. | -| [runSearch(request, signal, strategy)](./kibana-plugin-plugins-data-public.searchinterceptor.runsearch.md) | | | | [search(request, options)](./kibana-plugin-plugins-data-public.searchinterceptor.search.md) | | Searches using the given search method. Overrides the AbortSignal with one that will abort either when cancelPending is called, when the request times out, or when the original AbortSignal is aborted. Updates pendingCount$ when the request is started/finalized. | -| [setupTimers(options)](./kibana-plugin-plugins-data-public.searchinterceptor.setuptimers.md) | | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.requesttimeout.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.requesttimeout.md deleted file mode 100644 index 3123433762991..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.requesttimeout.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [requestTimeout](./kibana-plugin-plugins-data-public.searchinterceptor.requesttimeout.md) - -## SearchInterceptor.requestTimeout property - -Signature: - -```typescript -protected readonly requestTimeout?: number | undefined; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.runsearch.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.runsearch.md deleted file mode 100644 index ad1d1dcb59d7b..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.runsearch.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [runSearch](./kibana-plugin-plugins-data-public.searchinterceptor.runsearch.md) - -## SearchInterceptor.runSearch() method - -Signature: - -```typescript -protected runSearch(request: IEsSearchRequest, signal: AbortSignal, strategy?: string): Observable; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| request | IEsSearchRequest | | -| signal | AbortSignal | | -| strategy | string | | - -Returns: - -`Observable` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.setuptimers.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.setuptimers.md deleted file mode 100644 index fe35655258b4c..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.setuptimers.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [setupTimers](./kibana-plugin-plugins-data-public.searchinterceptor.setuptimers.md) - -## SearchInterceptor.setupTimers() method - -Signature: - -```typescript -protected setupTimers(options?: ISearchOptions): { - combinedSignal: AbortSignal; - cleanup: () => void; - }; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| options | ISearchOptions | | - -Returns: - -`{ - combinedSignal: AbortSignal; - cleanup: () => void; - }` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.showtimeouterror.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.showtimeouterror.md new file mode 100644 index 0000000000000..91ecb2821acbf --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.showtimeouterror.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [showTimeoutError](./kibana-plugin-plugins-data-public.searchinterceptor.showtimeouterror.md) + +## SearchInterceptor.showTimeoutError property + +Signature: + +```typescript +protected showTimeoutError: ((e: Error) => void) & import("lodash").Cancelable; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource._constructor_.md new file mode 100644 index 0000000000000..00e9050ee8ff9 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource._constructor_.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [(constructor)](./kibana-plugin-plugins-data-public.searchsource._constructor_.md) + +## SearchSource.(constructor) + +Constructs a new instance of the `SearchSource` class + +Signature: + +```typescript +constructor(fields: SearchSourceFields | undefined, dependencies: SearchSourceDependencies); +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fields | SearchSourceFields | undefined | | +| dependencies | SearchSourceDependencies | | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.create.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.create.md new file mode 100644 index 0000000000000..4264c3ff224b1 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.create.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [create](./kibana-plugin-plugins-data-public.searchsource.create.md) + +## SearchSource.create() method + +> Warning: This API is now obsolete. +> +> Don't use. +> + +Signature: + +```typescript +create(): SearchSource; +``` +Returns: + +`SearchSource` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.createchild.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.createchild.md new file mode 100644 index 0000000000000..0c2e75651b354 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.createchild.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [createChild](./kibana-plugin-plugins-data-public.searchsource.createchild.md) + +## SearchSource.createChild() method + +creates a new child search source + +Signature: + +```typescript +createChild(options?: {}): SearchSource; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | {} | | + +Returns: + +`SearchSource` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.createcopy.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.createcopy.md new file mode 100644 index 0000000000000..1053d31010d00 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.createcopy.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [createCopy](./kibana-plugin-plugins-data-public.searchsource.createcopy.md) + +## SearchSource.createCopy() method + +creates a copy of this search source (without its children) + +Signature: + +```typescript +createCopy(): SearchSource; +``` +Returns: + +`SearchSource` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.destroy.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.destroy.md new file mode 100644 index 0000000000000..8a7cc5ee75d11 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.destroy.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [destroy](./kibana-plugin-plugins-data-public.searchsource.destroy.md) + +## SearchSource.destroy() method + +Completely destroy the SearchSource. {undefined} + +Signature: + +```typescript +destroy(): void; +``` +Returns: + +`void` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.fetch.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.fetch.md new file mode 100644 index 0000000000000..8fd17e6b1a1d9 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.fetch.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [fetch](./kibana-plugin-plugins-data-public.searchsource.fetch.md) + +## SearchSource.fetch() method + +Fetch this source and reject the returned Promise on error + + +Signature: + +```typescript +fetch(options?: ISearchOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | ISearchOptions | | + +Returns: + +`Promise>` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfield.md new file mode 100644 index 0000000000000..7c516cc29df15 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfield.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [getField](./kibana-plugin-plugins-data-public.searchsource.getfield.md) + +## SearchSource.getField() method + +Gets a single field from the fields + +Signature: + +```typescript +getField(field: K, recurse?: boolean): SearchSourceFields[K]; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| field | K | | +| recurse | boolean | | + +Returns: + +`SearchSourceFields[K]` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfields.md new file mode 100644 index 0000000000000..1980227bee623 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfields.md @@ -0,0 +1,51 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [getFields](./kibana-plugin-plugins-data-public.searchsource.getfields.md) + +## SearchSource.getFields() method + +returns all search source fields + +Signature: + +```typescript +getFields(): { + type?: string | undefined; + query?: import("../..").Query | undefined; + filter?: Filter | Filter[] | (() => Filter | Filter[] | undefined) | undefined; + sort?: Record | Record[] | undefined; + highlight?: any; + highlightAll?: boolean | undefined; + aggs?: any; + from?: number | undefined; + size?: number | undefined; + source?: string | boolean | string[] | undefined; + version?: boolean | undefined; + fields?: string | boolean | string[] | undefined; + index?: import("../..").IndexPattern | undefined; + searchAfter?: import("./types").EsQuerySearchAfter | undefined; + timeout?: string | undefined; + terminate_after?: number | undefined; + }; +``` +Returns: + +`{ + type?: string | undefined; + query?: import("../..").Query | undefined; + filter?: Filter | Filter[] | (() => Filter | Filter[] | undefined) | undefined; + sort?: Record | Record[] | undefined; + highlight?: any; + highlightAll?: boolean | undefined; + aggs?: any; + from?: number | undefined; + size?: number | undefined; + source?: string | boolean | string[] | undefined; + version?: boolean | undefined; + fields?: string | boolean | string[] | undefined; + index?: import("../..").IndexPattern | undefined; + searchAfter?: import("./types").EsQuerySearchAfter | undefined; + timeout?: string | undefined; + terminate_after?: number | undefined; + }` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getid.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getid.md new file mode 100644 index 0000000000000..b33410d86ae85 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getid.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [getId](./kibana-plugin-plugins-data-public.searchsource.getid.md) + +## SearchSource.getId() method + +returns search source id + +Signature: + +```typescript +getId(): string; +``` +Returns: + +`string` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getownfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getownfield.md new file mode 100644 index 0000000000000..d5a133772264e --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getownfield.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [getOwnField](./kibana-plugin-plugins-data-public.searchsource.getownfield.md) + +## SearchSource.getOwnField() method + +Get the field from our own fields, don't traverse up the chain + +Signature: + +```typescript +getOwnField(field: K): SearchSourceFields[K]; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| field | K | | + +Returns: + +`SearchSourceFields[K]` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getparent.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getparent.md new file mode 100644 index 0000000000000..14578f7949ba6 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getparent.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [getParent](./kibana-plugin-plugins-data-public.searchsource.getparent.md) + +## SearchSource.getParent() method + +Get the parent of this SearchSource {undefined\|searchSource} + +Signature: + +```typescript +getParent(): SearchSource | undefined; +``` +Returns: + +`SearchSource | undefined` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getsearchrequestbody.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getsearchrequestbody.md new file mode 100644 index 0000000000000..cc50d3f017971 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getsearchrequestbody.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [getSearchRequestBody](./kibana-plugin-plugins-data-public.searchsource.getsearchrequestbody.md) + +## SearchSource.getSearchRequestBody() method + +Returns body contents of the search request, often referred as query DSL. + +Signature: + +```typescript +getSearchRequestBody(): Promise; +``` +Returns: + +`Promise` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getserializedfields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getserializedfields.md new file mode 100644 index 0000000000000..3f58a76b24cd0 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getserializedfields.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [getSerializedFields](./kibana-plugin-plugins-data-public.searchsource.getserializedfields.md) + +## SearchSource.getSerializedFields() method + +serializes search source fields (which can later be passed to [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md)) + +Signature: + +```typescript +getSerializedFields(): SearchSourceFields; +``` +Returns: + +`SearchSourceFields` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.history.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.history.md new file mode 100644 index 0000000000000..e77c9dac7239f --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.history.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [history](./kibana-plugin-plugins-data-public.searchsource.history.md) + +## SearchSource.history property + +Signature: + +```typescript +history: SearchRequest[]; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md new file mode 100644 index 0000000000000..87346f81b13e2 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md @@ -0,0 +1,49 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) + +## SearchSource class + +\* + +Signature: + +```typescript +export declare class SearchSource +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(fields, dependencies)](./kibana-plugin-plugins-data-public.searchsource._constructor_.md) | | Constructs a new instance of the SearchSource class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [history](./kibana-plugin-plugins-data-public.searchsource.history.md) | | SearchRequest[] | | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [create()](./kibana-plugin-plugins-data-public.searchsource.create.md) | | | +| [createChild(options)](./kibana-plugin-plugins-data-public.searchsource.createchild.md) | | creates a new child search source | +| [createCopy()](./kibana-plugin-plugins-data-public.searchsource.createcopy.md) | | creates a copy of this search source (without its children) | +| [destroy()](./kibana-plugin-plugins-data-public.searchsource.destroy.md) | | Completely destroy the SearchSource. {undefined} | +| [fetch(options)](./kibana-plugin-plugins-data-public.searchsource.fetch.md) | | Fetch this source and reject the returned Promise on error | +| [getField(field, recurse)](./kibana-plugin-plugins-data-public.searchsource.getfield.md) | | Gets a single field from the fields | +| [getFields()](./kibana-plugin-plugins-data-public.searchsource.getfields.md) | | returns all search source fields | +| [getId()](./kibana-plugin-plugins-data-public.searchsource.getid.md) | | returns search source id | +| [getOwnField(field)](./kibana-plugin-plugins-data-public.searchsource.getownfield.md) | | Get the field from our own fields, don't traverse up the chain | +| [getParent()](./kibana-plugin-plugins-data-public.searchsource.getparent.md) | | Get the parent of this SearchSource {undefined\|searchSource} | +| [getSearchRequestBody()](./kibana-plugin-plugins-data-public.searchsource.getsearchrequestbody.md) | | Returns body contents of the search request, often referred as query DSL. | +| [getSerializedFields()](./kibana-plugin-plugins-data-public.searchsource.getserializedfields.md) | | serializes search source fields (which can later be passed to [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md)) | +| [onRequestStart(handler)](./kibana-plugin-plugins-data-public.searchsource.onrequeststart.md) | | Add a handler that will be notified whenever requests start | +| [serialize()](./kibana-plugin-plugins-data-public.searchsource.serialize.md) | | Serializes the instance to a JSON string and a set of referenced objects. Use this method to get a representation of the search source which can be stored in a saved object.The references returned by this function can be mixed with other references in the same object, however make sure there are no name-collisions. The references will be named kibanaSavedObjectMeta.searchSourceJSON.index and kibanaSavedObjectMeta.searchSourceJSON.filter[<number>].meta.index.Using createSearchSource, the instance can be re-created. | +| [setField(field, value)](./kibana-plugin-plugins-data-public.searchsource.setfield.md) | | sets value to a single search source feild | +| [setFields(newFields)](./kibana-plugin-plugins-data-public.searchsource.setfields.md) | | Internal, do not use. Overrides all search source fields with the new field array. | +| [setParent(parent, options)](./kibana-plugin-plugins-data-public.searchsource.setparent.md) | | Set a searchSource that this source should inherit from | +| [setPreferredSearchStrategyId(searchStrategyId)](./kibana-plugin-plugins-data-public.searchsource.setpreferredsearchstrategyid.md) | | internal, dont use | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.onrequeststart.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.onrequeststart.md new file mode 100644 index 0000000000000..a9386ddae44e1 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.onrequeststart.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [onRequestStart](./kibana-plugin-plugins-data-public.searchsource.onrequeststart.md) + +## SearchSource.onRequestStart() method + +Add a handler that will be notified whenever requests start + +Signature: + +```typescript +onRequestStart(handler: (searchSource: SearchSource, options?: ISearchOptions) => Promise): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| handler | (searchSource: SearchSource, options?: ISearchOptions) => Promise<unknown> | | + +Returns: + +`void` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.serialize.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.serialize.md new file mode 100644 index 0000000000000..73ba8eb66040b --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.serialize.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [serialize](./kibana-plugin-plugins-data-public.searchsource.serialize.md) + +## SearchSource.serialize() method + +Serializes the instance to a JSON string and a set of referenced objects. Use this method to get a representation of the search source which can be stored in a saved object. + +The references returned by this function can be mixed with other references in the same object, however make sure there are no name-collisions. The references will be named `kibanaSavedObjectMeta.searchSourceJSON.index` and `kibanaSavedObjectMeta.searchSourceJSON.filter[].meta.index`. + +Using `createSearchSource`, the instance can be re-created. + +Signature: + +```typescript +serialize(): { + searchSourceJSON: string; + references: import("../../../../../core/public").SavedObjectReference[]; + }; +``` +Returns: + +`{ + searchSourceJSON: string; + references: import("../../../../../core/public").SavedObjectReference[]; + }` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setfield.md new file mode 100644 index 0000000000000..22619940f1589 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setfield.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [setField](./kibana-plugin-plugins-data-public.searchsource.setfield.md) + +## SearchSource.setField() method + +sets value to a single search source feild + +Signature: + +```typescript +setField(field: K, value: SearchSourceFields[K]): this; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| field | K | | +| value | SearchSourceFields[K] | | + +Returns: + +`this` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setfields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setfields.md new file mode 100644 index 0000000000000..f92ffc0fc991d --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setfields.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [setFields](./kibana-plugin-plugins-data-public.searchsource.setfields.md) + +## SearchSource.setFields() method + +Internal, do not use. Overrides all search source fields with the new field array. + + +Signature: + +```typescript +setFields(newFields: SearchSourceFields): this; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| newFields | SearchSourceFields | | + +Returns: + +`this` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setparent.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setparent.md new file mode 100644 index 0000000000000..19bf10bec210f --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setparent.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [setParent](./kibana-plugin-plugins-data-public.searchsource.setparent.md) + +## SearchSource.setParent() method + +Set a searchSource that this source should inherit from + +Signature: + +```typescript +setParent(parent?: ISearchSource, options?: SearchSourceOptions): this; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| parent | ISearchSource | | +| options | SearchSourceOptions | | + +Returns: + +`this` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setpreferredsearchstrategyid.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setpreferredsearchstrategyid.md new file mode 100644 index 0000000000000..e3261873ba104 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setpreferredsearchstrategyid.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [setPreferredSearchStrategyId](./kibana-plugin-plugins-data-public.searchsource.setpreferredsearchstrategyid.md) + +## SearchSource.setPreferredSearchStrategyId() method + +internal, dont use + +Signature: + +```typescript +setPreferredSearchStrategyId(searchStrategyId: string): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| searchStrategyId | string | | + +Returns: + +`void` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.aggs.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.aggs.md index 743646708b4c6..f6bab8e424857 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.aggs.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.aggs.md @@ -4,6 +4,8 @@ ## SearchSourceFields.aggs property +[AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.filter.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.filter.md index a14d33420a22d..5fd615cc647d2 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.filter.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.filter.md @@ -4,6 +4,8 @@ ## SearchSourceFields.filter property +[Filter](./kibana-plugin-plugins-data-public.filter.md) + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.index.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.index.md index fa1d1a552a560..cf1b1cfa253fd 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.index.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.index.md @@ -4,6 +4,7 @@ ## SearchSourceFields.index property + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.md index 7a64af0f8b2b8..d19f1da439cee 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.md @@ -4,6 +4,8 @@ ## SearchSourceFields interface +search source fields + Signature: ```typescript @@ -14,17 +16,17 @@ export interface SearchSourceFields | Property | Type | Description | | --- | --- | --- | -| [aggs](./kibana-plugin-plugins-data-public.searchsourcefields.aggs.md) | any | | +| [aggs](./kibana-plugin-plugins-data-public.searchsourcefields.aggs.md) | any | [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) | | [fields](./kibana-plugin-plugins-data-public.searchsourcefields.fields.md) | NameList | | -| [filter](./kibana-plugin-plugins-data-public.searchsourcefields.filter.md) | Filter[] | Filter | (() => Filter[] | Filter | undefined) | | +| [filter](./kibana-plugin-plugins-data-public.searchsourcefields.filter.md) | Filter[] | Filter | (() => Filter[] | Filter | undefined) | [Filter](./kibana-plugin-plugins-data-public.filter.md) | | [from](./kibana-plugin-plugins-data-public.searchsourcefields.from.md) | number | | | [highlight](./kibana-plugin-plugins-data-public.searchsourcefields.highlight.md) | any | | | [highlightAll](./kibana-plugin-plugins-data-public.searchsourcefields.highlightall.md) | boolean | | | [index](./kibana-plugin-plugins-data-public.searchsourcefields.index.md) | IndexPattern | | -| [query](./kibana-plugin-plugins-data-public.searchsourcefields.query.md) | Query | | +| [query](./kibana-plugin-plugins-data-public.searchsourcefields.query.md) | Query | [Query](./kibana-plugin-plugins-data-public.query.md) | | [searchAfter](./kibana-plugin-plugins-data-public.searchsourcefields.searchafter.md) | EsQuerySearchAfter | | | [size](./kibana-plugin-plugins-data-public.searchsourcefields.size.md) | number | | -| [sort](./kibana-plugin-plugins-data-public.searchsourcefields.sort.md) | EsQuerySortValue | EsQuerySortValue[] | | +| [sort](./kibana-plugin-plugins-data-public.searchsourcefields.sort.md) | EsQuerySortValue | EsQuerySortValue[] | [EsQuerySortValue](./kibana-plugin-plugins-data-public.esquerysortvalue.md) | | [source](./kibana-plugin-plugins-data-public.searchsourcefields.source.md) | NameList | | | [terminate\_after](./kibana-plugin-plugins-data-public.searchsourcefields.terminate_after.md) | number | | | [timeout](./kibana-plugin-plugins-data-public.searchsourcefields.timeout.md) | string | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.query.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.query.md index 687dafce798d1..661ce94a06afb 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.query.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.query.md @@ -4,6 +4,8 @@ ## SearchSourceFields.query property +[Query](./kibana-plugin-plugins-data-public.query.md) + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.sort.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.sort.md index c10f556cef6d6..32f513378e35e 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.sort.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.sort.md @@ -4,6 +4,8 @@ ## SearchSourceFields.sort property +[EsQuerySortValue](./kibana-plugin-plugins-data-public.esquerysortvalue.md) + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ui_settings.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ui_settings.md index e515c3513df6c..6ed20beb396f1 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ui_settings.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ui_settings.md @@ -20,6 +20,7 @@ UI_SETTINGS: { readonly COURIER_MAX_CONCURRENT_SHARD_REQUESTS: "courier:maxConcurrentShardRequests"; readonly COURIER_BATCH_SEARCHES: "courier:batchSearches"; readonly SEARCH_INCLUDE_FROZEN: "search:includeFrozen"; + readonly SEARCH_TIMEOUT: "search:timeout"; readonly HISTOGRAM_BAR_TARGET: "histogram:barTarget"; readonly HISTOGRAM_MAX_BARS: "histogram:maxBars"; readonly HISTORY_LIMIT: "history:limit"; diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.es_search_strategy.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.es_search_strategy.md new file mode 100644 index 0000000000000..8fac5cf4d7a9e --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.es_search_strategy.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ES\_SEARCH\_STRATEGY](./kibana-plugin-plugins-data-server.es_search_strategy.md) + +## ES\_SEARCH\_STRATEGY variable + +Signature: + +```typescript +ES_SEARCH_STRATEGY = "es" +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.getdefaultsearchparams.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.getdefaultsearchparams.md index 9de005c1fd0dd..e718ca42ca30f 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.getdefaultsearchparams.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.getdefaultsearchparams.md @@ -7,24 +7,26 @@ Signature: ```typescript -export declare function getDefaultSearchParams(config: SharedGlobalConfig): { - timeout: string; +export declare function getDefaultSearchParams(uiSettingsClient: IUiSettingsClient): Promise<{ + maxConcurrentShardRequests: number | undefined; + ignoreThrottled: boolean; ignoreUnavailable: boolean; - restTotalHitsAsInt: boolean; -}; + trackTotalHits: boolean; +}>; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| config | SharedGlobalConfig | | +| uiSettingsClient | IUiSettingsClient | | Returns: -`{ - timeout: string; +`Promise<{ + maxConcurrentShardRequests: number | undefined; + ignoreThrottled: boolean; ignoreUnavailable: boolean; - restTotalHitsAsInt: boolean; -}` + trackTotalHits: boolean; +}>` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.getshardtimeout.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.getshardtimeout.md new file mode 100644 index 0000000000000..d7e2a597ff33d --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.getshardtimeout.md @@ -0,0 +1,30 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [getShardTimeout](./kibana-plugin-plugins-data-server.getshardtimeout.md) + +## getShardTimeout() function + +Signature: + +```typescript +export declare function getShardTimeout(config: SharedGlobalConfig): { + timeout: string; +} | { + timeout?: undefined; +}; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| config | SharedGlobalConfig | | + +Returns: + +`{ + timeout: string; +} | { + timeout?: undefined; +}` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md index 77a2954428f8d..d106f3a35a91c 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md @@ -28,7 +28,7 @@ export interface IFieldType | [searchable](./kibana-plugin-plugins-data-server.ifieldtype.searchable.md) | boolean | | | [sortable](./kibana-plugin-plugins-data-server.ifieldtype.sortable.md) | boolean | | | [subType](./kibana-plugin-plugins-data-server.ifieldtype.subtype.md) | IFieldSubType | | -| [toSpec](./kibana-plugin-plugins-data-server.ifieldtype.tospec.md) | () => FieldSpec | | +| [toSpec](./kibana-plugin-plugins-data-server.ifieldtype.tospec.md) | (options?: {
getFormatterForField?: IndexPattern['getFormatterForField'];
}) => FieldSpec | | | [type](./kibana-plugin-plugins-data-server.ifieldtype.type.md) | string | | | [visualizable](./kibana-plugin-plugins-data-server.ifieldtype.visualizable.md) | boolean | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.tospec.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.tospec.md index d1863bebce4f0..6f8ee9d9eebf0 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.tospec.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.tospec.md @@ -7,5 +7,7 @@ Signature: ```typescript -toSpec?: () => FieldSpec; +toSpec?: (options?: { + getFormatterForField?: IndexPattern['getFormatterForField']; + }) => FieldSpec; ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.abortsignal.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.abortsignal.md new file mode 100644 index 0000000000000..693345f480a9a --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.abortsignal.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchOptions](./kibana-plugin-plugins-data-server.isearchoptions.md) > [abortSignal](./kibana-plugin-plugins-data-server.isearchoptions.abortsignal.md) + +## ISearchOptions.abortSignal property + +An `AbortSignal` that allows the caller of `search` to abort a search request. + +Signature: + +```typescript +abortSignal?: AbortSignal; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md index 002ce864a1aa4..21ddaef3a0b94 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md @@ -14,6 +14,6 @@ export interface ISearchOptions | Property | Type | Description | | --- | --- | --- | -| [signal](./kibana-plugin-plugins-data-server.isearchoptions.signal.md) | AbortSignal | An AbortSignal that allows the caller of search to abort a search request. | -| [strategy](./kibana-plugin-plugins-data-server.isearchoptions.strategy.md) | string | | +| [abortSignal](./kibana-plugin-plugins-data-server.isearchoptions.abortsignal.md) | AbortSignal | An AbortSignal that allows the caller of search to abort a search request. | +| [strategy](./kibana-plugin-plugins-data-server.isearchoptions.strategy.md) | string | Use this option to force using a specific server side search strategy. Leave empty to use the default strategy. | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.signal.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.signal.md deleted file mode 100644 index 948dfd66da7a0..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.signal.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchOptions](./kibana-plugin-plugins-data-server.isearchoptions.md) > [signal](./kibana-plugin-plugins-data-server.isearchoptions.signal.md) - -## ISearchOptions.signal property - -An `AbortSignal` that allows the caller of `search` to abort a search request. - -Signature: - -```typescript -signal?: AbortSignal; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.strategy.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.strategy.md index 6df72d023e2c0..65da7fddd13f6 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.strategy.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.strategy.md @@ -4,6 +4,8 @@ ## ISearchOptions.strategy property +Use this option to force using a specific server side search strategy. Leave empty to use the default strategy. + Signature: ```typescript diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md index 62d954cb80eb7..577532d22b3d3 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md @@ -16,5 +16,5 @@ export interface ISearchStartAggsStart | | | [getSearchStrategy](./kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md) | (name: string) => ISearchStrategy<SearchStrategyRequest, SearchStrategyResponse> | Get other registered search strategies. For example, if a new strategy needs to use the already-registered ES search strategy, it can use this function to accomplish that. | -| [search](./kibana-plugin-plugins-data-server.isearchstart.search.md) | (context: RequestHandlerContext, request: IKibanaSearchRequest, options: ISearchOptions) => Promise<IKibanaSearchResponse> | | +| [search](./kibana-plugin-plugins-data-server.isearchstart.search.md) | (context: RequestHandlerContext, request: IEsSearchRequest, options: ISearchOptions) => Promise<IEsSearchResponse> | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.search.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.search.md index 1c2ae91699559..33ca818afc769 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.search.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.search.md @@ -7,5 +7,5 @@ Signature: ```typescript -search: (context: RequestHandlerContext, request: IKibanaSearchRequest, options: ISearchOptions) => Promise; +search: (context: RequestHandlerContext, request: IEsSearchRequest, options: ISearchOptions) => Promise; ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md index 0292e08063fbb..f5b587d86b349 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md @@ -26,11 +26,13 @@ | Function | Description | | --- | --- | -| [getDefaultSearchParams(config)](./kibana-plugin-plugins-data-server.getdefaultsearchparams.md) | | +| [getDefaultSearchParams(uiSettingsClient)](./kibana-plugin-plugins-data-server.getdefaultsearchparams.md) | | +| [getShardTimeout(config)](./kibana-plugin-plugins-data-server.getshardtimeout.md) | | | [getTime(indexPattern, timeRange, options)](./kibana-plugin-plugins-data-server.gettime.md) | | | [parseInterval(interval)](./kibana-plugin-plugins-data-server.parseinterval.md) | | | [plugin(initializerContext)](./kibana-plugin-plugins-data-server.plugin.md) | Static code to be shared externally | | [shouldReadFieldFromDocValues(aggregatable, esType)](./kibana-plugin-plugins-data-server.shouldreadfieldfromdocvalues.md) | | +| [toSnakeCase(obj)](./kibana-plugin-plugins-data-server.tosnakecase.md) | | | [usageProvider(core)](./kibana-plugin-plugins-data-server.usageprovider.md) | | ## Interfaces @@ -71,6 +73,7 @@ | [AggGroupNames](./kibana-plugin-plugins-data-server.agggroupnames.md) | | | [castEsToKbnFieldTypeName](./kibana-plugin-plugins-data-server.castestokbnfieldtypename.md) | Get the KbnFieldType name for an esType string | | [config](./kibana-plugin-plugins-data-server.config.md) | | +| [ES\_SEARCH\_STRATEGY](./kibana-plugin-plugins-data-server.es_search_strategy.md) | | | [esFilters](./kibana-plugin-plugins-data-server.esfilters.md) | | | [esKuery](./kibana-plugin-plugins-data-server.eskuery.md) | | | [esQuery](./kibana-plugin-plugins-data-server.esquery.md) | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md index 2d9104ef894bc..455c5ecdd8195 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md @@ -8,7 +8,7 @@ ```typescript start(core: CoreStart): { - search: ISearchStart>; + search: ISearchStart>; fieldFormats: { fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; @@ -27,7 +27,7 @@ start(core: CoreStart): { Returns: `{ - search: ISearchStart>; + search: ISearchStart>; fieldFormats: { fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tosnakecase.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tosnakecase.md new file mode 100644 index 0000000000000..eda9e9c312e59 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tosnakecase.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [toSnakeCase](./kibana-plugin-plugins-data-server.tosnakecase.md) + +## toSnakeCase() function + +Signature: + +```typescript +export declare function toSnakeCase(obj: Record): import("lodash").Dictionary; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| obj | Record<string, any> | | + +Returns: + +`import("lodash").Dictionary` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ui_settings.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ui_settings.md index e419b64cd43aa..2d4ce75b956df 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ui_settings.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ui_settings.md @@ -20,6 +20,7 @@ UI_SETTINGS: { readonly COURIER_MAX_CONCURRENT_SHARD_REQUESTS: "courier:maxConcurrentShardRequests"; readonly COURIER_BATCH_SEARCHES: "courier:batchSearches"; readonly SEARCH_INCLUDE_FROZEN: "search:includeFrozen"; + readonly SEARCH_TIMEOUT: "search:timeout"; readonly HISTOGRAM_BAR_TARGET: "histogram:barTarget"; readonly HISTOGRAM_MAX_BARS: "histogram:maxBars"; readonly HISTORY_LIMIT: "history:limit"; diff --git a/docs/discover/search.asciidoc b/docs/discover/search.asciidoc index eef2a12a964b8..da58382deb89a 100644 --- a/docs/discover/search.asciidoc +++ b/docs/discover/search.asciidoc @@ -1,7 +1,7 @@ [[search]] == Search data Many Kibana apps embed a query bar for real-time search, including -*Discover*, *Visualize*, and *Dashboard*. +*Discover* and *Dashboard*. [float] === Search your data @@ -84,7 +84,7 @@ query language you can also submit queries using the {ref}/query-dsl.html[Elasti [[save-open-search]] === Save a search -A saved search persists your current view of Discover for later retrieval and reuse. You can reload a saved search into Discover, add it to a dashboard, and use it as the basis for a <>. +A saved search persists your current view of Discover for later retrieval and reuse. You can reload a saved search into Discover, add it to a dashboard, and use it as the basis for a visualization. A saved search includes the query text, filters, and optionally, the time filter. A saved search also includes the selected columns in the document table, the sort order, and the current index pattern. @@ -120,7 +120,7 @@ used for the saved search will also be automatically selected. [[save-load-delete-query]] === Save a query -A saved query is a portable collection of query text and filters that you can reuse in <>, <>, and <>. Save a query when you want to: +A saved query is a portable collection of query text and filters that you can reuse in <> and <>. Save a query when you want to: * Retrieve results from the same query at a later time without having to reenter the query text, add the filters or set the time filter * View the results of the same query in multiple apps @@ -148,7 +148,7 @@ image::discover/images/saved-query-save-form-default-filters.png["Example of the . Click *Save*. ==== Load a query -To load a saved query into Discover, Dashboard, or Visualize: +To load a saved query into Discover or Dashboard: . Click *#* in the search bar, next to the query text input. . Select the query you want to load. You might need to scroll down to find the query you are looking for. diff --git a/docs/drilldowns/drilldowns.asciidoc b/docs/drilldowns/drilldowns.asciidoc deleted file mode 100644 index e2dfaa5af39ce..0000000000000 --- a/docs/drilldowns/drilldowns.asciidoc +++ /dev/null @@ -1,108 +0,0 @@ -[[drilldowns]] -== Use drilldowns for dashboard actions - -Drilldowns, also known as custom actions, allow you to configure a -workflow for analyzing and troubleshooting your data. -Using a drilldown, you can navigate from one dashboard to another, -taking the current time range, filters, and other parameters with you, -so the context remains the same. You can continue your analysis from a new perspective. - -For example, you might have a dashboard that shows the overall status of multiple data centers. -You can create a drilldown that navigates from this dashboard to a dashboard -that shows a single data center or server. - -[float] -[[how-drilldowns-work]] -=== How drilldowns work - -Drilldowns are user-configurable {kib} actions that are stored with the -dashboard metadata. Drilldowns are specific to the dashboard panel -for which you create them—they are not shared across panels. -A panel can have multiple drilldowns. - -This example shows a dashboard panel that contains a pie chart. -Typically, clicking a pie slice applies the current filter. -When a panel has a drilldown, clicking a pie slice opens a menu with -the default action and your drilldowns. Refer to the <> -for instructions on how to create this drilldown. - -[role="screenshot"] -image::images/drilldown_on_piechart.gif[Drilldown on pie chart that navigates to another dashboard] - -Third-party developers can create drilldowns. -Refer to https://github.com/elastic/kibana/tree/master/x-pack/examples/ui_actions_enhanced_examples[this example plugin] -to learn how to code drilldowns. - -[float] -[[create-manage-drilldowns]] -=== Create and manage drilldowns - -Your dashboard must be in *Edit* mode to create a drilldown. -Once a panel has at least one drilldown, the menu also includes a *Manage drilldowns* action -for editing and deleting drilldowns. - -[role="screenshot"] -image::images/drilldown_menu.png[Panel menu with Create drilldown and Manage drilldown actions] - -[float] -[[drilldowns-example]] -=== Try it: Create a drilldown - -This example shows how to create the *Host Overview* drilldown shown earlier in this doc. - -[float] -==== Set up the dashboards - -. Add the <> data set. - -. Create a new dashboard, called `Host Overview`, and include these visualizations -from the sample data set: -+ -[%hardbreaks] -*[Logs] Heatmap* -*[Logs] Visitors by OS* -*[Logs] Host, Visits, and Bytes Table* -*[Logs] Total Requests and Bytes* -+ -TIP: If you don’t see data for a panel, try changing the time range. - -. Open the *[Logs] Web traffic* dashboard. - -. Set a search and filter. -+ -[%hardbreaks] -Search: `extension.keyword:( “gz” or “css” or “deb”)` -Filter: `geo.src : CN` - -[float] -==== Create the drilldown - - -. In the dashboard menu bar, click *Edit*. - -. In *[Logs] Visitors by OS*, open the panel menu, and then select *Create drilldown*. - -. Give the drilldown a name. - -. Select *Host Overview* as the destination dashboard. - -. Keep both filters enabled so that the drilldown carries over the global filters and date range. -+ -Your input should look similar to this: -+ -[role="screenshot"] -image::images/drilldown_create.png[Create drilldown with entries for drilldown name and destination] - -. Click *Create drilldown.* - -. Save the dashboard. -+ -If you don’t save the drilldown, and then navigate away, the drilldown is lost. - -. In *[Logs] Visitors by OS*, click the `win 8` slice of the pie, and then select the name of your drilldown. -+ -[role="screenshot"] -image::images/drilldown_on_panel.png[Drilldown on pie chart that navigates to another dashboard] -+ -You are navigated to your destination dashboard. Verify that the search query, filters, -and time range are carried over. diff --git a/docs/drilldowns/explore-underlying-data.asciidoc b/docs/drilldowns/explore-underlying-data.asciidoc deleted file mode 100644 index c2bba599730d8..0000000000000 --- a/docs/drilldowns/explore-underlying-data.asciidoc +++ /dev/null @@ -1,41 +0,0 @@ -[[explore-underlying-data]] -== Explore the underlying data for a visualization - -++++ -Explore the underlying data -++++ - -Dashboard panels have an *Explore underlying data* action that navigates you to *Discover*, -where you can narrow your documents to the ones you'll most likely use in a visualization. -This action is available for visualizations backed by a single index pattern. - -You can access *Explore underlying data* in two ways: from the panel context -menu or from the menu that appears when you interact with the chart. - -[float] -[[explore-data-from-panel-context-menu]] -=== Explore data from panel context menu - -The *Explore underlying data* action in the panel menu navigates you to Discover, -carrying over the index pattern, filters, query, and time range for the visualization. - -[role="screenshot"] -image::images/explore_data_context_menu.png[Explore underlying data from panel context menu] - -[float] -[[explore-data-from-chart]] -=== Explore data from chart action - -Initiating *Explore underlying data* from the chart also navigates to Discover, -carrying over the current context for the visualization. In addition, this action -applies the filters and time range created by the events that triggered the action. - -[role="screenshot"] -image::images/explore_data_in_chart.png[Explore underlying data from chart] - -To enable this action add the following line to your `kibana.yml` config. - -["source","yml"] ------------ -xpack.discoverEnhanced.actions.exploreDataInChart.enabled: true ------------ diff --git a/docs/getting-started/add-sample-data.asciidoc b/docs/getting-started/add-sample-data.asciidoc deleted file mode 100644 index ab43431601888..0000000000000 --- a/docs/getting-started/add-sample-data.asciidoc +++ /dev/null @@ -1,28 +0,0 @@ -[[add-sample-data]] -== Add sample data - -{kib} has several sample data sets that you can use to explore {kib} before loading your own data. -These sample data sets showcase a variety of use cases: - -* *eCommerce orders* includes visualizations for product-related information, -such as cost, revenue, and price. -* *Flight data* enables you to view and interact with flight routes. -* *Web logs* lets you analyze website traffic. - -To get started, go to the {kib} home page and click the link underneath *Add sample data*. - -Once you've loaded a data set, click *View data* to view prepackaged -visualizations, dashboards, Canvas workpads, Maps, and Machine Learning jobs. - -[role="screenshot"] -image::images/add-sample-data.png[] - -NOTE: The timestamps in the sample data sets are relative to when they are installed. -If you uninstall and reinstall a data set, the timestamps will change to reflect the most recent installation. - -[float] -=== Next steps - -* Explore {kib} by following the <>. - -* Learn how to load data, define index patterns, and build visualizations by <>. diff --git a/docs/getting-started/images/gs_maps_time_filter.png b/docs/getting-started/images/gs_maps_time_filter.png new file mode 100644 index 0000000000000..83e20c279906e Binary files /dev/null and b/docs/getting-started/images/gs_maps_time_filter.png differ diff --git a/docs/getting-started/images/tutorial-discover-2.png b/docs/getting-started/images/tutorial-discover-2.png index 7190c90d8e5ba..681e4834de830 100644 Binary files a/docs/getting-started/images/tutorial-discover-2.png and b/docs/getting-started/images/tutorial-discover-2.png differ diff --git a/docs/getting-started/images/tutorial-pattern-1.png b/docs/getting-started/images/tutorial-pattern-1.png index 8a289f93fc66e..0026b18775518 100644 Binary files a/docs/getting-started/images/tutorial-pattern-1.png and b/docs/getting-started/images/tutorial-pattern-1.png differ diff --git a/docs/getting-started/images/tutorial-visualize-bar-1.5.png b/docs/getting-started/images/tutorial-visualize-bar-1.5.png index c02b9ca59dff5..009152f9407e4 100644 Binary files a/docs/getting-started/images/tutorial-visualize-bar-1.5.png and b/docs/getting-started/images/tutorial-visualize-bar-1.5.png differ diff --git a/docs/getting-started/images/tutorial-visualize-map-2.png b/docs/getting-started/images/tutorial-visualize-map-2.png index f4d1d0e47fe6a..ed2fd47cb27de 100644 Binary files a/docs/getting-started/images/tutorial-visualize-map-2.png and b/docs/getting-started/images/tutorial-visualize-map-2.png differ diff --git a/docs/getting-started/images/tutorial-visualize-md-2.png b/docs/getting-started/images/tutorial-visualize-md-2.png index 9e9a670ba196f..af56faa3b0516 100644 Binary files a/docs/getting-started/images/tutorial-visualize-md-2.png and b/docs/getting-started/images/tutorial-visualize-md-2.png differ diff --git a/docs/getting-started/images/tutorial-visualize-pie-2.png b/docs/getting-started/images/tutorial-visualize-pie-2.png index ef5d62b4ceee7..ca8f5e92146bc 100644 Binary files a/docs/getting-started/images/tutorial-visualize-pie-2.png and b/docs/getting-started/images/tutorial-visualize-pie-2.png differ diff --git a/docs/getting-started/images/tutorial-visualize-pie-3.png b/docs/getting-started/images/tutorial-visualize-pie-3.png index 6974c8d34b0dd..59fce360096c0 100644 Binary files a/docs/getting-started/images/tutorial-visualize-pie-3.png and b/docs/getting-started/images/tutorial-visualize-pie-3.png differ diff --git a/docs/getting-started/images/tutorial_index_patterns.png b/docs/getting-started/images/tutorial_index_patterns.png new file mode 100644 index 0000000000000..430baf898b612 Binary files /dev/null and b/docs/getting-started/images/tutorial_index_patterns.png differ diff --git a/docs/getting-started/tutorial-dashboard.asciidoc b/docs/getting-started/tutorial-dashboard.asciidoc deleted file mode 100644 index 2ee2d76024aed..0000000000000 --- a/docs/getting-started/tutorial-dashboard.asciidoc +++ /dev/null @@ -1,53 +0,0 @@ -[[tutorial-dashboard]] -=== Add the visualizations to a dashboard - -Build a dashboard that contains the visualizations and map that you saved during -this tutorial. - -. Open the menu, go to *Dashboard*, then click *Create dashboard*. -. Set the time filter to May 18, 2015 to May 20, 2015. -. Click *Add*, then select the following: - * *Bar Example* - * *Map Example* - * *Markdown Example* - * *Pie Example* -+ -Your sample dashboard looks like this: -+ -[role="screenshot"] -image::images/tutorial-dashboard.png[] - -. Try out the editing controls. -+ -You can rearrange the visualizations by clicking a the header of a -visualization and dragging. The gear icon in the top right of a visualization -displays controls for editing and deleting the visualization. A resize control -is on the lower right. - -. *Save* your dashboard. - -==== Inspect the data - -Seeing visualizations of your data is great, -but sometimes you need to look at the actual data to -understand what's really going on. You can inspect the data behind any visualization -and view the {es} query used to retrieve it. - -. Click the pie chart *Options* menu, then select *Inspect*. -+ -[role="screenshot"] -image::images/tutorial-full-inspect1.png[] - -. To look at the query used to fetch the data for the visualization, select *View > Requests*. - -[float] -=== Next steps - -Now that you have the basics, you're ready to start exploring -your own data with {kib}. - -* To learn about searching and filtering your data, refer to {kibana-ref}/discover.html[Discover]. -* To learn about the visualization types {kib} has to offer, refer to {kibana-ref}/visualize.html[Visualize]. -* To learn about configuring {kib} and managing your saved objects, refer to {kibana-ref}/management.html[Management]. -* To learn about the interactive console you can use to submit REST requests to {es}, refer to {kibana-ref}/console-kibana.html[Console]. - diff --git a/docs/getting-started/tutorial-define-index.asciidoc b/docs/getting-started/tutorial-define-index.asciidoc index 254befa55faea..fbe7450683dbc 100644 --- a/docs/getting-started/tutorial-define-index.asciidoc +++ b/docs/getting-started/tutorial-define-index.asciidoc @@ -1,7 +1,7 @@ [[tutorial-define-index]] === Define your index patterns -Index patterns tell Kibana which Elasticsearch indices you want to explore. +Index patterns tell {kib} which {es} indices you want to explore. An index pattern can match the name of a single index, or include a wildcard (*) to match multiple indices. @@ -10,28 +10,29 @@ series of indices in the format `logstash-YYYY.MMM.DD`. To explore all of the log data from May 2018, you could specify the index pattern `logstash-2018.05*`. - [float] -==== Create your first index pattern +==== Create the index patterns First you'll create index patterns for the Shakespeare data set, which has an index named `shakespeare,` and the accounts data set, which has an index named `bank`. These data sets don't contain time series data. . Open the menu, then go to *Stack Management > {kib} > Index Patterns*. + . If this is your first index pattern, the *Create index pattern* page opens. -Otherwise, click *Create index pattern*. -. In the *Index pattern field*, enter `shakes*`. + +. In the *Index pattern name* field, enter `shakes*`. + [role="screenshot"] -image::images/tutorial-pattern-1.png[] +image::images/tutorial-pattern-1.png[shakes* index patterns] . Click *Next step*. -. Select the *Time Filter field name*, then click *Create index pattern*. + +. On the *Configure settings* page, *Create index pattern*. + You’re presented a table of all fields and associated data types in the index. -. Return to the *Index patterns* page and create a second index pattern named `ba*`. +. Create a second index pattern named `ba*`. [float] ==== Create an index pattern for the time series data @@ -39,15 +40,12 @@ You’re presented a table of all fields and associated data types in the index. Create an index pattern for the Logstash index, which contains the time series data. -. Define an index pattern named `logstash*`. -. Click *Next step*. -. From the *Time Filter field name* dropdown, select *@timestamp*. -. Click *Create index pattern*. +. Create an index pattern named `logstash*`, then click *Next step*. -NOTE: When you define an index pattern, the indices that match that pattern must -exist in Elasticsearch and they must contain data. To check which indices are -available, open the menu, then go to *Dev Tools > Console* and enter `GET _cat/indices`. Alternately, use -`curl -XGET "http://localhost:9200/_cat/indices"`. +. From the *Time field* dropdown, select *@timestamp, then click *Create index pattern*. ++ +[role="screenshot"] +image::images/tutorial_index_patterns.png[All tutorial index patterns] diff --git a/docs/getting-started/tutorial-discovering.asciidoc b/docs/getting-started/tutorial-discovering.asciidoc index 31d77be1275ee..ec07a74b8ac0d 100644 --- a/docs/getting-started/tutorial-discovering.asciidoc +++ b/docs/getting-started/tutorial-discovering.asciidoc @@ -1,9 +1,8 @@ -[[tutorial-discovering]] -=== Discover your data +[[explore-your-data]] +=== Explore your data -Using *Discover*, enter -an {ref}/query-dsl-query-string-query.html#query-string-syntax[Elasticsearch -query] to search your data and filter the results. +With *Discover*, you use {ref}/query-dsl-query-string-query.html#query-string-syntax[Elasticsearch +queries] to explore your data and narrow the results with filters. . Open the menu, then go to *Discover*. + @@ -13,7 +12,7 @@ The `shakes*` index pattern appears. + By default, all fields are shown for each matching document. -. In the *Search* field, enter the following: +. In the *Search* field, enter the following, then click *Update*: + [source,text] account_number<100 AND balance>47500 @@ -32,3 +31,5 @@ account numbers. + [role="screenshot"] image::images/tutorial-discover-3.png[] + +Now that you know what your documents contain, it's time to gain insight into your data with visualizations. diff --git a/docs/getting-started/tutorial-full-experience.asciidoc b/docs/getting-started/tutorial-full-experience.asciidoc index e6f2de87905bf..1e6fe39dbd013 100644 --- a/docs/getting-started/tutorial-full-experience.asciidoc +++ b/docs/getting-started/tutorial-full-experience.asciidoc @@ -1,32 +1,23 @@ -[[tutorial-build-dashboard]] -== Build your own dashboard +[[create-your-own-dashboard]] +== Create your own dashboard -Want to load some data into Kibana and build a dashboard? This tutorial shows you how to: +Ready to add data to {kib} and create your own dashboard? In this tutorial, you'll use three types of data sets that'll help you learn to: -* <> -* <> -* <> -* <> -* <> - -When you complete this tutorial, you'll have a dashboard that looks like this. - -[role="screenshot"] -image::images/tutorial-dashboard.png[] +* <> +* <> +* <> +* <> [float] -[[tutorial-load-dataset]] -=== Load sample data +[[download-the-data]] +=== Download the data -This tutorial requires you to download three data sets: +To complete the tutorial, you'll download and use the following data sets: * The complete works of William Shakespeare, suitably parsed into fields -* A set of fictitious accounts with randomly generated data +* A set of fictitious bank accounts with randomly generated data * A set of randomly generated log files -[float] -==== Download the data sets - Create a new working directory where you want to download the files. From that directory, run the following commands: [source,shell] @@ -34,7 +25,7 @@ curl -O https://download.elastic.co/demos/kibana/gettingstarted/8.x/shakespeare. curl -O https://download.elastic.co/demos/kibana/gettingstarted/8.x/accounts.zip curl -O https://download.elastic.co/demos/kibana/gettingstarted/8.x/logs.jsonl.gz -Two of the data sets are compressed. To extract the files, use these commands: +Two of the data sets are compressed. To extract the files, use the following commands: [source,shell] unzip accounts.zip @@ -43,7 +34,7 @@ gunzip logs.jsonl.gz [float] ==== Structure of the data sets -The Shakespeare data set has this structure: +The Shakespeare data set has the following structure: [source,json] { @@ -55,7 +46,7 @@ The Shakespeare data set has this structure: "text_entry": "String", } -The accounts data set is structured as follows: +The accounts data set has the following structure: [source,json] { @@ -72,7 +63,7 @@ The accounts data set is structured as follows: "state": "String" } -The logs data set has dozens of different fields. Here are the notable fields for this tutorial: +The logs data set has dozens of different fields. The notable fields include the following: [source,json] { @@ -94,7 +85,7 @@ You must also have the `create`, `manage` `read`, `write,` and `delete` index privileges. See {ref}/security-privileges.html[Security privileges] for more information. -Open *Dev Tools*. On the *Console* page, set up a mapping for the Shakespeare data set: +Open the menu, then go to *Dev Tools*. On the *Console* page, set up a mapping for the Shakespeare data set: [source,js] PUT /shakespeare @@ -111,10 +102,11 @@ PUT /shakespeare //CONSOLE -This mapping specifies field characteristics for the data set: +The mapping specifies field characteristics for the data set: * The `speaker` and `play_name` fields are keyword fields. These fields are not analyzed. The strings are treated as a single unit even if they contain multiple words. + * The `line_id` and `speech_number` fields are integers. The logs data set requires a mapping to label the latitude and longitude pairs @@ -177,6 +169,7 @@ PUT /logstash-2015.05.20 The accounts data set doesn't require any mappings. [float] +[[load-the-data-sets]] ==== Load the data sets At this point, you're ready to use the Elasticsearch {ref}/docs-bulk.html[bulk] @@ -195,14 +188,20 @@ Invoke-RestMethod "http://:/_bulk?pretty" -Method Post -ContentType These commands might take some time to execute, depending on the available computing resources. -Verify successful loading: +When you define an index pattern, the indices that match the pattern must +exist in {es} and contain data. + +To verify the availability of the indices, open the menu, go to *Dev Tools > Console*, then enter: [source,js] GET /_cat/indices?v -//CONSOLE +Alternately, use: + +[source,shell] +`curl -XGET "http://localhost:9200/_cat/indices"`. -Your output should look similar to this: +The output should look similar to: [source,shell] health status index pri rep docs.count docs.deleted store.size pri.store.size diff --git a/docs/getting-started/tutorial-sample-data.asciidoc b/docs/getting-started/tutorial-sample-data.asciidoc index 2460a55e13293..18ef862272f85 100644 --- a/docs/getting-started/tutorial-sample-data.asciidoc +++ b/docs/getting-started/tutorial-sample-data.asciidoc @@ -1,207 +1,159 @@ -[[tutorial-sample-data]] +[[explore-kibana-using-sample-data]] == Explore {kib} using sample data -Ready to get some hands-on experience with Kibana? -In this tutorial, you’ll work -with Kibana sample data and learn to: +Ready to get some hands-on experience with {kib}? +In this tutorial, you’ll work with {kib} sample data and learn to: -* <> -* <> -* <> -* <> +* <> +* <> -NOTE: If security is enabled, you must have `read`, `write`, and `manage` privileges -on the `kibana_sample_data_*` indices. See -{ref}/security-privileges.html[Security privileges] for more information. +* <> +NOTE: If security is enabled, you must have `read`, `write`, and `manage` privileges +on the `kibana_sample_data_*` indices. For more information, refer to +{ref}/security-privileges.html[Security privileges]. [float] -=== Add sample data +[[add-the-sample-data]] +=== Add the sample data -Install the Flights sample data set, if you haven't already. +Add the *Sample flight data*. . On the home page, click *Load a data set and a {kib} dashboard*. + . On the *Sample flight data* card, click *Add data*. -. Once the data is added, click *View data > Dashboard*. -+ -You’re taken to the *Global Flight* dashboard, a collection of charts, graphs, -maps, and other visualizations of the the data in the `kibana_sample_data_flights` index. -+ -[role="screenshot"] -image::getting-started/images/tutorial-sample-dashboard.png[] [float] -[[tutorial-sample-filter]] -=== Filter and query the data +[[explore-the-data]] +=== Explore the data -You can use filters and queries to -narrow the view of the data. -For more detailed information on these actions, see -{ref}/query-filter-context.html[Query and filter context]. +Explore the documents in the index that +match the selected index pattern. The index pattern tells {kib} which {es} index you want to +explore. -[float] -==== Filter the data +. Open the menu, then go to *Discover*. -. In the *Controls* visualization, select an *Origin City* and a *Destination City*. -. Click *Apply changes*. +. Make sure `kibana_sample_data_flights` is the current index pattern. +You might need to click *New* in the {kib} toolbar to refresh the data. + -The `OriginCityName` and the `DestCityName` fields filter the data on the dasbhoard to match -the data you specified. +You'll see a histogram that shows the distribution of +documents over time. A table lists the fields for +each document that matches the index. By default, all fields are shown. + -For example, the following dashboard shows the data for flights from London to Milan. +[role="screenshot"] +image::getting-started/images/tutorial-sample-discover1.png[] + +. Hover over the list of *Available fields*, then click *Add* next +to each field you want explore in the table. + [role="screenshot"] -image::getting-started/images/tutorial-sample-filter.png[] +image::getting-started/images/tutorial-sample-discover2.png[] -. To add a filter manually, click *Add filter*, -then specify the data you want to view. +[float] +[[view-and-analyze-the-data]] +=== View and analyze the data -. When you are finished experimenting, remove all filters. +A _dashboard_ is a collection of panels that provide you with an overview of your data that you can +use to analyze your data. Panels contain everything you need, including visualizations, +interactive controls, Markdown, and more. + +To open the *Global Flight* dashboard, open the menu, then go to *Dashboard*. +[role="screenshot"] +image::getting-started/images/tutorial-sample-dashboard.png[] [float] -[[tutorial-sample-query]] -==== Query the data +[[change-the-panel-data]] +==== Change the panel data -. To find all flights out of Rome, enter this query in the query bar and click *Update*: -+ -[source,text] -OriginCityName:Rome +To gain insights into your data, change the appearance and behavior of the panels. +For example, edit the metric panel to find the airline that has the lowest average fares. -. For a more complex query with AND and OR, try this: -+ -[source,text] -OriginCityName:Rome AND (Carrier:JetBeats OR "Kibana Airlines") -+ -The dashboard updates to show data for the flights out of Rome on JetBeats and -{kib} Airlines. -+ -[role="screenshot"] -image::getting-started/images/tutorial-sample-query.png[] +. In the {kib} toolbar, click *Edit*. -. When you are finished exploring the dashboard, remove the query by -clearing the contents in the query bar and clicking *Update*. +. In the *Average Ticket Price* metric panel, open the panel menu, then select *Edit visualization*. -[float] -[[tutorial-sample-discover]] -=== Discover the data +. To change the data on the panel, use an {es} {ref}/search-aggregations.html[bucket aggregation], +which sorts the documents that match your search criteria into different categories or buckets. -In Discover, you have access to every document in every index that -matches the selected index pattern. The index pattern tells {kib} which {es} index you are currently -exploring. You can submit search queries, filter the -search results, and view document data. +.. In the *Buckets* pane, select *Add > Split group*. -. From the menu, click *Discover*. +.. From the *Aggregation* dropdown, select *Terms*. -. Ensure `kibana_sample_data_flights` is the current index pattern. -You might need to click *New* in the menu bar to refresh the data. +.. From the *Field* dropdown, select *Carrier*. + +.. Set *Descending* to *4*, then click *Update*. + -You'll see a histogram that shows the distribution of -documents over time. A table lists the fields for -each matching document. By default, all fields are shown. +The average ticket price for all four airlines appear in the visualization builder. + [role="screenshot"] -image::getting-started/images/tutorial-sample-discover1.png[] +image::getting-started/images/tutorial-sample-edit1.png[] -. To choose which fields to display, -hover the pointer over the list of *Available fields*, and then click *add* next -to each field you want include as a column in the table. -+ -For example, if you add the `DestAirportID` and `DestWeather` fields, -the display includes columns for those two fields. +. To save your changes, click *Save and return* in the {kib} toolbar. + +. To save the dashboard, click *Save* in the {kib} toolbar. + [role="screenshot"] -image::getting-started/images/tutorial-sample-discover2.png[] +image::getting-started/images/tutorial-sample-edit2.png[] [float] -[[tutorial-sample-edit]] -=== Edit a visualization - -You have edit permissions for the *Global Flight* dashboard, so you can change -the appearance and behavior of the visualizations. For example, you might want -to see which airline has the lowest average fares. - -. In the side navigation, click *Recently viewed* and open the *Global Flight Dashboard*. -. In the menu bar, click *Edit*. -. In the *Average Ticket Price* visualization, click the gear icon in -the upper right. -. From the *Options* menu, select *Edit visualization*. -+ -*Average Ticket Price* is a metric visualization. -To specify which groups to display -in this visualization, you use an {es} {ref}/search-aggregations.html[bucket aggregation]. -This aggregation sorts the documents that match your search criteria into different -categories, or buckets. +[[filter-and-query-the-data]] +==== Filter and query the data -[float] -==== Create a bucket aggregation +To focus in on the data you want to explore, use filters and queries. +For more information, refer to +{ref}/query-filter-context.html[Query and filter context]. + +To filter the data: -. In the *Buckets* pane, select *Add > Split group*. -. In the *Aggregation* dropdown, select *Terms*. -. In the *Field* dropdown, select *Carrier*. -. Set *Descending* to *4*. -. Click *Apply changes* image:images/apply-changes-button.png[]. +. In the *Controls* visualization, select an *Origin City* and *Destination City*, then click *Apply changes*. + -You now see the average ticket price for all four airlines. +The `OriginCityName` and the `DestCityName` fields filter the data in the panels. + -[role="screenshot"] -image::getting-started/images/tutorial-sample-edit1.png[] - -[float] -==== Save the visualization - -. In the menu bar, click *Save*. -. Leave the visualization name as is and confirm the save. -. Go to the *Global Flight* dashboard and scroll the *Average Ticket Price* visualization to see the four prices. -. Optionally, edit the dashboard. Resize the panel -for the *Average Ticket Price* visualization by dragging the -handle in the lower right. You can also rearrange the visualizations by clicking -the header and dragging. Be sure to save the dashboard. +For example, the following dashboard shows the data for flights from London to Milan. + [role="screenshot"] -image::getting-started/images/tutorial-sample-edit2.png[] +image::getting-started/images/tutorial-sample-filter.png[] -[float] -[[tutorial-sample-inspect]] -=== Inspect the data +. To manually add a filter, click *Add filter*, +then specify the data you want to view. -Seeing visualizations of your data is great, -but sometimes you need to look at the actual data to -understand what's really going on. You can inspect the data behind any visualization -and view the {es} query used to retrieve it. +. When you are finished experimenting, remove all filters. -. In the dashboard, hover the pointer over the pie chart, and then click the icon in the upper right. -. From the *Options* menu, select *Inspect*. +[[query-the-data]] +To query the data: + +. To view all flights out of Rome, enter the following in the *KQL* query bar, then click *Update*: + -The initial view shows the document count. +[source,text] +OriginCityName: Rome + +. For a more complex query with AND and OR, enter: ++ +[source,text] +OriginCityName:Rome AND (Carrier:JetBeats OR Carrier:"Kibana Airlines") ++ +The dashboard panels update to display the flights out of Rome on JetBeats and +{kib} Airlines. + [role="screenshot"] -image::getting-started/images/tutorial-sample-inspect1.png[] - -. To look at the query used to fetch the data for the visualization, select *View > Requests* -in the upper right of the Inspect pane. - -[float] -[[tutorial-sample-remove]] -=== Remove the sample data set -When you’re done experimenting with the sample data set, you can remove it. +image::getting-started/images/tutorial-sample-query.png[] -. Go to the *Sample data* page. -. On the *Sample flight data* card, click *Remove*. +. When you are finished exploring, remove the query by +clearing the contents in the *KQL* query bar, then click *Update*. [float] === Next steps -Now that you have a handle on the {kib} basics, you might be interested in the -tutorial <>, where you'll learn to: +Now that you know the {kib} basics, try out the <> tutorial, where you'll learn to: + +* Add a data set to {kib} -* Load data * Define an index pattern -* Discover and explore data -* Create visualizations -* Add visualizations to a dashboard +* Discover and explore data +* Create and add panels to a dashboard diff --git a/docs/getting-started/tutorial-visualizing.asciidoc b/docs/getting-started/tutorial-visualizing.asciidoc index 20b4e33583072..33a7035160247 100644 --- a/docs/getting-started/tutorial-visualizing.asciidoc +++ b/docs/getting-started/tutorial-visualizing.asciidoc @@ -1,47 +1,76 @@ [[tutorial-visualizing]] === Visualize your data -In *Visualize*, you can shape your data using a variety -of charts, tables, and maps, and more. In this tutorial, you'll create four -visualizations: +Shape your data using a variety +of {kib} supported visualizations, tables, and more. In this tutorial, you'll create four +visualizations that you'll use to create a dashboard. -* <> -* <> -* <> -* <> +To begin, open the menu, go to *Dashboard*, then click *Create new dashboard*. [float] -[[tutorial-visualize-pie]] -=== Pie chart +[[compare-the-number-of-speaking-parts-in-the-play]] +=== Compare the number of speaking parts in the plays -Use the pie chart to -gain insight into the account balances in the bank account data. +To visualize the Shakespeare data and compare the number of speaking parts in the plays, create a bar chart using *Lens*. -. Open then menu, then go to *Visualize*. -. Click *Create visualization*. +. Click *Create new*, then click *Lens* on the *New Visualization* window. + [role="screenshot"] -image::images/tutorial-visualize-wizard-step-1.png[] -. Click *Pie*. +image::images/tutorial-visualize-wizard-step-1.png[Bar chart] -. On the *Choose a source* window, select `ba*`. +. Make sure the index pattern is *shakes*. + +. Display the play data along the x-axis. + +.. From the *Available fields* list, drag and drop *play_name* to the *X-axis* field. + +.. Click *Top values of play_name*. + +.. From the *Order direction* dropdown, select *Ascending*. + +.. In the *Label* field, enter `Play Name`. + +. Display the number of speaking parts per play along the y-axis. + +.. From the *Available fields* list, drag and drop *speaker* to the *Y-axis* field. + +.. Click *Unique count of speaker*. + +.. In the *Label* field, enter `Speaking Parts`. ++ +[role="screenshot"] +image::images/tutorial-visualize-bar-1.5.png[Bar chart] + +. *Save* the chart with the name `Bar Example`. + -Initially, the pie contains a single "slice." -That's because the default search matches all documents. +To show a tooltip with the number of speaking parts for that play, hover over a bar. + -To specify which slices to display in the pie, you use an Elasticsearch -{ref}/search-aggregations.html[bucket aggregation]. This aggregation -sorts the documents that match your search criteria into different -categories. You'll use a bucket aggregation to establish -multiple ranges of account balances and find out how many accounts fall into -each range. +Notice how the individual play names show up as whole phrases, instead of +broken up into individual words. This is the result of the mapping +you did at the beginning of the tutorial, when you marked the `play_name` field +as `not analyzed`. -. In the *Buckets* pane, click *Add > Split slices.* +[float] +[[view-the-average-account-balance-by-age]] +=== View the average account balance by age + +To gain insight into the account balances in the bank account data, create a pie chart. In this tutorial, you'll use the {es} +{ref}/search-aggregations.html[bucket aggregation] to specify the pie slices to display. The bucket aggregation sorts the documents that match your search criteria into different +categories and establishes multiple ranges of account balances so that you can find how many accounts fall into each range. + +. Click *Create new*, then click *Pie* on the *New Visualization* window. + +. On the *Choose a source* window, select `ba*`. + +Since the default search matches all documents, the pie contains a single slice. + +. In the *Buckets* pane, click *Add > Split slices.* + .. From the *Aggregation* dropdown, select *Range*. + .. From the *Field* dropdown, select *balance*. -.. Click *Add range* four times to bring the total number of ranges to six. -.. Define the following ranges: + +.. Click *Add range* until there are six rows of fields, then define the following ranges: + [source,text] 0 999 @@ -53,80 +82,83 @@ each range. . Click *Update*. + -Now you can see what proportion of the 1000 accounts fall into each balance -range. +The pie chart displays the proportion of the 1,000 accounts that fall into each of the ranges. + [role="screenshot"] -image::images/tutorial-visualize-pie-2.png[] +image::images/tutorial-visualize-pie-2.png[Pie chart] -. Add another bucket aggregation that looks at the ages of the account -holders. +. Add another bucket aggregation that displays the ages of the account holders. .. In the *Buckets* pane, click *Add*, then click *Split slices*. + .. From the *Sub aggregation* dropdown, select *Terms*. -.. From the *Field* dropdown, select *age*. -. Click *Update*. +.. From the *Field* dropdown, select *age*, then click *Update*. + The break down of the ages of the account holders are displayed in a ring around the balance ranges. + [role="screenshot"] -image::images/tutorial-visualize-pie-3.png[] +image::images/tutorial-visualize-pie-3.png[Final pie chart] . Click *Save*, then enter `Pie Example` in the *Title* field. [float] -[[tutorial-visualize-bar]] -=== Bar chart +[role="xpack"] +[[visualize-geographic-information]] +=== Visualize geographic information -Use a bar chart to look at the Shakespeare data set and compare -the number of speaking parts in the plays. +To visualize geographic information in the log file data, use <>. -. Click *Create visualization > Vertical Bar*, then set the source to `shakes*`. +. Click *Create new*, then click *Maps* on the *New Visualization* window. + +. To change the time, use the time filter. + +.. Set the *Start date* to `May 18, 2015 @ 12:00:00.000`. + +.. Set the *End date* to `May 20, 2015 @ 12:00:00.000`. + -Initially, the chart is a single bar that shows the total count -of documents that match the default wildcard query. +[role="screenshot"] +image::images/gs_maps_time_filter.png[Time filter for Maps tutorial] -. Show the number of speaking parts per play along the y-axis. +.. Click *Update* + +. Map the geo coordinates from the log files. -.. In the *Metrics* pane, expand *Y-axis*. -.. From the *Aggregation* dropdown, select *Unique Count*. -.. From the *Field* dropdown, select *speaker*. -.. In the *Custom label* field, enter `Speaking Parts`. +.. Click *Add layer > Clusters and grids*. -. Click *Update*. +.. From the *Index pattern* dropdown, select *logstash*. -. Show the plays along the x-axis. +.. Click *Add layer*. -.. In the *Buckets* pane, click *Add > X-axis*. -.. From the *Aggregation* dropdown, select *Terms*. -.. From the *Field* dropdown, select *play_name*. -.. To list the plays alphabetically, select *Ascending* from the *Order* dropdown. -.. In the *Custom label* field, enter `Play Name`. +. Specify the *Layer Style*. -. Click *Update*. +.. From the *Fill color* dropdown, select the yellow to red color ramp. + +.. In the *Border width* field, enter `3`. + +.. From the *Border color* dropdown, select *#FFF*, then click *Save & close*. + [role="screenshot"] -image::images/tutorial-visualize-bar-1.5.png[] -. *Save* the chart with the name `Bar Example`. -+ -Hovering over a bar shows a tooltip with the number of speaking parts for -that play. -+ -Notice how the individual play names show up as whole phrases, instead of -broken into individual words. This is the result of the mapping -you did at the beginning of the tutorial, when you marked the `play_name` field -as `not analyzed`. +image::images/tutorial-visualize-map-2.png[Map] + +. Click *Save*, then enter `Map Example` in the *Title* field. + +. Add the map to your dashboard. + +.. Open the menu, go to *Dashboard*, then click *Add*. + +.. On the *Add panels* flyout, click *Map Example*. [float] [[tutorial-visualize-markdown]] -=== Markdown +=== Add context to your visualizations with Markdown -Add formatted text to your dashboard with a markdown tool. +Add context to your new visualizations with Markdown text. -. Click *Create visualization > Markdown*. -. In the text field, enter the following: +. Click *Create new*, then click *Markdown* on the *New Visualization* window. + +. In the *Markdown* text field, enter: + [source,markdown] # This is a tutorial dashboard! @@ -140,40 +172,22 @@ The Markdown renders in the preview pane. [role="screenshot"] image::images/tutorial-visualize-md-2.png[] -. *Save* the tool with the name `Markdown Example`. +. Click *Save*, then enter `Markdown Example` in the *Title* field. -[float] -[[tutorial-visualize-map]] -=== Map +[role="screenshot"] +image::images/tutorial-dashboard.png[] -Using <>, you can visualize geographic information in the log file sample data. +[float] +=== Next steps -. Click *Create visualization > Maps*. +Now that you have the basics, you're ready to start exploring your own system data with {kib}. -. Set the time. -.. In the time filter, click *Show dates*. -.. Click the start date, then *Absolute*. -.. Set the *Start date* to May 18, 2015. -.. Click *now*, then *Absolute*. -.. Set the *End date* to May 20, 2015. -.. Click *Update* +* To add your own data to {kib}, refer to <>. -. Map the geo coordinates from the log files. +* To search and filter your data, refer to {kibana-ref}/discover.html[Discover]. -.. Click *Add layer > Clusters and Grids*. -.. From the *Index pattern* dropdown, select *logstash*. -.. Click *Add layer*. +* To create a dashboard with your own data, refer to <>. -. Set the *Layer Style*. -.. From the *Fill color* dropdown, select the yellow to red color ramp. -.. From the *Border color* dropdown, select white. -.. Click *Save & close*. -+ -The map looks like this: -+ -[role="screenshot"] -image::images/tutorial-visualize-map-2.png[] +* To create maps that you can add to your dashboards, refer to <>. -. Navigate the map by clicking and dragging. Use the controls -to zoom the map and set filters. -. *Save* the map with the name `Map Example`. +* To create presentations of your live data, refer to <>. diff --git a/docs/glossary.asciidoc b/docs/glossary.asciidoc index 1edb33032418b..be24402170bbe 100644 --- a/docs/glossary.asciidoc +++ b/docs/glossary.asciidoc @@ -151,7 +151,7 @@ that you are interested in. A navigation path that retains context (time range and filters) from the source to the destination, so you can view the data from a new perspective. A dashboard that shows the overall status of multiple data centers -might have a drilldown to a dashboard for a single data center. See {kibana-ref}/drilldowns.html[Drilldowns]. +might have a drilldown to a dashboard for a single data center. See {kibana-ref}/dashboard.html[Drilldowns]. // end::drilldown-def[] @@ -238,7 +238,7 @@ support for scripted fields. See Enables you to build visualizations by dragging and dropping data fields. Lens makes makes smart visualization suggestions for your data, allowing you to switch between visualization types. -See {kibana-ref}/lens.html[Lens]. +See {kibana-ref}/dashboard.html[Lens]. // end::lens-def[] @@ -350,7 +350,7 @@ A {kib} control that constrains the search results to a particular time period. [[glossary-timelion]] Timelion :: // tag::timelion-def[] A tool for building a time series visualization that analyzes data in time order. -See {kibana-ref}/timelion.html[Timelion]. +See {kibana-ref}/dashboard.html[Timelion]. // end::timelion-def[] @@ -364,7 +364,7 @@ Timestamped data such as logs, metrics, and events that is indexed on an ongoing // tag::tsvb-def[] A time series data visualizer that allows you to combine an infinite number of aggregations to display complex data. -See {kibana-ref}/TSVB.html[TSVB]. +See {kibana-ref}/dashboard.html[TSVB]. // end::tsvb-def[] @@ -388,7 +388,7 @@ indices and guides you through resolving issues, including reindexing. See [[glossary-vega]] Vega :: // tag::vega-def[] A declarative language used to create interactive visualizations. -See {kibana-ref}/vega-graph.html[Vega]. +See {kibana-ref}/dashboard.html[Vega]. // end::vega-def[] [[glossary-vector]] vector data:: diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 9f13c152b4cbe..ed20166c87f29 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -150,6 +150,12 @@ working on big documents. ==== Machine learning [horizontal] +`ml:anomalyDetection:results:enableTimeDefaults`:: Use the default time filter +in the *Single Metric Viewer* and *Anomaly Explorer*. If this setting is +disabled, the results for the full time range are shown. +`ml:anomalyDetection:results:timeDefaults`:: Sets the default time filter for +viewing {anomaly-job} results. This setting must contain `from` and `to` values (see {ref}/common-options.html#date-math[accepted formats]). It is ignored +unless `ml:anomalyDetection:results:enableTimeDefaults` is enabled. `ml:fileDataVisualizerMaxFileSize`:: Sets the file size limit when importing data in the {data-viz}. The default value is `100MB`. The highest supported value for this setting is `1GB`. @@ -219,6 +225,7 @@ be inconsistent because different shards might be in different refresh states. `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. +`search:timeout`:: Change the maximum timeout for a search session or set to 0 to disable the timeout and allow queries to run to completion. [float] [[kibana-siem-settings]] diff --git a/docs/management/index-lifecycle-policies/manage-policy.asciidoc b/docs/management/index-lifecycle-policies/manage-policy.asciidoc index a57af8a33494b..8e2dc96de4b99 100644 --- a/docs/management/index-lifecycle-policies/manage-policy.asciidoc +++ b/docs/management/index-lifecycle-policies/manage-policy.asciidoc @@ -25,4 +25,10 @@ created index. For more information, see {ref}/indices-templates.html[Index temp * *Delete a policy.* You can’t delete a policy that is currently in use or recover a deleted index. +[float] +=== Required permissions + +The `manage_ilm` cluster privilege is required to access *Index lifecycle policies*. + +You can add these privileges in *Stack Management > Security > Roles*. diff --git a/docs/management/index-patterns.asciidoc b/docs/management/index-patterns.asciidoc index 05036311c094c..7de2a042160e9 100644 --- a/docs/management/index-patterns.asciidoc +++ b/docs/management/index-patterns.asciidoc @@ -7,7 +7,7 @@ you want to work with. Once you create an index pattern, you're ready to: * Interactively explore your data in <>. -* Analyze your data in charts, tables, gauges, tag clouds, and more in <>. +* Analyze your data in charts, tables, gauges, tag clouds, and more in <>. * Show off your data in a <> workpad. * If your data includes geo data, visualize it with <>. diff --git a/docs/management/managing-ccr.asciidoc b/docs/management/managing-ccr.asciidoc index 67193b3b5a037..9c06e479e28b2 100644 --- a/docs/management/managing-ccr.asciidoc +++ b/docs/management/managing-ccr.asciidoc @@ -20,6 +20,13 @@ image::images/cross-cluster-replication-list-view.png[][Cross-cluster replicatio * The Elasticsearch version of the local cluster must be the same as or newer than the remote cluster. Refer to {ref}/ccr-overview.html[this document] for more information. +[float] +=== Required permissions + +The `manage` and `manage_ccr` cluster privileges are required to access *Cross-Cluster Replication*. + +You can add these privileges in *Stack Management > Security > Roles*. + [float] [[configure-replication]] === Configure replication diff --git a/docs/management/managing-licenses.asciidoc b/docs/management/managing-licenses.asciidoc index 25ae29036f656..b53bda95466dc 100644 --- a/docs/management/managing-licenses.asciidoc +++ b/docs/management/managing-licenses.asciidoc @@ -29,6 +29,13 @@ See {ref}/encrypting-communications.html[Encrypting communications]. {kib} and the {ref}/start-basic.html[start basic API] provide a list of all of the features that will no longer be supported if you revert to a basic license. +[float] +=== Required permissions + +The `manage` cluster privilege is required to access *License Management*. + +You can add this privilege in *Stack Management > Security > Roles*. + [discrete] [[update-license]] === Update your license diff --git a/docs/management/managing-remote-clusters.asciidoc b/docs/management/managing-remote-clusters.asciidoc index 83895838efec6..92e0fa822b056 100644 --- a/docs/management/managing-remote-clusters.asciidoc +++ b/docs/management/managing-remote-clusters.asciidoc @@ -11,6 +11,13 @@ To get started, open the menu, then go to *Stack Management > Data > Remote Clus [role="screenshot"] image::images/remote-clusters-list-view.png[Remote Clusters list view, including Add a remote cluster button] +[float] +=== Required permissions + +The `manage` cluster privilege is required to access *Remote Clusters*. + +You can add this privilege in *Stack Management > Security > Roles*. + [float] [[managing-remote-clusters]] === Add a remote cluster diff --git a/docs/management/managing-saved-objects.asciidoc b/docs/management/managing-saved-objects.asciidoc index 51de5ad620b46..8c885ddca52e5 100644 --- a/docs/management/managing-saved-objects.asciidoc +++ b/docs/management/managing-saved-objects.asciidoc @@ -92,5 +92,5 @@ index pattern. This is useful if the index you were working with has been rename WARNING: Validation is not performed for object properties. Submitting an invalid change will render the object unusable. A more failsafe approach is to use -*Discover*, *Visualize*, or *Dashboard* to create new objects instead of +*Discover* or *Dashboard* to create new objects instead of directly editing an existing one. diff --git a/docs/management/numeral.asciidoc b/docs/management/numeral.asciidoc index 5d4d48ca785e1..a8834a3278a9e 100644 --- a/docs/management/numeral.asciidoc +++ b/docs/management/numeral.asciidoc @@ -10,7 +10,7 @@ Numeral formatting patterns are used in multiple places in {kib}, including: * <> * <> -* <> +* <> * <> The simplest pattern format is `0`, and the default {kib} pattern is `0,0.[000]`. diff --git a/docs/management/rollups/create_and_manage_rollups.asciidoc b/docs/management/rollups/create_and_manage_rollups.asciidoc index 831b536f8c1cb..e20f384b5ed18 100644 --- a/docs/management/rollups/create_and_manage_rollups.asciidoc +++ b/docs/management/rollups/create_and_manage_rollups.asciidoc @@ -20,6 +20,13 @@ image::images/management_rollup_list.png[][List of currently active rollup jobs] Before using this feature, you should be familiar with how rollups work. {ref}/xpack-rollup.html[Rolling up historical data] is a good source for more detailed information. +[float] +=== Required permissions + +The `manage_rollup` cluster privilege is required to access *Rollup jobs*. + +You can add this privilege in *Stack Management > Security > Roles*. + [float] [[create-and-manage-rollup-job]] === Create a rollup job @@ -60,7 +67,7 @@ You can read more at {ref}/rollup-job-config.html[rollup job configuration]. === Try it: Create and visualize rolled up data This example creates a rollup job to capture log data from sample web logs. -To follow along, add the <>. +To follow along, add the <>. In this example, you want data that is older than 7 days in the target index pattern `kibana_sample_data_logs` to roll up once a day into the index `rollup_logstash`. You’ll bucket the @@ -145,7 +152,7 @@ is `rollup_logstash,kibana_sample_data_logs`. In this index pattern, `rollup_log matches the rolled up index pattern and `kibana_sample_data_logs` matches the index pattern for raw data. -. Go to *Visualize* and create a vertical bar chart. +. Go to *Dashboard* and create a vertical bar chart. . Choose `rollup_logstash,kibana_sample_data_logs` as your source to see both the raw and rolled up data. diff --git a/docs/management/upgrade-assistant/index.asciidoc b/docs/management/upgrade-assistant/index.asciidoc index c5fd6a3a555a1..2b8c2da2ef577 100644 --- a/docs/management/upgrade-assistant/index.asciidoc +++ b/docs/management/upgrade-assistant/index.asciidoc @@ -13,6 +13,14 @@ Before you upgrade, make sure that you are using the latest released minor version of {es} to see the most up-to-date deprecation issues. For example, if you want to upgrade to to 7.0, make sure that you are using 6.8. +[float] +=== Required permissions + +The `manage` cluster privilege is required to access the *Upgrade assistant*. +Additional privileges may be needed to perform certain actions. + +You can add this privilege in *Stack Management > Security > Roles*. + [float] === Reindexing diff --git a/docs/migration/migrate_8_0.asciidoc b/docs/migration/migrate_8_0.asciidoc index b80503750a26e..0cb28ce0fb6e7 100644 --- a/docs/migration/migrate_8_0.asciidoc +++ b/docs/migration/migrate_8_0.asciidoc @@ -115,7 +115,7 @@ URL that it derived from the actual server address and `xpack.security.public` s *Impact:* Any workflow that involved manually clearing generated bundles will have to be updated with the new path. -[float]] +[float] === kibana.keystore has moved from the `data` folder to the `config` folder *Details:* By default, kibana.keystore has moved from the configured `path.data` folder to `/config` for archive distributions and `/etc/kibana` for package distributions. If a pre-existing keystore exists in the data directory that path will continue to be used. @@ -136,6 +136,18 @@ custom roles with {kibana-ref}/kibana-privileges.html[{kib} privileges]. instead be assigned the `kibana_admin` role to maintain their current access level. +[float] +=== `kibana_dashboard_only_user` role has been removed. + +*Details:* The `kibana_dashboard_only_user` role has been removed. +If you wish to restrict access to just the Dashboard feature, create +custom roles with {kibana-ref}/kibana-privileges.html[{kib} privileges]. + +*Impact:* Any users currently assigned the `kibana_dashboard_only_user` role will need to be assigned a custom role which only grants access to the Dashboard feature. + +Granting additional cluster or index privileges may enable certain +**Stack Monitoring** features. + [float] [[breaking_80_reporting_changes]] === Reporting changes diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index 1a20c1df582e6..42d1d89145d79 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -59,7 +59,7 @@ This page has moved. Please see <>. [role="exclude",id="add-sample-data"] == Add sample data -This page has moved. Please see <>. +This page has moved. Please see <>. [role="exclude",id="tilemap"] == Coordinate map @@ -95,3 +95,8 @@ More information on this new feature is available in <>. == Role-based access control This content has moved to the <> page. + +[role="exclude",id="TSVB"] +== TSVB + +This page was deleted. See <>. diff --git a/docs/settings/alert-action-settings.asciidoc b/docs/settings/alert-action-settings.asciidoc index e02c7f212277e..13c1d20552fa1 100644 --- a/docs/settings/alert-action-settings.asciidoc +++ b/docs/settings/alert-action-settings.asciidoc @@ -37,12 +37,12 @@ You can configure the following settings in the `kibana.yml` file. [cols="2*<"] |=== -| `xpack.actions.whitelistedHosts` - | A list of hostnames that {kib} is allowed to connect to when built-in actions are triggered. It defaults to `[*]`, allowing any host, but keep in mind the potential for SSRF attacks when hosts are not explicitly whitelisted. An empty list `[]` can be used to block built-in actions from making any external connections. + +| `xpack.actions.allowedHosts` {ess-icon} + | A list of hostnames that {kib} is allowed to connect to when built-in actions are triggered. It defaults to `[*]`, allowing any host, but keep in mind the potential for SSRF attacks when hosts are not explicitly added to the allowed hosts. An empty list `[]` can be used to block built-in actions from making any external connections. + + - Note that hosts associated with built-in actions, such as Slack and PagerDuty, are not automatically whitelisted. If you are not using the default `[*]` setting, you must ensure that the corresponding endpoints are whitelisted as well. + Note that hosts associated with built-in actions, such as Slack and PagerDuty, are not automatically added to allowed hosts. If you are not using the default `[*]` setting, you must ensure that the corresponding endpoints are added to the allowed hosts as well. -| `xpack.actions.enabledActionTypes` +| `xpack.actions.enabledActionTypes` {ess-icon} | A list of action types that are enabled. It defaults to `[*]`, enabling all types. The names for built-in {kib} action types are prefixed with a `.` and include: `.server-log`, `.slack`, `.email`, `.index`, `.pagerduty`, and `.webhook`. An empty list `[]` will disable all action types. + + Disabled action types will not appear as an option when creating new connectors, but existing connectors and actions of that type will remain in {kib} and will not function. diff --git a/docs/settings/dev-settings.asciidoc b/docs/settings/dev-settings.asciidoc index e92e9c2928793..62553293a7d03 100644 --- a/docs/settings/dev-settings.asciidoc +++ b/docs/settings/dev-settings.asciidoc @@ -14,7 +14,7 @@ They are enabled by default. [cols="2*<"] |=== -| `xpack.grokdebugger.enabled` +| `xpack.grokdebugger.enabled` {ess-icon} | Set to `true` to enable the <>. Defaults to `true`. |=== diff --git a/docs/settings/graph-settings.asciidoc b/docs/settings/graph-settings.asciidoc index a66785242c19a..876e3dc936ccf 100644 --- a/docs/settings/graph-settings.asciidoc +++ b/docs/settings/graph-settings.asciidoc @@ -13,7 +13,7 @@ You do not need to configure any settings to use the {graph-features}. [cols="2*<"] |=== -| `xpack.graph.enabled` +| `xpack.graph.enabled` {ess-icon} | Set to `false` to disable the {graph-features}. |=== diff --git a/docs/settings/monitoring-settings.asciidoc b/docs/settings/monitoring-settings.asciidoc index d538519eefcc4..6c8632efa9cc0 100644 --- a/docs/settings/monitoring-settings.asciidoc +++ b/docs/settings/monitoring-settings.asciidoc @@ -37,6 +37,11 @@ For more information, see monitoring back-end does not run and {kib} stats are not sent to the monitoring cluster. +a|`monitoring.cluster_alerts.` +`email_notifications.email_address` {ess-icon} + | Specifies the email address where you want to receive cluster alerts. + See <> for details. + | `monitoring.ui.elasticsearch.hosts` | Specifies the location of the {es} cluster where your monitoring data is stored. By default, this is the same as `elasticsearch.hosts`. This setting enables @@ -85,7 +90,7 @@ These settings control how data is collected from {kib}. | Set to `true` (default) to enable data collection from the {kib} NodeJS server for {kib} dashboards to be featured in *{stack-monitor-app}*. -| `monitoring.kibana.collection.interval` +| `monitoring.kibana.collection.interval` {ess-icon} | Specifies the number of milliseconds to wait in between data sampling on the {kib} NodeJS server for the metrics that are displayed in the {kib} dashboards. Defaults to `10000` (10 seconds). @@ -111,7 +116,7 @@ about configuring {kib}, see | Set to `false` to hide *{stack-monitor-app}*. The monitoring back-end continues to run as an agent for sending {kib} stats to the monitoring cluster. Defaults to `true`. - + | `monitoring.ui.logs.index` | Specifies the name of the indices that are shown on the <> page in *{stack-monitor-app}*. The default value @@ -124,7 +129,7 @@ about configuring {kib}, see {ref}/search-aggregations-bucket-terms-aggregation.html#search-aggregations-bucket-terms-aggregation-size[Terms Aggregation]. Defaults to `10000`. -| `monitoring.ui.min_interval_seconds` +| `monitoring.ui.min_interval_seconds` {ess-icon} | Specifies the minimum number of seconds that a time bucket in a chart can represent. Defaults to 10. If you modify the `monitoring.ui.collection.interval` in `elasticsearch.yml`, use the same @@ -143,7 +148,7 @@ container, then Cgroup statistics are not useful. [cols="2*<"] |=== -| `monitoring.ui.container.elasticsearch.enabled` +| `monitoring.ui.container.elasticsearch.enabled` {ess-icon} | For {es} clusters that are running in containers, this setting changes the *Node Listing* to display the CPU utilization based on the reported Cgroup statistics. It also adds the calculated Cgroup CPU utilization to the diff --git a/docs/settings/reporting-settings.asciidoc b/docs/settings/reporting-settings.asciidoc index 0b6f94e86a39f..9c8d753a2d668 100644 --- a/docs/settings/reporting-settings.asciidoc +++ b/docs/settings/reporting-settings.asciidoc @@ -17,10 +17,10 @@ You can configure `xpack.reporting` settings in your `kibana.yml` to: [cols="2*<"] |=== -| [[xpack-enable-reporting]]`xpack.reporting.enabled` +| [[xpack-enable-reporting]]`xpack.reporting.enabled` {ess-icon} | Set to `false` to disable the {report-features}. -| `xpack.reporting.encryptionKey` +| `xpack.reporting.encryptionKey` {ess-icon} | Set to an alphanumeric, at least 32 characters long text string. By default, {kib} will generate a random key when it starts, which will cause pending reports to fail after restart. Configure this setting to preserve the same key across multiple restarts and multiple instances of {kib}. @@ -86,7 +86,7 @@ reports, you might need to change the following settings. | How often the index that stores reporting jobs rolls over to a new index. Valid values are `year`, `month`, `week`, `day`, and `hour`. Defaults to `week`. -| `xpack.reporting.queue.pollEnabled` +| `xpack.reporting.queue.pollEnabled` {ess-icon} | Set to `true` (default) to enable the {kib} instance to to poll the index for pending jobs and claim them for execution. Setting this to `false` allows the {kib} instance to only add new jobs to the reporting queue, list jobs, and @@ -107,7 +107,7 @@ security is enabled, `xpack.security.encryptionKey`. | Specifies the number of milliseconds that the reporting poller waits between polling the index for any pending Reporting jobs. Defaults to `3000` (3 seconds). -| [[xpack-reporting-q-timeout]] `xpack.reporting.queue.timeout` +| [[xpack-reporting-q-timeout]] `xpack.reporting.queue.timeout` {ess-icon} | How long each worker has to produce a report. If your machine is slow or under heavy load, you might need to increase this timeout. Specified in milliseconds. If a Reporting job execution time goes over this time limit, the job will be @@ -125,19 +125,22 @@ control the capturing process. [cols="2*<"] |=== -| `xpack.reporting.capture.timeouts.openUrl` +a| `xpack.reporting.capture.timeouts` +`.openUrl` {ess-icon} | Specify how long to allow the Reporting browser to wait for the "Loading..." screen to dismiss and find the initial data for the Kibana page. If the time is exceeded, a page screenshot is captured showing the current state, and the download link shows a warning message. Defaults to `60000` (1 minute). -| `xpack.reporting.capture.timeouts.waitForElements` +a| `xpack.reporting.capture.timeouts` +`.waitForElements` {ess-icon} | Specify how long to allow the Reporting browser to wait for all visualization panels to load on the Kibana page. If the time is exceeded, a page screenshot is captured showing the current state, and the download link shows a warning message. Defaults to `30000` (30 seconds). -| `xpack.reporting.capture.timeouts.renderComplete` +a| `xpack.reporting.capture.timeouts` +`.renderComplete` {ess-icon} | Specify how long to allow the Reporting browser to wait for all visualizations to fetch and render the data. If the time is exceeded, a page screenshot is captured showing the current state, and the download link shows a warning message. Defaults to @@ -155,7 +158,7 @@ available, but there will likely be errors in the visualizations in the report. [cols="2*<"] |=== -| `xpack.reporting.capture.maxAttempts` +| `xpack.reporting.capture.maxAttempts` {ess-icon} | If capturing a report fails for any reason, {kib} will re-attempt other reporting job, as many times as this setting. Defaults to `3`. @@ -166,7 +169,7 @@ available, but there will likely be errors in the visualizations in the report. visualizations, try increasing this value. Defaults to `3000` (3 seconds). -| [[xpack-reporting-browser]] `xpack.reporting.capture.browser.type` +| [[xpack-reporting-browser]] `xpack.reporting.capture.browser.type` {ess-icon} | Specifies the browser to use to capture screenshots. This setting exists for backward compatibility. The only valid option is `chromium`. @@ -180,20 +183,24 @@ When `xpack.reporting.capture.browser.type` is set to `chromium` (default) you c [cols="2*<"] |=== -| `xpack.reporting.capture.browser.chromium.disableSandbox` +a| `xpack.reporting.capture.browser` +`.chromium.disableSandbox` | It is recommended that you research the feasibility of enabling unprivileged user namespaces. See Chromium Sandbox for additional information. Defaults to false for all operating systems except Debian, Red Hat Linux, and CentOS which use true. -| `xpack.reporting.capture.browser.chromium.proxy.enabled` +a| `xpack.reporting.capture.browser` +`.chromium.proxy.enabled` | Enables the proxy for Chromium to use. When set to `true`, you must also specify the `xpack.reporting.capture.browser.chromium.proxy.server` setting. Defaults to `false`. -| `xpack.reporting.capture.browser.chromium.proxy.server` +a| `xpack.reporting.capture.browser` +.chromium.proxy.server` | The uri for the proxy server. Providing the username and password for the proxy server via the uri is not supported. -| `xpack.reporting.capture.browser.chromium.proxy.bypass` +a| `xpack.reporting.capture.browser` +.chromium.proxy.bypass` | An array of hosts that should not go through the proxy server and should use a direct connection instead. Examples of valid entries are "elastic.co", "*.elastic.co", ".elastic.co", ".elastic.co:5601". @@ -205,27 +212,27 @@ When `xpack.reporting.capture.browser.type` is set to `chromium` (default) you c [cols="2*<"] |=== -| [[xpack-reporting-csv]] `xpack.reporting.csv.maxSizeBytes` +| [[xpack-reporting-csv]] `xpack.reporting.csv.maxSizeBytes` {ess-icon} | The maximum size of a CSV file before being truncated. This setting exists to prevent large exports from causing performance and storage issues. Defaults to `10485760` (10mB). | `xpack.reporting.csv.scroll.size` - | Number of documents retrieved from {es} for each scroll iteration during a CSV + | Number of documents retrieved from {es} for each scroll iteration during a CSV export. Defaults to `500`. | `xpack.reporting.csv.scroll.duration` | Amount of time allowed before {kib} cleans the scroll context during a CSV export. Defaults to `30s`. - + | `xpack.reporting.csv.checkForFormulas` | Enables a check that warns you when there's a potential formula involved in the output (=, -, +, and @ chars). See OWASP: https://www.owasp.org/index.php/CSV_Injection Defaults to `true`. - + | `xpack.reporting.csv.enablePanelActionDownload` - | Enables CSV export from a saved search on a dashboard. This action is available in the dashboard + | Enables CSV export from a saved search on a dashboard. This action is available in the dashboard panel menu for the saved search. Defaults to `true`. diff --git a/docs/settings/security-settings.asciidoc b/docs/settings/security-settings.asciidoc index a0995cab984d4..b6eecc6ea9f04 100644 --- a/docs/settings/security-settings.asciidoc +++ b/docs/settings/security-settings.asciidoc @@ -73,27 +73,27 @@ The valid settings in the `xpack.security.authc.providers` namespace vary depend [cols="2*<"] |=== | `xpack.security.authc.providers.` -`..enabled` +`..enabled` {ess-icon} | Determines if the authentication provider should be enabled. By default, {kib} enables the provider as soon as you configure any of its properties. | `xpack.security.authc.providers.` -`..order` +`..order` {ess-icon} | Order of the provider in the authentication chain and on the Login Selector UI. | `xpack.security.authc.providers.` -`..description` +`..description` {ess-icon} | Custom description of the provider entry displayed on the Login Selector UI. | `xpack.security.authc.providers.` -`..hint` +`..hint` {ess-icon} | Custom hint for the provider entry displayed on the Login Selector UI. | `xpack.security.authc.providers.` -`..icon` +`..icon` {ess-icon} | Custom icon for the provider entry displayed on the Login Selector UI. | `xpack.security.authc.providers.` -`..showInSelector` +`..showInSelector` {ess-icon} | Flag that indicates if the provider should have an entry on the Login Selector UI. Setting this to `false` doesn't remove the provider from the authentication chain. 2+a| @@ -104,7 +104,7 @@ You are unable to set this setting to `false` for `basic` and `token` authentica ============ | `xpack.security.authc.providers.` -`..accessAgreement.message` +`..accessAgreement.message` {ess-icon} | Access agreement text in Markdown format. For more information, refer to <>. |=== @@ -118,11 +118,11 @@ In addition to <.realm` +`saml..realm` {ess-icon} | SAML realm in {es} that provider should use. | `xpack.security.authc.providers.` -`saml..useRelayStateDeepLink` +`saml..useRelayStateDeepLink` {ess-icon} | Determines if the provider should treat the `RelayState` parameter as a deep link in {kib} during Identity Provider initiated log in. By default, this setting is set to `false`. The link specified in `RelayState` should be a relative, URL-encoded {kib} URL. For example, the `/app/dashboards#/list` link in `RelayState` parameter would look like this: `RelayState=%2Fapp%2Fdashboards%23%2Flist`. |=== @@ -136,7 +136,7 @@ In addition to <.realm` +`oidc..realm` {ess-icon} | OpenID Connect realm in {es} that the provider should use. |=== @@ -168,13 +168,13 @@ You can configure the following settings in the `kibana.yml` file. [cols="2*<"] |=== -| `xpack.security.loginAssistanceMessage` +| `xpack.security.loginAssistanceMessage` {ess-icon} | Adds a message to the login UI. Useful for displaying information about maintenance windows, links to corporate sign up pages, and so on. -| `xpack.security.loginHelp` +| `xpack.security.loginHelp` {ess-icon} | Adds a message accessible at the login UI with additional help information for the login process. -| `xpack.security.authc.selector.enabled` +| `xpack.security.authc.selector.enabled` {ess-icon} | Determines if the login selector UI should be enabled. By default, this setting is set to `true` if more than one authentication provider is configured. |=== @@ -203,12 +203,12 @@ You can configure the following settings in the `kibana.yml` file. this to `true` if SSL is configured outside of {kib} (for example, you are routing requests through a load balancer or proxy). -| `xpack.security.sameSiteCookies` +| `xpack.security.sameSiteCookies` {ess-icon} | Sets the `SameSite` attribute of the session cookie. This allows you to declare whether your cookie should be restricted to a first-party or same-site context. Valid values are `Strict`, `Lax`, `None`. This is *not set* by default, which modern browsers will treat as `Lax`. If you use Kibana embedded in an iframe in modern browsers, you might need to set it to `None`. Setting this value to `None` requires cookies to be sent over a secure connection by setting `xpack.security.secureCookies: true`. -| `xpack.security.session.idleTimeout` +| `xpack.security.session.idleTimeout` {ess-icon} | Ensures that user sessions will expire after a period of inactivity. This and `xpack.security.session.lifespan` are both highly recommended. By default, this setting is not set. @@ -218,7 +218,7 @@ highly recommended. By default, this setting is not set. The format is a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). ============ -| `xpack.security.session.lifespan` +| `xpack.security.session.lifespan` {ess-icon} | Ensures that user sessions will expire after the defined time period. This behavior also known as an "absolute timeout". If this is _not_ set, user sessions could stay active indefinitely. This and `xpack.security.session.idleTimeout` are both highly recommended. By default, this setting is not set. diff --git a/docs/setup/connect-to-elasticsearch.asciidoc b/docs/setup/connect-to-elasticsearch.asciidoc index f750784c47043..ea02afb8a9fda 100644 --- a/docs/setup/connect-to-elasticsearch.asciidoc +++ b/docs/setup/connect-to-elasticsearch.asciidoc @@ -11,7 +11,7 @@ To start working with your data in {kib}, you can: * Connect {kib} with existing {es} indices. -If you're not ready to use your own data, you can add a <> +If you're not ready to use your own data, you can add a <> to see all that you can do in {kib}. [float] diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 4a931aabd3646..f03022e9e9f00 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -20,12 +20,12 @@ which may cause a delay before pages start being served. Set to `false` to disable Console. *Default: `true`* | `cpu.cgroup.path.override:` - | Override for cgroup cpu path when mounted in a -manner that is inconsistent with `/proc/self/cgroup`. + | *deprecated* This setting has been renamed to `ops.cGroupOverrides.cpuPath` +and the old name will no longer be supported as of 8.0. | `cpuacct.cgroup.path.override:` - | Override for cgroup cpuacct path when mounted -in a manner that is inconsistent with `/proc/self/cgroup`. + | *deprecated* This setting has been renamed to `ops.cGroupOverrides.cpuAcctPath` +and the old name will no longer be supported as of 8.0. | `csp.rules:` | A https://w3c.github.io/webappsec-csp/[content-security-policy] template @@ -438,6 +438,14 @@ not saved in {es}. *Default: `data`* | Set the interval in milliseconds to sample system and process performance metrics. The minimum value is 100. *Default: `5000`* +| `ops.cGroupOverrides.cpuPath:` + | Override for cgroup cpu path when mounted in a +manner that is inconsistent with `/proc/self/cgroup`. + +| `ops.cGroupOverrides.cpuAcctPath:` + | Override for cgroup cpuacct path when mounted +in a manner that is inconsistent with `/proc/self/cgroup`. + | `server.basePath:` | Enables you to specify a path to mount {kib} at if you are running behind a proxy. Use the `server.rewriteBasePath` setting to tell {kib} diff --git a/docs/user/alerting/action-types.asciidoc b/docs/user/alerting/action-types.asciidoc index 1743edb10f92b..be31458ff39fa 100644 --- a/docs/user/alerting/action-types.asciidoc +++ b/docs/user/alerting/action-types.asciidoc @@ -25,7 +25,7 @@ a| <> a| <> -| Push or update data to a new incident in ServiceNow. +| Create an incident in ServiceNow. a| <> diff --git a/docs/user/alerting/action-types/email.asciidoc b/docs/user/alerting/action-types/email.asciidoc index f6a02b9038c02..83e7edc5a016a 100644 --- a/docs/user/alerting/action-types/email.asciidoc +++ b/docs/user/alerting/action-types/email.asciidoc @@ -12,7 +12,7 @@ Email connectors have the following configuration properties: Name:: The name of the connector. The name is used to identify a connector in the management UI connector listing, or in the connector list when configuring an action. Sender:: The from address for all emails sent with this connector, specified in `user@host-name` format. -Host:: Host name of the service provider. If you are using the <> setting, make sure this hostname is whitelisted. +Host:: Host name of the service provider. If you are using the <> setting, make sure this hostname is added to the allowed hosts. Port:: The port to connect to on the service provider. Secure:: If true the connection will use TLS when connecting to the service provider. See https://nodemailer.com/smtp/#tls-options[nodemailer TLS documentation] for more information. Username:: username for 'login' type authentication. diff --git a/docs/user/alerting/action-types/pagerduty.asciidoc b/docs/user/alerting/action-types/pagerduty.asciidoc index 5fd85a1045265..2c9add5233c91 100644 --- a/docs/user/alerting/action-types/pagerduty.asciidoc +++ b/docs/user/alerting/action-types/pagerduty.asciidoc @@ -132,7 +132,7 @@ This is an irreversible action and impacts all alerts that use this connector. PagerDuty connectors have the following configuration properties: Name:: The name of the connector. The name is used to identify a connector in the management UI connector listing, or in the connector list when configuring an action. -API URL:: An optional PagerDuty event URL. Defaults to `https://events.pagerduty.com/v2/enqueue`. If you are using the <> setting, make sure the hostname is whitelisted. +API URL:: An optional PagerDuty event URL. Defaults to `https://events.pagerduty.com/v2/enqueue`. If you are using the <> setting, make sure the hostname is added to the allowed hosts. Integration Key:: A 32 character PagerDuty Integration Key for an integration on a service, also referred to as the routing key. [float] diff --git a/docs/user/alerting/action-types/slack.asciidoc b/docs/user/alerting/action-types/slack.asciidoc index 99bf73c0f5597..a1fe7a2521b22 100644 --- a/docs/user/alerting/action-types/slack.asciidoc +++ b/docs/user/alerting/action-types/slack.asciidoc @@ -11,7 +11,7 @@ The Slack action type uses https://api.slack.com/incoming-webhooks[Slack Incomin Slack connectors have the following configuration properties: Name:: The name of the connector. The name is used to identify a connector in the management UI connector listing, or in the connector list when configuring an action. -Webhook URL:: The URL of the incoming webhook. See https://api.slack.com/messaging/webhooks#getting_started[Slack Incoming Webhooks] for instructions on generating this URL. If you are using the <> setting, make sure the hostname is whitelisted. +Webhook URL:: The URL of the incoming webhook. See https://api.slack.com/messaging/webhooks#getting_started[Slack Incoming Webhooks] for instructions on generating this URL. If you are using the <> setting, make sure the hostname is added to the allowed hosts. [float] [[Preconfigured-slack-configuration]] diff --git a/docs/user/alerting/action-types/webhook.asciidoc b/docs/user/alerting/action-types/webhook.asciidoc index c91c24430e982..659c3afad6bd1 100644 --- a/docs/user/alerting/action-types/webhook.asciidoc +++ b/docs/user/alerting/action-types/webhook.asciidoc @@ -11,7 +11,7 @@ The Webhook action type uses https://github.com/axios/axios[axios] to send a POS Webhook connectors have the following configuration properties: Name:: The name of the connector. The name is used to identify a connector in the management UI connector listing, or in the connector list when configuring an action. -URL:: The request URL. If you are using the <> setting, make sure the hostname is whitelisted. +URL:: The request URL. If you are using the <> setting, make sure the hostname is added to the allowed hosts. Method:: HTTP request method, either `post`(default) or `put`. Headers:: A set of key-value pairs sent as headers with the request User:: An optional username. If set, HTTP basic authentication is used. Currently only basic authentication is supported. diff --git a/docs/user/canvas.asciidoc b/docs/user/canvas.asciidoc index 317ec67dd7c0a..0b0eb7a318495 100644 --- a/docs/user/canvas.asciidoc +++ b/docs/user/canvas.asciidoc @@ -137,7 +137,7 @@ image::images/canvas-map-embed.gif[] . To use the customization options, click the panel menu, then select one of the following options: -* *Edit map* — Opens <> or <> so that you can edit the original saved object. +* *Edit map* — Opens <> or a visualization builder so that you can edit the original saved object. * *Edit panel title* — Adds a title to the saved object. diff --git a/docs/user/dashboard.asciidoc b/docs/user/dashboard.asciidoc deleted file mode 100644 index b812af7e981bf..0000000000000 --- a/docs/user/dashboard.asciidoc +++ /dev/null @@ -1,191 +0,0 @@ -[[dashboard]] -= Dashboard - -[partintro] --- - -A _dashboard_ is a collection of visualizations, searches, and -maps, typically in real-time. Dashboards provide -at-a-glance insights into your data and enable you to drill down into details. - -With *Dashboard*, you can: - -* Add visualizations, saved searches, and maps for side-by-side analysis - -* Arrange dashboard elements to display exactly how you want - -* Customize time ranges to display only the data you want - -* Inspect and edit dashboard elements to find out exactly what kind of data is displayed - -[role="screenshot"] -image:images/Dashboard_example.png[Example dashboard] - -[float] -[[dashboard-read-only-access]] -=== [xpack]#Read only access# -If you see -the read-only icon in the application header, -then you don't have sufficient privileges to create and save dashboards. The buttons to create and edit -dashboards are not visible. For more information, see <>. - -[role="screenshot"] -image::images/dashboard-read-only-badge.png[Example of Dashboard read only access indicator in Kibana header] - --- - -[[dashboard-create-new-dashboard]] -== Create a dashboard - -To create a dashboard, you must have data indexed into {es}, an index pattern -to retrieve the data from {es}, and -visualizations, saved searches, or maps. If these don't exist, you're prompted to -add them as you create the dashboard, or you can add -<>, -which include pre-built dashboards. - -To begin, open the menu, go to *Dashboard*, then click *Create dashboard.* - -[float] -[[dashboard-add-elements]] -=== Add elements - -The visualizations, saved searches, and maps are stored as elements in panels -that you can move and resize. - -You can add elements from multiple indices, and the same element can appear in -multiple dashboards. - -To create an element: - -. Click *Create new*. -. On the *New Visualization* window, click the visualization type. -+ -[role="screenshot"] -image:images/Dashboard_add_new_visualization.png[Example add new visualization to dashboard] -+ -For information on how to create visualizations, see <>. -+ -For information on how to create maps, see <>. - -To add an existing element: - -. Click *Add*. - -. On the *Add panels* flyout, select the panel. -+ -When a dashboard element has a stored query, -both queries are applied. -+ -[role="screenshot"] -image:images/Dashboard_add_visualization.png[Example add visualization to dashboard] - -[float] -[[customizing-your-dashboard]] -=== Arrange dashboard elements - -In *Edit* mode, you can move, resize, customize, and delete panels to suit your needs. - -[[moving-containers]] -* To move a panel, click and hold the panel header and drag to the new location. - -[[resizing-containers]] -* To resize a panel, click the resize control and drag -to the new dimensions. - -* To toggle the use of margins and panel titles, use the *Options* menu. - -* To delete a panel, open the panel menu and select *Delete from dashboard.* Deleting a panel from a -dashboard does *not* delete the saved visualization or search. - -[float] -[[cloning-a-panel]] -=== Clone dashboard elements - -In *Edit* mode, you can clone any panel on a dashboard. - -To clone an existing panel, open the panel menu of the element you wish to clone, then select *Clone panel*. - -* Cloned panels appear beside the original, and will move other panels down to make room if necessary. - -* Clones support all of the original panel's functionality, including renaming, editing, and cloning. - -* All cloned visualizations will appear in the visualization list. - -[role="screenshot"] -image:images/clone_panel.gif[clone panel] - - -[float] -[[viewing-detailed-information]] -=== Inspect and edit elements - -Many dashboard elements allow you to drill down into the data and requests -behind the element. - -From the panel menu, select *Inspect*. -The data that displays depends on the element that you inspect. - -[role="screenshot"] -image:images/Dashboard_inspect.png[Inspect in dashboard] - -To open an element for editing, put the dashboard in *Edit* mode, -and then select *Edit visualization* from the panel menu. The changes you make appear in -every dashboard that uses the element. - -[float] -[[dashboard-customize-filter]] -=== Customize time ranges - -You can configure each visualization, saved search, and map on your dashboard -for a specific time range. For example, you might want one visualization to show -the monthly trend for CPU usage and another to show the current CPU usage. - -From the panel menu, select *Customize time range* to expose a time filter -dedicated to that panel. Panels that are not restricted by a specific -time range are controlled by the -global time filter. - -[role="screenshot"] -image:images/time_range_per_panel.gif[Time range per dashboard panel] - -[float] -[[save-dashboards]] -=== Save the dashboard - -When you're finished adding and arranging the panels, save the dashboard. - -. In the {kib} toolbar, click *Save*. - -. Enter the dashboard *Title* and optional *Description*, then *Save* the dashboard. - -include::{kib-repo-dir}/drilldowns/drilldowns.asciidoc[] -include::{kib-repo-dir}/drilldowns/explore-underlying-data.asciidoc[] - -[[sharing-dashboards]] -== Share the dashboard - -[[embedding-dashboards]] -Share your dashboard outside of {kib}. - -From the *Share* menu, you can: - -* Embed the code in a web page. Users must have {kib} access -to view an embedded dashboard. -* Share a direct link to a {kib} dashboard -* Generate a PDF report -* Generate a PNG report - -TIP: To create a link to a dashboard by title, use: + -`${domain}/${basepath?}/app/dashboards#/list?title=${yourdashboardtitle}` - -TIP: When sharing a link to a dashboard snapshot, use the *Short URL*. Snapshot -URLs are long and can be problematic for Internet Explorer and other -tools. To create a short URL, you must have write access to {kib}. - -[float] -[[import-dashboards]] -=== Export the dashboard - -To export the dashboard, open the menu, then click *Stack Management > Saved Objects*. For more information, -refer to <>. diff --git a/docs/user/dashboard/aggregation-reference.asciidoc b/docs/user/dashboard/aggregation-reference.asciidoc new file mode 100644 index 0000000000000..1bcea3bb36aea --- /dev/null +++ b/docs/user/dashboard/aggregation-reference.asciidoc @@ -0,0 +1,242 @@ +[[aggregation-reference]] +== Aggregation reference + +{kib} supports many types of {ref}/search-aggregations.html[{es} aggregations] that you can use to build complex summaries of your data. + +By using a series of {es} aggregations to extract and process your data, you can create panels that tell a +story about the trends, patterns, and outliers in your data. + +[float] +[[bucket-aggregations]] +=== Bucket aggregations + +For information about Elasticsearch bucket aggregations, refer to {ref}/search-aggregations-bucket.html[Bucket aggregations]. + +[options="header"] +|=== + +| Type | Visualizations | Data table | Markdown | Lens | TSVB + +| Histogram +^| X +^| X +^| X +| +| + +| Date histogram +^| X +^| X +^| X +^| X +^| X + +| Date range +^| X +^| X +^| X +| +| + +| Filter +^| X +^| X +^| X +| +^| X + +| Filters +^| X +^| X +^| X +| +^| X + +| GeoHash grid +^| X +^| X +^| X +| +| + +| IP range +^| X +^| X +^| X +| +| + +| Range +^| X +^| X +^| X +| +| + +| Terms +^| X +^| X +^| X +^| X +^| X + +| Significant terms +^| X +^| X +^| X +| +^| X + +|=== + +[float] +[[metrics-aggregations]] +=== Metrics aggregations + +For information about Elasticsearch metrics aggregations, refer to {ref}/search-aggregations-metrics.html[Metrics aggregations]. + +[options="header"] +|=== + +| Type | Visualizations | Data table | Markdown | Lens | TSVB + +| Average +^| X +^| X +^| X +^| X +^| X + +| Sum +^| X +^| X +^| X +^| X +^| X + +| Unique count (Cardinality) +^| X +^| X +^| X +^| X +^| X + +| Max +^| X +^| X +^| X +^| X +^| X + +| Min +^| X +^| X +^| X +^| X +^| X + +| Percentiles +^| X +^| X +^| X +| +^| X + +| Percentiles Rank +^| X +^| X +^| X +| +^| X + +| Top hit +^| X +^| X +^| X +| +^| X + +| Value count +| +| +| +| +^| X + +|=== + +[float] +[[pipeline-aggregations]] +=== Pipeline aggregations + +For information about Elasticsearch pipeline aggregations, refer to {ref}/search-aggregations-pipeline.html[Pipeline aggregations]. + +[options="header"] +|=== + +| Type | Visualizations | Data table | Markdown | Lens | TSVB + +| Avg bucket +^| X +^| X +^| X +| +^| X + +| Derivative +^| X +^| X +^| X +| +^| X + +| Max bucket +^| X +^| X +^| X +| +^| X + +| Min bucket +^| X +^| X +^| X +| +^| X + +| Sum bucket +^| X +^| X +^| X +^| +^| X + +| Moving average +^| X +^| X +^| X +^| +^| X + +| Cumulative sum +^| X +^| X +^| X +^| +^| X + +| Bucket script +| +| +| +| +^| X + +| Serial differencing +^| X +^| X +^| X +| +^| X + +|=== diff --git a/docs/user/dashboard/dashboard-drilldown.asciidoc b/docs/user/dashboard/dashboard-drilldown.asciidoc new file mode 100644 index 0000000000000..84701cae2ecc6 --- /dev/null +++ b/docs/user/dashboard/dashboard-drilldown.asciidoc @@ -0,0 +1,76 @@ +[[dashboard-drilldown]] +=== Dashboard drilldown + +The dashboard drilldown allows you to navigate from one dashboard to another dashboard. +For example, you might have a dashboard that shows the overall status of multiple data centers. +You can create a drilldown that navigates from this dashboard to a dashboard +that shows a single data center or server. + +This example shows a dashboard panel that contains a pie chart with a configured dashboard drilldown: + +[role="screenshot"] +image::images/drilldown_on_piechart.gif[Drilldown on pie chart that navigates to another dashboard] + +[float] +[[drilldowns-example]] +==== Try it: Create a dashboard drilldown + +Create the *Host Overview* drilldown shown above. + +*Set up the dashboards* + +. Add the <> data set. + +. Create a new dashboard, called `Host Overview`, and include these visualizations +from the sample data set: ++ +[%hardbreaks] +*[Logs] Heatmap* +*[Logs] Visitors by OS* +*[Logs] Host, Visits, and Bytes Table* +*[Logs] Total Requests and Bytes* ++ +TIP: If you don’t see data for a panel, try changing the time range. + +. Open the *[Logs] Web traffic* dashboard. + +. Set a search and filter. ++ +[%hardbreaks] +Search: `extension.keyword:( “gz” or “css” or “deb”)` +Filter: `geo.src : CN` + + +*Create the drilldown* + + +. In the dashboard menu bar, click *Edit*. + +. In *[Logs] Visitors by OS*, open the panel menu, and then select *Create drilldown*. + +. Pick *Go to dashboard* action. + +. Give the drilldown a name. + +. Select *Host Overview* as the destination dashboard. + +. Keep both filters enabled so that the drilldown carries over the global filters and date range. ++ +Your input should look similar to this: ++ +[role="screenshot"] +image::images/drilldown_create.png[Create drilldown with entries for drilldown name and destination] + +. Click *Create drilldown.* + +. Save the dashboard. ++ +If you don’t save the drilldown, and then navigate away, the drilldown is lost. + +. In *[Logs] Visitors by OS*, click the `win 8` slice of the pie, and then select the name of your drilldown. ++ +[role="screenshot"] +image::images/drilldown_on_panel.png[Drilldown on pie chart that navigates to another dashboard] ++ +You are navigated to your destination dashboard. Verify that the search query, filters, +and time range are carried over. diff --git a/docs/user/dashboard/dashboard.asciidoc b/docs/user/dashboard/dashboard.asciidoc new file mode 100644 index 0000000000000..c8bff91be91a6 --- /dev/null +++ b/docs/user/dashboard/dashboard.asciidoc @@ -0,0 +1,506 @@ +[[dashboard]] += Dashboard + +[partintro] +-- + +A _dashboard_ is a collection of panels that you use to analyze your data. On a dashboard, you can add a variety of panels that +you can rearrange and tell a story about your data. Panels contain everything you need, including visualizations, +interactive controls, markdown, and more. + +With *Dashboard*s, you can: + +* Add multiple panels to see many aspects and views of your data in one place. + +* Arrange panels for analysis and comparison. + +* Add text and images to provide context to the panels and make them easy to consume. + +* Create and apply filters to focus on the data you want to display. + +* Control who can use your data, and share the dashboard with a small or large audience. + +* Generate reports based on your findings. + +To begin, open the menu, go to *Dashboard*, then click *Create dashboard*. + +[role="screenshot"] +image:images/Dashboard_example.png[Example dashboard] + +[float] +[[dashboard-read-only-access]] +=== [xpack]#Read only access# +If you see +the read-only icon in the application header, +then you don't have sufficient privileges to create and save dashboards. The buttons to create and edit +dashboards are not visible. For more information, see <>. + +[role="screenshot"] +image::images/dashboard-read-only-badge.png[Example of Dashboard read only access indicator in Kibana header] + +[float] +[[types-of-panels]] +== Types of panels + +Panels contain everything you need to tell a story about you data, including visualizations, +interactive controls, Markdown, and more. + +[cols="50, 50"] +|=== + +a| *Area* + +Displays data points, connected by a line, where the area between the line and axes are shaded. +Use area charts to compare two or more categories over time, and display the magnitude of trends. + +| image:images/area.png[Area chart] + +a| *Stacked area* + +Displays the evolution of the value of several data groups. The values of each group are displayed +on top of each other. Use stacked area charts to visualize part-to-whole relationships, and to show +how each category contributes to the cumulative total. + +| image:images/stacked_area.png[Stacked area chart] + +a| *Bar* + +Displays bars side-by-side where each bar represents a category. Use bar charts to compare data across a +large number of categories, display data that includes categories with negative values, and easily identify +the categories that represent the highest and lowest values. Kibana also supports horizontal bar charts. + +| image:images/bar.png[Bar chart] + +a| *Stacked bar* + +Displays numeric values across two or more categories. Use stacked bar charts to compare numeric values between +levels of a categorical value. Kibana also supports stacked horizontal bar charts. + +| image:images/stacked_bar.png[Stacked area chart] + + +a| *Line* + +Displays data points that are connected by a line. Use line charts to visualize a sequence of values, discover +trends over time, and forecast future values. + +| image:images/line.png[Line chart] + +a| *Pie* + +Displays slices that represent a data category, where the slice size is proportional to the quantity it represents. +Use pie charts to show comparisons between multiple categories, illustrate the dominance of one category over others, +and show percentage or proportional data. + +| image:images/pie.png[Pie chart] + +a| *Donut* + +Similar to the pie chart, but the central circle is removed. Use donut charts when you’d like to display multiple statistics at once. + +| image:images/donut.png[Donut chart] + + +a| *Tree map* + +Relates different segments of your data to the whole. Each rectangle is subdivided into smaller rectangles, or sub branches, based on +its proportion to the whole. Use treemaps to make efficient use of space to show percent total for each category. + +| image:images/treemap.png[Tree map] + + +a| *Heat map* + +Displays graphical representations of data where the individual values are represented by colors. Use heat maps when your data set includes +categorical data. For example, use a heat map to see the flights of origin countries compared to destination countries using the sample flight data. + +| image:images/heat_map.png[Heat map] + +a| *Goal* + +Displays how your metric progresses toward a fixed goal. Use the goal to display an easy to read visual of the status of your goal progression. + +| image:images/goal.png[Goal] + + +a| *Gauge* + +Displays your data along a scale that changes color according to where your data falls on the expected scale. Use the gauge to show how metric +values relate to reference threshold values, or determine how a specified field is performing versus how it is expected to perform. + +| image:images/gauge.png[Gauge] + + +a| *Metric* + +Displays a single numeric value for an aggregation. Use the metric visualization when you have a numeric value that is powerful enough to tell +a story about your data. + +| image:images/metric.png[Metric] + + +a| *Data table* + +Displays your raw data or aggregation results in a tabular format. Use data tables to display server configuration details, track counts, min, +or max values for a specific field, and monitor the status of key services. + +| image:images/data_table.png[Data table] + + +a| *Tag cloud* + +Graphical representations of how frequently a word appears in the source text. Use tag clouds to easily produce a summary of large documents and +create visual art for a specific topic. + +| image:images/tag_cloud.png[Tag cloud] + + +a| *Maps* + +For all your mapping needs, use <>. + +| image:images/maps.png[Maps] + + +|=== + +[float] +[[create-panels]] +== Create panels + +To create a panel, make sure you have {ref}/getting-started-index.html[data indexed into {es}] and an <> +to retrieve the data from {es}. If you aren’t ready to use your own data, {kib} comes with several pre-built dashboards that you can test out. For more information, +refer to <>. + +To begin, click *Create new*, then choose one of the following options on the +*New Visualization* window: + +* Click on the type of panel you want to create, then configure the options. + +* Select an editor to help you create the panel. + +[role="screenshot"] +image:images/Dashboard_add_new_visualization.png[Example add new visualization to dashboard] + +{kib} provides you with several editors that help you create panels. + +[float] +[[lens]] +=== Create panels with Lens + +*Lens* is the simplest and fastest way to create powerful visualizations of your data. To use *Lens*, you drag and drop as many data fields +as you want onto the visualization builder pane, and *Lens* uses heuristics to decide how to apply each field to the visualization. + +With *Lens*, you can: + +* Use the automatically generated suggestions to change the visualization type. +* Create visualizations with multiple layers and indices. +* Change the aggregation and labels to customize the data. + +[role="screenshot"] +image::images/lens_drag_drop.gif[Drag and drop] + +TIP: Drag-and-drop capabilities are available only when *Lens* knows how to use the data. If *Lens* is unable to automatically generate a +visualization, configure the customization options for your visualization. + +[float] +[[fiter-the-data-fields]] +==== Filter the data fields + +The data fields that are displayed are based on the selected <> and the <>. + +To view the data fields in a different index pattern, click the index pattern, then select a new one. The data fields automatically update. + +To filter the data fields: + +* Enter the name in the *Search field names*. +* Click *Field by type*, then select the filter. To show all fields in the index pattern, deselect *Only show fields with data*. + +[float] +[[view-data-summaries]] +==== View data summaries + +To help you decide exactly the data you want to display, get a quick summary of each field. The summary shows the distribution of +values within the specified time range. + +To view the data field summary information, navigate to the field, then click *i*. + +[role="screenshot"] +image::images/lens_data_info.png[Data summary window] + +[float] +[[change-the-visualization-type]] +==== Change the visualization type + +Use the automatically generated suggestions to change the visualization type, or manually select the type of visualization you want to view. + +*Suggestions* are shortcuts to alternative visualizations that *Lens* generates for you. + +[role="screenshot"] +image::images/lens_suggestions.gif[Visualization suggestions] + +If you’d like to use a visualization type outside of the suggestions, click the visualization type, then select a new one. + +[role="screenshot"] +image::images/lens_viz_types.png[] + +When there is an exclamation point (!) next to a visualization type, *Lens* is unable to transfer your data, but still allows you to make the change. + +[float] +[[customize-the-data]] +==== Customize the data + +For each visualization type, you can customize the aggregation and labels. The options available depend on the selected visualization type. + +. Click a data field name in the editor, or click *Drop a field here*. +. Change the options that appear. ++ +[role="screenshot"] +image::images/lens_aggregation_labels.png[Quick function options] + +[float] +[[add-layers-and-indices]] +==== Add layers and indices + +To compare and analyze data from different sources, you can visualize multiple data layers and indices. Multiple layers and indices are +supported in area, line, and bar charts. + +To add a layer, click *+*, then drag and drop the data fields for the new layer. + +[role="screenshot"] +image::images/lens_layers.png[Add layers] + +To view a different index, click the index name in the editor, then select a new one. + +[role="screenshot"] +image::images/lens_index_pattern.png[Add index pattern] + +Ready to try out *Lens*? Refer to the <>. + +[float] +[[tsvb]] +=== Create panels with TSVB + +*TSVB* is a time series data visualizer that allows you to use the full power of the Elasticsearch aggregation framework. To use *TSVB*, +you can combine an infinite number of <> to display your data. + +With *TSVB*, you can: + +* Create visualizations, data tables, and markdown panels. +* Create visualizations with multiple indices. +* Change the aggregation and labels to customize the data. ++ +[role="screenshot"] +image::images/tsvb.png[TSVB UI] + +[float] +[[configure-the-data]] +==== Configure the data + +With *TSVB*, you can add and display multiple data sets to compare and analyze. {kib} uses many types of <> that you can use to build +complex summaries of that data. + +. Select *Data*. If you are using *Table*, select *Columns*. +. From the *Aggregation* drop down, select the aggregation you want to visualize. ++ +If you don’t see any data, change the <>. ++ +To add multiple aggregations, click *+*. +. From the *Group by* drop down, select how you want to group or split the data. +. To add another data set, click *+*. ++ +When you have more than one aggregation, the last value is displayed, which is indicated by the eye icon. + +[float] +[[change-the-data-display]] +==== Change the data display + +To find the best way to display your data, *TSVB* supports several types of panels and charts. + +To change the *Time Series* chart type: + +. Click *Data > Options*. +. Select the *Chart type*. + +To change the panel type, click on the panel options: + +[role="screenshot"] +image::images/tsvb_change_display.gif[TSVB change the panel type] + +[float] +[[custommize-the-data]] +==== Customize the data + +View data in a different <>, and change the data label name and colors. The options available depend on the panel type. + +To change the index pattern, click *Panel options*, then enter the new *Index Pattern*. + +To override the index pattern for a data set, click *Data > Options*. Select *Yes* to override, then enter the new *Index pattern*. + +To change the data labels and colors: + +. Click *Data*. +. Enter the *Label* name, which *TSVB* uses on the legends and data labels. +. Click the color picker, then select the color for the data. ++ +[role="screenshot"] +image::images/tsvb_color_picker.png[TSVB color picker] + +[float] +[[add-annotations]] +==== Add annotations + +You can overlay annotation events on top of your *Time Series* charts. The options available depend on the data source. + +To begin, click *Annotations*, click *Add data source*, then configure the options. + +[role="screenshot"] +image::images/tsvb_annotations.png[TSVB annotations] + +[float] +[[filter-the-panel]] +==== Filter the panel + +The data that displays on the panel is based on the <> and <>. +You can filter the data on the panels using the <>. + +Click *Panel options*, then enter the syntax in the *Panel Filter* field. + +If you want to ignore filters from all of {kib}, select *Yes* for *Ignore global filter*. + +[float] +[[vega]] +=== Create custom panels with Vega + +Build custom visualizations using *Vega* and *Vega-Lite*, backed by one or more data sources including {es}, Elastic Map Service, +URL, or static data. Use the {kib} extensions to embed *Vega* in your dashboard, and add interactive tools. + +Use *Vega* and *Vega-Lite* when you want to create a visualization for: + +* Aggregations that use `nested` or `parent/child` mapping +* Aggregations without an index pattern +* Queries that use custom time filters +* Complex calculations +* Extracting data from _source instead of aggregations +* Scatter charts, sankey charts, and custom maps +* Using an unsupported visual theme + +[role="screenshot"] +image::images/vega.png[Vega UI] + +*Vega* and *Vega-Lite* are declarative formats that: + +* Create complex visualizations +* Use JSON and a different syntax for declaring visualizations +* Are not fully interchangeable + +For more information about *Vega* and *Vega-Lite*, refer to: + +* <> +* <> +* <> +* <> + +[float] +[[timelion]] +=== Create panels with Timelion + +*Timelion* is a time series data visualizer that enables you to combine independent data sources within a single visualization. + +*Timelion* is driven by a simple expression language that you use to: + +* Retrieve time series data +* Perform calculation to tease out the answers to complex questions +* Visualize the results + +[role="screenshot"] +image::images/timelion.png[Timelion UI] + +Ready to try out Timelion? For step-by-step tutorials, refer to: + +* <> +* <> +* <> + +[float] +[[timelion-deprecation]] +==== Timelion app deprecation + +Deprecated since 7.0, the Timelion app will be removed in 8.0. If you have any Timelion worksheets, you must migrate them to a dashboard. + +NOTE: Only the Timelion app is deprecated. {kib} continues to support Timelion +visualizations on dashboards and in Visualize and Canvas. + +To migrate a Timelion worksheet to a dashboard: + +. Open the menu, click **Dashboard**, then click **Create dashboard**. + +. On the dashboard, click **Create New**, then select the Timelion visualization. + +. On a new tab, open the Timelion app, select the chart you want to copy, and copy its expression. ++ +[role="screenshot"] +image::images/timelion-copy-expression.png[] + +. Return to the other tab and paste the copied expression to the *Timelion Expression* field and click **Update**. ++ +[role="screenshot"] +image::images/timelion-vis-paste-expression.png[] + +. Save the new visualization, give it a name, and click **Save and Return**. ++ +Your Timelion visualization will appear on the dashboard. Repeat this for all your charts on each worksheet. ++ +[role="screenshot"] +image::images/timelion-dashboard.png[] + +[float] +[[save-panels]] +== Save panels + +When you’ve finished making changes, save the panels. + +. Click *Save*. +. Add the *Title* and optional *Description*. +. Click *Save and return*. + +[float] +[[add-existing-panels]] +== Add existing panels + +Add panels that you’ve already created to your dashboard. + +On the dashboard, click *Add an existing*, then select the panel you want to add. + +When a panel contains a stored query, both queries are applied. + +[role="screenshot"] +image:images/Dashboard_add_visualization.png[Example add visualization to dashboard] + +To make changes to the panel, put the dashboard in *Edit* mode, then select the edit option from the panel menu. +The changes you make appear in every dashboard that uses the panel, except if you edit the panel title. Changes to the panel title appear only on the dashboard where you made the change. + +[float] +[[save-dashboards]] +== Save dashboards + +When you’ve finished adding the panels, save the dashboard. + +. In the toolbar, click *Save*. + +. Enter the dashboard *Title* and optional *Description*, then *Save* the dashboard. + +-- +include::edit-dashboards.asciidoc[] + +include::explore-dashboard-data.asciidoc[] + +include::drilldowns.asciidoc[] + +include::share-dashboards.asciidoc[] + +include::tutorials.asciidoc[] + +include::aggregation-reference.asciidoc[] + +include::vega-reference.asciidoc[] diff --git a/docs/user/dashboard/drilldowns.asciidoc b/docs/user/dashboard/drilldowns.asciidoc new file mode 100644 index 0000000000000..85230f1b6f70d --- /dev/null +++ b/docs/user/dashboard/drilldowns.asciidoc @@ -0,0 +1,51 @@ +[[drilldowns]] +== Use drilldowns for dashboard actions + +Drilldowns, also known as custom actions, allow you to configure a +workflow for analyzing and troubleshooting your data. +For example, using a drilldown, you can navigate from one dashboard to another, +taking the current time range, filters, and other parameters with you, +so the context remains the same. You can continue your analysis from a new perspective. + +[role="screenshot"] +image::images/drilldown_on_piechart.gif[Drilldown on pie chart that navigates to another dashboard] + +Drilldowns are specific to the dashboard panel for which you create them—they are not shared across panels. A panel can have multiple drilldowns. + +[float] +[[actions]] +=== Drilldown actions + +Drilldowns are user-configurable {kib} actions that are stored with the dashboard metadata. +Kibana provides the following types of actions: + +[cols="2"] +|=== + +a| <> + +| Navigate to a dashboard. + +a| <> + +| Navigate to external or internal URL. + +|=== + +[NOTE] +============================================== +Some action types are paid commercial features, while others are free. +For a comparison of the Elastic subscription levels, +see https://www.elastic.co/subscriptions[the subscription page]. +============================================== + +[float] +[[code-drilldowns]] +=== Code drilldowns +Third-party developers can create drilldowns. +Refer to {kib-repo}blob/{branch}/x-pack/examples/ui_actions_enhanced_examples[this example plugin] +to learn how to code drilldowns. + +include::dashboard-drilldown.asciidoc[] +include::url-drilldown.asciidoc[] + diff --git a/docs/user/dashboard/edit-dashboards.asciidoc b/docs/user/dashboard/edit-dashboards.asciidoc new file mode 100644 index 0000000000000..7534ea1e9e9fb --- /dev/null +++ b/docs/user/dashboard/edit-dashboards.asciidoc @@ -0,0 +1,115 @@ +[[edit-dashboards]] +== Edit dashboards + +Now that you have added panels to your dashboard, you can add filter panels to interact with the data, and Markdown panels to add context to the dashboard. +To make your dashboard look the way you want, use the editing options. + +[float] +[[add-controls]] +=== Add controls + +To filter the data on your dashboard in real-time, add a *Controls* panel. + +You can add two types of *Controls*: + +* Options list — Filters content based on one or more specified options. The dropdown menu is dynamically populated with the results of a terms aggregation. +For example, use the options list on the sample flight dashboard when you want to filter the data by origin city and destination city. + +* Range slider — Filters data within a specified range of numbers. The minimum and maximum values are dynamically populated with the results of a +min and max aggregation. For example, use the range slider when you want to filter the sample flight dashboard by a specific average ticket price. + +[role="screenshot"] +image::images/dashboard-controls.png[] + +To configure *Controls* for your dashboard: + +. Click *Options*, then configure the following: + +* *Update Kibana filters on each change* — When selected, all interactive inputs create filters that refresh the dashboard. When unselected, + {kib} filters are created only when you click *Apply changes*. + +* *Use time filter* — When selected, the aggregations that generate the options list and time range are connected to the <>. + +* *Pin filters to global state* — When selected, all filters created by interacting with the inputs are automatically pinned. + +. Click *Update*. + +[float] +[[add-markdown]] +=== Add Markdown + +*Markdown* is a text entry field that accepts GitHub-flavored Markdown text. When you enter the text, the tool populates the results on the dashboard. + +Use Markdown when you want to add context to the other panels on your dashboard, such as important information, instructions and images. + +For information about GitHub-flavored Markdown text, click *Help*. + +For example, when you enter: + +[role="screenshot"] +image::images/markdown_example_1.png[] + +The following instructions are displayed: + +[role="screenshot"] +image::images/markdown_example_2.png[] + +Or when you enter: + +[role="screenshot"] +image::images/markdown_example_3.png[] + +The following image is displayed: + +[role="screenshot"] +image::images/markdown_example_4.png[] + +[float] +[[arrange-panels]] +[[moving-containers]] +[[resizing-containers]] +=== Arrange panels + +To make your dashboard panels look exactly how you want, you can move, resize, customize, and delete them. + +Put the dashboard in *Edit* mode, then use the following options: + +* To move, click and hold the panel header, then drag to the new location. + +* To resize, click the resize control, then drag to the new dimensions. + +* To delete, open the panel menu, then select Delete from dashboard. When you delete a panel from the dashboard, the +visualization or saved search from the panel is still available in Kibana. + +[float] +[[clone-panels]] +=== Clone panels + +To duplicate a panel and its configured functionality, clone the panel. Cloned panels support all of the original functionality, +including renaming, editing, and cloning. + +. Put the dashboard in *Edit* mode. + +. For the panel you want to clone, open the panel menu, then select *Clone panel*. + +Cloned panels appear beside the original, and move other panels down to make room when necessary. +All cloned visualization panels appear in the visualization list. + +[role="screenshot"] +image:images/clone_panel.gif[clone panel] + +[float] +[[dashboard-customize-filter]] +=== Customize time ranges + +You can configure each visualization, saved search, and map on your dashboard +for a specific time range. For example, you might want one visualization to show +the monthly trend for CPU usage and another to show the current CPU usage. + +From the panel menu, select *Customize time range* to expose a time filter +dedicated to that panel. Panels that are not restricted by a specific +time range are controlled by the +<>. + +[role="screenshot"] +image:images/time_range_per_panel.gif[Time range per dashboard panel] diff --git a/docs/user/dashboard/explore-dashboard-data.asciidoc b/docs/user/dashboard/explore-dashboard-data.asciidoc new file mode 100644 index 0000000000000..238dfb79e900b --- /dev/null +++ b/docs/user/dashboard/explore-dashboard-data.asciidoc @@ -0,0 +1,18 @@ +[[explore-dashboard-data]] +== Explore dashboard data + +Get a closer look at your data by inspecting elements and using drilldown actions. + +[float] +[[viewing-detailed-information]] +=== Inspect elements + +To view the data and requests behind the visualizations and saved searches, you can drill down into the elements. + +From the panel menu, select *Inspect*. +The data that displays depends on the element that you inspect. + +[role="screenshot"] +image:images/Dashboard_inspect.png[Inspect in dashboard] + +include::explore-underlying-data.asciidoc[] diff --git a/docs/user/dashboard/explore-underlying-data.asciidoc b/docs/user/dashboard/explore-underlying-data.asciidoc new file mode 100644 index 0000000000000..9b7be21dc45d2 --- /dev/null +++ b/docs/user/dashboard/explore-underlying-data.asciidoc @@ -0,0 +1,27 @@ +[float] +[[explore-the-underlying-data]] +=== Explore the underlying data for panels + +To explore the underlying data of the panels on your dashboard, {kib} opens *Discover*, +where you can view and filter the data in the visualization panel. When {kib} opens *Discover*, the index pattern, filters, query, and time range for the visualization continue to apply. + +TIP: The *Explore underlying data* option is available only for visualization panels with a single index pattern. + +To use the *Explore underlying data* option: + +* Click the from the panel menu, then click *Explore underlying data*. ++ +[role="screenshot"] +image::images/explore_data_context_menu.png[Explore underlying data from panel context menu] + +* Interact with the chart, then click *Explore underlying data* on the menu that appears. ++ +[role="screenshot"] +image::images/explore_data_in_chart.png[Explore underlying data from chart] ++ +To enable, open `kibana.yml`, then add the following: + +["source","yml"] +----------- +xpack.discoverEnhanced.actions.exploreDataInChart.enabled: true +----------- diff --git a/docs/user/dashboard/images/area.png b/docs/user/dashboard/images/area.png new file mode 100644 index 0000000000000..85d21a9e178c5 Binary files /dev/null and b/docs/user/dashboard/images/area.png differ diff --git a/docs/user/dashboard/images/bar.png b/docs/user/dashboard/images/bar.png new file mode 100644 index 0000000000000..f1db847655947 Binary files /dev/null and b/docs/user/dashboard/images/bar.png differ diff --git a/docs/user/dashboard/images/data_table.png b/docs/user/dashboard/images/data_table.png new file mode 100644 index 0000000000000..3e08ec526ba57 Binary files /dev/null and b/docs/user/dashboard/images/data_table.png differ diff --git a/docs/user/dashboard/images/donut.png b/docs/user/dashboard/images/donut.png new file mode 100644 index 0000000000000..a662f58ba553b Binary files /dev/null and b/docs/user/dashboard/images/donut.png differ diff --git a/docs/drilldowns/images/drilldown_create.png b/docs/user/dashboard/images/drilldown_create.png similarity index 100% rename from docs/drilldowns/images/drilldown_create.png rename to docs/user/dashboard/images/drilldown_create.png diff --git a/docs/drilldowns/images/drilldown_menu.png b/docs/user/dashboard/images/drilldown_menu.png similarity index 100% rename from docs/drilldowns/images/drilldown_menu.png rename to docs/user/dashboard/images/drilldown_menu.png diff --git a/docs/drilldowns/images/drilldown_on_panel.png b/docs/user/dashboard/images/drilldown_on_panel.png similarity index 100% rename from docs/drilldowns/images/drilldown_on_panel.png rename to docs/user/dashboard/images/drilldown_on_panel.png diff --git a/docs/drilldowns/images/drilldown_on_piechart.gif b/docs/user/dashboard/images/drilldown_on_piechart.gif similarity index 100% rename from docs/drilldowns/images/drilldown_on_piechart.gif rename to docs/user/dashboard/images/drilldown_on_piechart.gif diff --git a/docs/user/dashboard/images/drilldown_pick_an_action.png b/docs/user/dashboard/images/drilldown_pick_an_action.png new file mode 100644 index 0000000000000..c99e931e3fbe1 Binary files /dev/null and b/docs/user/dashboard/images/drilldown_pick_an_action.png differ diff --git a/docs/drilldowns/images/explore_data_context_menu.png b/docs/user/dashboard/images/explore_data_context_menu.png similarity index 100% rename from docs/drilldowns/images/explore_data_context_menu.png rename to docs/user/dashboard/images/explore_data_context_menu.png diff --git a/docs/drilldowns/images/explore_data_in_chart.png b/docs/user/dashboard/images/explore_data_in_chart.png similarity index 100% rename from docs/drilldowns/images/explore_data_in_chart.png rename to docs/user/dashboard/images/explore_data_in_chart.png diff --git a/docs/user/dashboard/images/gauge.png b/docs/user/dashboard/images/gauge.png new file mode 100644 index 0000000000000..c4aef7f5f6854 Binary files /dev/null and b/docs/user/dashboard/images/gauge.png differ diff --git a/docs/user/dashboard/images/goal.png b/docs/user/dashboard/images/goal.png new file mode 100644 index 0000000000000..967e64f722d74 Binary files /dev/null and b/docs/user/dashboard/images/goal.png differ diff --git a/docs/user/dashboard/images/heat_map.png b/docs/user/dashboard/images/heat_map.png new file mode 100644 index 0000000000000..d4a6502509f6f Binary files /dev/null and b/docs/user/dashboard/images/heat_map.png differ diff --git a/docs/user/dashboard/images/lens_aggregation_labels.png b/docs/user/dashboard/images/lens_aggregation_labels.png new file mode 100644 index 0000000000000..9dcf1d226a197 Binary files /dev/null and b/docs/user/dashboard/images/lens_aggregation_labels.png differ diff --git a/docs/user/dashboard/images/lens_data_info.png b/docs/user/dashboard/images/lens_data_info.png new file mode 100644 index 0000000000000..5ea6fc64a217d Binary files /dev/null and b/docs/user/dashboard/images/lens_data_info.png differ diff --git a/docs/user/dashboard/images/lens_drag_drop.gif b/docs/user/dashboard/images/lens_drag_drop.gif new file mode 100644 index 0000000000000..ca62115e7ea3a Binary files /dev/null and b/docs/user/dashboard/images/lens_drag_drop.gif differ diff --git a/docs/user/dashboard/images/lens_index_pattern.png b/docs/user/dashboard/images/lens_index_pattern.png new file mode 100644 index 0000000000000..90a34b7a5d225 Binary files /dev/null and b/docs/user/dashboard/images/lens_index_pattern.png differ diff --git a/docs/user/dashboard/images/lens_layers.png b/docs/user/dashboard/images/lens_layers.png new file mode 100644 index 0000000000000..7410425a6977e Binary files /dev/null and b/docs/user/dashboard/images/lens_layers.png differ diff --git a/docs/user/dashboard/images/lens_suggestions.gif b/docs/user/dashboard/images/lens_suggestions.gif new file mode 100644 index 0000000000000..3258e924cb205 Binary files /dev/null and b/docs/user/dashboard/images/lens_suggestions.gif differ diff --git a/docs/user/dashboard/images/lens_viz_types.png b/docs/user/dashboard/images/lens_viz_types.png new file mode 100644 index 0000000000000..2ecfa6bd0e0e3 Binary files /dev/null and b/docs/user/dashboard/images/lens_viz_types.png differ diff --git a/docs/user/dashboard/images/line.png b/docs/user/dashboard/images/line.png new file mode 100644 index 0000000000000..123fa74dc7e14 Binary files /dev/null and b/docs/user/dashboard/images/line.png differ diff --git a/docs/user/dashboard/images/maps.png b/docs/user/dashboard/images/maps.png new file mode 100644 index 0000000000000..65336451cc1c7 Binary files /dev/null and b/docs/user/dashboard/images/maps.png differ diff --git a/docs/user/dashboard/images/metric.png b/docs/user/dashboard/images/metric.png new file mode 100644 index 0000000000000..f8182d538a608 Binary files /dev/null and b/docs/user/dashboard/images/metric.png differ diff --git a/docs/user/dashboard/images/pie.png b/docs/user/dashboard/images/pie.png new file mode 100644 index 0000000000000..927fbb98adc07 Binary files /dev/null and b/docs/user/dashboard/images/pie.png differ diff --git a/docs/user/dashboard/images/stacked_area.png b/docs/user/dashboard/images/stacked_area.png new file mode 100644 index 0000000000000..ae66fc51176f9 Binary files /dev/null and b/docs/user/dashboard/images/stacked_area.png differ diff --git a/docs/user/dashboard/images/stacked_bar.png b/docs/user/dashboard/images/stacked_bar.png new file mode 100644 index 0000000000000..aa90ce3685cff Binary files /dev/null and b/docs/user/dashboard/images/stacked_bar.png differ diff --git a/docs/user/dashboard/images/tag_cloud.png b/docs/user/dashboard/images/tag_cloud.png new file mode 100644 index 0000000000000..976c456e4a1f1 Binary files /dev/null and b/docs/user/dashboard/images/tag_cloud.png differ diff --git a/docs/user/dashboard/images/timelion.png b/docs/user/dashboard/images/timelion.png new file mode 100644 index 0000000000000..a663791575077 Binary files /dev/null and b/docs/user/dashboard/images/timelion.png differ diff --git a/docs/user/dashboard/images/treemap.png b/docs/user/dashboard/images/treemap.png new file mode 100644 index 0000000000000..5df3c9526bfeb Binary files /dev/null and b/docs/user/dashboard/images/treemap.png differ diff --git a/docs/user/dashboard/images/tsvb.png b/docs/user/dashboard/images/tsvb.png new file mode 100644 index 0000000000000..09a3c7e86eb56 Binary files /dev/null and b/docs/user/dashboard/images/tsvb.png differ diff --git a/docs/user/dashboard/images/tsvb_annotations.png b/docs/user/dashboard/images/tsvb_annotations.png new file mode 100644 index 0000000000000..510f3c2672118 Binary files /dev/null and b/docs/user/dashboard/images/tsvb_annotations.png differ diff --git a/docs/user/dashboard/images/tsvb_change_display.gif b/docs/user/dashboard/images/tsvb_change_display.gif new file mode 100644 index 0000000000000..09d435b0a6b24 Binary files /dev/null and b/docs/user/dashboard/images/tsvb_change_display.gif differ diff --git a/docs/user/dashboard/images/tsvb_color_picker.png b/docs/user/dashboard/images/tsvb_color_picker.png new file mode 100644 index 0000000000000..4f033579d0005 Binary files /dev/null and b/docs/user/dashboard/images/tsvb_color_picker.png differ diff --git a/docs/user/dashboard/images/url_drilldown_github.png b/docs/user/dashboard/images/url_drilldown_github.png new file mode 100644 index 0000000000000..d2eaec311948e Binary files /dev/null and b/docs/user/dashboard/images/url_drilldown_github.png differ diff --git a/docs/user/dashboard/images/url_drilldown_go_to_github.gif b/docs/user/dashboard/images/url_drilldown_go_to_github.gif new file mode 100644 index 0000000000000..7cca3f72d5a68 Binary files /dev/null and b/docs/user/dashboard/images/url_drilldown_go_to_github.gif differ diff --git a/docs/user/dashboard/images/url_drilldown_pick_an_action.png b/docs/user/dashboard/images/url_drilldown_pick_an_action.png new file mode 100644 index 0000000000000..c99e931e3fbe1 Binary files /dev/null and b/docs/user/dashboard/images/url_drilldown_pick_an_action.png differ diff --git a/docs/user/dashboard/images/url_drilldown_popup.png b/docs/user/dashboard/images/url_drilldown_popup.png new file mode 100644 index 0000000000000..392edd16ea328 Binary files /dev/null and b/docs/user/dashboard/images/url_drilldown_popup.png differ diff --git a/docs/user/dashboard/images/url_drilldown_trigger_picker.png b/docs/user/dashboard/images/url_drilldown_trigger_picker.png new file mode 100644 index 0000000000000..2fe930f35dce8 Binary files /dev/null and b/docs/user/dashboard/images/url_drilldown_trigger_picker.png differ diff --git a/docs/user/dashboard/images/url_drilldown_url_template.png b/docs/user/dashboard/images/url_drilldown_url_template.png new file mode 100644 index 0000000000000..d8515afe66a80 Binary files /dev/null and b/docs/user/dashboard/images/url_drilldown_url_template.png differ diff --git a/docs/user/dashboard/images/vega.png b/docs/user/dashboard/images/vega.png new file mode 100644 index 0000000000000..6a0d8cb772adf Binary files /dev/null and b/docs/user/dashboard/images/vega.png differ diff --git a/docs/user/dashboard/share-dashboards.asciidoc b/docs/user/dashboard/share-dashboards.asciidoc new file mode 100644 index 0000000000000..cfa146d60fdac --- /dev/null +++ b/docs/user/dashboard/share-dashboards.asciidoc @@ -0,0 +1,27 @@ +[[share-dashboards]] +== Share dashboards + +[[embedding-dashboards]] +Share your dashboard outside of {kib}. + +From the *Share* menu, you can: + +* Embed the code in a web page. Users must have {kib} access +to view an embedded dashboard. +* Share a direct link to a {kib} dashboard +* Generate a PDF report +* Generate a PNG report + +TIP: To create a link to a dashboard by title, use: + +`${domain}/${basepath?}/app/dashboards#/list?title=${yourdashboardtitle}` + +TIP: When sharing a link to a dashboard snapshot, use the *Short URL*. Snapshot +URLs are long and can be problematic for Internet Explorer and other +tools. To create a short URL, you must have write access to {kib}. + +[float] +[[import-dashboards]] +=== Export the dashboard + +To export the dashboard, open the menu, then click *Stack Management > Saved Objects*. For more information, +refer to <>. \ No newline at end of file diff --git a/docs/user/dashboard/tutorials.asciidoc b/docs/user/dashboard/tutorials.asciidoc new file mode 100644 index 0000000000000..931720ccbe257 --- /dev/null +++ b/docs/user/dashboard/tutorials.asciidoc @@ -0,0 +1,1756 @@ +[[tutorials]] +== Tutorials + +Learn how to use *Lens*, *Vega*, and *Timelion* by going through one of the step-by-step tutorials. + +[[lens-tutorial]] +=== Compare sales over time with Lens + +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. + +. Select the *kibana_sample_data_ecommerce* index pattern. + +. Click image:images/time-filter-calendar.png[], then click *Last 7 days*. ++ +The fields in the data panel update. + +. Drag and drop the *taxful_total_price* data field to the visualization builder pane. ++ +[role="screenshot"] +image::images/lens_tutorial_1.png[Lens tutorial] + +To display the average order prices over time, *Lens* automatically added in *order_date* field. + +To break down your data, drag the *category.keyword* field to the visualization builder pane. Lens +knows 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 three categories: + +[role="screenshot"] +image::images/lens_tutorial_2.png[Lens tutorial] + +[float] +[[customize-lens-visualization]] +==== Customize your visualization + +Make your visualization look exactly how you want with the customization options. + +. Click *Average of taxful_total_price*, then change the *Label* to `Sales`. ++ +[role="screenshot"] +image::images/lens_tutorial_3.1.png[Lens tutorial] + +. Click *Top values of category.keyword*, then change *Number of values* to `10`. ++ +[role="screenshot"] +image::images/lens_tutorial_3.2.png[Lens tutorial] ++ +The visualization updates to show there are only six available categories. ++ +Look at the *Suggestions*. An area chart is not an option, but for the sales data, a stacked area chart might be the best option. + +. To switch the chart type, click *Stacked bar chart* in the column, then click *Stacked area* from the *Select a visualizations* window. ++ +[role="screenshot"] +image::images/lens_tutorial_3.png[Lens tutorial] + +[float] +[[lens-tutorial-next-steps]] +==== Next steps + +Now that you've created your visualization, you can add it to a <> or <>. + +[[vega-lite-tutorial-create-your-first-visualizations]] +=== Create your first visualization with Vega-Lite + +experimental[] + +In this tutorial, you will learn about how to edit Vega-Lite in {kib} to create +a stacked area chart from an {es} search query. It will give you a starting point +for a more comprehensive +https://vega.github.io/vega-lite/tutorials/getting_started.html[introduction to Vega-Lite], +while only covering the basics. + +In this tutorial, you will build a stacked area chart from one of the {kib} sample data +sets. + +[role="screenshot"] +image::visualize/images/vega_lite_tutorial_1.png[] + +Before beginning this tutorial, install the <> +set. + +When you first open the Vega editor in {kib}, you will see a pre-populated +line chart which shows the total number of documents across all your indices +within the time range. + +[role="screenshot"] +image::visualize/images/vega_lite_default.png[] + +The text editor contains a Vega-Lite spec written in https://hjson.github.io/[HJSON], +which is similar to JSON but optimized for human editing. HJSON supports: + +* Comments using // or /* syntax +* Object keys without quotes +* String values without quotes +* Optional commas +* Double or single quotes +* Multiline strings + +[float] +[[small-steps]] +==== Small steps + +Always work on Vega in the smallest steps possible, and save your work frequently. +Small changes will cause unexpected results. Click the "Save" button now. + +The first step is to change the index to one of the <> +sets. Change + +```yaml +index: _all +``` + +to: + +```yaml +index: kibana_sample_data_ecommerce +``` + +Click "Update". The result is probably not what you expect. You should see a flat +line with 0 results. + +You've only changed the index, so the difference must be the query is returning +no results. You can try the <>, +but intuition may be faster for this particular problem. + +In this case, the problem is that you are querying the field `@timestamp`, +which does not exist in the `kibana_sample_data_ecommerce` data. Find and replace +`@timestamp` with `order_date`. This fixes the problem, leaving you with this spec: + +.Expand Vega-Lite spec +[%collapsible%closed] +==== +[source,yaml] +---- +{ + $schema: https://vega.github.io/schema/vega-lite/v4.json + title: Event counts from ecommerce + data: { + url: { + %context%: true + %timefield%: order_date + index: kibana_sample_data_ecommerce + body: { + aggs: { + time_buckets: { + date_histogram: { + field: order_date + interval: {%autointerval%: true} + extended_bounds: { + min: {%timefilter%: "min"} + max: {%timefilter%: "max"} + } + min_doc_count: 0 + } + } + } + size: 0 + } + } + format: {property: "aggregations.time_buckets.buckets" } + } + + mark: line + + encoding: { + x: { + field: key + type: temporal + axis: { title: null } + } + y: { + field: doc_count + type: quantitative + axis: { title: "Document count" } + } + } +} +---- + +==== + +Now, let's make the visualization more interesting by adding another aggregation +to create a stacked area chart. To verify that you have constructed the right +query, it is easiest to use the {kib} Dev Tools in a separate tab from the +Vega editor. Open the Dev Tools from the Management section of the navigation. + +This query is roughly equivalent to the one that is used in the default +Vega-Lite spec. Copy it into the Dev Tools: + +```js +POST kibana_sample_data_ecommerce/_search +{ + "query": { + "range": { + "order_date": { + "gte": "now-7d" + } + } + }, + "aggs": { + "time_buckets": { + "date_histogram": { + "field": "order_date", + "fixed_interval": "1d", + "extended_bounds": { + "min": "now-7d" + }, + "min_doc_count": 0 + } + } + }, + "size": 0 +} +``` + +There's not enough data to create a stacked bar in the original query, so we +will add a new +{ref}/search-aggregations-bucket-terms-aggregation.html[terms aggregation]: + +```js +POST kibana_sample_data_ecommerce/_search +{ + "query": { + "range": { + "order_date": { + "gte": "now-7d" + } + } + }, + "aggs": { + "categories": { + "terms": { "field": "category.keyword" }, + "aggs": { + "time_buckets": { + "date_histogram": { + "field": "order_date", + "fixed_interval": "1d", + "extended_bounds": { + "min": "now-7d" + }, + "min_doc_count": 0 + } + } + } + } + }, + "size": 0 +} +``` + +You'll see that the response format looks different from the previous query: + +```json +{ + "aggregations" : { + "categories" : { + "doc_count_error_upper_bound" : 0, + "sum_other_doc_count" : 0, + "buckets" : [{ + "key" : "Men's Clothing", + "doc_count" : 1661, + "time_buckets" : { + "buckets" : [{ + "key_as_string" : "2020-06-30T00:00:00.000Z", + "key" : 1593475200000, + "doc_count" : 19 + }, { + "key_as_string" : "2020-07-01T00:00:00.000Z", + "key" : 1593561600000, + "doc_count" : 71 + }] + } + }] + } + } +} +``` + +Now that we have data that we're happy with, it's time to convert from an +isolated {es} query into a query with {kib} integration. Looking at the +<>, you will +see the full list of special tokens that are used in this query, such +as `%context: true`. This query has also replaced `"fixed_interval": "1d"` +with `interval: {%autointerval%: true}`. Copy the final query into +your spec: + +```yaml + data: { + url: { + %context%: true + %timefield%: order_date + index: kibana_sample_data_ecommerce + body: { + aggs: { + categories: { + terms: { field: "category.keyword" } + aggs: { + time_buckets: { + date_histogram: { + field: order_date + interval: {%autointerval%: true} + extended_bounds: { + min: {%timefilter%: "min"} + max: {%timefilter%: "max"} + } + min_doc_count: 0 + } + } + } + } + } + size: 0 + } + } + format: {property: "aggregations.categories.buckets" } + } +``` + +If you copy and paste that into your Vega-Lite spec, and click "Update", +you will see a warning saying `Infinite extent for field "key": [Infinity, -Infinity]`. +Let's use our <> to understand why. + +Vega-Lite generates data using the names `source_0` and `data_0`. `source_0` contains +the results from the {es} query, and `data_0` contains the visually encoded results +which are shown in the chart. To debug this problem, you need to compare both. + +To look at the source, open the browser dev tools console and type +`VEGA_DEBUG.view.data('source_0')`. You will see: + +```js +[{ + doc_count: 454 + key: "Men's Clothing" + time_buckets: {buckets: Array(57)} + Symbol(vega_id): 12822 +}, ...] +``` + +To compare to the visually encoded data, open the browser dev tools console and type +`VEGA_DEBUG.view.data('data_0')`. You will see: + +```js +[{ + doc_count: 454 + key: NaN + time_buckets: {buckets: Array(57)} + Symbol(vega_id): 13879 +}] +``` + +The issue seems to be that the `key` property is not being converted the right way, +which makes sense because the `key` is now `Men's Clothing` instead of a timestamp. + +To fix this, try updating the `encoding` of your Vega-Lite spec to: + +```yaml + encoding: { + x: { + field: time_buckets.buckets.key + type: temporal + axis: { title: null } + } + y: { + field: time_buckets.buckets.doc_count + type: quantitative + axis: { title: "Document count" } + } + } +``` + +This will show more errors, and you can inspect `VEGA_DEBUG.view.data('data_0')` to +understand why. This now shows: + +```js +[{ + doc_count: 454 + key: "Men's Clothing" + time_buckets: {buckets: Array(57)} + time_buckets.buckets.doc_count: undefined + time_buckets.buckets.key: null + Symbol(vega_id): 14094 +}] +``` + +It looks like the problem is that the `time_buckets` inner array is not being +extracted by Vega. The solution is to use a Vega-lite +https://vega.github.io/vega-lite/docs/flatten.html[flatten transformation], available in {kib} 7.9 and later. +If using an older version of Kibana, the flatten transformation is available in Vega +but not Vega-Lite. + +Add this section in between the `data` and `encoding` section: + +```yaml + transform: [{ + flatten: ["time_buckets.buckets"] + }] +``` + +This does not yet produce the results you expect. Inspect the transformed data +by typing `VEGA_DEBUG.view.data('data_0')` into the console again: + +```js +[{ + doc_count: 453 + key: "Men's Clothing" + time_bucket.buckets.doc_count: undefined + time_buckets: {buckets: Array(57)} + time_buckets.buckets: { + key_as_string: "2020-06-30T15:00:00.000Z", + key: 1593529200000, + doc_count: 2 + } + time_buckets.buckets.key: null + Symbol(vega_id): 21564 +}] +``` + +The debug view shows `undefined` values where you would expect to see numbers, and +the cause is that there are duplicate names which are confusing Vega-Lite. This can +be fixed by making this change to the `transform` and `encoding` blocks: + +```yaml + transform: [{ + flatten: ["time_buckets.buckets"], + as: ["buckets"] + }] + + mark: area + + encoding: { + x: { + field: buckets.key + type: temporal + axis: { title: null } + } + y: { + field: buckets.doc_count + type: quantitative + axis: { title: "Document count" } + } + color: { + field: key + type: nominal + } + } +``` + +At this point, you have a stacked area chart that shows the top categories, +but the chart is still missing some common features that we expect from a {kib} +visualization. Let's add hover states and tooltips next. + +Hover states are handled differently in Vega-Lite and Vega. In Vega-Lite this is +done using a concept called `selection`, which has many permutations that are not +covered in this tutorial. We will be adding a simple tooltip and hover state. + +Because {kib} has enabled the https://vega.github.io/vega-lite/docs/tooltip.html[Vega tooltip plugin], +tooltips can be defined in several ways: + +* Automatic tooltip based on the data, via `{ content: "data" }` +* Array of fields, like `[{ field: "key", type: "nominal" }]` +* Defining a custom Javascript object using the `calculate` transform + +For the simple tooltip, add this to your encoding: + +```yaml + encoding: { + tooltip: [{ + field: buckets.key + type: temporal + title: "Date" + }, { + field: key + type: nominal + title: "Category" + }, { + field: buckets.doc_count + type: quantitative + title: "Count" + }] + } +``` + +As you hover over the area series in your chart, a multi-line tooltip will +appear, but it won't indicate the nearest point that it's pointing to. To +indicate the nearest point, we need to add a second layer. + +The first step is to remove the `mark: area` from your visualization. +Once you've removed the previous mark, add a composite mark at the end of +the Vega-Lite spec: + +```yaml + layer: [{ + mark: area + }, { + mark: point + }] +``` + +You'll see that the points are not appearing to line up with the area chart, +and the reason is that the points are not being stacked. Change your Y encoding +to this: + +```yaml + y: { + field: buckets.doc_count + type: quantitative + axis: { title: "Document count" } + stack: true + } +``` + +Now, we will add a `selection` block inside the point mark: + +```yaml + layer: [{ + mark: area + }, { + mark: point + + selection: { + pointhover: { + type: single + on: mouseover + clear: mouseout + empty: none + fields: ["buckets.key", "key"] + nearest: true + } + } + + encoding: { + size: { + condition: { + selection: pointhover + value: 100 + } + value: 5 + } + fill: { + condition: { + selection: pointhover + value: white + } + } + } + }] +``` + +Now that you've enabled a selection, try moving the mouse around the visualization +and seeing the points respond to the nearest position: + +[role="screenshot"] +image::visualize/images/vega_lite_tutorial_2.png[] + +The final result of this tutorial is this spec: + +.Expand final Vega-Lite spec +[%collapsible%closed] +==== +[source,yaml] +---- +{ + $schema: https://vega.github.io/schema/vega-lite/v4.json + title: Event counts from ecommerce + data: { + url: { + %context%: true + %timefield%: order_date + index: kibana_sample_data_ecommerce + body: { + aggs: { + categories: { + terms: { field: "category.keyword" } + aggs: { + time_buckets: { + date_histogram: { + field: order_date + interval: {%autointerval%: true} + extended_bounds: { + min: {%timefilter%: "min"} + max: {%timefilter%: "max"} + } + min_doc_count: 0 + } + } + } + } + } + size: 0 + } + } + format: {property: "aggregations.categories.buckets" } + } + + transform: [{ + flatten: ["time_buckets.buckets"] + as: ["buckets"] + }] + + encoding: { + x: { + field: buckets.key + type: temporal + axis: { title: null } + } + y: { + field: buckets.doc_count + type: quantitative + axis: { title: "Document count" } + stack: true + } + color: { + field: key + type: nominal + title: "Category" + } + tooltip: [{ + field: buckets.key + type: temporal + title: "Date" + }, { + field: key + type: nominal + title: "Category" + }, { + field: buckets.doc_count + type: quantitative + title: "Count" + }] + } + + layer: [{ + mark: area + }, { + mark: point + + selection: { + pointhover: { + type: single + on: mouseover + clear: mouseout + empty: none + fields: ["buckets.key", "key"] + nearest: true + } + } + + encoding: { + size: { + condition: { + selection: pointhover + value: 100 + } + value: 5 + } + fill: { + condition: { + selection: pointhover + value: white + } + } + } + }] +} +---- + +==== + +[[vega-tutorial-update-kibana-filters-from-vega]] +=== Update {kib} filters from Vega + +experimental[] + +In this tutorial you will build an area chart in Vega using an {es} search query, +and add a click handler and drag handler to update {kib} filters. +This tutorial is not a full https://vega.github.io/vega/tutorials/[Vega tutorial], +but will cover the basics of creating Vega visualizations into {kib}. + +First, create an almost-blank Vega chart by pasting this into the editor: + +```yaml +{ + $schema: "https://vega.github.io/schema/vega/v5.json" + data: [{ + name: source_0 + }] + + scales: [{ + name: x + type: time + range: width + }, { + name: y + type: linear + range: height + }] + + axes: [{ + orient: bottom + scale: x + }, { + orient: left + scale: y + }] + + marks: [ + { + type: area + from: { + data: source_0 + } + encode: { + update: { + } + } + } + ] +} +``` + +Despite being almost blank, this Vega spec still contains the minimum requirements: + +* Data +* Scales +* Marks +* (optional) Axes + +Next, add a valid {es} search query in the `data` block: + +```yaml + data: [ + { + name: source_0 + url: { + %context%: true + %timefield%: order_date + index: kibana_sample_data_ecommerce + body: { + aggs: { + time_buckets: { + date_histogram: { + field: order_date + fixed_interval: "3h" + extended_bounds: { + min: {%timefilter%: "min"} + max: {%timefilter%: "max"} + } + min_doc_count: 0 + } + } + } + size: 0 + } + } + format: { property: "aggregations.time_buckets.buckets" } + } + ] +``` + +Click "Update", and nothing will change in the visualization. The first step +is to change the X and Y scales based on the data: + +```yaml + scales: [{ + name: x + type: time + range: width + domain: { + data: source_0 + field: key + } + }, { + name: y + type: linear + range: height + domain: { + data: source_0 + field: doc_count + } + }] +``` + +Click "Update", and you will see that the X and Y axes are now showing labels based +on the real data. + +Next, encode the fields `key` and `doc_count` as the X and Y values: + +```yaml + marks: [ + { + type: area + from: { + data: source_0 + } + encode: { + update: { + x: { + scale: x + field: key + } + y: { + scale: y + value: 0 + } + y2: { + scale: y + field: doc_count + } + } + } + } + ] +``` + +Click "Update" and you will get a basic area chart: + +[role="screenshot"] +image::visualize/images/vega_tutorial_3.png[] + +Next, add a new block to the `marks` section. This will show clickable points to filter for a specific +date: + +```yaml + { + name: point + type: symbol + style: ["point"] + from: { + data: source_0 + } + encode: { + update: { + x: { + scale: x + field: key + } + y: { + scale: y + field: doc_count + } + size: { + value: 100 + } + fill: { + value: black + } + } + } + } +``` + +Next, we will create a Vega signal to make the points clickable. You can access +the clicked `datum` in the expression used to update. In this case, you want +clicks on points to add a time filter with the 3-hour interval defined above. + +```yaml + signals: [ + { + name: point_click + on: [{ + events: { + source: scope + type: click + markname: point + } + update: '''kibanaSetTimeFilter(datum.key, datum.key + 3 * 60 * 60 * 1000)''' + }] + } + ] +``` + +This event is using the {kib} custom function `kibanaSetTimeFilter` to generate a filter that +gets applied to the entire dashboard on click. + +The mouse cursor does not currently indicate that the chart is interactive. Find the `marks` section, +and update the mark named `point` by adding `cursor: { value: "pointer" }` to +the `encoding` section like this: + +```yaml + { + name: point + type: symbol + style: ["point"] + from: { + data: source_0 + } + encode: { + update: { + ... + cursor: { value: "pointer" } + } + } + } +``` + +Next, we will add a drag interaction which will allow the user to narrow into +a specific time range in the visualization. This will require adding more signals, and +adding a rectangle overlay: + +[role="screenshot"] +image::visualize/images/vega_tutorial_4.png[] + +The first step is to add a new `signal` to track the X position of the cursor: + +```yaml + { + name: currentX + value: -1 + on: [{ + events: { + type: mousemove + source: view + }, + update: "clamp(x(), 0, width)" + }, { + events: { + type: mouseout + source: view + } + update: "-1" + }] + } +``` + +Now add a new `mark` to indicate the current cursor position: + +```yaml + { + type: rule + interactive: false + encode: { + update: { + y: {value: 0} + y2: {signal: "height"} + stroke: {value: "gray"} + strokeDash: { + value: [2, 1] + } + x: {signal: "max(currentX,0)"} + defined: {signal: "currentX > 0"} + } + } + } +``` + +Next, add a signal to track the current selected range, which will update +until the user releases the mouse button or uses the escape key: + + +```yaml + { + name: selected + value: [0, 0] + on: [{ + events: { + type: mousedown + source: view + } + update: "[clamp(x(), 0, width), clamp(x(), 0, width)]" + }, { + events: { + type: mousemove + source: window + consume: true + between: [{ + type: mousedown + source: view + }, { + merge: [{ + type: mouseup + source: window + }, { + type: keydown + source: window + filter: "event.key === 'Escape'" + }] + }] + } + update: "[selected[0], clamp(x(), 0, width)]" + }, { + events: { + type: keydown + source: window + filter: "event.key === 'Escape'" + } + update: "[0, 0]" + }] + } +``` + +Now that there is a signal which tracks the time range from the user, we need to indicate +the range visually by adding a new mark which only appears conditionally: + +```yaml + { + type: rect + name: selectedRect + encode: { + update: { + height: {signal: "height"} + fill: {value: "#333"} + fillOpacity: {value: 0.2} + x: {signal: "selected[0]"} + x2: {signal: "selected[1]"} + defined: {signal: "selected[0] !== selected[1]"} + } + } + } +``` + +Finally, add a new signal which will update the {kib} time filter when the mouse is released while +dragging: + +```yaml + { + name: applyTimeFilter + value: null + on: [{ + events: { + type: mouseup + source: view + } + update: '''selected[0] !== selected[1] ? kibanaSetTimeFilter( + invert('x',selected[0]), + invert('x',selected[1])) : null''' + }] + } +``` + +Putting this all together, your visualization now supports the main features of +standard visualizations in {kib}, but with the potential to add even more control. +The final Vega spec for this tutorial is here: + +.Expand final Vega spec +[%collapsible%closed] +==== +[source,yaml] +---- +{ + $schema: "https://vega.github.io/schema/vega/v5.json" + data: [ + { + name: source_0 + url: { + %context%: true + %timefield%: order_date + index: kibana_sample_data_ecommerce + body: { + aggs: { + time_buckets: { + date_histogram: { + field: order_date + fixed_interval: "3h" + extended_bounds: { + min: {%timefilter%: "min"} + max: {%timefilter%: "max"} + } + min_doc_count: 0 + } + } + } + size: 0 + } + } + format: { property: "aggregations.time_buckets.buckets" } + } + ] + + scales: [{ + name: x + type: time + range: width + domain: { + data: source_0 + field: key + } + }, { + name: y + type: linear + range: height + domain: { + data: source_0 + field: doc_count + } + }] + + axes: [{ + orient: bottom + scale: x + }, { + orient: left + scale: y + }] + + marks: [ + { + type: area + from: { + data: source_0 + } + encode: { + update: { + x: { + scale: x + field: key + } + y: { + scale: y + value: 0 + } + y2: { + scale: y + field: doc_count + } + } + } + }, + { + name: point + type: symbol + style: ["point"] + from: { + data: source_0 + } + encode: { + update: { + x: { + scale: x + field: key + } + y: { + scale: y + field: doc_count + } + size: { + value: 100 + } + fill: { + value: black + } + cursor: { value: "pointer" } + } + } + }, + { + type: rule + interactive: false + encode: { + update: { + y: {value: 0} + y2: {signal: "height"} + stroke: {value: "gray"} + strokeDash: { + value: [2, 1] + } + x: {signal: "max(currentX,0)"} + defined: {signal: "currentX > 0"} + } + } + }, + { + type: rect + name: selectedRect + encode: { + update: { + height: {signal: "height"} + fill: {value: "#333"} + fillOpacity: {value: 0.2} + x: {signal: "selected[0]"} + x2: {signal: "selected[1]"} + defined: {signal: "selected[0] !== selected[1]"} + } + } + } + ] + + signals: [ + { + name: point_click + on: [{ + events: { + source: scope + type: click + markname: point + } + update: '''kibanaSetTimeFilter(datum.key, datum.key + 3 * 60 * 60 * 1000)''' + }] + } + { + name: currentX + value: -1 + on: [{ + events: { + type: mousemove + source: view + }, + update: "clamp(x(), 0, width)" + }, { + events: { + type: mouseout + source: view + } + update: "-1" + }] + } + { + name: selected + value: [0, 0] + on: [{ + events: { + type: mousedown + source: view + } + update: "[clamp(x(), 0, width), clamp(x(), 0, width)]" + }, { + events: { + type: mousemove + source: window + consume: true + between: [{ + type: mousedown + source: view + }, { + merge: [{ + type: mouseup + source: window + }, { + type: keydown + source: window + filter: "event.key === 'Escape'" + }] + }] + } + update: "[selected[0], clamp(x(), 0, width)]" + }, { + events: { + type: keydown + source: window + filter: "event.key === 'Escape'" + } + update: "[0, 0]" + }] + } + { + name: applyTimeFilter + value: null + on: [{ + events: { + type: mouseup + source: view + } + update: '''selected[0] !== selected[1] ? kibanaSetTimeFilter( + invert('x',selected[0]), + invert('x',selected[1])) : null''' + }] + } + ] +} + +---- +==== + +[[timelion-tutorial-create-time-series-visualizations]] +=== Create time series visualizations with Timelion + +To compare the real-time percentage of CPU time spent in user space to the results offset by one hour, create a time series visualization. + +[float] +[[define-the-functions]] +==== Define the functions + +To start tracking the real-time percentage of CPU, enter the following in the *Timelion Expression* field: + +[source,text] +---------------------------------- +.es(index=metricbeat-*, + timefield='@timestamp', + metric='avg:system.cpu.user.pct') +---------------------------------- + +[role="screenshot"] +image::images/timelion-create01.png[] +{nbsp} + +[float] +[[compare-the-data]] +==== Compare the data + +To compare the two data sets, add another series with data from the previous hour, separated by a comma: + +[source,text] +---------------------------------- +.es(index=metricbeat-*, + timefield='@timestamp', + metric='avg:system.cpu.user.pct'), +.es(offset=-1h, <1> + index=metricbeat-*, + timefield='@timestamp', + metric='avg:system.cpu.user.pct') +---------------------------------- + +<1> `offset` offsets the data retrieval by a date expression. In this example, `-1h` offsets the data back by one hour. + +[role="screenshot"] +image::images/timelion-create02.png[] +{nbsp} + +[float] +[[add-label-names]] +==== Add label names + +To easily distinguish between the two data sets, add the label names: + +[source,text] +---------------------------------- +.es(offset=-1h,index=metricbeat-*, + timefield='@timestamp', + metric='avg:system.cpu.user.pct').label('last hour'), +.es(index=metricbeat-*, + timefield='@timestamp', + metric='avg:system.cpu.user.pct').label('current hour') <1> +---------------------------------- + +<1> `.label()` adds custom labels to the visualization. + +[role="screenshot"] +image::images/timelion-create03.png[] +{nbsp} + +[float] +[[add-a-title]] +==== Add a title + +Add a meaningful title: + +[source,text] +---------------------------------- +.es(offset=-1h, + index=metricbeat-*, + timefield='@timestamp', + metric='avg:system.cpu.user.pct') + .label('last hour'), +.es(index=metricbeat-*, + timefield='@timestamp', + metric='avg:system.cpu.user.pct') + .label('current hour') + .title('CPU usage over time') <1> +---------------------------------- + +<1> `.title()` adds a title with a meaningful name. Titles make is easier for unfamiliar users to understand the purpose of the visualization. + +[role="screenshot"] +image::images/timelion-customize01.png[] +{nbsp} + +[float] +[[change-the-chart-type]] +==== Change the chart type + +To differentiate between the current hour data and the last hour data, change the chart type: + +[source,text] +---------------------------------- +.es(offset=-1h, + index=metricbeat-*, + timefield='@timestamp', + metric='avg:system.cpu.user.pct') + .label('last hour') + .lines(fill=1,width=0.5), <1> +.es(index=metricbeat-*, + timefield='@timestamp', + metric='avg:system.cpu.user.pct') + .label('current hour') + .title('CPU usage over time') +---------------------------------- + +<1> `.lines()` changes the appearance of the chart lines. In this example, `.lines(fill=1,width=0.5)` sets the fill level to `1`, and the border width to `0.5`. + +[role="screenshot"] +image::images/timelion-customize02.png[] +{nbsp} + +[float] +[[change-the-line-colors]] +==== Change the line colors + +To make the current hour data stand out, change the line colors: + +[source,text] +---------------------------------- +.es(offset=-1h, + index=metricbeat-*, + timefield='@timestamp', + metric='avg:system.cpu.user.pct') + .label('last hour') + .lines(fill=1,width=0.5) + .color(gray), <1> +.es(index=metricbeat-*, + timefield='@timestamp', + metric='avg:system.cpu.user.pct') + .label('current hour') + .title('CPU usage over time') + .color(#1E90FF) +---------------------------------- + +<1> `.color()` changes the color of the data. Supported color types include standard color names, hexadecimal values, or a color schema for grouped data. In this example, `.color(gray)` represents the last hour, and `.color(#1E90FF)` represents the current hour. + +[role="screenshot"] +image::images/timelion-customize03.png[] +{nbsp} + +[float] +[[make-adjustments-to-the-legend]] +==== Make adjustments to the legend + +Change the position and style of the legend: + +[source,text] +---------------------------------- +.es(offset=-1h, + index=metricbeat-*, + timefield='@timestamp', + metric='avg:system.cpu.user.pct') + .label('last hour') + .lines(fill=1,width=0.5) + .color(gray), +.es(index=metricbeat-*, + timefield='@timestamp', + metric='avg:system.cpu.user.pct') + .label('current hour') + .title('CPU usage over time') + .color(#1E90FF) + .legend(columns=2, position=nw) <1> +---------------------------------- + +<1> `.legend()` sets the position and style of the legend. In this example, `.legend(columns=2, position=nw)` places the legend in the north west position of the visualization with two columns. + +[role="screenshot"] +image::images/timelion-customize04.png[] +{nbsp} + +[[timelion-tutorial-create-visualizations-with-mathematical-functions]] +=== Timelion tutorial: Create visualizations with mathematical functions + +To create a visualization for inbound and outbound network traffic, use mathematical functions. + +[float] +[[mathematical-functions-define-functions]] +==== Define the functions + +To start tracking the inbound and outbound network traffic, enter the following in the *Timelion Expression* field: + +[source,text] +---------------------------------- +.es(index=metricbeat*, + timefield=@timestamp, + metric=max:system.network.in.bytes) +---------------------------------- + +[role="screenshot"] +image::images/timelion-math01.png[] +{nbsp} + +[float] +[[mathematical-functions-plot-change]] +==== Plot the rate of change + +Change how the data is displayed so that you can easily monitor the inbound traffic: + +[source,text] +---------------------------------- +.es(index=metricbeat*, + timefield=@timestamp, + metric=max:system.network.in.bytes) + .derivative() <1> +---------------------------------- + +<1> `.derivative` plots the change in values over time. + +[role="screenshot"] +image::images/timelion-math02.png[] +{nbsp} + +Add a similar calculation for outbound traffic: + +[source,text] +---------------------------------- +.es(index=metricbeat*, + timefield=@timestamp, + metric=max:system.network.in.bytes) + .derivative(), +.es(index=metricbeat*, + timefield=@timestamp, + metric=max:system.network.out.bytes) + .derivative() + .multiply(-1) <1> +---------------------------------- + +<1> `.multiply()` multiplies the data series by a number, the result of a data series, or a list of data series. For this example, `.multiply(-1)` converts the outbound network traffic to a negative value since the outbound network traffic is leaving your machine. + +[role="screenshot"] +image::images/timelion-math03.png[] +{nbsp} + +[float] +[[mathematical-functions-convert-data]] +==== Change the data metric + +To make the visualization easier to analyze, change the data metric from bytes to megabytes: + +[source,text] +---------------------------------- +.es(index=metricbeat*, + timefield=@timestamp, + metric=max:system.network.in.bytes) + .derivative() + .divide(1048576), +.es(index=metricbeat*, + timefield=@timestamp, + metric=max:system.network.out.bytes) + .derivative() + .multiply(-1) + .divide(1048576) <1> +---------------------------------- + +<1> `.divide()` accepts the same input as `.multiply()`, then divides the data series by the defined divisor. + +[role="screenshot"] +image::images/timelion-math04.png[] +{nbsp} + +[float] +[[mathematical-functions-add-labels]] +==== Customize and format the visualization + +Customize and format the visualization using functions: + +[source,text] +---------------------------------- +.es(index=metricbeat*, + timefield=@timestamp, + metric=max:system.network.in.bytes) + .derivative() + .divide(1048576) + .lines(fill=2, width=1) + .color(green) + .label("Inbound traffic") <1> + .title("Network traffic (MB/s)"), <2> +.es(index=metricbeat*, + timefield=@timestamp, + metric=max:system.network.out.bytes) + .derivative() + .multiply(-1) + .divide(1048576) + .lines(fill=2, width=1) <3> + .color(blue) <4> + .label("Outbound traffic") + .legend(columns=2, position=nw) <5> +---------------------------------- + +<1> `.label()` adds custom labels to the visualization. +<2> `.title()` adds a title with a meaningful name. +<3> `.lines()` changes the appearance of the chart lines. In this example, `.lines(fill=2, width=1)` sets the fill level to `2`, and the border width to `1`. +<4> `.color()` changes the color of the data. Supported color types include standard color names, hexadecimal values, or a color schema for grouped data. In this example, `.color(green)` represents the inbound network traffic, and `.color(blue)` represents the outbound network traffic. +<5> `.legend()` sets the position and style of the legend. For this example, `legend(columns=2, position=nw)` places the legend in the north west position of the visualization with two columns. + +[role="screenshot"] +image::images/timelion-math05.png[] +{nbsp} + +[[timelion-tutorial-create-visualizations-withconditional-logic-and-tracking-trends]] +=== Create visualizations with conditional logic and tracking trends using Timelion + +To easily detect outliers and discover patterns over time, modify time series data with conditional logic and create a trend with a moving average. + +With Timelion conditional logic, you can use the following operator values to compare your data: + +[horizontal] +`eq`:: equal +`ne`:: not equal +`lt`:: less than +`lte`:: less than or equal to +`gt`:: greater than +`gte`:: greater than or equal to + +[float] +[[conditional-define-functions]] +==== Define the functions + +To chart the maximum value of `system.memory.actual.used.bytes`, enter the following in the *Timelion Expression* field: + +[source,text] +---------------------------------- +.es(index=metricbeat-*, + timefield='@timestamp', + metric='max:system.memory.actual.used.bytes') +---------------------------------- + +[role="screenshot"] +image::images/timelion-conditional01.png[] +{nbsp} + +[float] +[[conditional-track-memory]] +==== Track used memory + +To track the amount of memory used, create two thresholds: + +[source,text] +---------------------------------- +.es(index=metricbeat-*, + timefield='@timestamp', + metric='max:system.memory.actual.used.bytes'), +.es(index=metricbeat-*, + timefield='@timestamp', + metric='max:system.memory.actual.used.bytes') + .if(gt, <1> + 11300000000, <2> + .es(index=metricbeat-*, + timefield='@timestamp', + metric='max:system.memory.actual.used.bytes'), + null) + .label('warning') + .color('#FFCC11'), +.es(index=metricbeat-*, + timefield='@timestamp', + metric='max:system.memory.actual.used.bytes') + .if(gt, + 11375000000, + .es(index=metricbeat-*, + timefield='@timestamp', + metric='max:system.memory.actual.used.bytes'), + null) + .label('severe') + .color('red') +---------------------------------- + +<1> Timelion conditional logic for the _greater than_ operator. In this example, the warning threshold is 11.3GB (`11300000000`), and the severe threshold is 11.375GB (`11375000000`). If the threshold values are too high or low for your machine, adjust the values accordingly. +<2> `if()` compares each point to a number. If the condition evaluates to `true`, adjust the styling. If the condition evaluates to `false`, use the default styling. + +[role="screenshot"] +image::images/timelion-conditional02.png[] +{nbsp} + +[float] +[[conditional-determine-trend]] +==== Determine the trend + +To determine the trend, create a new data series: + +[source,text] +---------------------------------- +.es(index=metricbeat-*, + timefield='@timestamp', + metric='max:system.memory.actual.used.bytes'), +.es(index=metricbeat-*, + timefield='@timestamp', + metric='max:system.memory.actual.used.bytes') + .if(gt,11300000000, + .es(index=metricbeat-*, + timefield='@timestamp', + metric='max:system.memory.actual.used.bytes'), + null) + .label('warning') + .color('#FFCC11'), +.es(index=metricbeat-*, + timefield='@timestamp', + metric='max:system.memory.actual.used.bytes') + .if(gt,11375000000, + .es(index=metricbeat-*, + timefield='@timestamp', + metric='max:system.memory.actual.used.bytes'), + null). + label('severe') + .color('red'), +.es(index=metricbeat-*, + timefield='@timestamp', + metric='max:system.memory.actual.used.bytes') + .mvavg(10) <1> +---------------------------------- + +<1> `mvavg()` calculates the moving average over a specified period of time. In this example, `.mvavg(10)` creates a moving average with a window of 10 data points. + +[role="screenshot"] +image::images/timelion-conditional03.png[] +{nbsp} + +[float] +[[conditional-format-visualization]] +==== Customize and format the visualization + +Customize and format the visualization using functions: + +[source,text] +---------------------------------- +.es(index=metricbeat-*, + timefield='@timestamp', + metric='max:system.memory.actual.used.bytes') + .label('max memory') <1> + .title('Memory consumption over time'), <2> +.es(index=metricbeat-*, + timefield='@timestamp', + metric='max:system.memory.actual.used.bytes') + .if(gt, + 11300000000, + .es(index=metricbeat-*, + timefield='@timestamp', + metric='max:system.memory.actual.used.bytes'), + null) + .label('warning') + .color('#FFCC11') <3> + .lines(width=5), <4> +.es(index=metricbeat-*, + timefield='@timestamp', + metric='max:system.memory.actual.used.bytes') + .if(gt, + 11375000000, + .es(index=metricbeat-*, + timefield='@timestamp', + metric='max:system.memory.actual.used.bytes'), + null) + .label('severe') + .color('red') + .lines(width=5), +.es(index=metricbeat-*, + timefield='@timestamp', + metric='max:system.memory.actual.used.bytes') + .mvavg(10) + .label('mvavg') + .lines(width=2) + .color(#5E5E5E) + .legend(columns=4, position=nw) <5> +---------------------------------- + +<1> `.label()` adds custom labels to the visualization. +<2> `.title()` adds a title with a meaningful name. +<3> `.color()` changes the color of the data. Supported color types include standard color names, hexadecimal values, or a color schema for grouped data. +<4> `.lines()` changes the appearance of the chart lines. In this example, .lines(width=5) sets border width to `5`. +<5> `.legend()` sets the position and style of the legend. For this example, `(columns=4, position=nw)` places the legend in the north west position of the visualization with four columns. + +[role="screenshot"] +image::images/timelion-conditional04.png[] +{nbsp} + +For additional information on Timelion conditional capabilities, go to https://www.elastic.co/blog/timeseries-if-then-else-with-timelion[I have but one .condition()]. \ No newline at end of file diff --git a/docs/user/dashboard/url-drilldown.asciidoc b/docs/user/dashboard/url-drilldown.asciidoc new file mode 100644 index 0000000000000..16f82477756b7 --- /dev/null +++ b/docs/user/dashboard/url-drilldown.asciidoc @@ -0,0 +1,221 @@ +[[url-drilldown]] +=== URL drilldown + +The URL drilldown allows you to navigate from a dashboard to an internal or external URL. +The destination URL can be dynamic, depending on the dashboard context or user’s interaction with a visualization. + +For example, you might have a dashboard that shows data from a Github repository. +You can create a drilldown that navigates from this dashboard to Github. + +[role="screenshot"] +image:images/url_drilldown_go_to_github.gif[Drilldown on pie chart that navigates to Github] + +NOTE: URL drilldown is available with the https://www.elastic.co/subscriptions[Gold subscription] and higher. + +[float] +[[try-it]] +==== Try it: Create a URL drilldown + +This example shows how to create the "Show on Github" drilldown shown above. + +. Add the <> data set. +. Open the *[Logs] Web traffic* dashboard. This isn’t data from Github, but it should work for demonstration purposes. +. In the dashboard menu bar, click *Edit*. +. In *[Logs] Visitors by OS*, open the panel menu, and then select *Create drilldown*. +. Give the drilldown a name: *Show on Github*. +. Select a drilldown action: *Go to URL*. ++ +[role="screenshot"] +image:images/url_drilldown_pick_an_action.png[Action picker] +. Enter a URL template: ++ +[source, bash] +---- +https://github.com/elastic/kibana/issues?q=is:issue+is:open+{{event.value}} +---- ++ +This example URL navigates to {kib} issues on Github. `{{event.value}}` will be substituted with a value associated with a clicked pie slice. In _preview_ `{{event.value}}` is substituted with a <> value. +[role="screenshot"] +image:images/url_drilldown_url_template.png[URL template input] +. Click *Create drilldown*. +. Save the dashboard. ++ +If you don’t save the drilldown, and then navigate away, the drilldown is lost. + +. In *[Logs] Visitors by OS*, click any slice of the pie, and then select the drilldown *Show on Github*. ++ +[role="screenshot"] +image:images/url_drilldown_popup.png[URL drilldown popup] ++ +You are navigated to the issue list in the {kib} repository. Verify that value from a pie slice you’ve clicked on is carried over to Github. ++ +[role="screenshot"] +image:images/url_drilldown_github.png[Github] + +[float] +[[trigger-picker]] +==== Picking a trigger for a URL drilldown + +Some panels support multiple user interactions (called triggers) for which you can configure a URL drilldown. The list of supported variables in the URL template depends on the trigger you selected. +In the preceding example, you configured a URL drilldown on a pie chart. The only trigger that pie chart supports is clicking on a pie slice, so you didn’t have to pick a trigger. + +However, the sample *[Logs] Unique Visitors vs. Average Bytes* chart supports both clicking on a data point and selecting a range. When you create a URL drilldown for this chart, you have the following choices: + +[role="screenshot"] +image:images/url_drilldown_trigger_picker.png[Trigger picker: Single click and Range selection] + +Variables in the URL template differ per trigger. +For example, *Single click* has `{{event.value}}` and *Range selection* has `{{event.from}}` and `{{event.to}}`. +You can create multiple URL drilldowns per panel and attach them to different triggers. + +[float] +[[templating]] +==== URL templating language + +The URL template input uses Handlebars — a simple templating language. Handlebars templates look like regular text with embedded Handlebars expressions. + +[source, bash] +---- +https://github.com/elastic/kibana/issues?q={{event.value}} +---- + +A Handlebars expression is a `{{`, some contents, followed by a `}}`. When the drilldown is executed, these expressions are replaced by values from the dashboard and interaction context. + +Refer to Handlebars https://handlebarsjs.com/guide/expressions.html#expressions[documentation] to learn about advanced use cases. + +[[helpers]] +In addition to https://handlebarsjs.com/guide/builtin-helpers.html[built-in] Handlebars helpers, you can use the following custom helpers: + + +|=== +|Helper |Use case + +|json +a|Serialize variables in JSON format. + +Example: + +`{{json event}}` + +`{{json event.key event.value}}` + +`{{json filters=context.panel.filters}}` + + +|rison +a|Serialize variables in https://github.com/w33ble/rison-node[rison] format. Rison is a common format for {kib} apps for storing state in the URL. + +Example: + +`{{rison event}}` + +`{{rison event.key event.value}}` + +`{{rison filters=context.panel.filters}}` + + +|date +a|Format dates. Supports relative dates expressions (for example, "now-15d"). Refer to the https://momentjs.com/docs/#/displaying/format/[moment] docs for different formatting options. + +Example: + +`{{ date event.from “YYYY MM DD”}}` + +`{{date “now-15”}}` +|=== + + +[float] +[[variables]] +==== URL template variables + +The URL drilldown template has three sources for variables: + +* *Global* static variables that don’t change depending on the place where the URL drilldown is used or which user interaction executed the drilldown. For example: `{{kibanaUrl}}`. +* *Context* variables that change depending on where the drilldown is created and used. These variables are extracted from a context of a panel on a dashboard. For example, `{{context.panel.filters}}` gives access to filters that applied to the current panel. +* *Event* variables that depend on the trigger context. These variables are dynamically extracted from the interaction context when the drilldown is executed. + +[[values-in-preview]] +A subtle but important difference between *context* and *event* variables is that *context* variables use real values in previews when creating a URL drilldown. +For example, `{{context.panel.filters}}` are previewed with the current filters that applied to a panel. +*Event* variables are extracted during drilldown execution from a user interaction with a panel (for example, from a pie slice that the user clicked on). + +Because there is no user interaction with a panel in preview, there is no interaction context to use in a preview. +To work around this, {kib} provides a sample interaction that relies on a picked <>. +So in a preview, you might notice that `{{event.value}}` is replaced with `{{event.value}}` instead of with a sample from your data. +Such previews can help you make sure that the structure of your URL template is valid. +However, to ensure that the configured URL drilldown works as expected with your data, you have to save the dashboard and test in the panel. + +You can access the full list of variables available for the current panel and selected trigger by clicking *Add variable* in the top-right corner of a URL template input. + +[float] +[[variables-reference]] +==== Variables reference + + +|=== +|Source |Variable |Description + +|*Global* +| kibanaUrl +| {kib} base URL. Useful for creating URL drilldowns that navigate within {kib}. + +| *Context* +| context.panel +| Context provided by current dashboard panel. + +| +| context.panel.id +| ID of a panel. + +| +| context.panel.title +| Title of a panel. + +| +| context.panel.filters +| List of {kib} filters applied to a panel. + +Tip: Use in combination with <> helper for +internal {kib} navigations with carrying over current filters. + +| +| context.panel.query.query +| Current query string. + +| +| context.panel.query.lang +| Current query language. + +| +| context.panel.timeRange.from + +context.panel.timeRange.to +| Current time picker values. + +Tip: Use in combination with <> helper to format date. + +| +| context.panel.timeRange.indexPatternId + +context.panel.timeRange.indexPatternIds +|Index pattern ids used by a panel. + +| +| context.panel.savedObjectId +| ID of saved object behind a panel. + +| *Single click* +| event.value +| Value behind clicked data point. + +| +| event.key +| Field name behind clicked data point + +| +| event.negate +| Boolean, indicating whether clicked data point resulted in negative filter. + +| *Range selection* +| event.from + +event.to +| `from` and `to` values of selected range. Depending on your data, could be either a date or number. + +Tip: Consider using <> helper for date formatting. + +| +| event.key +| Aggregation field behind the selected range, if available. + +|=== diff --git a/docs/user/dashboard/vega-reference.asciidoc b/docs/user/dashboard/vega-reference.asciidoc new file mode 100644 index 0000000000000..eed8d9a35b874 --- /dev/null +++ b/docs/user/dashboard/vega-reference.asciidoc @@ -0,0 +1,437 @@ +[[vega-reference]] +== Vega reference + +experimental[] + +For additional *Vega* and *Vega-Lite* information, refer to the reference sections. + +[float] +[[reference-for-kibana-extensions]] +=== Reference for {kib} extensions + +{kib} has extended Vega and Vega-Lite with extensions that support: + +* Default height and width +* Default theme to match {kib} +* Writing {es} queries using the time range and filters from dashboards +* Using the Elastic Map Service in Vega maps +* Additional tooltip styling +* Advanced setting to enable URL loading from any domain +* Limited debugging support using the browser dev tools +* (Vega only) Expression functions which can update the time range and dashboard filters + +[float] +[[vega-sizing-and-positioning]] +==== Default height and width + +By default, Vega visualizations use the `autosize = { type: 'fit', contains: 'padding' }` layout. +`fit` uses all available space, ignores `width` and `height` values, +and respects the padding values. To override this behavior, change the +`autosize` value. + +[float] +[[vega-theme]] +==== Default theme to match {kib} + +{kib} registers a default https://vega.github.io/vega/docs/schemes/[Vega color scheme] +with the id `elastic`, and sets a default color for each `mark` type. +Override it by providing a different `stroke`, `fill`, or `color` (Vega-Lite) value. + +[float] +[[vega-queries]] +==== Writing {es} queries in Vega + +experimental[] {kib} extends the Vega https://vega.github.io/vega/docs/data/[data] elements +with support for direct {es} queries specified as a `url`. + +Because of this, {kib} is **unable to support dynamically loaded data**, +which would otherwise work in Vega. All data is fetched before it's passed to +the Vega renderer. + +To define an {es} query in Vega, set the `url` to an object. {kib} will parse +the object looking for special tokens that allow your query to integrate with {kib}. +These tokens are: + +* `%context%: true`: Set at the top level, and replaces the `query` section with filters from dashboard +* `%timefield%: `: Set at the top level, integrates the query with the dashboard time filter +* `{%timefilter%: true}`: Replaced by an {es} range query with upper and lower bounds +* `{%timefilter%: "min" | "max"}`: Replaced only by the upper or lower bounds +* `{%timefilter: true, shift: -1, unit: 'hour'}`: Generates a time range query one hour in the past +* `{%autointerval%: true}`: Replaced by the string which contains the automatic {kib} time interval, such as `1h` +* `{%autointerval%: 10}`: Replaced by a string which is approximately dividing the time into 10 ranges, allowing + you to influence the automatic interval +* `"%dashboard_context-must_clause%"`: String replaced by object containing filters +* `"%dashboard_context-filter_clause%"`: String replaced by an object containing filters +* `"%dashboard_context-must_not_clause%"`: String replaced by an object containing filters + +Putting this together, an example query that counts the number of documents in +a specific index: + +[source,yaml] +---- +// An object instead of a string for the URL value +// is treated as a context-aware Elasticsearch query. +url: { + // Specify the time filter. + %timefield%: @timestamp + // Apply dashboard context filters when set + %context%: true + + // Which indexes to search + index: kibana_sample_data_logs + // The body element may contain "aggs" and "query" keys + body: { + aggs: { + time_buckets: { + date_histogram: { + // Use date histogram aggregation on @timestamp field + field: @timestamp <1> + // interval value will depend on the time filter + // Use an integer to set approximate bucket count + interval: { %autointerval%: true } + // Make sure we get an entire range, even if it has no data + extended_bounds: { + min: { %timefilter%: "min" } + max: { %timefilter%: "max" } + } + // Use this for linear (e.g. line, area) graphs + // Without it, empty buckets will not show up + min_doc_count: 0 + } + } + } + // Speed up the response by only including aggregation results + size: 0 + } +} +---- + +<1> `@timestamp` — Filters the time range and breaks it into histogram +buckets. + +The full result includes the following structure: + +[source,yaml] +---- +{ + "aggregations": { + "time_buckets": { + "buckets": [{ + "key_as_string": "2015-11-30T22:00:00.000Z", + "key": 1448920800000,<1> + "doc_count": 28 + }, { + "key_as_string": "2015-11-30T23:00:00.000Z", + "key": 1448924400000, <1> + "doc_count": 330 + }, ... +---- + +<1> `"key"` — The unix timestamp you can use without conversions by the +Vega date expressions. + +For most visualizations, you only need the list of bucket values. To focus on +only the data you need, use `format: {property: "aggregations.time_buckets.buckets"}`. + +Specify a query with individual range and dashboard context. The query is +equivalent to `"%context%": true, "%timefield%": "@timestamp"`, +except that the time range is shifted back by 10 minutes: + +[source,yaml] +---- +{ + body: { + query: { + bool: { + must: [ + // This string will be replaced + // with the auto-generated "MUST" clause + "%dashboard_context-must_clause%" + { + range: { + // apply timefilter (upper right corner) + // to the @timestamp variable + @timestamp: { + // "%timefilter%" will be replaced with + // the current values of the time filter + // (from the upper right corner) + "%timefilter%": true + // Only work with %timefilter% + // Shift current timefilter by 10 units back + shift: 10 + // week, day (default), hour, minute, second + unit: minute + } + } + } + ] + must_not: [ + // This string will be replaced with + // the auto-generated "MUST-NOT" clause + "%dashboard_context-must_not_clause%" + ] + filter: [ + // This string will be replaced + // with the auto-generated "FILTER" clause + "%dashboard_context-filter_clause%" + ] + } + } + } +} +---- + +NOTE: When using `"%context%": true` or defining a value for `"%timefield%"` the body cannot contain a query. To customize the query within the VEGA specification (e.g. add an additional filter, or shift the timefilter), define your query and use the placeholders as in the example above. The placeholders will be replaced by the actual context of the dashboard or visualization once parsed. + +The `"%timefilter%"` can also be used to specify a single min or max +value. The date_histogram's `extended_bounds` can be set +with two values - min and max. Instead of hardcoding a value, you may +use `"min": {"%timefilter%": "min"}`, which will be replaced with the +beginning of the current time range. The `shift` and `unit` values are +also supported. The `"interval"` can also be set dynamically, depending +on the currently picked range: `"interval": {"%autointerval%": 10}` will +try to get about 10-15 data points (buckets). + +[float] +[[vega-esmfiles]] +=== Access Elastic Map Service files + +experimental[] Access the Elastic Map Service files via the same mechanism: + +[source,yaml] +---- +url: { + // "type" defaults to "elasticsearch" otherwise + type: emsfile + // Name of the file, exactly as in the Region map visualization + name: World Countries +} +// The result is a geojson file, get its features to use +// this data source with the "shape" marks +// https://vega.github.io/vega/docs/marks/shape/ +format: {property: "features"} +---- + +To enable Maps, the graph must specify `type=map` in the host +configuration: + +[source,yaml] +---- +{ + "config": { + "kibana": { + "type": "map", + + // Initial map position + "latitude": 40.7, // default 0 + "longitude": -74, // default 0 + "zoom": 7, // default 2 + + // defaults to "default". Use false to disable base layer. + "mapStyle": false, + + // default 0 + "minZoom": 5, + + // defaults to the maximum for the given style, + // or 25 when base is disabled + "maxZoom": 13, + + // defaults to true, shows +/- buttons to zoom in/out + "zoomControl": false, + + // Defaults to 'false', disables mouse wheel zoom. If set to + // 'true', map may zoom unexpectedly while scrolling dashboard + "scrollWheelZoom": false, + + // When false, repaints on each move frame. + // Makes the graph slower when moving the map + "delayRepaint": true, // default true + } + }, + /* the rest of Vega JSON */ +} +---- + +The visualization automatically injects a `"projection"`, which you can use to +calculate the position of all geo-aware marks. +Additionally, you can use `latitude`, `longitude`, and `zoom` signals. +These signals can be used in the graph, or can be updated to modify the +position of the map. + +[float] +[[vega-tooltip]] +==== Additional tooltip styling + +{kib} has installed the https://vega.github.io/vega-lite/docs/tooltip.html[Vega tooltip plugin], +so tooltips can be defined in the ways documented there. Beyond that, {kib} also supports +a configuration option for changing the tooltip position and padding: + +```js +{ + config: { + kibana: { + tooltips: { + position: 'top', + padding: 15 + } + } + } +} +``` + +[float] +[[vega-url-loading]] +==== Advanced setting to enable URL loading from any domain + +Vega can load data from any URL, but this is disabled by default in {kib}. +To change this, set `vis_type_vega.enableExternalUrls: true` in `kibana.yml`, +then restart {kib}. + +[float] +[[vega-inspector]] +==== Vega Inspector +Use the contextual *Inspect* tool to gain insights into different elements. +For Vega visualizations, there are two different views: *Request* and *Vega debug*. + +[float] +[[inspect-elasticsearch-requests]] +===== Inspect {es} requests + +Vega uses the {ref}/search-search.html[{es} search API] to get documents and aggregation +results from {es}. To troubleshoot these requests, click *Inspect*, which shows the most recent requests. +In case your specification has more than one request, you can switch between the views using the *View* dropdown. + +[role="screenshot"] +image::visualize/images/vega_tutorial_inspect_requests.png[] + +[float] +[[vega-debugging]] +===== Vega debugging + +With the *Vega debug* view, you can inspect the *Data sets* and *Signal Values* runtime data. + +The runtime data is read from the +https://vega.github.io/vega/docs/api/debugging/#scope[runtime scope]. + +[role="screenshot"] +image::visualize/images/vega_tutorial_inspect_data_sets.png[] + +To debug more complex specs, access to the `view` variable. For more information, refer to +the <>. + +[float] +[[asking-for-help-with-a-vega-spec]] +===== Asking for help with a Vega spec + +Because of the dynamic nature of the data in {es}, it is hard to help you with +Vega specs unless you can share a dataset. To do this, click *Inspect*, select the *Vega debug* view, +then select the *Spec* tab: + +[role="screenshot"] +image::visualize/images/vega_tutorial_getting_help.png[] + +To copy the response, click *Copy to clipboard*. Paste the copied data to +https://gist.github.com/[gist.github.com], possibly with a .json extension. Use the [raw] button, +and share that when asking for help. + +[float] +[[vega-browser-debugging-console]] +==== Browser debugging console + +experimental[] Use browser debugging tools (for example, F12 or Ctrl+Shift+J in Chrome) to +inspect the `VEGA_DEBUG` variable: + +* `view` — Access to the Vega View object. See https://vega.github.io/vega/docs/api/debugging/[Vega Debugging Guide] +on how to inspect data and signals at runtime. For Vega-Lite, +`VEGA_DEBUG.view.data('source_0')` gets the pre-transformed data, and `VEGA_DEBUG.view.data('data_0')` +gets the encoded data. For Vega, it uses the data name as defined in your Vega spec. + +* `vega_spec` — Vega JSON graph specification after some modifications by {kib}. In case +of Vega-Lite, this is the output of the Vega-Lite compiler. + +* `vegalite_spec` — If this is a Vega-Lite graph, JSON specification of the graph before +Vega-Lite compilation. + +[float] +[[vega-expression-functions]] +==== (Vega only) Expression functions which can update the time range and dashboard filters + +{kib} has extended the Vega expression language with these functions: + +```js +/** + * @param {object} query Elastic Query DSL snippet, as used in the query DSL editor + * @param {string} [index] as defined in Kibana, or default if missing + */ +kibanaAddFilter(query, index) + +/** + * @param {object} query Elastic Query DSL snippet, as used in the query DSL editor + * @param {string} [index] as defined in Kibana, or default if missing + */ +kibanaRemoveFilter(query, index) + +kibanaRemoveAllFilters() + +/** + * Update dashboard time filter to the new values + * @param {number|string|Date} start + * @param {number|string|Date} end + */ +kibanaSetTimeFilter(start, end) +``` + +[float] +[[vega-additional-configuration-options]] +==== Additional configuration options + +[source,yaml] +---- +{ + config: { + kibana: { + // Placement of the Vega-defined signal bindings. + // Can be `left`, `right`, `top`, or `bottom` (default). + controlsLocation: top + // Can be `vertical` or `horizontal` (default). + controlsDirection: vertical + // If true, hides most of Vega and Vega-Lite warnings + hideWarnings: true + // Vega renderer to use: `svg` or `canvas` (default) + renderer: canvas + } + } +} +---- + +[[vega-notes]] +[[resources-and-examples]] +=== Resources and examples + +experimental[] To learn more about Vega and Vega-Lite, refer to the resources and examples. + +[float] +[[vega-editor]] +==== Vega editor +The https://vega.github.io/editor/[Vega Editor] includes examples for Vega & Vega-Lite, but does not support any +{kib}-specific features like {es} requests and interactive base maps. + +[float] +[[vega-lite-resources]] +==== Vega-Lite resources +* https://vega.github.io/vega-lite/tutorials/getting_started.html[Tutorials] +* https://vega.github.io/vega-lite/docs/[Docs] +* https://vega.github.io/vega-lite/examples/[Examples] + +[float] +[[vega-resources]] +==== Vega resources +* https://vega.github.io/vega/tutorials/[Tutorials] +* https://vega.github.io/vega/docs/[Docs] +* https://vega.github.io/vega/examples/[Examples] + +TIP: When you use the examples in {kib}, you may +need to modify the "data" section to use absolute URL. For example, +replace `"url": "data/world-110m.json"` with +`"url": "https://vega.github.io/editor/data/world-110m.json"`. diff --git a/docs/user/getting-started.asciidoc b/docs/user/getting-started.asciidoc index 2ff3a09152df4..a877f6a66a79a 100644 --- a/docs/user/getting-started.asciidoc +++ b/docs/user/getting-started.asciidoc @@ -1,19 +1,19 @@ -[[getting-started]] +[[get-started]] = Get started [partintro] -- -Ready to try out {kib} and see what it can do? To quickest way to get started with {kib} is to set up on Cloud, then add a sample data set that helps you get a handle on the full range of {kib} features. +Ready to try out {kib} and see what it can do? The quickest way to get started with {kib} is to set up on Cloud, then add a sample data set to explore the full range of {kib} features. [float] -[[cloud-set-up]] +[[set-up-on-cloud]] == Set up on cloud include::{docs-root}/shared/cloud/ess-getting-started.asciidoc[] [float] -[[get-data-in]] +[[gs-get-data-into-kibana]] == Get data into {kib} The easiest way to get data into {kib} is to add a sample data set. @@ -42,12 +42,11 @@ NOTE: The timestamps in the sample data sets are relative to when they are insta If you uninstall and reinstall a data set, the timestamps change to reflect the most recent installation. [float] -[[getting-started-next-steps]] == Next steps -* To get a hands-on experience creating visualizations, follow the <> tutorial. +* To get a hands-on experience creating visualizations, follow the <> tutorial. -* If you're ready to load an actual data set and build a dashboard, follow the <> tutorial. +* If you're ready to load an actual data set and build a dashboard, follow the <> tutorial. -- @@ -60,5 +59,3 @@ include::{kib-repo-dir}/getting-started/tutorial-define-index.asciidoc[] include::{kib-repo-dir}/getting-started/tutorial-discovering.asciidoc[] include::{kib-repo-dir}/getting-started/tutorial-visualizing.asciidoc[] - -include::{kib-repo-dir}/getting-started/tutorial-dashboard.asciidoc[] diff --git a/docs/user/index.asciidoc b/docs/user/index.asciidoc index abbdbeb68d9cb..e909626c5779c 100644 --- a/docs/user/index.asciidoc +++ b/docs/user/index.asciidoc @@ -2,8 +2,6 @@ include::introduction.asciidoc[] include::whats-new.asciidoc[] -include::getting-started.asciidoc[] - include::setup.asciidoc[] include::monitoring/configuring-monitoring.asciidoc[leveloffset=+1] @@ -13,9 +11,11 @@ include::monitoring/monitoring-kibana.asciidoc[leveloffset=+2] include::security/securing-kibana.asciidoc[] +include::getting-started.asciidoc[] + include::discover.asciidoc[] -include::dashboard.asciidoc[] +include::dashboard/dashboard.asciidoc[] include::canvas.asciidoc[] @@ -25,8 +25,6 @@ include::ml/index.asciidoc[] include::graph/index.asciidoc[] -include::visualize.asciidoc[] - include::{kib-repo-dir}/observability/index.asciidoc[] include::{kib-repo-dir}/logs/index.asciidoc[] diff --git a/docs/user/introduction.asciidoc b/docs/user/introduction.asciidoc index ff936fb4d5569..079d183dd959d 100644 --- a/docs/user/introduction.asciidoc +++ b/docs/user/introduction.asciidoc @@ -83,12 +83,6 @@ image::images/intro-dashboard.png[] {kib} also offers these visualization features: -* <> allows you to display your data in -charts, graphs, and tables -(just to name a few). It's also home to Lens. -Visualize supports the ability to add interactive -controls to your dashboard, filter dashboard content in real time, and add your own images and logos for your brand. - * <> gives you the ability to present your data in a visually compelling, pixel-perfect report. Give your data the “wow” factor needed to impress your CEO or to captivate coworkers with a big-screen display. @@ -98,7 +92,7 @@ questions of your location-based data. Maps supports multiple layers and data sources, mapping of individual geo points and shapes, and dynamic client-side styling. -* <> allows you to combine +* <> allows you to combine an infinite number of aggregations to display complex data. With TSVB, you can analyze multiple index patterns and customize every aspect of your visualization. Choose your own date format and color @@ -161,6 +155,6 @@ and start exploring data in minutes. You can also <> — no code, no additional infrastructure required. -Our <> and in-product guidance can +Our <> and in-product guidance can help you get up and running, faster. Click the help icon image:images/intro-help-icon.png[] in the top navigation bar for help with questions or to provide feedback. diff --git a/docs/user/reporting/automating-report-generation.asciidoc b/docs/user/reporting/automating-report-generation.asciidoc index 3e227229ddcc5..371855deb2f3c 100644 --- a/docs/user/reporting/automating-report-generation.asciidoc +++ b/docs/user/reporting/automating-report-generation.asciidoc @@ -13,7 +13,7 @@ URL that triggers a report to generate. To create the POST URL for PDF reports: -. Go to *Visualize* or *Dashboard*, then open the visualization or dashboard. +. Go to *Dashboard*, then open the visualization or dashboard. + To specify a relative or absolute time period, use the time filter. diff --git a/docs/user/reporting/index.asciidoc b/docs/user/reporting/index.asciidoc index 4f4d59315fafa..50ae92382fb24 100644 --- a/docs/user/reporting/index.asciidoc +++ b/docs/user/reporting/index.asciidoc @@ -11,7 +11,7 @@ saved search, or Canvas workpad. Depending on the object type, you can export th a PDF, PNG, or CSV document, which you can keep for yourself, or share with others. Reporting is available from the *Share* menu -in *Discover*, *Visualize*, *Dashboard*, and *Canvas*. +in *Discover*, *Dashboard*, and *Canvas*. [role="screenshot"] image::user/reporting/images/share-button.png["Share"] diff --git a/docs/user/reporting/reporting-troubleshooting.asciidoc b/docs/user/reporting/reporting-troubleshooting.asciidoc index dc4ffdfebdae9..82f0aa7ca0f19 100644 --- a/docs/user/reporting/reporting-troubleshooting.asciidoc +++ b/docs/user/reporting/reporting-troubleshooting.asciidoc @@ -7,6 +7,7 @@ Having trouble? Here are solutions to common problems you might encounter while using Reporting. +* <> * <> * <> * <> @@ -15,6 +16,11 @@ Having trouble? Here are solutions to common problems you might encounter while * <> * <> +[float] +[[reporting-diagnostics]] +=== Reporting Diagnostics +Reporting comes with a built-in utility to try to automatically find common issues. When Kibana is running, navigate to the Report Listing page, and click the "Run reporting diagnostics..." button. This will open up a diagnostic tool that checks various parts of the Kibana deployment to come up with any relevant recommendations. + [float] [[reporting-troubleshooting-system-dependencies]] === System dependencies diff --git a/docs/user/security/rbac_tutorial.asciidoc b/docs/user/security/rbac_tutorial.asciidoc index 3a4b2202201e2..cc4af9041bcd9 100644 --- a/docs/user/security/rbac_tutorial.asciidoc +++ b/docs/user/security/rbac_tutorial.asciidoc @@ -28,7 +28,7 @@ To complete this tutorial, you'll need the following: * **A space**: In this tutorial, use `Dev Mortgage` as the space name. See <> for details on creating a space. -* **Data**: You can use <> or +* **Data**: You can use <> or live data. In the following steps, Filebeat and Metricbeat data are used. [float] diff --git a/docs/user/security/reporting.asciidoc b/docs/user/security/reporting.asciidoc index 4e02759ce99cb..daf9720a0f1d8 100644 --- a/docs/user/security/reporting.asciidoc +++ b/docs/user/security/reporting.asciidoc @@ -47,7 +47,7 @@ image::user/security/images/reporting-privileges-example.png["Reporting privileg Reporting users typically save searches, create visualizations, and build dashboards. They require a space that provides read and write privileges in -*Discover*, *Visualize*, and *Dashboard*. +*Discover* and *Dashboard*. . Save your new role. diff --git a/docs/user/visualize.asciidoc b/docs/user/visualize.asciidoc deleted file mode 100644 index dc116962f9e96..0000000000000 --- a/docs/user/visualize.asciidoc +++ /dev/null @@ -1,142 +0,0 @@ -[[visualize]] -= Visualize - -[partintro] --- -_Visualize_ enables you to create visualizations of the data from your {es} indices, which you can then add to dashboards for analysis. - -{kib} visualizations are based on {es} queries. By using a series of {es} {ref}/search-aggregations.html[aggregations] to extract and process your data, you can create charts that show you the trends, spikes, and dips you need to know about. - -To begin, open the menu, go to *Visualize*, then click *Create visualization*. - -[float] -[[visualization-types]] -== Types of visualizations - -{kib} supports several types of visualizations. - -<>:: -Quickly build several types of basic visualizations by simply dragging and dropping the data fields you want to display. - -<>:: - -* *Line, area, and bar charts* — Compares different series in X/Y charts. - -* *Pie chart* — Displays each source contribution to a total. - -* *Data table* — Flattens aggregations into table format. - -* *Metric* — Displays a single number. - -* *Goal and gauge* — Displays a number with progress indicators. - -* *Tag cloud* — Displays words in a cloud, where the size of the word corresponds to its importance. - -<>:: Visualizes time series data using pipeline aggregations. - -<>:: Computes and combine data from multiple time series -data sets. - -<>:: -* *<>* — Displays geospatial data in {kib}. - -* <>:: Display shaded cells within a matrix. - -<>:: - -* *Markdown widget* — Displays free-form information or instructions. - -* *Controls* — Adds interactive inputs to a dashboard. - -<>:: Completes control over query and display. - -[float] -[[choose-your-data]] -== Choose your data - -Specify a search query to retrieve the data for your visualization, or used rolled up data. - -* To enter new search criteria, select the <> for the indices that -contain the data you want to visualize. The visualization builder opens -with a wildcard query that matches all of the documents in the selected -indices. - -* To build a visualization from a saved search, click the name of the saved -search you want to use. The visualization builder opens and loads the -selected query. -+ -NOTE: When you build a visualization from a saved search, any subsequent -modifications to the saved search are reflected in the -visualization. To disable automatic updates, delete the visualization -on the *Saved Object* page. - -* To build a visualization using <>, select -the index pattern that includes the data. Rolled up data is summarized into -time buckets that can be split into sub buckets for numeric field values or -terms. To lower granularity, use a time aggregation that uses and combines -several time buckets. For an example, refer to <>. - -[float] -[[vis-inspector]] -== Inspect visualizations - -Many visualizations allow you to inspect the query and data behind the visualization. - -. In the {kib} toolbar, click *Inspect*. -. To download the data, click *Download CSV*, then choose one of the following options: -* *Formatted CSV* - Downloads the data in table format. -* *Raw CSV* - Downloads the data as provided. -. To view the requests for collecting data, select *Requests* from the *View* -dropdown. - -[float] -[[save-visualize]] -== Save visualizations -To use your visualizations in <>, you must save them. - -. In the {kib} toolbar, click *Save*. -. Enter the visualization *Title* and optional *Description*, then *Save* the visualization. - -To access the saved visualization, go to *Management > {kib} > Saved Objects*. - -[float] -[[save-visualization-read-only-access]] -==== Read only access -When you have insufficient privileges to save visualizations, the following indicator is -displayed and the *Save* button is not visible. - -For more information, refer to <>. - -[role="screenshot"] -image::visualize/images/read-only-badge.png[Example of Visualize's read only access indicator in Kibana's header] - -[float] -[[visualize-share-options]] -== Share visualizations - -When you've finished your visualization, you can share it outside of {kib}. - -From the *Share* menu, you can: - -* Embed the code in a web page. Users must have {kib} access -to view an embedded visualization. -* Share a direct link to a {kib} visualization. -* Generate a PDF report. -* Generate a PNG report. - --- -include::{kib-repo-dir}/visualize/aggregations.asciidoc[] - -include::{kib-repo-dir}/visualize/lens.asciidoc[] - -include::{kib-repo-dir}/visualize/most-frequent.asciidoc[] - -include::{kib-repo-dir}/visualize/tsvb.asciidoc[] - -include::{kib-repo-dir}/visualize/timelion.asciidoc[] - -include::{kib-repo-dir}/visualize/tilemap.asciidoc[] - -include::{kib-repo-dir}/visualize/for-dashboard.asciidoc[] - -include::{kib-repo-dir}/visualize/vega.asciidoc[] diff --git a/docs/visualize/aggregations.asciidoc b/docs/visualize/aggregations.asciidoc deleted file mode 100644 index ef38f716f2303..0000000000000 --- a/docs/visualize/aggregations.asciidoc +++ /dev/null @@ -1,110 +0,0 @@ -[[supported-aggregations]] -== Supported aggregations - -Use the supported aggregations to build your visualizations. - -[float] -[[visualize-metric-aggregations]] -=== Metric aggregations - -Metric aggregations extract field from documents to generate data values. - -{ref}/search-aggregations-metrics-avg-aggregation.html[Average]:: The mean value. - -{ref}/search-aggregations-metrics-valuecount-aggregation.html[Count]:: The total number of documents that match the query, which allows you to visualize the number of documents in a bucket. Count is the default value. - -{ref}/search-aggregations-metrics-max-aggregation.html[Max]:: The highest value. - -{ref}/search-aggregations-metrics-percentile-aggregation.html[Median]:: The value that is in the 50% percentile. - -{ref}/search-aggregations-metrics-min-aggregation.html[Min]:: The lowest value. - -{ref}/search-aggregations-metrics-percentile-rank-aggregation.html[Percentile ranks]:: Returns the percentile rankings for the values in the specified numeric field. Select a numeric field from the drop-down, then specify one or more percentile rank values in the *Values* fields. - -{ref}/search-aggregations-metrics-percentile-aggregation.html[Percentiles]:: Divides the -values in a numeric field into specified percentile bands. Select a field from the drop-down, then specify one or more ranges in the *Percentiles* fields. - -Standard Deviation:: Requires a numeric field. Uses the {ref}/search-aggregations-metrics-extendedstats-aggregation.html[_extended stats_] aggregation. - -{ref}/search-aggregations-metrics-sum-aggregation.html[Sum]:: The total value. - -{ref}/search-aggregations-metrics-top-hits-aggregation.html[Top hit]:: Returns a sample of individual documents. When the Top Hit aggregation is matched to more than one document, you must choose a technique for combining the values. Techniques include average, minimum, maximum, and sum. - -Unique Count:: The {ref}/search-aggregations-metrics-cardinality-aggregation.html[Cardinality] of the field within the bucket. - -Alternatively, you can override the field values with a script using JSON input. For example: - -[source,shell] -{ "script" : "doc['grade'].value * 1.2" } - -The example implements a {es} {ref}/search-aggregations.html[Script Value Source], which replaces -the value in the metric. The options available depend on the aggregation you choose. - -[float] -[[visualize-parent-pipeline-aggregations]] -=== Parent pipeline aggregations - -Parent pipeline aggregations assume the bucket aggregations are ordered and are especially useful for time series data. For each parent pipeline aggregation, you must define a bucket aggregation and metric aggregation. - -You can also nest these aggregations. For example, if you want to produce a third derivative. - -{ref}/search-aggregations-pipeline-bucket-script-aggregation.html[Bucket script]:: Executes a script that performs computations for each bucket that specifies metrics in the parent multi-bucket aggregation. - -{ref}/search-aggregations-pipeline-cumulative-sum-aggregation.html[Cumulative sum]:: Calculates the cumulative sum of a specified metric in a parent histogram. - -{ref}/search-aggregations-pipeline-derivative-aggregation.html[Derivative]:: Calculates the derivative of specific metrics. - -{ref}/search-aggregations-pipeline-movavg-aggregation.html[Moving avg]:: Slides a window across the data and emits the average value of the window. - -{ref}/search-aggregations-pipeline-serialdiff-aggregation.html[Serial diff]:: Values in a time series are subtracted from itself at different time lags or periods. - -[float] -[[visualize-sibling-pipeline-aggregations]] -=== Sibling pipeline aggregations - -Sibling pipeline aggregations condense many buckets into one. For each sibling pipeline aggregation, you must define a bucket aggregations and metric aggregation. - -{ref}/search-aggregations-pipeline-avg-bucket-aggregation.html[Average bucket]:: Calculates the mean, or average, value of a specified metric in a sibling aggregation. - -{ref}/search-aggregations-pipeline-avg-bucket-aggregation.html[Max Bucket]:: Calculates the maximum value of a specified metric in a sibling aggregation. - -{ref}/search-aggregations-pipeline-avg-bucket-aggregation.html[Min Bucket]:: Calculates the minimum value of a specified metric in a sibling aggregation. - -{ref}/search-aggregations-pipeline-avg-bucket-aggregation.html[Sum Bucket]:: Calculates the sum of the values of a specified metric in a sibling aggregation. - -[float] -[[visualize-bucket-aggregations]] -=== Bucket aggregations - -Bucket aggregations sort documents into buckets, depending on the contents of the document. - -{ref}/search-aggregations-bucket-datehistogram-aggregation.html[Date histogram]:: Splits a date field into buckets by interval. If the date field is the primary time field for the index pattern, it chooses an automatic interval for you. Intervals are labeled at the start of the interval, using the date-key returned by {es}. For example, the tooltip for a monthly interval displays the first day of the month. - -{ref}/search-aggregations-bucket-daterange-aggregation.html[Date range]:: Reports values that are within a range of dates that you specify. You can specify the ranges for the dates using {ref}/common-options.html#date-math[_date math_] expressions. - -{ref}/search-aggregations-bucket-filter-aggregation.html[Filter]:: Each filter creates a bucket of documents. You can specify a filter as a -<> or <> query string. - -{ref}/search-aggregations-bucket-geohashgrid-aggregation.html[Geohash]:: Displays points based on a geohash. Supported by data table visualizations and <>. - -{ref}/search-aggregations-bucket-geotilegrid-aggregation.html[Geotile]:: Groups points based on web map tiling. Supported by data table visualizations and <>. - -{ref}/search-aggregations-bucket-histogram-aggregation.html[Histogram]:: Builds from a numeric field. - -{ref}/search-aggregations-bucket-iprange-aggregation.html[IPv4 range]:: Specify ranges of IPv4 addresses. - -{ref}/search-aggregations-bucket-range-aggregation.html[Range]:: Specify ranges of values for a numeric field. - -{ref}/search-aggregations-bucket-significantterms-aggregation.html[Significant terms]:: Returns interesting or unusual occurrences of terms in a set. Supports {es} {ref}/search-aggregations-bucket-terms-aggregation.html#_filtering_values_4[exclude and include patterns]. - -{ref}/search-aggregations-bucket-terms-aggregation.html[Terms]:: Specify the top or bottom _n_ elements of a given field to display, ordered by count or a custom metric. Supports {es} {ref}/search-aggregations-bucket-terms-aggregation.html#_filtering_values_4[exclude and include patterns]. - -{kib} filters string fields with only regular expression patterns, and does not filter numeric fields or match with arrays. - -For example: - -* You want to exclude the metricbeat process from your visualization of top processes: `metricbeat.*` -* You only want to show processes collecting beats: `.*beat` -* You want to exclude two specific values, the string `"empty"` and `"none"`: `empty|none` - -Patterns are case sensitive. diff --git a/docs/visualize/for-dashboard.asciidoc b/docs/visualize/for-dashboard.asciidoc deleted file mode 100644 index 400179e9ceae7..0000000000000 --- a/docs/visualize/for-dashboard.asciidoc +++ /dev/null @@ -1,67 +0,0 @@ -[[for-dashboard]] -== Dashboard tools - -Visualize comes with controls and Markdown tools that you can add to dashboards for an interactive experience. - -[float] -[[controls]] -=== Controls -experimental[] - -The controls tool enables you to add interactive inputs -on a dashboard. - -You can add two types of interactive inputs: - -* *Options list* — Filters content based on one or more specified options. The dropdown menu is dynamically populated with the results of a terms aggregation. For example, use the options list on the sample flight dashboard when you want to filter the data by origin city and destination city. - -* *Range slider* — Filters data within a specified range of numbers. The minimum and maximum values are dynamically populated with the results of a min and max aggregation. For example, use the range slider when you want to filter the sample flight dashboard by a specific average ticket price. - -[role="screenshot"] -image::images/dashboard-controls.png[] - -[float] -[[controls-options]] -==== Controls options - -Configure the settings that apply to the interactive inputs on a dashboard. - -. Click *Options*, then configure the following: - -* *Update {kib} filters on each change* — When selected, all interactive inputs create filters that refresh the dashboard. When unselected, {kib} filters are created only when you click *Apply changes*. - -* *Use time filter* — When selected, the aggregations that generate the options list and time range are connected to the <>. - -* *Pin filters to global state* — When selected, all filters created by interacting with the inputs are automatically pinned. - -. Click *Update*. - -[float] -[[markdown-widget]] -=== Markdown - -The Markdown tool is a text entry field that accepts GitHub-flavored Markdown text. When you enter the text, the tool populates the results on the dashboard. - -Markdown is helpful when you want to include important information, instructions, and images on your dashboard. - -For information about GitHub-flavored Markdown text, click *Help*. - -For example, when you enter: - -[role="screenshot"] -image::images/markdown_example_1.png[] - -The following instructions are displayed: - -[role="screenshot"] -image::images/markdown_example_2.png[] - -Or when you enter: - -[role="screenshot"] -image::images/markdown_example_3.png[] - -The following image is displayed: - -[role="screenshot"] -image::images/markdown_example_4.png[] diff --git a/docs/visualize/images/lens_drag_drop.gif b/docs/visualize/images/lens_drag_drop.gif index ca62115e7ea3a..1f8580d462702 100644 Binary files a/docs/visualize/images/lens_drag_drop.gif and b/docs/visualize/images/lens_drag_drop.gif differ diff --git a/docs/visualize/lens.asciidoc b/docs/visualize/lens.asciidoc deleted file mode 100644 index 6e51433bca3f6..0000000000000 --- a/docs/visualize/lens.asciidoc +++ /dev/null @@ -1,173 +0,0 @@ -[role="xpack"] -[[lens]] -== Lens - -beta[] - -*Lens* is a simple and fast way to create visualizations of your {es} data. To create visualizations, -you drag and drop your data fields onto the visualization builder pane, and *Lens* automatically generates -a visualization that best displays your data. - -With Lens, you can: - -* Use the automatically generated visualization suggestions to change the visualization type. - -* Create visualizations with multiple layers and indices. - -* Add your visualizations to dashboards and Canvas workpads. - -To get started with *Lens*, select a field in the data panel, then drag and drop the field on a highlighted area. - -[role="screenshot"] -image::images/lens_drag_drop.gif[Drag and drop] - -You can incorporate many fields into your visualization, and Lens uses heuristics to decide how to apply each one to the visualization. - -TIP: Drag-and-drop capabilities are available only when Lens knows how to use the data. If *Lens* is unable to automatically generate a visualization, -you can still configure the customization options for your visualization. - -[float] -[[apply-lens-filters]] -==== Change the data panel fields - -The fields in the data panel are based on the selected <> and <>. - -To change the index pattern, click it, then select a new one. The fields in the data panel automatically update. - -To filter the fields in the data panel: - -* Enter the name in *Search field names*. - -* Click *Filter by type*, then select the filter. To show all of the fields in the index pattern, deselect *Only show fields with data*. - -[float] -[[view-data-summaries]] -==== Data summaries - -To help you decide exactly the data you want to display, get a quick summary of each field. The summary shows the distribution of values within the selected time range. - -To view the field summary information, navigate to the field, then click *i*. - -[role="screenshot"] -image::images/lens_data_info.png[Data summary window] - -[float] -[[change-the-visualization-type]] -==== Change the visualization type - -*Lens* enables you to switch between any supported visualization type at any time. - -*Suggestions* are shortcuts to alternate visualizations that *Lens* generates for you. - -[role="screenshot"] -image::images/lens_suggestions.gif[Visualization suggestions] - -If you'd like to use a visualization type that is not suggested, click the visualization type, -then select a new one. - -[role="screenshot"] -image::images/lens_viz_types.png[] - -When there is an exclamation point (!) -next to a visualization type, Lens is unable to transfer your data, but -still allows you to make the change. - -[float] -[[customize-operation]] -==== Change the aggregation and labels - -For each visualization, Lens allows some customizations of the data. - -. Click *Drop a field here* or the field name in the column. - -. Change the options that appear. Options vary depending on the type of field. -+ -[role="screenshot"] -image::images/lens_aggregation_labels.png[Quick function options] - -[float] -[[layers]] -==== Add layers and indices - -Area, line, and bar charts allow you to visualize multiple data layers and indices so that you can compare and analyze data from multiple sources. - -To add a layer, click *+*, then drag and drop the fields for the new layer. - -[role="screenshot"] -image::images/lens_layers.png[Add layers] - -To view a different index, click it, then select a new one. - -[role="screenshot"] -image::images/lens_index_pattern.png[Add index pattern] - -[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. - -. Select the *kibana_sample_data_ecommerce* index pattern. - -. Click image:images/time-filter-calendar.png[], then click *Last 7 days*. -+ -The fields in the data panel update. - -. Drag and drop the *taxful_total_price* data field to the visualization builder pane. -+ -[role="screenshot"] -image::images/lens_tutorial_1.png[Lens tutorial] - -To display the average order prices over time, *Lens* automatically added in *order_date* field. - -To break down your data, drag the *category.keyword* field to the visualization builder pane. Lens -knows 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 three categories: - -[role="screenshot"] -image::images/lens_tutorial_2.png[Lens tutorial] - -[float] -[[customize-lens-visualization]] -==== Customize your visualization - -Make your visualization look exactly how you want with the customization options. - -. Click *Average of taxful_total_price*, then change the *Label* to `Sales`. -+ -[role="screenshot"] -image::images/lens_tutorial_3.1.png[Lens tutorial] - -. Click *Top values of category.keyword*, then change *Number of values* to `10`. -+ -[role="screenshot"] -image::images/lens_tutorial_3.2.png[Lens tutorial] -+ -The visualization updates to show there are only six available categories. -+ -Look at the *Suggestions*. An area chart is not an option, but for the sales data, a stacked area chart might be the best option. - -. To switch the chart type, click *Stacked bar chart* in the column, then click *Stacked area* from the *Select a visualizations* window. -+ -[role="screenshot"] -image::images/lens_tutorial_3.png[Lens tutorial] - -[float] -[[lens-tutorial-next-steps]] -==== Next steps - -Now that you've created your visualization, you can add it to a dashboard or Canvas workpad. - -For more information, refer to <> or <>. diff --git a/docs/visualize/most-frequent.asciidoc b/docs/visualize/most-frequent.asciidoc deleted file mode 100644 index f716930e7e65c..0000000000000 --- a/docs/visualize/most-frequent.asciidoc +++ /dev/null @@ -1,59 +0,0 @@ -[[most-frequent]] -== Most frequently used visualizations - -The most frequently used visualizations allow you to plot aggregated data from a <> or <>. - -The most frequently used visualizations include: - -* Line, area, and bar charts -* Pie chart -* Data table -* Metric, goal, and gauge -* Tag cloud - -[[metric-chart]] - -[float] -=== Configure your visualization - -You configure visualizations using the default editor. Each visualization supports different configurations of the metrics and buckets. - -For example, a bar chart allows you to add an x-axis: - -[role="screenshot"] -image::images/add-bucket.png["",height=478] - -A common configuration for the x-axis is to use a {es} {ref}/search-aggregations-bucket-datehistogram-aggregation.html[date histogram] aggregation: - -[role="screenshot"] -image::images/visualize-date-histogram.png[] - -To see your changes, click *Apply changes* image:images/apply-changes-button.png[] - -If it's supported by the visualization, you can add more buckets. In this example we have -added a -{es} {ref}/search-aggregations-bucket-terms-aggregation.html[terms] aggregation on the field -`geo.src` to show the top 5 sources of log traffic. - -[role="screenshot"] -image::images/visualize-date-histogram-split-1.png[] - -The new aggregation is added after the first one, so the result shows -the top 5 sources of traffic per 3 hours. If you want to change the aggregation order, you can do -so by dragging: - -[role="screenshot"] -image::images/visualize-drag-reorder.png["",width=366] - -The visualization -now shows the top 5 sources of traffic overall, and compares them in 3 hour increments: - -[role="screenshot"] -image::images/visualize-date-histogram-split-2.png[] - -For more information about how aggregations are used in visualizations, see <>. - -Each visualization also has its own customization options. Most visualizations allow you to customize the color of a specific series: - -[role="screenshot"] -image::images/color-picker.png[An array of color dots that users can select,height=267] diff --git a/docs/visualize/tilemap.asciidoc b/docs/visualize/tilemap.asciidoc deleted file mode 100644 index c889bd0bb6ca0..0000000000000 --- a/docs/visualize/tilemap.asciidoc +++ /dev/null @@ -1,27 +0,0 @@ -[[heat-map]] -== Heat map - -Display graphical representations of data where the individual values are represented by colors. Use heat maps when your data set includes categorical data. For example, use a heat map to see the flights of origin countries compared to destination countries using the sample flight data. - -[role="screenshot"] -image::images/visualize_heat_map_example.png[] - -[float] -[[navigate-heatmap]] -=== Change the color ranges - -When only one color displays on the heat map, you might need to change the color ranges. - -To specify the number of color ranges: - -. Click *Options*. - -. Enter the *Number of colors* to display. - -To specify custom ranges: - -. Click *Options*. - -. Select *Use custom ranges*. - -. Enter the ranges to display. diff --git a/docs/visualize/timelion.asciidoc b/docs/visualize/timelion.asciidoc deleted file mode 100644 index 4869664fab0a4..0000000000000 --- a/docs/visualize/timelion.asciidoc +++ /dev/null @@ -1,547 +0,0 @@ -[[timelion]] -== Timelion - -Timelion is a time series data visualizer that enables you to combine totally -independent data sources within a single visualization. It's driven by a simple -expression language you use to retrieve time series data, perform calculations -to tease out the answers to complex questions, and visualize the results. - -For example, Timelion enables you to easily get the answers to questions like: - -* <> -* <> -* <> - -[float] -[[time-series-before-you-begin]] -=== Before you begin - -In this tutorial, you'll use the time series data from https://www.elastic.co/guide/en/beats/metricbeat/current/index.html[Metricbeat]. To ingest the data locally, link:https://www.elastic.co/downloads/beats/metricbeat[download Metricbeat]. - -[float] -[[time-series-intro]] -=== Create time series visualizations - -To compare the real-time percentage of CPU time spent in user space to the results offset by one hour, create a time series visualization. - -[float] -[[time-series-define-functions]] -==== Define the functions - -To start tracking the real-time percentage of CPU, enter the following in the *Timelion Expression* field: - -[source,text] ----------------------------------- -.es(index=metricbeat-*, - timefield='@timestamp', - metric='avg:system.cpu.user.pct') ----------------------------------- - -[role="screenshot"] -image::images/timelion-create01.png[] -{nbsp} - -[float] -[[time-series-compare-data]] -==== Compare the data - -To compare the two data sets, add another series with data from the previous hour, separated by a comma: - -[source,text] ----------------------------------- -.es(index=metricbeat-*, - timefield='@timestamp', - metric='avg:system.cpu.user.pct'), -.es(offset=-1h, <1> - index=metricbeat-*, - timefield='@timestamp', - metric='avg:system.cpu.user.pct') ----------------------------------- - -<1> `offset` offsets the data retrieval by a date expression. In this example, `-1h` offsets the data back by one hour. - -[role="screenshot"] -image::images/timelion-create02.png[] -{nbsp} - -[float] -[[time-series-add-labels]] -==== Add label names - -To easily distinguish between the two data sets, add the label names: - -[source,text] ----------------------------------- -.es(offset=-1h,index=metricbeat-*, - timefield='@timestamp', - metric='avg:system.cpu.user.pct').label('last hour'), -.es(index=metricbeat-*, - timefield='@timestamp', - metric='avg:system.cpu.user.pct').label('current hour') <1> ----------------------------------- - -<1> `.label()` adds custom labels to the visualization. - -[role="screenshot"] -image::images/timelion-create03.png[] -{nbsp} - -[float] -[[time-series-title]] -==== Add a title - -Add a meaningful title: - -[source,text] ----------------------------------- -.es(offset=-1h, - index=metricbeat-*, - timefield='@timestamp', - metric='avg:system.cpu.user.pct') - .label('last hour'), -.es(index=metricbeat-*, - timefield='@timestamp', - metric='avg:system.cpu.user.pct') - .label('current hour') - .title('CPU usage over time') <1> ----------------------------------- - -<1> `.title()` adds a title with a meaningful name. Titles make is easier for unfamiliar users to understand the purpose of the visualization. - -[role="screenshot"] -image::images/timelion-customize01.png[] -{nbsp} - -[float] -[[time-series-change-chart-type]] -==== Change the chart type - -To differentiate between the current hour data and the last hour data, change the chart type: - -[source,text] ----------------------------------- -.es(offset=-1h, - index=metricbeat-*, - timefield='@timestamp', - metric='avg:system.cpu.user.pct') - .label('last hour') - .lines(fill=1,width=0.5), <1> -.es(index=metricbeat-*, - timefield='@timestamp', - metric='avg:system.cpu.user.pct') - .label('current hour') - .title('CPU usage over time') ----------------------------------- - -<1> `.lines()` changes the appearance of the chart lines. In this example, `.lines(fill=1,width=0.5)` sets the fill level to `1`, and the border width to `0.5`. - -[role="screenshot"] -image::images/timelion-customize02.png[] -{nbsp} - -[float] -[[time-series-change-color]] -==== Change the line colors - -To make the current hour data stand out, change the line colors: - -[source,text] ----------------------------------- -.es(offset=-1h, - index=metricbeat-*, - timefield='@timestamp', - metric='avg:system.cpu.user.pct') - .label('last hour') - .lines(fill=1,width=0.5) - .color(gray), <1> -.es(index=metricbeat-*, - timefield='@timestamp', - metric='avg:system.cpu.user.pct') - .label('current hour') - .title('CPU usage over time') - .color(#1E90FF) ----------------------------------- - -<1> `.color()` changes the color of the data. Supported color types include standard color names, hexadecimal values, or a color schema for grouped data. In this example, `.color(gray)` represents the last hour, and `.color(#1E90FF)` represents the current hour. - -[role="screenshot"] -image::images/timelion-customize03.png[] -{nbsp} - -[float] -[[time-series-adjust-legend]] -==== Make adjustments to the legend - -Change the position and style of the legend: - -[source,text] ----------------------------------- -.es(offset=-1h, - index=metricbeat-*, - timefield='@timestamp', - metric='avg:system.cpu.user.pct') - .label('last hour') - .lines(fill=1,width=0.5) - .color(gray), -.es(index=metricbeat-*, - timefield='@timestamp', - metric='avg:system.cpu.user.pct') - .label('current hour') - .title('CPU usage over time') - .color(#1E90FF) - .legend(columns=2, position=nw) <1> ----------------------------------- - -<1> `.legend()` sets the position and style of the legend. In this example, `.legend(columns=2, position=nw)` places the legend in the north west position of the visualization with two columns. - -[role="screenshot"] -image::images/timelion-customize04.png[] -{nbsp} - -[float] -[[mathematical-functions-intro]] -=== Create visualizations with mathematical functions - -To create a visualization for inbound and outbound network traffic, use mathematical functions. - -[float] -[[mathematical-functions-define-functions]] -==== Define the functions - -To start tracking the inbound and outbound network traffic, enter the following in the *Timelion Expression* field: - -[source,text] ----------------------------------- -.es(index=metricbeat*, - timefield=@timestamp, - metric=max:system.network.in.bytes) ----------------------------------- - -[role="screenshot"] -image::images/timelion-math01.png[] -{nbsp} - -[float] -[[mathematical-functions-plot-change]] -==== Plot the rate of change - -Change how the data is displayed so that you can easily monitor the inbound traffic: - -[source,text] ----------------------------------- -.es(index=metricbeat*, - timefield=@timestamp, - metric=max:system.network.in.bytes) - .derivative() <1> ----------------------------------- - -<1> `.derivative` plots the change in values over time. - -[role="screenshot"] -image::images/timelion-math02.png[] -{nbsp} - -Add a similar calculation for outbound traffic: - -[source,text] ----------------------------------- -.es(index=metricbeat*, - timefield=@timestamp, - metric=max:system.network.in.bytes) - .derivative(), -.es(index=metricbeat*, - timefield=@timestamp, - metric=max:system.network.out.bytes) - .derivative() - .multiply(-1) <1> ----------------------------------- - -<1> `.multiply()` multiplies the data series by a number, the result of a data series, or a list of data series. For this example, `.multiply(-1)` converts the outbound network traffic to a negative value since the outbound network traffic is leaving your machine. - -[role="screenshot"] -image::images/timelion-math03.png[] -{nbsp} - -[float] -[[mathematical-functions-convert-data]] -==== Change the data metric - -To make the visualization easier to analyze, change the data metric from bytes to megabytes: - -[source,text] ----------------------------------- -.es(index=metricbeat*, - timefield=@timestamp, - metric=max:system.network.in.bytes) - .derivative() - .divide(1048576), -.es(index=metricbeat*, - timefield=@timestamp, - metric=max:system.network.out.bytes) - .derivative() - .multiply(-1) - .divide(1048576) <1> ----------------------------------- - -<1> `.divide()` accepts the same input as `.multiply()`, then divides the data series by the defined divisor. - -[role="screenshot"] -image::images/timelion-math04.png[] -{nbsp} - -[float] -[[mathematical-functions-add-labels]] -==== Customize and format the visualization - -Customize and format the visualization using functions: - -[source,text] ----------------------------------- -.es(index=metricbeat*, - timefield=@timestamp, - metric=max:system.network.in.bytes) - .derivative() - .divide(1048576) - .lines(fill=2, width=1) - .color(green) - .label("Inbound traffic") <1> - .title("Network traffic (MB/s)"), <2> -.es(index=metricbeat*, - timefield=@timestamp, - metric=max:system.network.out.bytes) - .derivative() - .multiply(-1) - .divide(1048576) - .lines(fill=2, width=1) <3> - .color(blue) <4> - .label("Outbound traffic") - .legend(columns=2, position=nw) <5> ----------------------------------- - -<1> `.label()` adds custom labels to the visualization. -<2> `.title()` adds a title with a meaningful name. -<3> `.lines()` changes the appearance of the chart lines. In this example, `.lines(fill=2, width=1)` sets the fill level to `2`, and the border width to `1`. -<4> `.color()` changes the color of the data. Supported color types include standard color names, hexadecimal values, or a color schema for grouped data. In this example, `.color(green)` represents the inbound network traffic, and `.color(blue)` represents the outbound network traffic. -<5> `.legend()` sets the position and style of the legend. For this example, `legend(columns=2, position=nw)` places the legend in the north west position of the visualization with two columns. - -[role="screenshot"] -image::images/timelion-math05.png[] -{nbsp} - -[float] -[[timelion-conditional-intro]] -=== Create visualizations with conditional logic and tracking trends - -To easily detect outliers and discover patterns over time, modify time series data with conditional logic and create a trend with a moving average. - -With Timelion conditional logic, you can use the following operator values to compare your data: - -[horizontal] -`eq`:: equal -`ne`:: not equal -`lt`:: less than -`lte`:: less than or equal to -`gt`:: greater than -`gte`:: greater than or equal to - -[float] -[[conditional-define-functions]] -==== Define the functions - -To chart the maximum value of `system.memory.actual.used.bytes`, enter the following in the *Timelion Expression* field: - -[source,text] ----------------------------------- -.es(index=metricbeat-*, - timefield='@timestamp', - metric='max:system.memory.actual.used.bytes') ----------------------------------- - -[role="screenshot"] -image::images/timelion-conditional01.png[] -{nbsp} - -[float] -[[conditional-track-memory]] -==== Track used memory - -To track the amount of memory used, create two thresholds: - -[source,text] ----------------------------------- -.es(index=metricbeat-*, - timefield='@timestamp', - metric='max:system.memory.actual.used.bytes'), -.es(index=metricbeat-*, - timefield='@timestamp', - metric='max:system.memory.actual.used.bytes') - .if(gt, <1> - 11300000000, <2> - .es(index=metricbeat-*, - timefield='@timestamp', - metric='max:system.memory.actual.used.bytes'), - null) - .label('warning') - .color('#FFCC11'), -.es(index=metricbeat-*, - timefield='@timestamp', - metric='max:system.memory.actual.used.bytes') - .if(gt, - 11375000000, - .es(index=metricbeat-*, - timefield='@timestamp', - metric='max:system.memory.actual.used.bytes'), - null) - .label('severe') - .color('red') ----------------------------------- - -<1> Timelion conditional logic for the _greater than_ operator. In this example, the warning threshold is 11.3GB (`11300000000`), and the severe threshold is 11.375GB (`11375000000`). If the threshold values are too high or low for your machine, adjust the values accordingly. -<2> `if()` compares each point to a number. If the condition evaluates to `true`, adjust the styling. If the condition evaluates to `false`, use the default styling. - -[role="screenshot"] -image::images/timelion-conditional02.png[] -{nbsp} - -[float] -[[conditional-determine-trend]] -==== Determine the trend - -To determine the trend, create a new data series: - -[source,text] ----------------------------------- -.es(index=metricbeat-*, - timefield='@timestamp', - metric='max:system.memory.actual.used.bytes'), -.es(index=metricbeat-*, - timefield='@timestamp', - metric='max:system.memory.actual.used.bytes') - .if(gt,11300000000, - .es(index=metricbeat-*, - timefield='@timestamp', - metric='max:system.memory.actual.used.bytes'), - null) - .label('warning') - .color('#FFCC11'), -.es(index=metricbeat-*, - timefield='@timestamp', - metric='max:system.memory.actual.used.bytes') - .if(gt,11375000000, - .es(index=metricbeat-*, - timefield='@timestamp', - metric='max:system.memory.actual.used.bytes'), - null). - label('severe') - .color('red'), -.es(index=metricbeat-*, - timefield='@timestamp', - metric='max:system.memory.actual.used.bytes') - .mvavg(10) <1> ----------------------------------- - -<1> `mvavg()` calculates the moving average over a specified period of time. In this example, `.mvavg(10)` creates a moving average with a window of 10 data points. - -[role="screenshot"] -image::images/timelion-conditional03.png[] -{nbsp} - -[float] -[[conditional-format-visualization]] -==== Customize and format the visualization - -Customize and format the visualization using functions: - -[source,text] ----------------------------------- -.es(index=metricbeat-*, - timefield='@timestamp', - metric='max:system.memory.actual.used.bytes') - .label('max memory') <1> - .title('Memory consumption over time'), <2> -.es(index=metricbeat-*, - timefield='@timestamp', - metric='max:system.memory.actual.used.bytes') - .if(gt, - 11300000000, - .es(index=metricbeat-*, - timefield='@timestamp', - metric='max:system.memory.actual.used.bytes'), - null) - .label('warning') - .color('#FFCC11') <3> - .lines(width=5), <4> -.es(index=metricbeat-*, - timefield='@timestamp', - metric='max:system.memory.actual.used.bytes') - .if(gt, - 11375000000, - .es(index=metricbeat-*, - timefield='@timestamp', - metric='max:system.memory.actual.used.bytes'), - null) - .label('severe') - .color('red') - .lines(width=5), -.es(index=metricbeat-*, - timefield='@timestamp', - metric='max:system.memory.actual.used.bytes') - .mvavg(10) - .label('mvavg') - .lines(width=2) - .color(#5E5E5E) - .legend(columns=4, position=nw) <5> ----------------------------------- - -<1> `.label()` adds custom labels to the visualization. -<2> `.title()` adds a title with a meaningful name. -<3> `.color()` changes the color of the data. Supported color types include standard color names, hexadecimal values, or a color schema for grouped data. -<4> `.lines()` changes the appearance of the chart lines. In this example, .lines(width=5) sets border width to `5`. -<5> `.legend()` sets the position and style of the legend. For this example, `(columns=4, position=nw)` places the legend in the north west position of the visualization with four columns. - -[role="screenshot"] -image::images/timelion-conditional04.png[] -{nbsp} - -For additional information on Timelion conditional capabilities, go to https://www.elastic.co/blog/timeseries-if-then-else-with-timelion[I have but one .condition()]. - -[float] -[[timelion-deprecation]] -=== Timelion App deprecation - -Deprecated since 7.0, the Timelion app will be removed in 8.0. If you have any Timelion worksheets, you must migrate them to a dashboard. - -NOTE: Only the Timelion app is deprecated. {kib} continues to support Timelion visualizations on dashboards, in Visualize, and in Canvas. - -[float] -[[timelion-app-to-vis]] -==== Create a dashboard from a Timelion worksheet - -To replace a Timelion worksheet with a dashboard, follow the same process for adding a visualization. -In addition, you must migrate the Timelion graphs to Visualize. - -. Open the menu, click **Dashboard**, then click **Create dashboard**. - -. On the dashboard, click **Create New**, then select the Timelion visualization. -+ -[role="screenshot"] -image::images/timelion-create-new-dashboard.png[] -+ -The only thing you need is the Timelion expression for each graph. - -. Open the Timelion app on a new tab, select the chart you want to copy, and copy its expression. -+ -[role="screenshot"] -image::images/timelion-copy-expression.png[] - -. Return to the other tab and paste the copied expression to the *Timelion Expression* field and click **Update**. -+ -[role="screenshot"] -image::images/timelion-vis-paste-expression.png[] - -. Save the new visualization, give it a name, and click **Save and Return**. -+ -Your Timelion visualization will appear on the dashboard. Repeat this for all your charts on each worksheet. -+ -[role="screenshot"] -image::images/timelion-dashboard.png[] diff --git a/docs/visualize/tsvb.asciidoc b/docs/visualize/tsvb.asciidoc deleted file mode 100644 index 9a1e81670b654..0000000000000 --- a/docs/visualize/tsvb.asciidoc +++ /dev/null @@ -1,138 +0,0 @@ -[[TSVB]] -== TSVB - -TSVB is a time series data visualizer that allows you to use the full power of the -Elasticsearch aggregation framework. With TSVB, you can combine an infinite -number of aggregations to display complex data. - -NOTE: In Elasticsearch version 7.3.0 and later, the time series data visualizer is now referred to as TSVB instead of Time Series Visual Builder. - -[float] -[[tsvb-visualization-types]] -=== Types of TSVB visualizations - -TSVB comes with these types of visualizations: - -Time Series:: A histogram visualization that supports area, line, bar, and steps along with multiple y-axis. - -[role="screenshot"] -image:images/tsvb-screenshot.png["Time series visualization"] - -Metric:: A metric that displays the latest number in a data series. - -[role="screenshot"] -image:images/tsvb-metric.png["Metric visualization"] - -Top N:: A horizontal bar chart where the y-axis is based on a series of metrics, and the x-axis is the latest value in the series. - -[role="screenshot"] -image:images/tsvb-top-n.png["Top N visualization"] - -Gauge:: A single value gauge visualization based on the latest value in a series. - -[role="screenshot"] -image:images/tsvb-gauge.png["Gauge visualization"] - -Markdown:: Edit the data using using Markdown text and Mustache template syntax. - -[role="screenshot"] -image:images/tsvb-markdown.png["Markdown visualization"] - -Table:: Display data from multiple time series by defining the field group to show in the rows, and the columns of data to display. - -[role="screenshot"] -image:images/tsvb-table.png["Table visualization"] - -[float] -[[create-tsvb-visualization]] -=== Create TSVB visualizations - -To create a TSVB visualization, choose the data series you want to display, then choose how you want to display the data. The options available are dependent on the visualization. - -[float] -[[tsvb-data-series-options]] -==== Configure the data series - -To create a single metric, add multiple data series with multiple aggregations. - -. Select the visualization type. - -. Specify the data series labels and colors. - -.. Select *Data*. -+ -If you are using the *Table* visualization, select *Columns*. - -.. In the *Label* field, enter a name for the data series, which is used on legends and titles. -+ -For series that are grouped by a term, you can specify a mustache variable of `{{key}}` to substitute the term. - -.. If supported by the visualization, click the swatch and choose a color for the data series. - -.. To add another data series, click *+*, then repeat the steps to specify the labels and colors. - -. Specify the data series metrics. - -.. Select *Metrics*. - -.. From the dropdown lists, choose your options. - -.. To add another metric, click *+*. -+ -When you add more than one metric, the last metric value is displayed, which is indicated by the eye icon. - -. To specify the format and display options, select *Options*. - -. To specify how to group or split the data, choose an option from the *Group by* drop down list. -+ -By default, the data series are grouped by everything. - -[float] -[[tsvb-panel-options]] -==== Configure the panel - -Change the data that you want to display and choose the style options for the panel. - -. Select *Panel options*. - -. Under *Data*, specify how much of the data that you want to display in the visualization. - -. Under *Style*, specify how you want the visualization to look. - -[float] -[[tsvb-add-annotations]] -==== Add annotations - -If you are using the Time Series visualization, add annotation data sources. - -. Select *Annotations*. - -. Click *Add data source*, then specify the options. - -[float] -[[tsvb-enter-markdown]] -==== Enter Markdown text - -Edit the source for the Markdown visualization. - -. Select *Markdown*. - -. In the editor, enter enter your Markdown text, then press Enter. - -. To insert the mustache template variable into the editor, click the variable name. -+ -The http://mustache.github.io/mustache.5.html[mustache syntax] uses the Handlebar.js processor, which is an extended version of the Mustache template language. - -[float] -[[tsvb-style-markdown]] -==== Style Markdown text - -Style your Markdown visualization using http://lesscss.org/features/[less syntax]. - -. Select *Markdown*. - -. Select *Panel options*. - -. Enter styling rules in *Custom CSS* section -+ -Less in TSVB does not support custom plugins or inline JavaScript. diff --git a/docs/visualize/vega.asciidoc b/docs/visualize/vega.asciidoc deleted file mode 100644 index b231159e86bde..0000000000000 --- a/docs/visualize/vega.asciidoc +++ /dev/null @@ -1,1639 +0,0 @@ -[[vega-graph]] -== Vega - -Build custom visualizations using Vega and Vega-Lite, backed by one or more -data sources including {es}, Elastic Map Service, URL, -or static data. Use the {kib} extensions to Vega to embed Vega into -your dashboard, and to add interactivity to the visualizations. - -Vega and Vega-Lite are both declarative formats to create visualizations -using JSON. Both use a different syntax for declaring visualizations, -and are not fully interchangeable. - -[float] -[[when-to-vega]] -=== When to use Vega - -Vega and Vega-Lite are capable of building most of the visualizations -that {kib} provides, but with higher complexity. The most common reason -to use Vega in {kib} is that {kib} is missing support for the query or -visualization, for example: - -* Aggregations using the `nested` or `parent/child` mapping -* Aggregations without a {kib} index pattern -* Queries using custom time filters -* Complex calculations -* Extracting data from _source instead of aggregation -* Scatter charts -* Sankey charts -* Custom maps -* Using a visual theme that {kib} does not provide - -[[vega-lite-tutorial]] -=== Tutorial: First visualization in Vega-Lite - -In this tutorial, you will learn about how to edit Vega-Lite in {kib} to create -a stacked area chart from an {es} search query. It will give you a starting point -for a more comprehensive -https://vega.github.io/vega-lite/tutorials/getting_started.html[introduction to Vega-Lite], -while only covering the basics. - -In this tutorial, you will build a stacked area chart from one of the {kib} sample data -sets. - -[role="screenshot"] -image::visualize/images/vega_lite_tutorial_1.png[] - -Before beginning this tutorial, install the <> -set. - -When you first open the Vega editor in {kib}, you will see a pre-populated -line chart which shows the total number of documents across all your indices -within the time range. - -[role="screenshot"] -image::visualize/images/vega_lite_default.png[] - -The text editor contains a Vega-Lite spec written in https://hjson.github.io/[HJSON], -which is similar to JSON but optimized for human editing. HJSON supports: - -* Comments using // or /* syntax -* Object keys without quotes -* String values without quotes -* Optional commas -* Double or single quotes -* Multiline strings - -[float] -==== Small steps - -Always work on Vega in the smallest steps possible, and save your work frequently. -Small changes will cause unexpected results. Click the "Save" button now. - -The first step is to change the index to one of the <> -sets. Change - -```yaml -index: _all -``` - -to: - -```yaml -index: kibana_sample_data_ecommerce -``` - -Click "Update". The result is probably not what you expect. You should see a flat -line with 0 results. - -You've only changed the index, so the difference must be the query is returning -no results. You can try the <>, -but intuition may be faster for this particular problem. - -In this case, the problem is that you are querying the field `@timestamp`, -which does not exist in the `kibana_sample_data_ecommerce` data. Find and replace -`@timestamp` with `order_date`. This fixes the problem, leaving you with this spec: - -.Expand Vega-Lite spec -[%collapsible%closed] -==== -[source,yaml] ----- -{ - $schema: https://vega.github.io/schema/vega-lite/v4.json - title: Event counts from ecommerce - data: { - url: { - %context%: true - %timefield%: order_date - index: kibana_sample_data_ecommerce - body: { - aggs: { - time_buckets: { - date_histogram: { - field: order_date - interval: {%autointerval%: true} - extended_bounds: { - min: {%timefilter%: "min"} - max: {%timefilter%: "max"} - } - min_doc_count: 0 - } - } - } - size: 0 - } - } - format: {property: "aggregations.time_buckets.buckets" } - } - - mark: line - - encoding: { - x: { - field: key - type: temporal - axis: { title: null } - } - y: { - field: doc_count - type: quantitative - axis: { title: "Document count" } - } - } -} ----- - -==== - -Now, let's make the visualization more interesting by adding another aggregation -to create a stacked area chart. To verify that you have constructed the right -query, it is easiest to use the {kib} Dev Tools in a separate tab from the -Vega editor. Open the Dev Tools from the Management section of the navigation. - -This query is roughly equivalent to the one that is used in the default -Vega-Lite spec. Copy it into the Dev Tools: - -```js -POST kibana_sample_data_ecommerce/_search -{ - "query": { - "range": { - "order_date": { - "gte": "now-7d" - } - } - }, - "aggs": { - "time_buckets": { - "date_histogram": { - "field": "order_date", - "fixed_interval": "1d", - "extended_bounds": { - "min": "now-7d" - }, - "min_doc_count": 0 - } - } - }, - "size": 0 -} -``` - -There's not enough data to create a stacked bar in the original query, so we -will add a new -{ref}/search-aggregations-bucket-terms-aggregation.html[terms aggregation]: - -```js -POST kibana_sample_data_ecommerce/_search -{ - "query": { - "range": { - "order_date": { - "gte": "now-7d" - } - } - }, - "aggs": { - "categories": { - "terms": { "field": "category.keyword" }, - "aggs": { - "time_buckets": { - "date_histogram": { - "field": "order_date", - "fixed_interval": "1d", - "extended_bounds": { - "min": "now-7d" - }, - "min_doc_count": 0 - } - } - } - } - }, - "size": 0 -} -``` - -You'll see that the response format looks different from the previous query: - -```json -{ - "aggregations" : { - "categories" : { - "doc_count_error_upper_bound" : 0, - "sum_other_doc_count" : 0, - "buckets" : [{ - "key" : "Men's Clothing", - "doc_count" : 1661, - "time_buckets" : { - "buckets" : [{ - "key_as_string" : "2020-06-30T00:00:00.000Z", - "key" : 1593475200000, - "doc_count" : 19 - }, { - "key_as_string" : "2020-07-01T00:00:00.000Z", - "key" : 1593561600000, - "doc_count" : 71 - }] - } - }] - } - } -} -``` - -Now that we have data that we're happy with, it's time to convert from an -isolated {es} query into a query with {kib} integration. Looking at the -<>, you will -see the full list of special tokens that are used in this query, such -as `%context: true`. This query has also replaced `"fixed_interval": "1d"` -with `interval: {%autointerval%: true}`. Copy the final query into -your spec: - -```yaml - data: { - url: { - %context%: true - %timefield%: order_date - index: kibana_sample_data_ecommerce - body: { - aggs: { - categories: { - terms: { field: "category.keyword" } - aggs: { - time_buckets: { - date_histogram: { - field: order_date - interval: {%autointerval%: true} - extended_bounds: { - min: {%timefilter%: "min"} - max: {%timefilter%: "max"} - } - min_doc_count: 0 - } - } - } - } - } - size: 0 - } - } - format: {property: "aggregations.categories.buckets" } - } -``` - -If you copy and paste that into your Vega-Lite spec, and click "Update", -you will see a warning saying `Infinite extent for field "key": [Infinity, -Infinity]`. -Let's use our <> to understand why. - -Vega-Lite generates data using the names `source_0` and `data_0`. `source_0` contains -the results from the {es} query, and `data_0` contains the visually encoded results -which are shown in the chart. To debug this problem, you need to compare both. - -To look at the source, open the browser dev tools console and type -`VEGA_DEBUG.view.data('source_0')`. You will see: - -```js -[{ - doc_count: 454 - key: "Men's Clothing" - time_buckets: {buckets: Array(57)} - Symbol(vega_id): 12822 -}, ...] -``` - -To compare to the visually encoded data, open the browser dev tools console and type -`VEGA_DEBUG.view.data('data_0')`. You will see: - -```js -[{ - doc_count: 454 - key: NaN - time_buckets: {buckets: Array(57)} - Symbol(vega_id): 13879 -}] -``` - -The issue seems to be that the `key` property is not being converted the right way, -which makes sense because the `key` is now `Men's Clothing` instead of a timestamp. - -To fix this, try updating the `encoding` of your Vega-Lite spec to: - -```yaml - encoding: { - x: { - field: time_buckets.buckets.key - type: temporal - axis: { title: null } - } - y: { - field: time_buckets.buckets.doc_count - type: quantitative - axis: { title: "Document count" } - } - } -``` - -This will show more errors, and you can inspect `VEGA_DEBUG.view.data('data_0')` to -understand why. This now shows: - -```js -[{ - doc_count: 454 - key: "Men's Clothing" - time_buckets: {buckets: Array(57)} - time_buckets.buckets.doc_count: undefined - time_buckets.buckets.key: null - Symbol(vega_id): 14094 -}] -``` - -It looks like the problem is that the `time_buckets` inner array is not being -extracted by Vega. The solution is to use a Vega-lite -https://vega.github.io/vega-lite/docs/flatten.html[flatten transformation], available in {kib} 7.9 and later. -If using an older version of Kibana, the flatten transformation is available in Vega -but not Vega-Lite. - -Add this section in between the `data` and `encoding` section: - -```yaml - transform: [{ - flatten: ["time_buckets.buckets"] - }] -``` - -This does not yet produce the results you expect. Inspect the transformed data -by typing `VEGA_DEBUG.view.data('data_0')` into the console again: - -```js -[{ - doc_count: 453 - key: "Men's Clothing" - time_bucket.buckets.doc_count: undefined - time_buckets: {buckets: Array(57)} - time_buckets.buckets: { - key_as_string: "2020-06-30T15:00:00.000Z", - key: 1593529200000, - doc_count: 2 - } - time_buckets.buckets.key: null - Symbol(vega_id): 21564 -}] -``` - -The debug view shows `undefined` values where you would expect to see numbers, and -the cause is that there are duplicate names which are confusing Vega-Lite. This can -be fixed by making this change to the `transform` and `encoding` blocks: - -```yaml - transform: [{ - flatten: ["time_buckets.buckets"], - as: ["buckets"] - }] - - mark: area - - encoding: { - x: { - field: buckets.key - type: temporal - axis: { title: null } - } - y: { - field: buckets.doc_count - type: quantitative - axis: { title: "Document count" } - } - color: { - field: key - type: nominal - } - } -``` - -At this point, you have a stacked area chart that shows the top categories, -but the chart is still missing some common features that we expect from a {kib} -visualization. Let's add hover states and tooltips next. - -Hover states are handled differently in Vega-Lite and Vega. In Vega-Lite this is -done using a concept called `selection`, which has many permutations that are not -covered in this tutorial. We will be adding a simple tooltip and hover state. - -Because {kib} has enabled the https://vega.github.io/vega-lite/docs/tooltip.html[Vega tooltip plugin], -tooltips can be defined in several ways: - -* Automatic tooltip based on the data, via `{ content: "data" }` -* Array of fields, like `[{ field: "key", type: "nominal" }]` -* Defining a custom Javascript object using the `calculate` transform - -For the simple tooltip, add this to your encoding: - -```yaml - encoding: { - tooltip: [{ - field: buckets.key - type: temporal - title: "Date" - }, { - field: key - type: nominal - title: "Category" - }, { - field: buckets.doc_count - type: quantitative - title: "Count" - }] - } -``` - -As you hover over the area series in your chart, a multi-line tooltip will -appear, but it won't indicate the nearest point that it's pointing to. To -indicate the nearest point, we need to add a second layer. - -The first step is to remove the `mark: area` from your visualization. -Once you've removed the previous mark, add a composite mark at the end of -the Vega-Lite spec: - -```yaml - layer: [{ - mark: area - }, { - mark: point - }] -``` - -You'll see that the points are not appearing to line up with the area chart, -and the reason is that the points are not being stacked. Change your Y encoding -to this: - -```yaml - y: { - field: buckets.doc_count - type: quantitative - axis: { title: "Document count" } - stack: true - } -``` - -Now, we will add a `selection` block inside the point mark: - -```yaml - layer: [{ - mark: area - }, { - mark: point - - selection: { - pointhover: { - type: single - on: mouseover - clear: mouseout - empty: none - fields: ["buckets.key", "key"] - nearest: true - } - } - - encoding: { - size: { - condition: { - selection: pointhover - value: 100 - } - value: 5 - } - fill: { - condition: { - selection: pointhover - value: white - } - } - } - }] -``` - -Now that you've enabled a selection, try moving the mouse around the visualization -and seeing the points respond to the nearest position: - -[role="screenshot"] -image::visualize/images/vega_lite_tutorial_2.png[] - -The final result of this tutorial is this spec: - -.Expand final Vega-Lite spec -[%collapsible%closed] -==== -[source,yaml] ----- -{ - $schema: https://vega.github.io/schema/vega-lite/v4.json - title: Event counts from ecommerce - data: { - url: { - %context%: true - %timefield%: order_date - index: kibana_sample_data_ecommerce - body: { - aggs: { - categories: { - terms: { field: "category.keyword" } - aggs: { - time_buckets: { - date_histogram: { - field: order_date - interval: {%autointerval%: true} - extended_bounds: { - min: {%timefilter%: "min"} - max: {%timefilter%: "max"} - } - min_doc_count: 0 - } - } - } - } - } - size: 0 - } - } - format: {property: "aggregations.categories.buckets" } - } - - transform: [{ - flatten: ["time_buckets.buckets"] - as: ["buckets"] - }] - - encoding: { - x: { - field: buckets.key - type: temporal - axis: { title: null } - } - y: { - field: buckets.doc_count - type: quantitative - axis: { title: "Document count" } - stack: true - } - color: { - field: key - type: nominal - title: "Category" - } - tooltip: [{ - field: buckets.key - type: temporal - title: "Date" - }, { - field: key - type: nominal - title: "Category" - }, { - field: buckets.doc_count - type: quantitative - title: "Count" - }] - } - - layer: [{ - mark: area - }, { - mark: point - - selection: { - pointhover: { - type: single - on: mouseover - clear: mouseout - empty: none - fields: ["buckets.key", "key"] - nearest: true - } - } - - encoding: { - size: { - condition: { - selection: pointhover - value: 100 - } - value: 5 - } - fill: { - condition: { - selection: pointhover - value: white - } - } - } - }] -} ----- - -==== - -[[vega-tutorial]] -=== Tutorial: Updating {kib} filters from Vega - -In this tutorial you will build an area chart in Vega using an {es} search query, -and add a click handler and drag handler to update {kib} filters. -This tutorial is not a full https://vega.github.io/vega/tutorials/[Vega tutorial], -but will cover the basics of creating Vega visualizations into {kib}. - -First, create an almost-blank Vega chart by pasting this into the editor: - -```yaml -{ - $schema: "https://vega.github.io/schema/vega/v5.json" - data: [{ - name: source_0 - }] - - scales: [{ - name: x - type: time - range: width - }, { - name: y - type: linear - range: height - }] - - axes: [{ - orient: bottom - scale: x - }, { - orient: left - scale: y - }] - - marks: [ - { - type: area - from: { - data: source_0 - } - encode: { - update: { - } - } - } - ] -} -``` - -Despite being almost blank, this Vega spec still contains the minimum requirements: - -* Data -* Scales -* Marks -* (optional) Axes - -Next, add a valid {es} search query in the `data` block: - -```yaml - data: [ - { - name: source_0 - url: { - %context%: true - %timefield%: order_date - index: kibana_sample_data_ecommerce - body: { - aggs: { - time_buckets: { - date_histogram: { - field: order_date - fixed_interval: "3h" - extended_bounds: { - min: {%timefilter%: "min"} - max: {%timefilter%: "max"} - } - min_doc_count: 0 - } - } - } - size: 0 - } - } - format: { property: "aggregations.time_buckets.buckets" } - } - ] -``` - -Click "Update", and nothing will change in the visualization. The first step -is to change the X and Y scales based on the data: - -```yaml - scales: [{ - name: x - type: time - range: width - domain: { - data: source_0 - field: key - } - }, { - name: y - type: linear - range: height - domain: { - data: source_0 - field: doc_count - } - }] -``` - -Click "Update", and you will see that the X and Y axes are now showing labels based -on the real data. - -Next, encode the fields `key` and `doc_count` as the X and Y values: - -```yaml - marks: [ - { - type: area - from: { - data: source_0 - } - encode: { - update: { - x: { - scale: x - field: key - } - y: { - scale: y - value: 0 - } - y2: { - scale: y - field: doc_count - } - } - } - } - ] -``` - -Click "Update" and you will get a basic area chart: - -[role="screenshot"] -image::visualize/images/vega_tutorial_3.png[] - -Next, add a new block to the `marks` section. This will show clickable points to filter for a specific -date: - -```yaml - { - name: point - type: symbol - style: ["point"] - from: { - data: source_0 - } - encode: { - update: { - x: { - scale: x - field: key - } - y: { - scale: y - field: doc_count - } - size: { - value: 100 - } - fill: { - value: black - } - } - } - } -``` - -Next, we will create a Vega signal to make the points clickable. You can access -the clicked `datum` in the expression used to update. In this case, you want -clicks on points to add a time filter with the 3-hour interval defined above. - -```yaml - signals: [ - { - name: point_click - on: [{ - events: { - source: scope - type: click - markname: point - } - update: '''kibanaSetTimeFilter(datum.key, datum.key + 3 * 60 * 60 * 1000)''' - }] - } - ] -``` - -This event is using the {kib} custom function `kibanaSetTimeFilter` to generate a filter that -gets applied to the entire dashboard on click. - -The mouse cursor does not currently indicate that the chart is interactive. Find the `marks` section, -and update the mark named `point` by adding `cursor: { value: "pointer" }` to -the `encoding` section like this: - -```yaml - { - name: point - type: symbol - style: ["point"] - from: { - data: source_0 - } - encode: { - update: { - ... - cursor: { value: "pointer" } - } - } - } -``` - -Next, we will add a drag interaction which will allow the user to narrow into -a specific time range in the visualization. This will require adding more signals, and -adding a rectangle overlay: - -[role="screenshot"] -image::visualize/images/vega_tutorial_4.png[] - -The first step is to add a new `signal` to track the X position of the cursor: - -```yaml - { - name: currentX - value: -1 - on: [{ - events: { - type: mousemove - source: view - }, - update: "clamp(x(), 0, width)" - }, { - events: { - type: mouseout - source: view - } - update: "-1" - }] - } -``` - -Now add a new `mark` to indicate the current cursor position: - -```yaml - { - type: rule - interactive: false - encode: { - update: { - y: {value: 0} - y2: {signal: "height"} - stroke: {value: "gray"} - strokeDash: { - value: [2, 1] - } - x: {signal: "max(currentX,0)"} - defined: {signal: "currentX > 0"} - } - } - } -``` - -Next, add a signal to track the current selected range, which will update -until the user releases the mouse button or uses the escape key: - - -```yaml - { - name: selected - value: [0, 0] - on: [{ - events: { - type: mousedown - source: view - } - update: "[clamp(x(), 0, width), clamp(x(), 0, width)]" - }, { - events: { - type: mousemove - source: window - consume: true - between: [{ - type: mousedown - source: view - }, { - merge: [{ - type: mouseup - source: window - }, { - type: keydown - source: window - filter: "event.key === 'Escape'" - }] - }] - } - update: "[selected[0], clamp(x(), 0, width)]" - }, { - events: { - type: keydown - source: window - filter: "event.key === 'Escape'" - } - update: "[0, 0]" - }] - } -``` - -Now that there is a signal which tracks the time range from the user, we need to indicate -the range visually by adding a new mark which only appears conditionally: - -```yaml - { - type: rect - name: selectedRect - encode: { - update: { - height: {signal: "height"} - fill: {value: "#333"} - fillOpacity: {value: 0.2} - x: {signal: "selected[0]"} - x2: {signal: "selected[1]"} - defined: {signal: "selected[0] !== selected[1]"} - } - } - } -``` - -Finally, add a new signal which will update the {kib} time filter when the mouse is released while -dragging: - -```yaml - { - name: applyTimeFilter - value: null - on: [{ - events: { - type: mouseup - source: view - } - update: '''selected[0] !== selected[1] ? kibanaSetTimeFilter( - invert('x',selected[0]), - invert('x',selected[1])) : null''' - }] - } -``` - -Putting this all together, your visualization now supports the main features of -standard visualizations in {kib}, but with the potential to add even more control. -The final Vega spec for this tutorial is here: - -.Expand final Vega spec -[%collapsible%closed] -==== -[source,yaml] ----- -{ - $schema: "https://vega.github.io/schema/vega/v5.json" - data: [ - { - name: source_0 - url: { - %context%: true - %timefield%: order_date - index: kibana_sample_data_ecommerce - body: { - aggs: { - time_buckets: { - date_histogram: { - field: order_date - fixed_interval: "3h" - extended_bounds: { - min: {%timefilter%: "min"} - max: {%timefilter%: "max"} - } - min_doc_count: 0 - } - } - } - size: 0 - } - } - format: { property: "aggregations.time_buckets.buckets" } - } - ] - - scales: [{ - name: x - type: time - range: width - domain: { - data: source_0 - field: key - } - }, { - name: y - type: linear - range: height - domain: { - data: source_0 - field: doc_count - } - }] - - axes: [{ - orient: bottom - scale: x - }, { - orient: left - scale: y - }] - - marks: [ - { - type: area - from: { - data: source_0 - } - encode: { - update: { - x: { - scale: x - field: key - } - y: { - scale: y - value: 0 - } - y2: { - scale: y - field: doc_count - } - } - } - }, - { - name: point - type: symbol - style: ["point"] - from: { - data: source_0 - } - encode: { - update: { - x: { - scale: x - field: key - } - y: { - scale: y - field: doc_count - } - size: { - value: 100 - } - fill: { - value: black - } - cursor: { value: "pointer" } - } - } - }, - { - type: rule - interactive: false - encode: { - update: { - y: {value: 0} - y2: {signal: "height"} - stroke: {value: "gray"} - strokeDash: { - value: [2, 1] - } - x: {signal: "max(currentX,0)"} - defined: {signal: "currentX > 0"} - } - } - }, - { - type: rect - name: selectedRect - encode: { - update: { - height: {signal: "height"} - fill: {value: "#333"} - fillOpacity: {value: 0.2} - x: {signal: "selected[0]"} - x2: {signal: "selected[1]"} - defined: {signal: "selected[0] !== selected[1]"} - } - } - } - ] - - signals: [ - { - name: point_click - on: [{ - events: { - source: scope - type: click - markname: point - } - update: '''kibanaSetTimeFilter(datum.key, datum.key + 3 * 60 * 60 * 1000)''' - }] - } - { - name: currentX - value: -1 - on: [{ - events: { - type: mousemove - source: view - }, - update: "clamp(x(), 0, width)" - }, { - events: { - type: mouseout - source: view - } - update: "-1" - }] - } - { - name: selected - value: [0, 0] - on: [{ - events: { - type: mousedown - source: view - } - update: "[clamp(x(), 0, width), clamp(x(), 0, width)]" - }, { - events: { - type: mousemove - source: window - consume: true - between: [{ - type: mousedown - source: view - }, { - merge: [{ - type: mouseup - source: window - }, { - type: keydown - source: window - filter: "event.key === 'Escape'" - }] - }] - } - update: "[selected[0], clamp(x(), 0, width)]" - }, { - events: { - type: keydown - source: window - filter: "event.key === 'Escape'" - } - update: "[0, 0]" - }] - } - { - name: applyTimeFilter - value: null - on: [{ - events: { - type: mouseup - source: view - } - update: '''selected[0] !== selected[1] ? kibanaSetTimeFilter( - invert('x',selected[0]), - invert('x',selected[1])) : null''' - }] - } - ] -} - ----- -==== - -[[vega-reference]] -=== Reference for {kib} extensions - -{kib} has extended Vega and Vega-Lite with extensions that support: - -* Default height and width -* Default theme to match {kib} -* Writing {es} queries using the time range and filters from dashboards -* Using the Elastic Map Service in Vega maps -* Additional tooltip styling -* Advanced setting to enable URL loading from any domain -* Limited debugging support using the browser dev tools -* (Vega only) Expression functions which can update the time range and dashboard filters - -[[vega-sizing-and-positioning]] -==== Default height and width - -By default, Vega visualizations use the `autosize = { type: 'fit', contains: 'padding' }` layout. -`fit` uses all available space, ignores `width` and `height` values, -and respects the padding values. To override this behavior, change the -`autosize` value. - -[[vega-theme]] -==== Default theme to match {kib} - -{kib} registers a default https://vega.github.io/vega/docs/schemes/[Vega color scheme] -with the id `elastic`, and sets a default color for each `mark` type. -Override it by providing a different `stroke`, `fill`, or `color` (Vega-Lite) value. - -[[vega-queries]] -==== Writing {es} queries in Vega - -{kib} extends the Vega https://vega.github.io/vega/docs/data/[data] elements -with support for direct {es} queries specified as a `url`. - -Because of this, {kib} is **unable to support dynamically loaded data**, -which would otherwise work in Vega. All data is fetched before it's passed to -the Vega renderer. - -To define an {es} query in Vega, set the `url` to an object. {kib} will parse -the object looking for special tokens that allow your query to integrate with {kib}. -These tokens are: - -* `%context%: true`: Set at the top level, and replaces the `query` section with filters from dashboard -* `%timefield%: `: Set at the top level, integrates the query with the dashboard time filter -* `{%timefilter%: true}`: Replaced by an {es} range query with upper and lower bounds -* `{%timefilter%: "min" | "max"}`: Replaced only by the upper or lower bounds -* `{%timefilter: true, shift: -1, unit: 'hour'}`: Generates a time range query one hour in the past -* `{%autointerval%: true}`: Replaced by the string which contains the automatic {kib} time interval, such as `1h` -* `{%autointerval%: 10}`: Replaced by a string which is approximately dividing the time into 10 ranges, allowing - you to influence the automatic interval -* `"%dashboard_context-must_clause%"`: String replaced by object containing filters -* `"%dashboard_context-filter_clause%"`: String replaced by an object containing filters -* `"%dashboard_context-must_not_clause%"`: String replaced by an object containing filters - -Putting this together, an example query that counts the number of documents in -a specific index: - -[source,yaml] ----- -// An object instead of a string for the URL value -// is treated as a context-aware Elasticsearch query. -url: { - // Specify the time filter. - %timefield%: @timestamp - // Apply dashboard context filters when set - %context%: true - - // Which indexes to search - index: kibana_sample_data_logs - // The body element may contain "aggs" and "query" keys - body: { - aggs: { - time_buckets: { - date_histogram: { - // Use date histogram aggregation on @timestamp field - field: @timestamp <1> - // interval value will depend on the time filter - // Use an integer to set approximate bucket count - interval: { %autointerval%: true } - // Make sure we get an entire range, even if it has no data - extended_bounds: { - min: { %timefilter%: "min" } - max: { %timefilter%: "max" } - } - // Use this for linear (e.g. line, area) graphs - // Without it, empty buckets will not show up - min_doc_count: 0 - } - } - } - // Speed up the response by only including aggregation results - size: 0 - } -} ----- - -<1> `@timestamp` — Filters the time range and breaks it into histogram -buckets. - -The full result includes the following structure: - -[source,yaml] ----- -{ - "aggregations": { - "time_buckets": { - "buckets": [{ - "key_as_string": "2015-11-30T22:00:00.000Z", - "key": 1448920800000,<1> - "doc_count": 28 - }, { - "key_as_string": "2015-11-30T23:00:00.000Z", - "key": 1448924400000, <1> - "doc_count": 330 - }, ... ----- - -<1> `"key"` — The unix timestamp you can use without conversions by the -Vega date expressions. - -For most visualizations, you only need the list of bucket values. To focus on -only the data you need, use `format: {property: "aggregations.time_buckets.buckets"}`. - -Specify a query with individual range and dashboard context. The query is -equivalent to `"%context%": true, "%timefield%": "@timestamp"`, -except that the time range is shifted back by 10 minutes: - -[source,yaml] ----- -{ - body: { - query: { - bool: { - must: [ - // This string will be replaced - // with the auto-generated "MUST" clause - "%dashboard_context-must_clause%" - { - range: { - // apply timefilter (upper right corner) - // to the @timestamp variable - @timestamp: { - // "%timefilter%" will be replaced with - // the current values of the time filter - // (from the upper right corner) - "%timefilter%": true - // Only work with %timefilter% - // Shift current timefilter by 10 units back - shift: 10 - // week, day (default), hour, minute, second - unit: minute - } - } - } - ] - must_not: [ - // This string will be replaced with - // the auto-generated "MUST-NOT" clause - "%dashboard_context-must_not_clause%" - ] - filter: [ - // This string will be replaced - // with the auto-generated "FILTER" clause - "%dashboard_context-filter_clause%" - ] - } - } - } -} ----- - -NOTE: When using `"%context%": true` or defining a value for `"%timefield%"` the body cannot contain a query. To customize the query within the VEGA specification (e.g. add an additional filter, or shift the timefilter), define your query and use the placeholders as in the example above. The placeholders will be replaced by the actual context of the dashboard or visualization once parsed. - -The `"%timefilter%"` can also be used to specify a single min or max -value. The date_histogram's `extended_bounds` can be set -with two values - min and max. Instead of hardcoding a value, you may -use `"min": {"%timefilter%": "min"}`, which will be replaced with the -beginning of the current time range. The `shift` and `unit` values are -also supported. The `"interval"` can also be set dynamically, depending -on the currently picked range: `"interval": {"%autointerval%": 10}` will -try to get about 10-15 data points (buckets). - -[float] -[[vega-esmfiles]] -=== Access Elastic Map Service files - -Access the Elastic Map Service files via the same mechanism: - -[source,yaml] ----- -url: { - // "type" defaults to "elasticsearch" otherwise - type: emsfile - // Name of the file, exactly as in the Region map visualization - name: World Countries -} -// The result is a geojson file, get its features to use -// this data source with the "shape" marks -// https://vega.github.io/vega/docs/marks/shape/ -format: {property: "features"} ----- - -To enable Maps, the graph must specify `type=map` in the host -configuration: - -[source,yaml] ----- -{ - "config": { - "kibana": { - "type": "map", - - // Initial map position - "latitude": 40.7, // default 0 - "longitude": -74, // default 0 - "zoom": 7, // default 2 - - // defaults to "default". Use false to disable base layer. - "mapStyle": false, - - // default 0 - "minZoom": 5, - - // defaults to the maximum for the given style, - // or 25 when base is disabled - "maxZoom": 13, - - // defaults to true, shows +/- buttons to zoom in/out - "zoomControl": false, - - // Defaults to 'false', disables mouse wheel zoom. If set to - // 'true', map may zoom unexpectedly while scrolling dashboard - "scrollWheelZoom": false, - - // When false, repaints on each move frame. - // Makes the graph slower when moving the map - "delayRepaint": true, // default true - } - }, - /* the rest of Vega JSON */ -} ----- - -The visualization automatically injects a `"projection"`, which you can use to -calculate the position of all geo-aware marks. -Additionally, you can use `latitude`, `longitude`, and `zoom` signals. -These signals can be used in the graph, or can be updated to modify the -position of the map. - -[float] -[[vega-tooltip]] -==== Additional tooltip styling - -{kib} has installed the https://vega.github.io/vega-lite/docs/tooltip.html[Vega tooltip plugin], -so tooltips can be defined in the ways documented there. Beyond that, {kib} also supports -a configuration option for changing the tooltip position and padding: - -```js -{ - config: { - kibana: { - tooltips: { - position: 'top', - padding: 15 - } - } - } -} -``` - -[[vega-url-loading]] -==== Advanced setting to enable URL loading from any domain - -Vega can load data from any URL, but this is disabled by default in {kib}. -To change this, set `vis_type_vega.enableExternalUrls: true` in `kibana.yml`, -then restart {kib}. - -[[vega-inspector]] -==== Vega Inspector -Use the contextual *Inspect* tool to gain insights into different elements. -For Vega visualizations, there are two different views: *Request* and *Vega debug*. - -===== Inspect Elasticsearch requests - -Vega uses the {ref}/search-search.html[{es} search API] to get documents and aggregation -results from {es}. To troubleshoot these requests, click *Inspect*, which shows the most recent requests. -In case your specification has more than one request, you can switch between the views using the *View* dropdown. - -[role="screenshot"] -image::visualize/images/vega_tutorial_inspect_requests.png[] - -===== Vega debugging - -With the *Vega debug* view, you can inspect the *Data sets* and *Signal Values* runtime data. - -The runtime data is read from the -https://vega.github.io/vega/docs/api/debugging/#scope[runtime scope]. - -[role="screenshot"] -image::visualize/images/vega_tutorial_inspect_data_sets.png[] - -To debug more complex specs, access to the `view` variable. For more information, refer to -the <>. - -===== Asking for help with a Vega spec - -Because of the dynamic nature of the data in {es}, it is hard to help you with -Vega specs unless you can share a dataset. To do this, click *Inspect*, select the *Vega debug* view, -then select the *Spec* tab: - -[role="screenshot"] -image::visualize/images/vega_tutorial_getting_help.png[] - -To copy the response, click *Copy to clipboard*. Paste the copied data to -https://gist.github.com/[gist.github.com], possibly with a .json extension. Use the [raw] button, -and share that when asking for help. - -[[vega-browser-debugging-console]] -==== Browser debugging console - -experimental[] Use browser debugging tools (for example, F12 or Ctrl+Shift+J in Chrome) to -inspect the `VEGA_DEBUG` variable: - -* `view` — Access to the Vega View object. See https://vega.github.io/vega/docs/api/debugging/[Vega Debugging Guide] -on how to inspect data and signals at runtime. For Vega-Lite, -`VEGA_DEBUG.view.data('source_0')` gets the pre-transformed data, and `VEGA_DEBUG.view.data('data_0')` -gets the encoded data. For Vega, it uses the data name as defined in your Vega spec. - -* `vega_spec` — Vega JSON graph specification after some modifications by {kib}. In case -of Vega-Lite, this is the output of the Vega-Lite compiler. - -* `vegalite_spec` — If this is a Vega-Lite graph, JSON specification of the graph before -Vega-Lite compilation. - -[float] -[[vega-expression-functions]] -==== (Vega only) Expression functions which can update the time range and dashboard filters - -{kib} has extended the Vega expression language with these functions: - -```js -/** - * @param {object} query Elastic Query DSL snippet, as used in the query DSL editor - * @param {string} [index] as defined in Kibana, or default if missing - */ -kibanaAddFilter(query, index) - -/** - * @param {object} query Elastic Query DSL snippet, as used in the query DSL editor - * @param {string} [index] as defined in Kibana, or default if missing - */ -kibanaRemoveFilter(query, index) - -kibanaRemoveAllFilters() - -/** - * Update dashboard time filter to the new values - * @param {number|string|Date} start - * @param {number|string|Date} end - */ -kibanaSetTimeFilter(start, end) -``` - -[float] -[[vega-additional-configuration-options]] -==== Additional configuration options - -[source,yaml] ----- -{ - config: { - kibana: { - // Placement of the Vega-defined signal bindings. - // Can be `left`, `right`, `top`, or `bottom` (default). - controlsLocation: top - // Can be `vertical` or `horizontal` (default). - controlsDirection: vertical - // If true, hides most of Vega and Vega-Lite warnings - hideWarnings: true - // Vega renderer to use: `svg` or `canvas` (default) - renderer: canvas - } - } -} ----- - - -[[vega-notes]] -[[vega-useful-links]] -=== Resources and examples - -To learn more about Vega and Vega-Lite, refer to the resources and examples. - -==== Vega editor -The https://vega.github.io/editor/[Vega Editor] includes examples for Vega & Vega-Lite, but does not support any -{kib}-specific features like {es} requests and interactive base maps. - -==== Vega-Lite resources -* https://vega.github.io/vega-lite/tutorials/getting_started.html[Tutorials] -* https://vega.github.io/vega-lite/docs/[Docs] -* https://vega.github.io/vega-lite/examples/[Examples] - -==== Vega resources -* https://vega.github.io/vega/tutorials/[Tutorials] -* https://vega.github.io/vega/docs/[Docs] -* https://vega.github.io/vega/examples/[Examples] - -TIP: When you use the examples in {kib}, you may -need to modify the "data" section to use absolute URL. For example, -replace `"url": "data/world-110m.json"` with -`"url": "https://vega.github.io/editor/data/world-110m.json"`. diff --git a/examples/alerting_example/server/plugin.ts b/examples/alerting_example/server/plugin.ts index e74cad28f77f4..8e246960937ec 100644 --- a/examples/alerting_example/server/plugin.ts +++ b/examples/alerting_example/server/plugin.ts @@ -38,7 +38,7 @@ export class AlertingExamplePlugin implements Plugin, + private attributeService: AttributeService, { parent, }: { @@ -99,18 +95,21 @@ export class BookEmbeddable }); } - inputIsRefType = (input: BookEmbeddableInput): input is BookByReferenceInput => { + readonly inputIsRefType = (input: BookEmbeddableInput): input is BookByReferenceInput => { return this.attributeService.inputIsRefType(input); }; - getInputAsValueType = async (): Promise => { + readonly getInputAsValueType = async (): Promise => { const input = this.attributeService.getExplicitInputFromEmbeddable(this); return this.attributeService.getInputAsValueType(input); }; - getInputAsRefType = async (): Promise => { + readonly getInputAsRefType = async (): Promise => { const input = this.attributeService.getExplicitInputFromEmbeddable(this); - return this.attributeService.getInputAsRefType(input, { showSaveModal: true }); + return this.attributeService.getInputAsRefType(input, { + showSaveModal: true, + saveModalTitle: this.getTitle(), + }); }; public render(node: HTMLElement) { diff --git a/examples/embeddable_examples/public/book/book_embeddable_factory.tsx b/examples/embeddable_examples/public/book/book_embeddable_factory.tsx index 4c144c3843c47..292261ee16c59 100644 --- a/examples/embeddable_examples/public/book/book_embeddable_factory.tsx +++ b/examples/embeddable_examples/public/book/book_embeddable_factory.tsx @@ -31,8 +31,6 @@ import { BOOK_EMBEDDABLE, BookEmbeddableInput, BookEmbeddableOutput, - BookByValueInput, - BookByReferenceInput, } from './book_embeddable'; import { CreateEditBookComponent } from './create_edit_book_component'; import { OverlayStart } from '../../../../src/core/public'; @@ -66,11 +64,7 @@ export class BookEmbeddableFactoryDefinition getIconForSavedObject: () => 'pencil', }; - private attributeService?: AttributeService< - BookSavedObjectAttributes, - BookByValueInput, - BookByReferenceInput - >; + private attributeService?: AttributeService; constructor(private getStartServices: () => Promise) {} @@ -126,9 +120,7 @@ export class BookEmbeddableFactoryDefinition private async getAttributeService() { if (!this.attributeService) { this.attributeService = await (await this.getStartServices()).getAttributeService< - BookSavedObjectAttributes, - BookByValueInput, - BookByReferenceInput + BookSavedObjectAttributes >(this.type); } return this.attributeService!; diff --git a/examples/embeddable_examples/public/book/edit_book_action.tsx b/examples/embeddable_examples/public/book/edit_book_action.tsx index 5b14dc85b1fc7..3541ace1e5e7e 100644 --- a/examples/embeddable_examples/public/book/edit_book_action.tsx +++ b/examples/embeddable_examples/public/book/edit_book_action.tsx @@ -57,13 +57,13 @@ export const createEditBookAction = (getStartServices: () => Promise { const { openModal, getAttributeService } = await getStartServices(); - const attributeService = getAttributeService< - BookSavedObjectAttributes, - BookByValueInput, - BookByReferenceInput - >(BOOK_SAVED_OBJECT); + const attributeService = getAttributeService(BOOK_SAVED_OBJECT); const onSave = async (attributes: BookSavedObjectAttributes, useRefType: boolean) => { - const newInput = await attributeService.wrapAttributes(attributes, useRefType, embeddable); + const newInput = await attributeService.wrapAttributes( + attributes, + useRefType, + attributeService.getExplicitInputFromEmbeddable(embeddable) + ); if (!useRefType && (embeddable.getInput() as SavedObjectEmbeddableInput).savedObjectId) { // Set the saved object ID to null so that update input will remove the existing savedObjectId... (newInput as BookByValueInput & { savedObjectId: unknown }).savedObjectId = null; diff --git a/examples/embeddable_examples/tsconfig.json b/examples/embeddable_examples/tsconfig.json index 7fa03739119b4..caeed2c1a434f 100644 --- a/examples/embeddable_examples/tsconfig.json +++ b/examples/embeddable_examples/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./target", "skipLibCheck": true diff --git a/examples/embeddable_explorer/tsconfig.json b/examples/embeddable_explorer/tsconfig.json index d508076b33199..798a9c222c5ab 100644 --- a/examples/embeddable_explorer/tsconfig.json +++ b/examples/embeddable_explorer/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./target", "skipLibCheck": true diff --git a/examples/routing_example/tsconfig.json b/examples/routing_example/tsconfig.json index 9bbd9021b2e0a..761a5c4da65ba 100644 --- a/examples/routing_example/tsconfig.json +++ b/examples/routing_example/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./target", "skipLibCheck": true diff --git a/examples/search_examples/tsconfig.json b/examples/search_examples/tsconfig.json index 8a3ced743d0fa..8bec69ca40ccc 100644 --- a/examples/search_examples/tsconfig.json +++ b/examples/search_examples/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./target", "skipLibCheck": true diff --git a/examples/state_containers_examples/tsconfig.json b/examples/state_containers_examples/tsconfig.json index 3f43072c2aade..007322e2d9525 100644 --- a/examples/state_containers_examples/tsconfig.json +++ b/examples/state_containers_examples/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./target", "skipLibCheck": true diff --git a/examples/ui_action_examples/tsconfig.json b/examples/ui_action_examples/tsconfig.json index d508076b33199..798a9c222c5ab 100644 --- a/examples/ui_action_examples/tsconfig.json +++ b/examples/ui_action_examples/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./target", "skipLibCheck": true diff --git a/examples/ui_actions_explorer/tsconfig.json b/examples/ui_actions_explorer/tsconfig.json index 199fbe1fcfa26..119209114a7bb 100644 --- a/examples/ui_actions_explorer/tsconfig.json +++ b/examples/ui_actions_explorer/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./target", "skipLibCheck": true diff --git a/examples/url_generators_examples/tsconfig.json b/examples/url_generators_examples/tsconfig.json index 091130487791b..327b4642a8e7f 100644 --- a/examples/url_generators_examples/tsconfig.json +++ b/examples/url_generators_examples/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./target", "skipLibCheck": true diff --git a/examples/url_generators_explorer/tsconfig.json b/examples/url_generators_explorer/tsconfig.json index 091130487791b..327b4642a8e7f 100644 --- a/examples/url_generators_explorer/tsconfig.json +++ b/examples/url_generators_explorer/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./target", "skipLibCheck": true diff --git a/kibana.d.ts b/kibana.d.ts index d64752abd8b60..517bda374af9d 100644 --- a/kibana.d.ts +++ b/kibana.d.ts @@ -39,8 +39,6 @@ export namespace Legacy { export type KibanaConfig = LegacyKibanaServer.KibanaConfig; export type Request = LegacyKibanaServer.Request; export type ResponseToolkit = LegacyKibanaServer.ResponseToolkit; - export type SavedObjectsClient = LegacyKibanaServer.SavedObjectsClient; - export type SavedObjectsService = LegacyKibanaServer.SavedObjectsLegacyService; export type Server = LegacyKibanaServer.Server; export type InitPluginFunction = LegacyKibanaPluginSpec.InitPluginFunction; diff --git a/package.json b/package.json index 28f2025300f39..7468a49d56959 100644 --- a/package.json +++ b/package.json @@ -63,9 +63,9 @@ "uiFramework:createComponent": "cd packages/kbn-ui-framework && yarn createComponent", "uiFramework:documentComponent": "cd packages/kbn-ui-framework && yarn documentComponent", "kbn:watch": "node scripts/kibana --dev --logging.json=false", - "build:types": "tsc --p tsconfig.types.json", + "build:types": "rm -rf ./target/types && tsc --p tsconfig.types.json", "docs:acceptApiChanges": "node --max-old-space-size=6144 scripts/check_published_api_changes.js --accept", - "kbn:bootstrap": "node scripts/register_git_hook", + "kbn:bootstrap": "node scripts/build_ts_refs && node scripts/register_git_hook", "spec_to_console": "node scripts/spec_to_console", "backport-skip-ci": "backport --prDescription \"[skip-ci]\"", "storybook": "node scripts/storybook", @@ -92,6 +92,7 @@ "**/istanbul-instrumenter-loader/schema-utils": "1.0.0", "**/image-diff/gm/debug": "^2.6.9", "**/load-grunt-config/lodash": "^4.17.20", + "**/node-jose/node-forge": "^0.10.0", "**/react-dom": "^16.12.0", "**/react": "^16.12.0", "**/react-test-renderer": "^16.12.0", @@ -191,7 +192,7 @@ "moment-timezone": "^0.5.27", "mustache": "2.3.2", "node-fetch": "1.7.3", - "node-forge": "^0.9.1", + "node-forge": "^0.10.0", "opn": "^5.5.0", "oppsy": "^2.0.0", "p-map": "^4.0.0", @@ -230,7 +231,7 @@ "@babel/parser": "^7.11.2", "@babel/types": "^7.11.0", "@elastic/apm-rum": "^5.5.0", - "@elastic/charts": "21.0.1", + "@elastic/charts": "21.1.2", "@elastic/ems-client": "7.9.3", "@elastic/eslint-config-kibana": "0.15.0", "@elastic/eslint-plugin-eui": "0.0.2", @@ -305,7 +306,7 @@ "@types/moment-timezone": "^0.5.12", "@types/mustache": "^0.8.31", "@types/node": ">=10.17.17 <10.20.0", - "@types/node-forge": "^0.9.0", + "@types/node-forge": "^0.9.5", "@types/normalize-path": "^3.0.0", "@types/opn": "^5.1.0", "@types/pegjs": "^0.10.1", @@ -366,7 +367,6 @@ "dedent": "^0.7.0", "deepmerge": "^4.2.2", "delete-empty": "^2.0.0", - "elasticsearch-browser": "^16.7.0", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.2", "enzyme-adapter-utils": "^1.13.0", diff --git a/packages/elastic-datemath/tsconfig.json b/packages/elastic-datemath/tsconfig.json index 3604f1004cf6c..cbfe1e8047433 100644 --- a/packages/elastic-datemath/tsconfig.json +++ b/packages/elastic-datemath/tsconfig.json @@ -1,6 +1,9 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": "../../build/tsbuildinfo/packages/elastic-datemath" + }, "include": [ "index.d.ts" - ], + ] } diff --git a/packages/elastic-eslint-config-kibana/typescript.js b/packages/elastic-eslint-config-kibana/typescript.js index 18b11eb62beef..d3e80b7448151 100644 --- a/packages/elastic-eslint-config-kibana/typescript.js +++ b/packages/elastic-eslint-config-kibana/typescript.js @@ -223,7 +223,8 @@ module.exports = { 'no-undef-init': 'error', 'no-unsafe-finally': 'error', 'no-unsanitized/property': 'error', - 'no-unused-expressions': 'error', + 'no-unused-expressions': 'off', + '@typescript-eslint/no-unused-expressions': 'error', 'no-unused-labels': 'error', 'no-var': 'error', 'object-shorthand': 'error', diff --git a/packages/elastic-safer-lodash-set/package.json b/packages/elastic-safer-lodash-set/package.json index f0f425661f605..7602f2fa5924f 100644 --- a/packages/elastic-safer-lodash-set/package.json +++ b/packages/elastic-safer-lodash-set/package.json @@ -16,7 +16,7 @@ "scripts": { "lint": "dependency-check --no-dev package.json set.js setWith.js fp/*.js", "test": "npm run lint && tape test/*.js && npm run test:types", - "test:types": "./scripts/tsd.sh", + "test:types": "tsc --noEmit", "update": "./scripts/update.sh", "save_state": "./scripts/save_state.sh" }, @@ -42,8 +42,5 @@ "ignore": [ "/lodash/" ] - }, - "tsd": { - "directory": "test" } } diff --git a/packages/elastic-safer-lodash-set/scripts/tsd.sh b/packages/elastic-safer-lodash-set/scripts/tsd.sh deleted file mode 100755 index 4572367df415d..0000000000000 --- a/packages/elastic-safer-lodash-set/scripts/tsd.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -# Elasticsearch B.V licenses this file to you under the MIT License. -# See `packages/elastic-safer-lodash-set/LICENSE` for more information. - -# tsd will get confused if it finds a tsconfig.json file in the project -# directory and start to scan the entirety of Kibana. We don't want that. -mv tsconfig.json tsconfig.tmp - -clean_up () { - exit_code=$? - mv tsconfig.tmp tsconfig.json - exit $exit_code -} -trap clean_up EXIT - -./node_modules/.bin/tsd diff --git a/packages/elastic-safer-lodash-set/test/fp.test-d.ts b/packages/elastic-safer-lodash-set/test/fp.ts similarity index 100% rename from packages/elastic-safer-lodash-set/test/fp.test-d.ts rename to packages/elastic-safer-lodash-set/test/fp.ts diff --git a/packages/elastic-safer-lodash-set/test/fp_assoc.test-d.ts b/packages/elastic-safer-lodash-set/test/fp_assoc.ts similarity index 100% rename from packages/elastic-safer-lodash-set/test/fp_assoc.test-d.ts rename to packages/elastic-safer-lodash-set/test/fp_assoc.ts diff --git a/packages/elastic-safer-lodash-set/test/fp_assocPath.test-d.ts b/packages/elastic-safer-lodash-set/test/fp_assocPath.ts similarity index 100% rename from packages/elastic-safer-lodash-set/test/fp_assocPath.test-d.ts rename to packages/elastic-safer-lodash-set/test/fp_assocPath.ts diff --git a/packages/elastic-safer-lodash-set/test/fp_set.test-d.ts b/packages/elastic-safer-lodash-set/test/fp_set.ts similarity index 100% rename from packages/elastic-safer-lodash-set/test/fp_set.test-d.ts rename to packages/elastic-safer-lodash-set/test/fp_set.ts diff --git a/packages/elastic-safer-lodash-set/test/fp_setWith.test-d.ts b/packages/elastic-safer-lodash-set/test/fp_setWith.ts similarity index 100% rename from packages/elastic-safer-lodash-set/test/fp_setWith.test-d.ts rename to packages/elastic-safer-lodash-set/test/fp_setWith.ts diff --git a/packages/elastic-safer-lodash-set/test/index.test-d.ts b/packages/elastic-safer-lodash-set/test/index.test-d.ts deleted file mode 100644 index ab29d7de5a03f..0000000000000 --- a/packages/elastic-safer-lodash-set/test/index.test-d.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Elasticsearch B.V licenses this file to you under the MIT License. - * See `packages/elastic-safer-lodash-set/LICENSE` for more information. - */ - -import { expectType } from 'tsd'; -import { set, setWith } from '../'; - -const someObj: object = {}; -const anyValue: any = 'any value'; - -expectType(set(someObj, 'a.b.c', anyValue)); -expectType( - setWith(someObj, 'a.b.c', anyValue, (value, key, obj) => { - expectType(value); - expectType(key); - expectType(obj); - }) -); - -expectType(set(someObj, ['a.b.c'], anyValue)); -expectType( - setWith(someObj, ['a.b.c'], anyValue, (value, key, obj) => { - expectType(value); - expectType(key); - expectType(obj); - }) -); - -expectType(set(someObj, ['a.b.c', 2, Symbol('hep')], anyValue)); -expectType( - setWith(someObj, ['a.b.c', 2, Symbol('hep')], anyValue, (value, key, obj) => { - expectType(value); - expectType(key); - expectType(obj); - }) -); diff --git a/packages/elastic-safer-lodash-set/test/index.ts b/packages/elastic-safer-lodash-set/test/index.ts new file mode 100644 index 0000000000000..2090c1adcfce1 --- /dev/null +++ b/packages/elastic-safer-lodash-set/test/index.ts @@ -0,0 +1,37 @@ +/* + * Elasticsearch B.V licenses this file to you under the MIT License. + * See `packages/elastic-safer-lodash-set/LICENSE` for more information. + */ + +import { expectType } from 'tsd'; +import { set, setWith } from '..'; + +const someObj: object = {}; +const anyValue: any = 'any value'; + +expectType(set(someObj, 'a.b.c', anyValue)); +expectType( + setWith(someObj, 'a.b.c', anyValue, (value, key, obj) => { + expectType(value); + expectType(key); + expectType(obj); + }) +); + +expectType(set(someObj, ['a.b.c'], anyValue)); +expectType( + setWith(someObj, ['a.b.c'], anyValue, (value, key, obj) => { + expectType(value); + expectType(key); + expectType(obj); + }) +); + +expectType(set(someObj, ['a.b.c', 2, Symbol('hep')], anyValue)); +expectType( + setWith(someObj, ['a.b.c', 2, Symbol('hep')], anyValue, (value, key, obj) => { + expectType(value); + expectType(key); + expectType(obj); + }) +); diff --git a/packages/elastic-safer-lodash-set/test/set.test-d.ts b/packages/elastic-safer-lodash-set/test/set.ts similarity index 100% rename from packages/elastic-safer-lodash-set/test/set.test-d.ts rename to packages/elastic-safer-lodash-set/test/set.ts diff --git a/packages/elastic-safer-lodash-set/test/setWith.test-d.ts b/packages/elastic-safer-lodash-set/test/setWith.ts similarity index 100% rename from packages/elastic-safer-lodash-set/test/setWith.test-d.ts rename to packages/elastic-safer-lodash-set/test/setWith.ts diff --git a/packages/elastic-safer-lodash-set/tsconfig.json b/packages/elastic-safer-lodash-set/tsconfig.json index bc1d1a3a7e413..6517e5c60ee01 100644 --- a/packages/elastic-safer-lodash-set/tsconfig.json +++ b/packages/elastic-safer-lodash-set/tsconfig.json @@ -1,9 +1,9 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": "../../build/tsbuildinfo/packages/elastic-safer-lodash-set" + }, "include": [ - "**/*" + "**/*", ], - "exclude": [ - "**/*.test-d.ts" - ] } diff --git a/packages/kbn-analytics/scripts/build.js b/packages/kbn-analytics/scripts/build.js index 448d1ca9332f2..0e00a144d0b92 100644 --- a/packages/kbn-analytics/scripts/build.js +++ b/packages/kbn-analytics/scripts/build.js @@ -71,7 +71,6 @@ run( proc.run(padRight(10, 'tsc'), { cmd: 'tsc', args: [ - '--emitDeclarationOnly', ...(flags.watch ? ['--watch', '--preserveWatchOutput', 'true'] : []), ...(flags['source-maps'] ? ['--declarationMap', 'true'] : []), ], diff --git a/packages/kbn-analytics/tsconfig.json b/packages/kbn-analytics/tsconfig.json index fdd9e8281fba8..861e0204a31a2 100644 --- a/packages/kbn-analytics/tsconfig.json +++ b/packages/kbn-analytics/tsconfig.json @@ -1,8 +1,9 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "declaration": true, - "declarationDir": "./target/types", + "emitDeclarationOnly": true, + "outDir": "./target/types", "stripInternal": true, "declarationMap": true, "types": [ @@ -11,7 +12,7 @@ ] }, "include": [ - "./src/**/*.ts" + "src/**/*" ], "exclude": [ "target" diff --git a/packages/kbn-config-schema/package.json b/packages/kbn-config-schema/package.json index 10b1741d98441..9abe7f31dd060 100644 --- a/packages/kbn-config-schema/package.json +++ b/packages/kbn-config-schema/package.json @@ -11,7 +11,7 @@ }, "devDependencies": { "typescript": "4.0.2", - "tsd": "^0.7.4" + "tsd": "^0.13.1" }, "peerDependencies": { "lodash": "^4.17.15", diff --git a/packages/kbn-config-schema/tsconfig.json b/packages/kbn-config-schema/tsconfig.json index f6c61268da17c..6a268f2e7c016 100644 --- a/packages/kbn-config-schema/tsconfig.json +++ b/packages/kbn-config-schema/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "declaration": true, "declarationDir": "./target/types", diff --git a/packages/kbn-dev-utils/tsconfig.json b/packages/kbn-dev-utils/tsconfig.json index 0ec058eeb8a28..1c6c671d0b768 100644 --- a/packages/kbn-dev-utils/tsconfig.json +++ b/packages/kbn-dev-utils/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "target", "target": "ES2019", diff --git a/packages/kbn-es-archiver/src/actions/save.ts b/packages/kbn-es-archiver/src/actions/save.ts index 2f87cabadee6c..84a0ce09936d0 100644 --- a/packages/kbn-es-archiver/src/actions/save.ts +++ b/packages/kbn-es-archiver/src/actions/save.ts @@ -39,6 +39,7 @@ export async function saveAction({ dataDir, log, raw, + query, }: { name: string; indices: string | string[]; @@ -46,6 +47,7 @@ export async function saveAction({ dataDir: string; log: ToolingLog; raw: boolean; + query?: Record; }) { const outputDir = resolve(dataDir, name); const stats = createStats(name, log); @@ -69,7 +71,7 @@ export async function saveAction({ // export all documents from matching indexes into data.json.gz createPromiseFromStreams([ createListStream(indices), - createGenerateDocRecordsStream(client, stats, progress), + createGenerateDocRecordsStream({ client, stats, progress, query }), ...createFormatArchiveStreams({ gzip: !raw }), createWriteStream(resolve(outputDir, `data.json${raw ? '' : '.gz'}`)), ] as [Readable, ...Writable[]]), diff --git a/packages/kbn-es-archiver/src/cli.ts b/packages/kbn-es-archiver/src/cli.ts index 1745bd862b434..41abe83c148cd 100644 --- a/packages/kbn-es-archiver/src/cli.ts +++ b/packages/kbn-es-archiver/src/cli.ts @@ -122,8 +122,10 @@ export function runCli() { `, flags: { boolean: ['raw'], + string: ['query'], help: ` --raw don't gzip the archives + --query query object to limit the documents being archived, needs to be properly escaped JSON `, }, async run({ flags, esArchiver }) { @@ -140,7 +142,17 @@ export function runCli() { throw createFlagError('--raw does not take a value'); } - await esArchiver.save(name, indices, { raw }); + const query = flags.query; + let parsedQuery; + if (typeof query === 'string') { + try { + parsedQuery = JSON.parse(query); + } catch (err) { + throw createFlagError('--query should be valid JSON'); + } + } + + await esArchiver.save(name, indices, { raw, query: parsedQuery }); }, }) .command({ diff --git a/packages/kbn-es-archiver/src/es_archiver.ts b/packages/kbn-es-archiver/src/es_archiver.ts index e335652195b86..d61e7d2a422e8 100644 --- a/packages/kbn-es-archiver/src/es_archiver.ts +++ b/packages/kbn-es-archiver/src/es_archiver.ts @@ -62,7 +62,11 @@ export class EsArchiver { * @property {Boolean} options.raw - should the archive be raw (unzipped) or not * @return Promise */ - async save(name: string, indices: string | string[], { raw = false }: { raw?: boolean } = {}) { + async save( + name: string, + indices: string | string[], + { raw = false, query }: { raw?: boolean; query?: Record } = {} + ) { return await saveAction({ name, indices, @@ -70,6 +74,7 @@ export class EsArchiver { client: this.client, dataDir: this.dataDir, log: this.log, + query, }); } diff --git a/packages/kbn-es-archiver/src/lib/docs/__tests__/generate_doc_records_stream.ts b/packages/kbn-es-archiver/src/lib/docs/__tests__/generate_doc_records_stream.ts index 2214f7ae9f2ea..3c5fc742a6e10 100644 --- a/packages/kbn-es-archiver/src/lib/docs/__tests__/generate_doc_records_stream.ts +++ b/packages/kbn-es-archiver/src/lib/docs/__tests__/generate_doc_records_stream.ts @@ -47,7 +47,7 @@ describe('esArchiver: createGenerateDocRecordsStream()', () => { const progress = new Progress(); await createPromiseFromStreams([ createListStream(['logstash-*']), - createGenerateDocRecordsStream(client, stats, progress), + createGenerateDocRecordsStream({ client, stats, progress }), ]); expect(progress.getTotal()).to.be(0); @@ -74,7 +74,7 @@ describe('esArchiver: createGenerateDocRecordsStream()', () => { const progress = new Progress(); await createPromiseFromStreams([ createListStream(['logstash-*']), - createGenerateDocRecordsStream(client, stats, progress), + createGenerateDocRecordsStream({ client, stats, progress }), ]); expect(progress.getTotal()).to.be(0); @@ -115,7 +115,7 @@ describe('esArchiver: createGenerateDocRecordsStream()', () => { const progress = new Progress(); const docRecords = await createPromiseFromStreams([ createListStream(['index1', 'index2']), - createGenerateDocRecordsStream(client, stats, progress), + createGenerateDocRecordsStream({ client, stats, progress }), createConcatStream([]), ]); diff --git a/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.ts b/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.ts index e255a0abc36c5..87c166fe275cc 100644 --- a/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.ts +++ b/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.ts @@ -25,7 +25,17 @@ import { Progress } from '../progress'; const SCROLL_SIZE = 1000; const SCROLL_TIMEOUT = '1m'; -export function createGenerateDocRecordsStream(client: Client, stats: Stats, progress: Progress) { +export function createGenerateDocRecordsStream({ + client, + stats, + progress, + query, +}: { + client: Client; + stats: Stats; + progress: Progress; + query?: Record; +}) { return new Transform({ writableObjectMode: true, readableObjectMode: true, @@ -41,6 +51,9 @@ export function createGenerateDocRecordsStream(client: Client, stats: Stats, pro scroll: SCROLL_TIMEOUT, size: SCROLL_SIZE, _source: true, + body: { + query, + }, rest_total_hits_as_int: true, // not declared on SearchParams type } as SearchParams); remainingHits = resp.hits.total; diff --git a/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.ts b/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.ts index b4b98f8ae262c..07ee1420741c9 100644 --- a/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.ts +++ b/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.ts @@ -37,6 +37,10 @@ export function createGenerateIndexRecordsStream(client: Client, stats: Stats) { '-*.settings.index.uuid', '-*.settings.index.version', '-*.settings.index.provided_name', + '-*.settings.index.frozen', + '-*.settings.index.search.throttled', + '-*.settings.index.query', + '-*.settings.index.routing', ], })) as Record; diff --git a/packages/kbn-es-archiver/src/lib/streams.ts b/packages/kbn-es-archiver/src/lib/streams.ts deleted file mode 100644 index a90afbe0c4d25..0000000000000 --- a/packages/kbn-es-archiver/src/lib/streams.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 '../../../../src/legacy/utils/streams'; diff --git a/src/legacy/utils/streams/concat_stream.test.js b/packages/kbn-es-archiver/src/lib/streams/concat_stream.test.js similarity index 100% rename from src/legacy/utils/streams/concat_stream.test.js rename to packages/kbn-es-archiver/src/lib/streams/concat_stream.test.js diff --git a/packages/kbn-es-archiver/src/lib/streams/concat_stream.ts b/packages/kbn-es-archiver/src/lib/streams/concat_stream.ts new file mode 100644 index 0000000000000..03dd894067afc --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/concat_stream.ts @@ -0,0 +1,41 @@ +/* + * 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 { createReduceStream } from './reduce_stream'; + +/** + * Creates a Transform stream that consumes all provided + * values and concatenates them using each values `concat` + * method. + * + * Concatenate strings: + * createListStream(['f', 'o', 'o']) + * .pipe(createConcatStream()) + * .on('data', console.log) + * // logs "foo" + * + * Concatenate values into an array: + * createListStream([1,2,3]) + * .pipe(createConcatStream([])) + * .on('data', console.log) + * // logs "[1,2,3]" + */ +export function createConcatStream(initial: any) { + return createReduceStream((acc, chunk) => acc.concat(chunk), initial); +} diff --git a/src/legacy/utils/streams/concat_stream_providers.test.js b/packages/kbn-es-archiver/src/lib/streams/concat_stream_providers.test.js similarity index 100% rename from src/legacy/utils/streams/concat_stream_providers.test.js rename to packages/kbn-es-archiver/src/lib/streams/concat_stream_providers.test.js diff --git a/packages/kbn-es-archiver/src/lib/streams/concat_stream_providers.ts b/packages/kbn-es-archiver/src/lib/streams/concat_stream_providers.ts new file mode 100644 index 0000000000000..4794d76cc7f84 --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/concat_stream_providers.ts @@ -0,0 +1,60 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PassThrough, TransformOptions } from 'stream'; + +/** + * Write the data and errors from a list of stream providers + * to a single stream in order. Stream providers are only + * called right before they will be consumed, and only one + * provider will be active at a time. + */ +export function concatStreamProviders( + sourceProviders: Array<() => NodeJS.ReadableStream>, + options: TransformOptions = {} +) { + const destination = new PassThrough(options); + const queue = sourceProviders.slice(); + + (function pipeNext() { + const provider = queue.shift(); + + if (!provider) { + return; + } + + const source = provider(); + const isLast = !queue.length; + + // if there are more sources to pipe, hook + // into the source completion + if (!isLast) { + source.once('end', pipeNext); + } + + source + // proxy errors from the source to the destination + .once('error', (error) => destination.emit('error', error)) + // pipe the source to the destination but only proxy the + // end event if this is the last source + .pipe(destination, { end: isLast }); + })(); + + return destination; +} diff --git a/src/legacy/utils/streams/filter_stream.test.ts b/packages/kbn-es-archiver/src/lib/streams/filter_stream.test.ts similarity index 100% rename from src/legacy/utils/streams/filter_stream.test.ts rename to packages/kbn-es-archiver/src/lib/streams/filter_stream.test.ts diff --git a/packages/kbn-es-archiver/src/lib/streams/filter_stream.ts b/packages/kbn-es-archiver/src/lib/streams/filter_stream.ts new file mode 100644 index 0000000000000..738b9d5793d06 --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/filter_stream.ts @@ -0,0 +1,33 @@ +/* + * 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 { Transform } from 'stream'; + +export function createFilterStream(fn: (obj: T) => boolean) { + return new Transform({ + objectMode: true, + async transform(obj, _, done) { + const canPushDownStream = fn(obj); + if (canPushDownStream) { + this.push(obj); + } + done(); + }, + }); +} diff --git a/src/legacy/utils/streams/index.js b/packages/kbn-es-archiver/src/lib/streams/index.ts similarity index 100% rename from src/legacy/utils/streams/index.js rename to packages/kbn-es-archiver/src/lib/streams/index.ts diff --git a/src/legacy/utils/streams/intersperse_stream.test.js b/packages/kbn-es-archiver/src/lib/streams/intersperse_stream.test.js similarity index 100% rename from src/legacy/utils/streams/intersperse_stream.test.js rename to packages/kbn-es-archiver/src/lib/streams/intersperse_stream.test.js diff --git a/packages/kbn-es-archiver/src/lib/streams/intersperse_stream.ts b/packages/kbn-es-archiver/src/lib/streams/intersperse_stream.ts new file mode 100644 index 0000000000000..eb2e3d3087d4a --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/intersperse_stream.ts @@ -0,0 +1,61 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Transform } from 'stream'; + +/** + * Create a Transform stream that receives values in object mode, + * and intersperses a chunk between each object received. + * + * This is useful for writing lists: + * + * createListStream(['foo', 'bar']) + * .pipe(createIntersperseStream('\n')) + * .pipe(process.stdout) // outputs "foo\nbar" + * + * Combine with a concat stream to get "join" like functionality: + * + * await createPromiseFromStreams([ + * createListStream(['foo', 'bar']), + * createIntersperseStream(' '), + * createConcatStream() + * ]) // produces a single value "foo bar" + */ +export function createIntersperseStream(intersperseChunk: any) { + let first = true; + + return new Transform({ + writableObjectMode: true, + readableObjectMode: true, + transform(chunk, _, callback) { + try { + if (first) { + first = false; + } else { + this.push(intersperseChunk); + } + + this.push(chunk); + callback(undefined); + } catch (err) { + callback(err); + } + }, + }); +} diff --git a/src/legacy/utils/streams/list_stream.test.js b/packages/kbn-es-archiver/src/lib/streams/list_stream.test.js similarity index 100% rename from src/legacy/utils/streams/list_stream.test.js rename to packages/kbn-es-archiver/src/lib/streams/list_stream.test.js diff --git a/packages/kbn-es-archiver/src/lib/streams/list_stream.ts b/packages/kbn-es-archiver/src/lib/streams/list_stream.ts new file mode 100644 index 0000000000000..c061b969b3c09 --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/list_stream.ts @@ -0,0 +1,41 @@ +/* + * 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 { Readable } from 'stream'; + +/** + * Create a Readable stream that provides the items + * from a list as objects to subscribers + */ +export function createListStream(items: any | any[] = []) { + const queue: any[] = [].concat(items); + + return new Readable({ + objectMode: true, + read(size) { + queue.splice(0, size).forEach((item) => { + this.push(item); + }); + + if (!queue.length) { + this.push(null); + } + }, + }); +} diff --git a/src/legacy/utils/streams/map_stream.test.js b/packages/kbn-es-archiver/src/lib/streams/map_stream.test.js similarity index 100% rename from src/legacy/utils/streams/map_stream.test.js rename to packages/kbn-es-archiver/src/lib/streams/map_stream.test.js diff --git a/packages/kbn-es-archiver/src/lib/streams/map_stream.ts b/packages/kbn-es-archiver/src/lib/streams/map_stream.ts new file mode 100644 index 0000000000000..e88c512a38653 --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/map_stream.ts @@ -0,0 +1,36 @@ +/* + * 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 { Transform } from 'stream'; + +export function createMapStream(fn: (chunk: any, i: number) => T | Promise) { + let i = 0; + + return new Transform({ + objectMode: true, + async transform(value, _, done) { + try { + this.push(await fn(value, i++)); + done(); + } catch (err) { + done(err); + } + }, + }); +} diff --git a/src/legacy/utils/streams/promise_from_streams.test.js b/packages/kbn-es-archiver/src/lib/streams/promise_from_streams.test.js similarity index 100% rename from src/legacy/utils/streams/promise_from_streams.test.js rename to packages/kbn-es-archiver/src/lib/streams/promise_from_streams.test.js diff --git a/packages/kbn-es-archiver/src/lib/streams/promise_from_streams.ts b/packages/kbn-es-archiver/src/lib/streams/promise_from_streams.ts new file mode 100644 index 0000000000000..fefb18be14780 --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/promise_from_streams.ts @@ -0,0 +1,64 @@ +/* + * 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. + */ + +/** + * Take an array of streams, pipe the output + * from each one into the next, listening for + * errors from any of the streams, and then resolve + * the promise once the final stream has finished + * writing/reading. + * + * If the last stream is readable, it's final value + * will be provided as the promise value. + * + * Errors emitted from any stream will cause + * the promise to be rejected with that error. + */ + +import { pipeline, Writable } from 'stream'; +import { promisify } from 'util'; + +const asyncPipeline = promisify(pipeline); + +export async function createPromiseFromStreams(streams: any): Promise { + let finalChunk: any; + const last = streams[streams.length - 1]; + if (typeof last.read !== 'function' && streams.length === 1) { + // For a nicer error than what stream.pipeline throws + throw new Error('A minimum of 2 streams is required when a non-readable stream is given'); + } + if (typeof last.read === 'function') { + // We are pushing a writable stream to capture the last chunk + streams.push( + new Writable({ + // Use object mode even when "last" stream isn't. This allows to + // capture the last chunk as-is. + objectMode: true, + write(chunk, _, done) { + finalChunk = chunk; + done(); + }, + }) + ); + } + + await asyncPipeline(...(streams as [any])); + + return finalChunk; +} diff --git a/src/legacy/utils/streams/reduce_stream.test.js b/packages/kbn-es-archiver/src/lib/streams/reduce_stream.test.js similarity index 100% rename from src/legacy/utils/streams/reduce_stream.test.js rename to packages/kbn-es-archiver/src/lib/streams/reduce_stream.test.js diff --git a/packages/kbn-es-archiver/src/lib/streams/reduce_stream.ts b/packages/kbn-es-archiver/src/lib/streams/reduce_stream.ts new file mode 100644 index 0000000000000..d9458e9a11c33 --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/reduce_stream.ts @@ -0,0 +1,77 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Transform } from 'stream'; + +/** + * Create a transform stream that consumes each chunk it receives + * and passes it to the reducer, which will return the new value + * for the stream. Once all chunks have been received the reduce + * stream provides the result of final call to the reducer to + * subscribers. + */ +export function createReduceStream( + reducer: (acc: any, chunk: any, env: string) => any, + initial: any +) { + let i = -1; + let value = initial; + + // if the reducer throws an error then the value is + // considered invalid and the stream will never provide + // it to subscribers. We will also stop calling the + // reducer for any new data that is provided to us + let failed = false; + + if (typeof reducer !== 'function') { + throw new TypeError('reducer must be a function'); + } + + return new Transform({ + readableObjectMode: true, + writableObjectMode: true, + async transform(chunk, enc, callback) { + try { + if (failed) { + return callback(); + } + + i += 1; + if (i === 0 && initial === undefined) { + value = chunk; + } else { + value = await reducer(value, chunk, enc); + } + + callback(); + } catch (err) { + failed = true; + callback(err); + } + }, + + flush(callback) { + if (!failed) { + this.push(value); + } + + callback(); + }, + }); +} diff --git a/src/legacy/utils/streams/replace_stream.test.js b/packages/kbn-es-archiver/src/lib/streams/replace_stream.test.js similarity index 100% rename from src/legacy/utils/streams/replace_stream.test.js rename to packages/kbn-es-archiver/src/lib/streams/replace_stream.test.js diff --git a/packages/kbn-es-archiver/src/lib/streams/replace_stream.ts b/packages/kbn-es-archiver/src/lib/streams/replace_stream.ts new file mode 100644 index 0000000000000..fe2ba1fcdf31c --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/replace_stream.ts @@ -0,0 +1,84 @@ +/* + * 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 { Transform } from 'stream'; + +export function createReplaceStream(toReplace: string, replacement: string) { + if (typeof toReplace !== 'string') { + throw new TypeError('toReplace must be a string'); + } + + let buffer = Buffer.alloc(0); + return new Transform({ + objectMode: false, + async transform(value, _, done) { + try { + buffer = Buffer.concat([buffer, value], buffer.length + value.length); + + while (true) { + // try to find the next instance of `toReplace` in buffer + const index = buffer.indexOf(toReplace); + + // if there is no next instance, break + if (index === -1) { + break; + } + + // flush everything to the left of the next instance + // of `toReplace` + this.push(buffer.slice(0, index)); + + // then flush an instance of `replacement` + this.push(replacement); + + // and finally update the buffer to include everything + // to the right of `toReplace`, dropping to replace from the buffer + buffer = buffer.slice(index + toReplace.length); + } + + // until now we have only flushed data that is to the left + // of a discovered instance of `toReplace`. If `toReplace` is + // never found this would lead to us buffering the entire stream. + // + // Instead, we only keep enough buffer to complete a potentially + // partial instance of `toReplace` + if (buffer.length > toReplace.length) { + // the entire buffer except the last `toReplace.length` bytes + // so that if all but one byte from `toReplace` is in the buffer, + // and the next chunk delivers the necessary byte, the buffer will then + // contain a complete `toReplace` token. + this.push(buffer.slice(0, buffer.length - toReplace.length)); + buffer = buffer.slice(-toReplace.length); + } + + done(); + } catch (err) { + done(err); + } + }, + + flush(callback) { + if (buffer.length) { + this.push(buffer); + } + + callback(); + }, + }); +} diff --git a/src/legacy/utils/streams/split_stream.test.js b/packages/kbn-es-archiver/src/lib/streams/split_stream.test.js similarity index 100% rename from src/legacy/utils/streams/split_stream.test.js rename to packages/kbn-es-archiver/src/lib/streams/split_stream.test.js diff --git a/packages/kbn-es-archiver/src/lib/streams/split_stream.ts b/packages/kbn-es-archiver/src/lib/streams/split_stream.ts new file mode 100644 index 0000000000000..1c9b59449bd92 --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/split_stream.ts @@ -0,0 +1,71 @@ +/* + * 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 { Transform } from 'stream'; + +/** + * Creates a Transform stream that consumes a stream of Buffers + * and produces a stream of strings (in object mode) by splitting + * the received bytes using the splitChunk. + * + * Ways this is behaves like String#split: + * - instances of splitChunk are removed from the input + * - splitChunk can be on any size + * - if there are no bytes found after the last splitChunk + * a final empty chunk is emitted + * + * Ways this deviates from String#split: + * - splitChunk cannot be a regexp + * - an empty string or Buffer will not produce a stream of individual + * bytes like `string.split('')` would + */ +export function createSplitStream(splitChunk: string) { + let unsplitBuffer = Buffer.alloc(0); + + return new Transform({ + writableObjectMode: false, + readableObjectMode: true, + transform(chunk, _, callback) { + try { + let i; + let toSplit = Buffer.concat([unsplitBuffer, chunk]); + while ((i = toSplit.indexOf(splitChunk)) !== -1) { + const slice = toSplit.slice(0, i); + toSplit = toSplit.slice(i + splitChunk.length); + this.push(slice.toString('utf8')); + } + + unsplitBuffer = toSplit; + callback(undefined); + } catch (err) { + callback(err); + } + }, + + flush(callback) { + try { + this.push(unsplitBuffer.toString('utf8')); + + callback(undefined); + } catch (err) { + callback(err); + } + }, + }); +} diff --git a/packages/kbn-es-archiver/tsconfig.json b/packages/kbn-es-archiver/tsconfig.json index 6ffa64d91fba0..02209a29e5817 100644 --- a/packages/kbn-es-archiver/tsconfig.json +++ b/packages/kbn-es-archiver/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./target", "declaration": true, diff --git a/packages/kbn-es/tsconfig.json b/packages/kbn-es/tsconfig.json index 6bb61453c99e7..9487a28232684 100644 --- a/packages/kbn-es/tsconfig.json +++ b/packages/kbn-es/tsconfig.json @@ -1,6 +1,9 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": "../../build/tsbuildinfo/packages/kbn-es" + }, "include": [ - "src/**/*.ts" + "src/**/*" ] } diff --git a/packages/kbn-eslint-import-resolver-kibana/lib/get_webpack_config.js b/packages/kbn-eslint-import-resolver-kibana/lib/get_webpack_config.js index d4e234e3a6a2e..60a03ae8a104e 100755 --- a/packages/kbn-eslint-import-resolver-kibana/lib/get_webpack_config.js +++ b/packages/kbn-eslint-import-resolver-kibana/lib/get_webpack_config.js @@ -27,11 +27,7 @@ exports.getWebpackConfig = function (kibanaPath) { mainFields: ['browser', 'main'], modules: ['node_modules', resolve(kibanaPath, 'node_modules')], alias: { - // Kibana defaults https://github.com/elastic/kibana/blob/6998f074542e8c7b32955db159d15661aca253d7/src/legacy/ui/ui_bundler_env.js#L30-L36 - ui: resolve(kibanaPath, 'src/legacy/ui/public'), - // Dev defaults for test bundle https://github.com/elastic/kibana/blob/6998f074542e8c7b32955db159d15661aca253d7/src/core_plugins/tests_bundle/index.js#L73-L78 - ng_mock$: resolve(kibanaPath, 'src/test_utils/public/ng_mock'), fixtures: resolve(kibanaPath, 'src/fixtures'), test_utils: resolve(kibanaPath, 'src/test_utils/public'), }, diff --git a/packages/kbn-expect/tsconfig.json b/packages/kbn-expect/tsconfig.json index a09ae2d7ae641..ae7e9ff090cc2 100644 --- a/packages/kbn-expect/tsconfig.json +++ b/packages/kbn-expect/tsconfig.json @@ -1,5 +1,8 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": "../../build/tsbuildinfo/packages/kbn-expect" + }, "include": [ "expect.js.d.ts" ] diff --git a/packages/kbn-i18n/scripts/build.js b/packages/kbn-i18n/scripts/build.js index 62e1a35f00399..1d2b5031e37d7 100644 --- a/packages/kbn-i18n/scripts/build.js +++ b/packages/kbn-i18n/scripts/build.js @@ -71,7 +71,6 @@ run( proc.run(padRight(10, 'tsc'), { cmd: 'tsc', args: [ - '--emitDeclarationOnly', ...(flags.watch ? ['--watch', '--preserveWatchOutput', 'true'] : []), ...(flags['source-maps'] ? ['--declarationMap', 'true'] : []), ], diff --git a/packages/kbn-i18n/tsconfig.json b/packages/kbn-i18n/tsconfig.json index d3dae3078c1d7..c6380f1cde969 100644 --- a/packages/kbn-i18n/tsconfig.json +++ b/packages/kbn-i18n/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "include": [ "src/**/*.ts", "src/**/*.tsx", @@ -11,7 +11,8 @@ ], "compilerOptions": { "declaration": true, - "declarationDir": "./target/types", + "emitDeclarationOnly": true, + "outDir": "./target/types", "types": [ "jest", "node" diff --git a/packages/kbn-interpreter/tsconfig.json b/packages/kbn-interpreter/tsconfig.json index 63376a7ca1ae8..3b81bbb118a55 100644 --- a/packages/kbn-interpreter/tsconfig.json +++ b/packages/kbn-interpreter/tsconfig.json @@ -1,4 +1,7 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": "../../build/tsbuildinfo/packages/kbn-interpreter" + }, "include": ["index.d.ts", "src/**/*.d.ts"] } diff --git a/packages/kbn-monaco/tsconfig.json b/packages/kbn-monaco/tsconfig.json index 95acfd32b24dd..6d3f433c6a6d1 100644 --- a/packages/kbn-monaco/tsconfig.json +++ b/packages/kbn-monaco/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./target", "declaration": true, diff --git a/packages/kbn-optimizer/src/worker/webpack.config.ts b/packages/kbn-optimizer/src/worker/webpack.config.ts index 6b07384910abb..9f2c5654a8bd4 100644 --- a/packages/kbn-optimizer/src/worker/webpack.config.ts +++ b/packages/kbn-optimizer/src/worker/webpack.config.ts @@ -79,7 +79,6 @@ export function getWebpackConfig(bundle: Bundle, bundleRefs: BundleRefs, worker: // or which have require() statements that should be ignored because the file is // already bundled with all its necessary depedencies noParse: [ - /[\/\\]node_modules[\/\\]elasticsearch-browser[\/\\]/, /[\/\\]node_modules[\/\\]lodash[\/\\]index\.js$/, /[\/\\]node_modules[\/\\]vega[\/\\]build[\/\\]vega\.js$/, ], diff --git a/packages/kbn-optimizer/tsconfig.json b/packages/kbn-optimizer/tsconfig.json index e2994f4d02414..20b06b5658cbc 100644 --- a/packages/kbn-optimizer/tsconfig.json +++ b/packages/kbn-optimizer/tsconfig.json @@ -1,5 +1,8 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": "../../build/tsbuildinfo/packages/kbn-optimizer" + }, "include": [ "index.d.ts", "src/**/*" diff --git a/packages/kbn-plugin-generator/tsconfig.json b/packages/kbn-plugin-generator/tsconfig.json index fc88223dae4b2..c54ff041d7065 100644 --- a/packages/kbn-plugin-generator/tsconfig.json +++ b/packages/kbn-plugin-generator/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "target", "target": "ES2019", diff --git a/packages/kbn-plugin-helpers/tsconfig.json b/packages/kbn-plugin-helpers/tsconfig.json index e794b11b14afa..651bc79d6e707 100644 --- a/packages/kbn-plugin-helpers/tsconfig.json +++ b/packages/kbn-plugin-helpers/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "target", "declaration": true, diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index eb2d0d2581a34..9a3bb1c687032 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -27651,6 +27651,7 @@ var eos = function(stream, opts, callback) { var rs = stream._readableState; var readable = opts.readable || (opts.readable !== false && stream.readable); var writable = opts.writable || (opts.writable !== false && stream.writable); + var cancelled = false; var onlegacyfinish = function() { if (!stream.writable) onfinish(); @@ -27675,8 +27676,13 @@ var eos = function(stream, opts, callback) { }; var onclose = function() { - if (readable && !(rs && rs.ended)) return callback.call(stream, new Error('premature close')); - if (writable && !(ws && ws.ended)) return callback.call(stream, new Error('premature close')); + process.nextTick(onclosenexttick); + }; + + var onclosenexttick = function() { + if (cancelled) return; + if (readable && !(rs && (rs.ended && !rs.destroyed))) return callback.call(stream, new Error('premature close')); + if (writable && !(ws && (ws.ended && !ws.destroyed))) return callback.call(stream, new Error('premature close')); }; var onrequest = function() { @@ -27701,6 +27707,7 @@ var eos = function(stream, opts, callback) { stream.on('close', onclose); return function() { + cancelled = true; stream.removeListener('complete', onfinish); stream.removeListener('abort', onclose); stream.removeListener('request', onrequest); diff --git a/packages/kbn-pm/tsconfig.json b/packages/kbn-pm/tsconfig.json index c13a9243c50aa..175c4701f2e5b 100644 --- a/packages/kbn-pm/tsconfig.json +++ b/packages/kbn-pm/tsconfig.json @@ -1,12 +1,13 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "include": [ "./index.d.ts", "./src/**/*.ts", - "./dist/*.d.ts", + "./dist/*.d.ts" ], "exclude": [], "compilerOptions": { + "tsBuildInfoFile": "../../build/tsbuildinfo/packages/kbn-pm", "types": [ "jest", "node" diff --git a/packages/kbn-release-notes/src/cli.ts b/packages/kbn-release-notes/src/cli.ts index 44b4a7a0282d2..7dcfa38078391 100644 --- a/packages/kbn-release-notes/src/cli.ts +++ b/packages/kbn-release-notes/src/cli.ts @@ -25,8 +25,7 @@ import { run, createFlagError, createFailError, REPO_ROOT } from '@kbn/dev-utils import { FORMATS, SomeFormat } from './formats'; import { - iterRelevantPullRequests, - getPr, + PrApi, Version, ClassifiedPr, streamFromIterable, @@ -48,6 +47,7 @@ export function runReleaseNotesCli() { if (!token || typeof token !== 'string') { throw createFlagError('--token must be defined'); } + const prApi = new PrApi(log, token); const version = Version.fromFlag(flags.version); if (!version) { @@ -80,7 +80,7 @@ export function runReleaseNotesCli() { } const summary = new IrrelevantPrSummary(log); - const pr = await getPr(token, number); + const pr = await prApi.getPr(number); log.success( inspect( { @@ -101,7 +101,7 @@ export function runReleaseNotesCli() { const summary = new IrrelevantPrSummary(log); const prsToReport: ClassifiedPr[] = []; - const prIterable = iterRelevantPullRequests(token, version, log); + const prIterable = prApi.iterRelevantPullRequests(version); for await (const pr of prIterable) { if (!isPrRelevant(pr, version, includeVersions, summary)) { continue; diff --git a/packages/kbn-release-notes/src/lib/classify_pr.ts b/packages/kbn-release-notes/src/lib/classify_pr.ts index c567935ab7e48..2dfe6916235ee 100644 --- a/packages/kbn-release-notes/src/lib/classify_pr.ts +++ b/packages/kbn-release-notes/src/lib/classify_pr.ts @@ -27,7 +27,7 @@ import { ASCIIDOC_SECTIONS, UNKNOWN_ASCIIDOC_SECTION, } from '../release_notes_config'; -import { PullRequest } from './pull_request'; +import { PullRequest } from './pr_api'; export interface ClassifiedPr extends PullRequest { area: Area; diff --git a/packages/kbn-release-notes/src/lib/index.ts b/packages/kbn-release-notes/src/lib/index.ts index 00d8f49cf763f..8d27a26d96d0a 100644 --- a/packages/kbn-release-notes/src/lib/index.ts +++ b/packages/kbn-release-notes/src/lib/index.ts @@ -17,7 +17,7 @@ * under the License. */ -export * from './pull_request'; +export * from './pr_api'; export * from './version'; export * from './is_pr_relevant'; export * from './streams'; diff --git a/packages/kbn-release-notes/src/lib/irrelevant_pr_summary.ts b/packages/kbn-release-notes/src/lib/irrelevant_pr_summary.ts index 1a458a04c7740..ba82ab8780465 100644 --- a/packages/kbn-release-notes/src/lib/irrelevant_pr_summary.ts +++ b/packages/kbn-release-notes/src/lib/irrelevant_pr_summary.ts @@ -19,7 +19,7 @@ import { ToolingLog } from '@kbn/dev-utils'; -import { PullRequest } from './pull_request'; +import { PullRequest } from './pr_api'; import { Version } from './version'; export class IrrelevantPrSummary { diff --git a/packages/kbn-release-notes/src/lib/is_pr_relevant.ts b/packages/kbn-release-notes/src/lib/is_pr_relevant.ts index af2ef9440dede..452a14e919ed4 100644 --- a/packages/kbn-release-notes/src/lib/is_pr_relevant.ts +++ b/packages/kbn-release-notes/src/lib/is_pr_relevant.ts @@ -18,7 +18,7 @@ */ import { Version } from './version'; -import { PullRequest } from './pull_request'; +import { PullRequest } from './pr_api'; import { IGNORE_LABELS } from '../release_notes_config'; import { IrrelevantPrSummary } from './irrelevant_pr_summary'; diff --git a/packages/kbn-release-notes/src/lib/pr_api.ts b/packages/kbn-release-notes/src/lib/pr_api.ts new file mode 100644 index 0000000000000..1f26aa7ad86c3 --- /dev/null +++ b/packages/kbn-release-notes/src/lib/pr_api.ts @@ -0,0 +1,231 @@ +/* + * 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 { inspect } from 'util'; + +import Axios from 'axios'; +import gql from 'graphql-tag'; +import * as GraphqlPrinter from 'graphql/language/printer'; +import { DocumentNode } from 'graphql/language/ast'; +import makeTerminalLink from 'terminal-link'; +import { ToolingLog, isAxiosResponseError } from '@kbn/dev-utils'; + +import { Version } from './version'; +import { getFixReferences } from './get_fix_references'; +import { getNoteFromDescription } from './get_note_from_description'; + +const PrNodeFragment = gql` + fragment PrNode on PullRequest { + number + url + title + bodyText + bodyHTML + mergedAt + baseRefName + state + author { + login + ... on User { + name + } + } + labels(first: 100) { + nodes { + name + } + } + } +`; + +export interface PullRequest { + number: number; + url: string; + title: string; + targetBranch: string; + mergedAt: string; + state: string; + labels: string[]; + fixes: string[]; + user: { + name: string; + login: string; + }; + versions: Version[]; + terminalLink: string; + note?: string; +} + +export class PrApi { + constructor(private readonly log: ToolingLog, private readonly token: string) {} + + async getPr(number: number) { + const resp = await this.gqlRequest( + gql` + query($number: Int!) { + repository(owner: "elastic", name: "kibana") { + pullRequest(number: $number) { + ...PrNode + } + } + } + ${PrNodeFragment} + `, + { + number, + } + ); + + const node = resp.data?.repository?.pullRequest; + if (!node) { + throw new Error(`unexpected github response, unable to fetch PR: ${inspect(resp)}`); + } + + return this.parsePullRequestNode(node); + } + + /** + * Iterate all of the PRs which have the `version` label + */ + async *iterRelevantPullRequests(version: Version) { + let nextCursor: string | undefined; + let hasNextPage = true; + + while (hasNextPage) { + const resp = await this.gqlRequest( + gql` + query($cursor: String, $labels: [String!]) { + repository(owner: "elastic", name: "kibana") { + pullRequests(first: 100, after: $cursor, labels: $labels, states: MERGED) { + pageInfo { + hasNextPage + endCursor + } + nodes { + ...PrNode + } + } + } + } + ${PrNodeFragment} + `, + { + cursor: nextCursor, + labels: [version.label], + } + ); + + const pullRequests = resp.data?.repository?.pullRequests; + if (!pullRequests) { + throw new Error(`unexpected github response, unable to fetch PRs: ${inspect(resp)}`); + } + + hasNextPage = pullRequests.pageInfo?.hasNextPage; + nextCursor = pullRequests.pageInfo?.endCursor; + + if (hasNextPage === undefined || (hasNextPage && !nextCursor)) { + throw new Error( + `github response does not include valid pagination information: ${inspect(resp)}` + ); + } + + for (const node of pullRequests.nodes) { + yield this.parsePullRequestNode(node); + } + } + } + + /** + * Convert the Github API response into the structure used by this tool + * + * @param node A GraphQL response from Github using the PrNode fragment + */ + private parsePullRequestNode(node: any): PullRequest { + const terminalLink = makeTerminalLink(`#${node.number}`, node.url); + + const labels: string[] = node.labels.nodes.map((l: { name: string }) => l.name); + + return { + number: node.number, + url: node.url, + terminalLink, + title: node.title, + targetBranch: node.baseRefName, + state: node.state, + mergedAt: node.mergedAt, + labels, + fixes: getFixReferences(node.bodyText), + user: { + login: node.author?.login || 'deleted user', + name: node.author?.name, + }, + versions: labels + .map((l) => Version.fromLabel(l)) + .filter((v): v is Version => v instanceof Version), + note: getNoteFromDescription(node.bodyHTML), + }; + } + + /** + * Send a single request to the Github v4 GraphQL API + */ + private async gqlRequest(query: DocumentNode, variables: Record = {}) { + let attempt = 0; + + while (true) { + attempt += 1; + + try { + const resp = await Axios.request({ + url: 'https://api.github.com/graphql', + method: 'POST', + headers: { + 'user-agent': '@kbn/release-notes', + authorization: `bearer ${this.token}`, + }, + data: { + query: GraphqlPrinter.print(query), + variables, + }, + }); + + return resp.data; + } catch (error) { + if (!isAxiosResponseError(error) || error.response.status < 500) { + // rethrow error unless it is a 500+ response from github + throw error; + } + + const { status, data } = error.response; + const resp = inspect(data); + + if (attempt === 5) { + throw new Error( + `${status} response from Github, attempted request ${attempt} times: [${resp}]` + ); + } + + const delay = attempt * 2000; + this.log.debug(`Github responded with ${status}, retrying in ${delay} ms: [${resp}]`); + await new Promise((resolve) => setTimeout(resolve, delay)); + continue; + } + } + } +} diff --git a/packages/kbn-release-notes/src/lib/pull_request.ts b/packages/kbn-release-notes/src/lib/pull_request.ts deleted file mode 100644 index e61e496642062..0000000000000 --- a/packages/kbn-release-notes/src/lib/pull_request.ts +++ /dev/null @@ -1,206 +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 { inspect } from 'util'; - -import Axios from 'axios'; -import gql from 'graphql-tag'; -import * as GraphqlPrinter from 'graphql/language/printer'; -import { DocumentNode } from 'graphql/language/ast'; -import makeTerminalLink from 'terminal-link'; -import { ToolingLog } from '@kbn/dev-utils'; - -import { Version } from './version'; -import { getFixReferences } from './get_fix_references'; -import { getNoteFromDescription } from './get_note_from_description'; - -const PrNodeFragment = gql` - fragment PrNode on PullRequest { - number - url - title - bodyText - bodyHTML - mergedAt - baseRefName - state - author { - login - ... on User { - name - } - } - labels(first: 100) { - nodes { - name - } - } - } -`; - -export interface PullRequest { - number: number; - url: string; - title: string; - targetBranch: string; - mergedAt: string; - state: string; - labels: string[]; - fixes: string[]; - user: { - name: string; - login: string; - }; - versions: Version[]; - terminalLink: string; - note?: string; -} - -/** - * Send a single request to the Github v4 GraphQL API - */ -async function gqlRequest( - token: string, - query: DocumentNode, - variables: Record = {} -) { - const resp = await Axios.request({ - url: 'https://api.github.com/graphql', - method: 'POST', - headers: { - 'user-agent': '@kbn/release-notes', - authorization: `bearer ${token}`, - }, - data: { - query: GraphqlPrinter.print(query), - variables, - }, - }); - - return resp.data; -} - -/** - * Convert the Github API response into the structure used by this tool - * - * @param node A GraphQL response from Github using the PrNode fragment - */ -function parsePullRequestNode(node: any): PullRequest { - const terminalLink = makeTerminalLink(`#${node.number}`, node.url); - - const labels: string[] = node.labels.nodes.map((l: { name: string }) => l.name); - - return { - number: node.number, - url: node.url, - terminalLink, - title: node.title, - targetBranch: node.baseRefName, - state: node.state, - mergedAt: node.mergedAt, - labels, - fixes: getFixReferences(node.bodyText), - user: { - login: node.author?.login || 'deleted user', - name: node.author?.name, - }, - versions: labels - .map((l) => Version.fromLabel(l)) - .filter((v): v is Version => v instanceof Version), - note: getNoteFromDescription(node.bodyHTML), - }; -} - -/** - * Iterate all of the PRs which have the `version` label - */ -export async function* iterRelevantPullRequests(token: string, version: Version, log: ToolingLog) { - let nextCursor: string | undefined; - let hasNextPage = true; - - while (hasNextPage) { - const resp = await gqlRequest( - token, - gql` - query($cursor: String, $labels: [String!]) { - repository(owner: "elastic", name: "kibana") { - pullRequests(first: 100, after: $cursor, labels: $labels, states: MERGED) { - pageInfo { - hasNextPage - endCursor - } - nodes { - ...PrNode - } - } - } - } - ${PrNodeFragment} - `, - { - cursor: nextCursor, - labels: [version.label], - } - ); - - const pullRequests = resp.data?.repository?.pullRequests; - if (!pullRequests) { - throw new Error(`unexpected github response, unable to fetch PRs: ${inspect(resp)}`); - } - - hasNextPage = pullRequests.pageInfo?.hasNextPage; - nextCursor = pullRequests.pageInfo?.endCursor; - - if (hasNextPage === undefined || (hasNextPage && !nextCursor)) { - throw new Error( - `github response does not include valid pagination information: ${inspect(resp)}` - ); - } - - for (const node of pullRequests.nodes) { - yield parsePullRequestNode(node); - } - } -} - -export async function getPr(token: string, number: number) { - const resp = await gqlRequest( - token, - gql` - query($number: Int!) { - repository(owner: "elastic", name: "kibana") { - pullRequest(number: $number) { - ...PrNode - } - } - } - ${PrNodeFragment} - `, - { - number, - } - ); - - const node = resp.data?.repository?.pullRequest; - if (!node) { - throw new Error(`unexpected github response, unable to fetch PR: ${inspect(resp)}`); - } - - return parsePullRequestNode(node); -} diff --git a/packages/kbn-release-notes/tsconfig.json b/packages/kbn-release-notes/tsconfig.json index 6ffa64d91fba0..02209a29e5817 100644 --- a/packages/kbn-release-notes/tsconfig.json +++ b/packages/kbn-release-notes/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./target", "declaration": true, diff --git a/packages/kbn-telemetry-tools/GUIDELINE.md b/packages/kbn-telemetry-tools/GUIDELINE.md new file mode 100644 index 0000000000000..e7d09babbf9e2 --- /dev/null +++ b/packages/kbn-telemetry-tools/GUIDELINE.md @@ -0,0 +1,211 @@ +# Collector Schema Guideline + +Table of contents: +- [Collector Schema Guideline](#collector-schema-guideline) + - [Adding schema to your collector](#adding-schema-to-your-collector) + - [1. Update the telemetryrc file](#1-update-the-telemetryrc-file) + - [2. Type the `fetch` function](#2-type-the-fetch-function) + - [3. Add a `schema` field](#3-add-a-schema-field) + - [4. Run the telemetry check](#4-run-the-telemetry-check) + - [5. Update the stored json files](#5-update-the-stored-json-files) + - [Updating the collector schema](#updating-the-collector-schema) + - [Writing the schema](#writing-the-schema) + - [Basics](#basics) + - [Allowed types](#allowed-types) + - [Dealing with arrays](#dealing-with-arrays) + - [Schema Restrictions](#schema-restrictions) + - [Root of schema can only be an object](#root-of-schema-can-only-be-an-object) + + +## Adding schema to your collector + +To add a `schema` to the collector, follow these steps until the telemetry check passes. +To check the next step needed simply run the telemetry check with the path of your collector: + +``` +node scripts/telemetry_check.js --path=.ts +``` + +### 1. Update the telemetryrc file + +Make sure your collector is not excluded in the `telemetryrc.json` files (located at the root of the kibana project, and another on in the `x-pack` dir). + +```s +[ + { + ... + "exclude": [ + "" + ] + } +] +``` + +Note that the check will fail if the collector in --path is excluded. + +### 2. Type the `fetch` function +1. Make sure the return of the `fetch` function is typed. + +The function `makeUsageCollector` accepts a generic type parameter of the returned type of the `fetch` function. + +``` +interface Usage { + someStat: number; +} + +usageCollection.makeUsageCollector({ + fetch: async () => { + return { + someStat: 3, + } + }, + ... +}) +``` + +The generic type passed to `makeUsageCollector` will automatically unwrap the `Promise` to check for the resolved type. + +### 3. Add a `schema` field + +Add a `schema` field to your collector. After passing the return type of the fetch function to the `makeUsageCollector` generic parameter. It will automaticallly figure out the correct type of the schema based on that provided type. + + +``` +interface Usage { + someStat: number; +} + +usageCollection.makeUsageCollector({ + schema: { + someStat: { + type: 'long' + } + }, + ... +}) +``` + +For full details on writing the `schema` object, check the [Writing the schema](#writing-the-schema) section. + +### 4. Run the telemetry check + +To make sure your changes pass the telemetry check you can run the following: + +``` +node scripts/telemetry_check.js --ignore-stored-json --path=.ts +``` + +### 5. Update the stored json files + +The `--fix` flag will automatically update the persisted json files used by the telemetry team. + +``` +node scripts/telemetry_check.js --fix +``` + +Note that any updates to the stored json files will require a review by the kibana-telemetry team to help us update the telemetry cluster mappings and ensure your changes adhere to our best practices. + + +## Updating the collector schema + +Simply update the fetch function to start returning the updated fields back to our cluster. The update the schema to accomodate these changes. + +Once youre run the changes to both the `fetch` function and the `schema` field run the following command + +``` +node scripts/telemetry_check.js --fix +``` + +The `--fix` flag will automatically update the persisted json files used by the telemetry team. Note that any updates to the stored json files will require a review by the kibana-telemetry team to help us update the telemetry cluster mappings and ensure your changes adhere to our best practices. + + +## Writing the schema + +We've designed the schema object to closely resemble elasticsearch mapping object to reduce any cognitive complexity. + +### Basics + +The function `makeUsageCollector` will automatically translate the returned `Usage` fetch type to the `schema` object. This way you'll have the typescript type checker helping you write the correct corrisponding schema. + +``` +interface Usage { + someStat: number; +} + +usageCollection.makeUsageCollector({ + schema: { + someStat: { + type: 'long' + } + }, + ... +}) +``` + + +### Allowed types + +Any field property in the schema accepts a `type` field. By default the type is `object` which accepts nested properties under it. Currently we accept the following property types: + +``` +AllowedSchemaTypes = + | 'keyword' + | 'text' + | 'number' + | 'boolean' + | 'long' + | 'date' + | 'float'; +``` + + +### Dealing with arrays + +You can optionally define a property to be an array by setting the `isArray` to `true`. Note that the `isArray` property is not currently required. + + +``` +interface Usage { + arrayOfStrings: string[]; + arrayOfObjects: {key: string; value: number; }[]; +} + +usageCollection.makeUsageCollector({ + fetch: () => { + return { + arrayOfStrings: ['item_one', 'item_two'], + arrayOfObjects: [ + { key: 'key_one', value: 13 }, + ] + } + } + schema: { + arrayOfStrings: { + type: 'keyword', + isArray: true, + }, + arrayOfObjects: { + isArray: true, + key: { + type: 'keyword', + }, + value: { + type: 'long', + }, + } + }, + ... +}) +``` + +Be careful adding arrays of objects due to the limitation in correlating the properties inside those objects inside kibana. It is advised to look for an alternative schema based on your use cases. + + +## Schema Restrictions + +We have enforced some restrictions to the schema object to adhere to our telemetry best practices. These practices are derived from the usablity of the sent data in our telemetry cluster. + + +### Root of schema can only be an object + +The root of the schema can only be an object. Currently any property must be nested inside the main schema object. \ No newline at end of file diff --git a/packages/kbn-telemetry-tools/package.json b/packages/kbn-telemetry-tools/package.json index 63a8fcf30335e..4318cbcf2ec4e 100644 --- a/packages/kbn-telemetry-tools/package.json +++ b/packages/kbn-telemetry-tools/package.json @@ -10,12 +10,12 @@ "kbn:watch": "yarn build --watch" }, "devDependencies": { - "lodash": "npm:@elastic/lodash@3.10.1-kibana4", + "lodash": "^4.17.20", "@kbn/dev-utils": "1.0.0", "@kbn/utility-types": "1.0.0", "@types/normalize-path": "^3.0.0", "normalize-path": "^3.0.0", - "@types/lodash": "^3.10.1", + "@types/lodash": "^4.14.159", "moment": "^2.24.0", "typescript": "4.0.2" } diff --git a/packages/kbn-telemetry-tools/src/cli/run_telemetry_check.ts b/packages/kbn-telemetry-tools/src/cli/run_telemetry_check.ts index 2f85fd2cdd2a4..87ba68c1bcb27 100644 --- a/packages/kbn-telemetry-tools/src/cli/run_telemetry_check.ts +++ b/packages/kbn-telemetry-tools/src/cli/run_telemetry_check.ts @@ -35,7 +35,7 @@ import { export function runTelemetryCheck() { run( - async ({ flags: { fix = false, path }, log }) => { + async ({ flags: { fix = false, 'ignore-stored-json': ignoreStoredJson, path }, log }) => { if (typeof fix !== 'boolean') { throw createFailError(`${chalk.white.bgRed(' TELEMETRY ERROR ')} --fix can't have a value`); } @@ -50,6 +50,14 @@ export function runTelemetryCheck() { ); } + if (fix && typeof ignoreStoredJson !== 'undefined') { + throw createFailError( + `${chalk.white.bgRed( + ' TELEMETRY ERROR ' + )} --fix is incompatible with --ignore-stored-json flag.` + ); + } + const list = new Listr([ { title: 'Checking .telemetryrc.json files', @@ -59,11 +67,28 @@ export function runTelemetryCheck() { title: 'Extracting Collectors', task: (context) => new Listr(extractCollectorsTask(context, path), { exitOnError: true }), }, + { + enabled: () => typeof path !== 'undefined', + title: 'Checking collectors in --path are not excluded', + task: ({ roots }: TaskContext) => { + const totalCollections = roots.reduce((acc, root) => { + return acc + (root.parsedCollections?.length || 0); + }, 0); + const collectorsInPath = Array.isArray(path) ? path.length : 1; + + if (totalCollections !== collectorsInPath) { + throw new Error( + 'Collector specified in `path` is excluded; Check the telemetryrc.json files.' + ); + } + }, + }, { title: 'Checking Compatible collector.schema with collector.fetch type', task: (context) => new Listr(checkCompatibleTypesTask(context), { exitOnError: true }), }, { + enabled: (_) => !!ignoreStoredJson, title: 'Checking Matching collector.schema against stored json files', task: (context) => new Listr(checkMatchingSchemasTask(context, !fix), { exitOnError: true }), diff --git a/packages/kbn-telemetry-tools/src/tools/serializer.ts b/packages/kbn-telemetry-tools/src/tools/serializer.ts index d5412f64f3615..7afe828298b4b 100644 --- a/packages/kbn-telemetry-tools/src/tools/serializer.ts +++ b/packages/kbn-telemetry-tools/src/tools/serializer.ts @@ -18,7 +18,7 @@ */ import * as ts from 'typescript'; -import { uniq } from 'lodash'; +import { uniqBy } from 'lodash'; import { getResolvedModuleSourceFile, getIdentifierDeclarationFromSource, @@ -148,7 +148,7 @@ export function getDescriptor(node: ts.Node, program: ts.Program): Descriptor | .map((typeNode) => getDescriptor(typeNode, program)) .filter(discardNullOrUndefined); - const uniqueKinds = uniq(kinds, 'kind'); + const uniqueKinds = uniqBy(kinds, 'kind'); if (uniqueKinds.length !== 1) { throw Error('Mapping does not support conflicting union types.'); diff --git a/packages/kbn-telemetry-tools/src/tools/utils.ts b/packages/kbn-telemetry-tools/src/tools/utils.ts index c1424785b22a5..3d6764117374c 100644 --- a/packages/kbn-telemetry-tools/src/tools/utils.ts +++ b/packages/kbn-telemetry-tools/src/tools/utils.ts @@ -18,7 +18,18 @@ */ import * as ts from 'typescript'; -import { pick, isObject, each, isArray, reduce, isEmpty, merge, transform, isEqual } from 'lodash'; +import { + pick, + pickBy, + isObject, + forEach, + isArray, + reduce, + isEmpty, + merge, + transform, + isEqual, +} from 'lodash'; import * as path from 'path'; import glob from 'glob'; import { readFile, writeFile } from 'fs'; @@ -186,17 +197,17 @@ export function getPropertyValue( } } -export function pickDeep(collection: any, identity: any, thisArg?: any) { - const picked: any = pick(collection, identity, thisArg); - const collections = pick(collection, isObject, thisArg); +export function pickDeep(collection: any, identity: any) { + const picked: any = pick(collection, identity); + const collections = pickBy(collection, isObject); - each(collections, function (item, key) { + forEach(collections, function (item, key) { let object; if (isArray(item)) { object = reduce( item, function (result, value) { - const pickedDeep = pickDeep(value, identity, thisArg); + const pickedDeep = pickDeep(value, identity); if (!isEmpty(pickedDeep)) { result.push(pickedDeep); } @@ -205,7 +216,7 @@ export function pickDeep(collection: any, identity: any, thisArg?: any) { [] as any[] ); } else { - object = pickDeep(item, identity, thisArg); + object = pickDeep(item, identity); } if (!isEmpty(object)) { @@ -230,33 +241,38 @@ export const flattenKeys = (obj: any, keyPath: any[] = []): any => { return { [keyPath.join('.')]: obj }; }; +type ObjectDict = Record; export function difference(actual: any, expected: any) { - function changes(obj: { [key: string]: any }, base: { [key: string]: any }) { - return transform(obj, function (result, value, key) { - if (key && /@@INDEX@@/.test(`${key}`)) { - // The type definition is an Index Signature, fuzzy searching for similar keys - const regexp = new RegExp(`${key}`.replace(/@@INDEX@@/g, '(.+)?')); - const keysInBase = Object.keys(base) - .map((k) => { - const match = k.match(regexp); - return match && match[0]; - }) - .filter((s): s is string => !!s); - - if (keysInBase.length === 0) { - // Mark this key as wrong because we couldn't find any matching keys - result[key] = value; - } - - keysInBase.forEach((k) => { - if (!isEqual(value, base[k])) { - result[k] = isObject(value) && isObject(base[k]) ? changes(value, base[k]) : value; + function changes(obj: ObjectDict, base: ObjectDict) { + return transform( + obj, + function (result, value, key) { + if (key && /@@INDEX@@/.test(`${key}`)) { + // The type definition is an Index Signature, fuzzy searching for similar keys + const regexp = new RegExp(`${key}`.replace(/@@INDEX@@/g, '(.+)?')); + const keysInBase = Object.keys(base) + .map((k) => { + const match = k.match(regexp); + return match && match[0]; + }) + .filter((s): s is string => !!s); + + if (keysInBase.length === 0) { + // Mark this key as wrong because we couldn't find any matching keys + result[key] = value; } - }); - } else if (key && !isEqual(value, base[key])) { - result[key] = isObject(value) && isObject(base[key]) ? changes(value, base[key]) : value; - } - }); + + keysInBase.forEach((k) => { + if (!isEqual(value, base[k])) { + result[k] = isObject(value) && isObject(base[k]) ? changes(value, base[k]) : value; + } + }); + } else if (key && !isEqual(value, base[key])) { + result[key] = isObject(value) && isObject(base[key]) ? changes(value, base[key]) : value; + } + }, + {} as ObjectDict + ); } return changes(actual, expected); } diff --git a/packages/kbn-telemetry-tools/tsconfig.json b/packages/kbn-telemetry-tools/tsconfig.json index 13ce8ef2bad60..98512053a5c92 100644 --- a/packages/kbn-telemetry-tools/tsconfig.json +++ b/packages/kbn-telemetry-tools/tsconfig.json @@ -1,5 +1,8 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": "../../build/tsbuildinfo/packages/kbn-telemetry-tools" + }, "include": [ "src/**/*", ] diff --git a/packages/kbn-test-subj-selector/tsconfig.json b/packages/kbn-test-subj-selector/tsconfig.json index 3604f1004cf6c..a1e1c1af372c6 100644 --- a/packages/kbn-test-subj-selector/tsconfig.json +++ b/packages/kbn-test-subj-selector/tsconfig.json @@ -1,6 +1,9 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": "../../build/tsbuildinfo/packages/kbn-test-subj-selector" + }, "include": [ "index.d.ts" - ], + ] } diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/read_config_file.ts b/packages/kbn-test/src/functional_test_runner/lib/config/read_config_file.ts index 96fd525efa3ec..2e40aeec4f43d 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/read_config_file.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/read_config_file.ts @@ -21,7 +21,6 @@ import { ToolingLog } from '@kbn/dev-utils'; import { defaultsDeep } from 'lodash'; import { Config } from './config'; -import { transformDeprecations } from './transform_deprecations'; const cache = new WeakMap(); @@ -52,8 +51,7 @@ async function getSettingsFromFile(log: ToolingLog, path: string, settingOverrid await cache.get(configProvider)! ); - const logDeprecation = (error: string | Error) => log.error(error); - return transformDeprecations(settingsWithDefaults, logDeprecation); + return settingsWithDefaults; } export async function readConfigFile(log: ToolingLog, path: string, settingOverrides: any = {}) { diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts index e1d3bf1a8d901..701171876ad2c 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts @@ -262,7 +262,7 @@ export const schema = Joi.object() // settings for the find service layout: Joi.object() .keys({ - fixedHeaderHeight: Joi.number().default(50), + fixedHeaderHeight: Joi.number().default(100), }) .default(), diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/transform_deprecations.ts b/packages/kbn-test/src/functional_test_runner/lib/config/transform_deprecations.ts deleted file mode 100644 index 08dfc4a4f61f4..0000000000000 --- a/packages/kbn-test/src/functional_test_runner/lib/config/transform_deprecations.ts +++ /dev/null @@ -1,32 +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. - */ - -// @ts-ignore -import { createTransform, Deprecations } from '../../../../../../src/legacy/deprecation'; - -type DeprecationTransformer = ( - settings: object, - log: (msg: string) => void -) => { - [key: string]: any; -}; - -export const transformDeprecations: DeprecationTransformer = createTransform([ - Deprecations.unused('servers.webdriver'), -]); diff --git a/packages/kbn-test/tsconfig.json b/packages/kbn-test/tsconfig.json index fdb53de52687b..fec35e45b2a15 100644 --- a/packages/kbn-test/tsconfig.json +++ b/packages/kbn-test/tsconfig.json @@ -1,5 +1,8 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": "../../build/tsbuildinfo/packages/kbn-test" + }, "include": [ "types/**/*", "src/**/*", diff --git a/packages/kbn-ui-framework/src/components/local_nav/_local_search.scss b/packages/kbn-ui-framework/src/components/local_nav/_local_search.scss index 130807790e987..740ae664c7f5b 100644 --- a/packages/kbn-ui-framework/src/components/local_nav/_local_search.scss +++ b/packages/kbn-ui-framework/src/components/local_nav/_local_search.scss @@ -26,13 +26,6 @@ border-radius: 0; border-left-width: 0; } - -.kuiLocalSearchAssistedInput { - display: flex; - flex: 1 1 100%; - position: relative; -} - /** * 1. em used for right padding so documentation link and query string * won't overlap if the user increases their default browser font size diff --git a/packages/kbn-ui-shared-deps/entry.js b/packages/kbn-ui-shared-deps/entry.js index 0f981f3d07610..365b84b83bd5f 100644 --- a/packages/kbn-ui-shared-deps/entry.js +++ b/packages/kbn-ui-shared-deps/entry.js @@ -54,6 +54,3 @@ export const ElasticEuiChartsTheme = require('@elastic/eui/dist/eui_charts_theme import * as Theme from './theme.ts'; export { Theme }; - -// massive deps that we should really get rid of or reduce in size substantially -export const ElasticsearchBrowser = require('elasticsearch-browser/elasticsearch.js'); diff --git a/packages/kbn-ui-shared-deps/index.js b/packages/kbn-ui-shared-deps/index.js index 40e89f199b6a1..84ca3435e02bc 100644 --- a/packages/kbn-ui-shared-deps/index.js +++ b/packages/kbn-ui-shared-deps/index.js @@ -62,12 +62,5 @@ exports.externals = { '@elastic/eui/dist/eui_charts_theme': '__kbnSharedDeps__.ElasticEuiChartsTheme', '@elastic/eui/dist/eui_theme_light.json': '__kbnSharedDeps__.Theme.euiLightVars', '@elastic/eui/dist/eui_theme_dark.json': '__kbnSharedDeps__.Theme.euiDarkVars', - - /** - * massive deps that we should really get rid of or reduce in size substantially - */ - elasticsearch: '__kbnSharedDeps__.ElasticsearchBrowser', - 'elasticsearch-browser': '__kbnSharedDeps__.ElasticsearchBrowser', - 'elasticsearch-browser/elasticsearch': '__kbnSharedDeps__.ElasticsearchBrowser', }; exports.publicPathLoader = require.resolve('./public_path_loader'); diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index 0067228f1c1f3..bbe7b1bc2e8da 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -9,7 +9,7 @@ "kbn:watch": "node scripts/build --dev --watch" }, "dependencies": { - "@elastic/charts": "21.0.1", + "@elastic/charts": "21.1.2", "@elastic/eui": "28.2.0", "@elastic/numeral": "^2.5.0", "@kbn/i18n": "1.0.0", @@ -19,7 +19,6 @@ "compression-webpack-plugin": "^4.0.0", "core-js": "^3.6.4", "custom-event-polyfill": "^0.3.0", - "elasticsearch-browser": "^16.7.0", "jquery": "^3.5.0", "mini-css-extract-plugin": "0.8.0", "moment": "^2.24.0", diff --git a/packages/kbn-ui-shared-deps/tsconfig.json b/packages/kbn-ui-shared-deps/tsconfig.json index cef9a442d17bc..88699027f85de 100644 --- a/packages/kbn-ui-shared-deps/tsconfig.json +++ b/packages/kbn-ui-shared-deps/tsconfig.json @@ -1,5 +1,8 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": "../../build/tsbuildinfo/packages/kbn-ui-shared-deps" + }, "include": [ "index.d.ts", "theme.ts" diff --git a/packages/kbn-ui-shared-deps/webpack.config.js b/packages/kbn-ui-shared-deps/webpack.config.js index c81da4689052a..fa80dfdeef20f 100644 --- a/packages/kbn-ui-shared-deps/webpack.config.js +++ b/packages/kbn-ui-shared-deps/webpack.config.js @@ -32,22 +32,10 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({ mode: dev ? 'development' : 'production', entry: { 'kbn-ui-shared-deps': './entry.js', - 'kbn-ui-shared-deps.v7.dark': [ - '@elastic/eui/dist/eui_theme_dark.css', - '@elastic/charts/dist/theme_only_dark.css', - ], - 'kbn-ui-shared-deps.v7.light': [ - '@elastic/eui/dist/eui_theme_light.css', - '@elastic/charts/dist/theme_only_light.css', - ], - 'kbn-ui-shared-deps.v8.dark': [ - '@elastic/eui/dist/eui_theme_amsterdam_dark.css', - '@elastic/charts/dist/theme_only_dark.css', - ], - 'kbn-ui-shared-deps.v8.light': [ - '@elastic/eui/dist/eui_theme_amsterdam_light.css', - '@elastic/charts/dist/theme_only_light.css', - ], + 'kbn-ui-shared-deps.v7.dark': ['@elastic/eui/dist/eui_theme_dark.css'], + 'kbn-ui-shared-deps.v7.light': ['@elastic/eui/dist/eui_theme_light.css'], + 'kbn-ui-shared-deps.v8.dark': ['@elastic/eui/dist/eui_theme_amsterdam_dark.css'], + 'kbn-ui-shared-deps.v8.light': ['@elastic/eui/dist/eui_theme_amsterdam_light.css'], }, context: __dirname, devtool: dev ? '#cheap-source-map' : false, diff --git a/packages/kbn-utility-types/package.json b/packages/kbn-utility-types/package.json index a999eb41eb781..d1d7a1c0397cf 100644 --- a/packages/kbn-utility-types/package.json +++ b/packages/kbn-utility-types/package.json @@ -16,7 +16,7 @@ "utility-types": "^3.10.0" }, "devDependencies": { - "del-cli": "^3.0.0", - "tsd": "^0.7.4" + "del-cli": "^3.0.1", + "tsd": "^0.13.1" } } diff --git a/packages/kbn-utility-types/test-d/union_to_intersection.ts b/packages/kbn-utility-types/test-d/union_to_intersection.ts index ba385268475e7..8b49436bdd953 100644 --- a/packages/kbn-utility-types/test-d/union_to_intersection.ts +++ b/packages/kbn-utility-types/test-d/union_to_intersection.ts @@ -17,12 +17,12 @@ * under the License. */ -import { expectType } from 'tsd'; +import { expectAssignable } from 'tsd'; import { UnionToIntersection } from '../index'; type INTERSECTED = UnionToIntersection<{ foo: 'bar' } | { baz: 'qux' }>; -expectType({ +expectAssignable({ foo: 'bar', baz: 'qux', }); diff --git a/packages/kbn-utility-types/test-d/unwrap_observable.ts b/packages/kbn-utility-types/test-d/unwrap_observable.ts index af4fa9abf6ec7..e9791cfd36beb 100644 --- a/packages/kbn-utility-types/test-d/unwrap_observable.ts +++ b/packages/kbn-utility-types/test-d/unwrap_observable.ts @@ -17,9 +17,9 @@ * under the License. */ -import { expectType } from 'tsd'; +import { expectAssignable } from 'tsd'; import { UnwrapObservable, ObservableLike } from '../index'; type STRING = UnwrapObservable>; -expectType('adf'); +expectAssignable('adf'); diff --git a/packages/kbn-utility-types/test-d/unwrap_promise.ts b/packages/kbn-utility-types/test-d/unwrap_promise.ts index 9c4b1bc76b805..b61b24e4b3f15 100644 --- a/packages/kbn-utility-types/test-d/unwrap_promise.ts +++ b/packages/kbn-utility-types/test-d/unwrap_promise.ts @@ -17,11 +17,11 @@ * under the License. */ -import { expectType } from 'tsd'; +import { expectAssignable } from 'tsd'; import { UnwrapPromise } from '../index'; type STRING = UnwrapPromise>; type TUPLE = UnwrapPromise>; -expectType('adf'); -expectType([1, 2]); +expectAssignable('adf'); +expectAssignable([1, 2]); diff --git a/packages/kbn-utility-types/test-d/values.ts b/packages/kbn-utility-types/test-d/values.ts index 9e50cfebde1db..69bee9c3c9655 100644 --- a/packages/kbn-utility-types/test-d/values.ts +++ b/packages/kbn-utility-types/test-d/values.ts @@ -17,22 +17,22 @@ * under the License. */ -import { expectType } from 'tsd'; +import { expectAssignable } from 'tsd'; import { Values } from '../index'; // Arrays type STRING = Values; type ASDF_FOO = Values>; -expectType('adf'); -expectType('asdf'); -expectType('foo'); +expectAssignable('adf'); +expectAssignable('asdf'); +expectAssignable('foo'); // Objects type STRING2 = Values>; type FOO = Values>; type BAR = Values<{ foo: 'bar' }>; -expectType('adf'); -expectType('foo'); -expectType('bar'); +expectAssignable('adf'); +expectAssignable('foo'); +expectAssignable('bar'); diff --git a/packages/kbn-utility-types/tsconfig.json b/packages/kbn-utility-types/tsconfig.json index 202df37faf561..03cace5b9cb2c 100644 --- a/packages/kbn-utility-types/tsconfig.json +++ b/packages/kbn-utility-types/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "declaration": true, "declarationDir": "./target", diff --git a/scripts/build_ts_refs.js b/scripts/build_ts_refs.js new file mode 100644 index 0000000000000..29fd66bab4ca9 --- /dev/null +++ b/scripts/build_ts_refs.js @@ -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. + */ +require('../src/setup_node_env'); +require('../src/dev/typescript/build_refs').runBuildRefs(); diff --git a/src/legacy/utils/binder.ts b/src/cli/cluster/binder.ts similarity index 100% rename from src/legacy/utils/binder.ts rename to src/cli/cluster/binder.ts diff --git a/src/legacy/utils/binder_for.ts b/src/cli/cluster/binder_for.ts similarity index 100% rename from src/legacy/utils/binder_for.ts rename to src/cli/cluster/binder_for.ts diff --git a/src/cli/cluster/worker.ts b/src/cli/cluster/worker.ts index 097a549187429..c8a8a067d30bf 100644 --- a/src/cli/cluster/worker.ts +++ b/src/cli/cluster/worker.ts @@ -21,7 +21,7 @@ import _ from 'lodash'; import cluster from 'cluster'; import { EventEmitter } from 'events'; -import { BinderFor } from '../../legacy/utils/binder_for'; +import { BinderFor } from './binder_for'; import { fromRoot } from '../../core/server/utils'; const cliPath = fromRoot('src/cli'); diff --git a/src/cli_keystore/add.js b/src/cli_keystore/add.js index 44737e387c2d2..232392f34c63b 100644 --- a/src/cli_keystore/add.js +++ b/src/cli_keystore/add.js @@ -18,8 +18,8 @@ */ import { Logger } from '../cli_plugin/lib/logger'; -import { confirm, question } from '../legacy/server/utils'; -import { createPromiseFromStreams, createConcatStream } from '../legacy/utils'; +import { confirm, question } from './utils'; +import { createPromiseFromStreams, createConcatStream } from '../core/server/utils'; /** * @param {Keystore} keystore diff --git a/src/cli_keystore/add.test.js b/src/cli_keystore/add.test.js index b5d5009667eb4..f1adee8879bc2 100644 --- a/src/cli_keystore/add.test.js +++ b/src/cli_keystore/add.test.js @@ -42,7 +42,7 @@ import { PassThrough } from 'stream'; import { Keystore } from '../legacy/server/keystore'; import { add } from './add'; import { Logger } from '../cli_plugin/lib/logger'; -import * as prompt from '../legacy/server/utils/prompt'; +import * as prompt from './utils/prompt'; describe('Kibana keystore', () => { describe('add', () => { diff --git a/src/cli_keystore/create.js b/src/cli_keystore/create.js index 8be1eb36882f1..55fe2c151dec0 100644 --- a/src/cli_keystore/create.js +++ b/src/cli_keystore/create.js @@ -18,7 +18,7 @@ */ import { Logger } from '../cli_plugin/lib/logger'; -import { confirm } from '../legacy/server/utils'; +import { confirm } from './utils'; export async function create(keystore, command, options) { const logger = new Logger(options); diff --git a/src/cli_keystore/create.test.js b/src/cli_keystore/create.test.js index f48b3775ddfff..cb85475eab1cb 100644 --- a/src/cli_keystore/create.test.js +++ b/src/cli_keystore/create.test.js @@ -41,7 +41,7 @@ import sinon from 'sinon'; import { Keystore } from '../legacy/server/keystore'; import { create } from './create'; import { Logger } from '../cli_plugin/lib/logger'; -import * as prompt from '../legacy/server/utils/prompt'; +import * as prompt from './utils/prompt'; describe('Kibana keystore', () => { describe('create', () => { diff --git a/src/legacy/server/utils/index.js b/src/cli_keystore/utils/index.js similarity index 100% rename from src/legacy/server/utils/index.js rename to src/cli_keystore/utils/index.js diff --git a/src/legacy/server/utils/prompt.js b/src/cli_keystore/utils/prompt.js similarity index 100% rename from src/legacy/server/utils/prompt.js rename to src/cli_keystore/utils/prompt.js diff --git a/src/legacy/server/utils/prompt.test.js b/src/cli_keystore/utils/prompt.test.js similarity index 100% rename from src/legacy/server/utils/prompt.test.js rename to src/cli_keystore/utils/prompt.test.js diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index ea0e8d66d58f2..6a21dcb1b0686 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -1231,7 +1231,7 @@ import { npStart: { plugins } } from 'ui/new_platform'; | `import 'ui/filter_bar'` | `import { FilterBar } from '../data/public'` | Directive is deprecated. | | `import 'ui/query_bar'` | `import { QueryStringInput } 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 was moved to `src/plugins/kibana_legacy`. | +| `import 'ui/kbn_top_nav'` | `import { TopNavMenu } from '../navigation/public'` | Directive was removed. | | `ui/saved_objects/components/saved_object_finder` | `import { SavedObjectFinder } from '../saved_objects/public'` | | | `core_plugins/interpreter` | `plugins.data.expressions` | | `ui/courier` | `plugins.data.search` | @@ -1284,7 +1284,7 @@ _See also: [Server's CoreSetup API Docs](/docs/development/core/server/kibana-pl | Legacy Platform | New Platform | Notes | | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ----- | -| `server.plugins.xpack_main.registerFeature` | [`plugins.features.registerFeature`](x-pack/plugins/features/server/plugin.ts) | | +| `server.plugins.xpack_main.registerFeature` | [`plugins.features.registerKibanaFeature`](x-pack/plugins/features/server/plugin.ts) | | | `server.plugins.xpack_main.feature(pluginID).registerLicenseCheckResultsGenerator` | [`x-pack licensing plugin`](/x-pack/plugins/licensing/README.md) | | #### UI Exports diff --git a/src/core/TESTING.md b/src/core/TESTING.md index a62922d9b5d64..a0fd0a6ffc255 100644 --- a/src/core/TESTING.md +++ b/src/core/TESTING.md @@ -330,7 +330,7 @@ Cons: To have access to Kibana TestUtils, you should create `integration_tests` folder and import `test_utils` within a test file: ```typescript // src/plugins/my_plugin/server/integration_tests/formatter.test.ts -import * as kbnTestServer from 'src/test_utils/kbn_server'; +import * as kbnTestServer from 'src/core/test_helpers/kbn_server'; describe('myPlugin', () => { describe('GET /myPlugin/formatter', () => { diff --git a/src/core/public/_variables.scss b/src/core/public/_variables.scss new file mode 100644 index 0000000000000..8c054e770bd4b --- /dev/null +++ b/src/core/public/_variables.scss @@ -0,0 +1,3 @@ +@import '@elastic/eui/src/global_styling/variables/header'; + +$kbnHeaderOffset: $euiHeaderHeightCompensation * 2; diff --git a/src/core/public/application/application_service.mock.ts b/src/core/public/application/application_service.mock.ts index 2bdf56ee34211..5609e7eb5d17d 100644 --- a/src/core/public/application/application_service.mock.ts +++ b/src/core/public/application/application_service.mock.ts @@ -28,7 +28,6 @@ import { ApplicationStart, InternalApplicationSetup, PublicAppInfo, - PublicLegacyAppInfo, } from './types'; import { ApplicationServiceContract } from './test_types'; @@ -40,7 +39,6 @@ const createSetupContractMock = (): jest.Mocked => ({ const createInternalSetupContractMock = (): jest.Mocked => ({ register: jest.fn(), - registerLegacyApp: jest.fn(), registerAppUpdater: jest.fn(), registerMountContext: jest.fn(), }); @@ -49,7 +47,7 @@ const createStartContractMock = (): jest.Mocked => { const currentAppId$ = new Subject(); return { - applications$: new BehaviorSubject>(new Map()), + applications$: new BehaviorSubject>(new Map()), currentAppId$: currentAppId$.asObservable(), capabilities: capabilitiesServiceMock.createStartContract().capabilities, navigateToApp: jest.fn(), @@ -85,7 +83,7 @@ const createInternalStartContractMock = (): jest.Mocked(); return { - applications$: new BehaviorSubject>(new Map()), + applications$: new BehaviorSubject>(new Map()), capabilities: capabilitiesServiceMock.createStartContract().capabilities, currentAppId$: currentAppId$.asObservable(), currentActionMenu$: new BehaviorSubject(undefined), diff --git a/src/core/public/application/application_service.test.ts b/src/core/public/application/application_service.test.ts index d0c2ac111eb1f..afcebc06506c2 100644 --- a/src/core/public/application/application_service.test.ts +++ b/src/core/public/application/application_service.test.ts @@ -28,21 +28,12 @@ import { BehaviorSubject, Subject } from 'rxjs'; import { bufferCount, take, takeUntil } from 'rxjs/operators'; import { shallow, mount } from 'enzyme'; -import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock'; import { contextServiceMock } from '../context/context_service.mock'; import { httpServiceMock } from '../http/http_service.mock'; import { overlayServiceMock } from '../overlays/overlay_service.mock'; import { MockLifecycle } from './test_types'; import { ApplicationService } from './application_service'; -import { - App, - PublicAppInfo, - AppNavLinkStatus, - AppStatus, - AppUpdater, - LegacyApp, - PublicLegacyAppInfo, -} from './types'; +import { App, PublicAppInfo, AppNavLinkStatus, AppStatus, AppUpdater } from './types'; import { act } from 'react-dom/test-utils'; const createApp = (props: Partial): App => { @@ -54,15 +45,6 @@ const createApp = (props: Partial): App => { }; }; -const createLegacyApp = (props: Partial): LegacyApp => { - return { - id: 'some-id', - title: 'some-title', - appUrl: '/my-url', - ...props, - }; -}; - let setupDeps: MockLifecycle<'setup'>; let startDeps: MockLifecycle<'start'>; let service: ApplicationService; @@ -73,10 +55,8 @@ describe('#setup()', () => { setupDeps = { http, context: contextServiceMock.createSetupContract(), - injectedMetadata: injectedMetadataServiceMock.createSetupContract(), redirectTo: jest.fn(), }; - setupDeps.injectedMetadata.getLegacyMode.mockReturnValue(false); startDeps = { http, overlays: overlayServiceMock.createStartContract() }; service = new ApplicationService(); }); @@ -116,7 +96,6 @@ describe('#setup()', () => { expect(applications.get('app1')).toEqual( expect.objectContaining({ id: 'app1', - legacy: false, navLinkStatus: AppNavLinkStatus.visible, status: AppStatus.accessible, }) @@ -124,7 +103,6 @@ describe('#setup()', () => { expect(applications.get('app2')).toEqual( expect.objectContaining({ id: 'app2', - legacy: false, navLinkStatus: AppNavLinkStatus.visible, status: AppStatus.accessible, }) @@ -141,7 +119,6 @@ describe('#setup()', () => { expect(applications.get('app1')).toEqual( expect.objectContaining({ id: 'app1', - legacy: false, navLinkStatus: AppNavLinkStatus.hidden, status: AppStatus.inaccessible, defaultPath: 'foo/bar', @@ -151,7 +128,6 @@ describe('#setup()', () => { expect(applications.get('app2')).toEqual( expect.objectContaining({ id: 'app2', - legacy: false, navLinkStatus: AppNavLinkStatus.visible, status: AppStatus.accessible, }) @@ -159,7 +135,7 @@ describe('#setup()', () => { }); it('throws an error if an App with the same appRoute is registered', () => { - const { register, registerLegacyApp } = service.setup(setupDeps); + const { register } = service.setup(setupDeps); register(Symbol(), createApp({ id: 'app1' })); @@ -168,7 +144,6 @@ describe('#setup()', () => { ).toThrowErrorMatchingInlineSnapshot( `"An application is already registered with the appRoute \\"/app/app1\\""` ); - expect(() => registerLegacyApp(createLegacyApp({ id: 'app1' }))).toThrow(); register(Symbol(), createApp({ id: 'app-next', appRoute: '/app/app3' })); @@ -177,7 +152,6 @@ describe('#setup()', () => { ).toThrowErrorMatchingInlineSnapshot( `"An application is already registered with the appRoute \\"/app/app3\\""` ); - expect(() => registerLegacyApp(createLegacyApp({ id: 'app3' }))).not.toThrow(); }); it('throws an error if an App starts with the HTTP base path', () => { @@ -195,41 +169,6 @@ describe('#setup()', () => { }); }); - describe('registerLegacyApp', () => { - it('throws an error if two apps with the same id are registered', () => { - const { registerLegacyApp } = service.setup(setupDeps); - - registerLegacyApp(createLegacyApp({ id: 'app2' })); - expect(() => - registerLegacyApp(createLegacyApp({ id: 'app2' })) - ).toThrowErrorMatchingInlineSnapshot( - `"An application is already registered with the id \\"app2\\""` - ); - }); - - it('throws error if additional apps are registered after setup', async () => { - const { registerLegacyApp } = service.setup(setupDeps); - - await service.start(startDeps); - expect(() => - registerLegacyApp(createLegacyApp({ id: 'app2' })) - ).toThrowErrorMatchingInlineSnapshot(`"Applications cannot be registered after \\"setup\\""`); - }); - - it('throws an error if a LegacyApp with the same appRoute is registered', () => { - const { register, registerLegacyApp } = service.setup(setupDeps); - - registerLegacyApp(createLegacyApp({ id: 'app1' })); - - expect(() => - register(Symbol(), createApp({ id: 'app2', appRoute: '/app/app1' })) - ).toThrowErrorMatchingInlineSnapshot( - `"An application is already registered with the appRoute \\"/app/app1\\""` - ); - expect(() => registerLegacyApp(createLegacyApp({ id: 'app1:other' }))).not.toThrow(); - }); - }); - describe('registerAppUpdater', () => { it('updates status fields', async () => { const setup = service.setup(setupDeps); @@ -258,7 +197,6 @@ describe('#setup()', () => { expect(applications.get('app1')).toEqual( expect.objectContaining({ id: 'app1', - legacy: false, navLinkStatus: AppNavLinkStatus.disabled, status: AppStatus.inaccessible, tooltip: 'App inaccessible due to reason', @@ -267,7 +205,6 @@ describe('#setup()', () => { expect(applications.get('app2')).toEqual( expect.objectContaining({ id: 'app2', - legacy: false, navLinkStatus: AppNavLinkStatus.visible, status: AppStatus.accessible, tooltip: 'App accessible', @@ -307,7 +244,6 @@ describe('#setup()', () => { expect(applications.get('app1')).toEqual( expect.objectContaining({ id: 'app1', - legacy: false, navLinkStatus: AppNavLinkStatus.disabled, status: AppStatus.inaccessible, tooltip: 'App inaccessible due to reason', @@ -316,7 +252,6 @@ describe('#setup()', () => { expect(applications.get('app2')).toEqual( expect.objectContaining({ id: 'app2', - legacy: false, status: AppStatus.inaccessible, navLinkStatus: AppNavLinkStatus.hidden, }) @@ -352,7 +287,6 @@ describe('#setup()', () => { expect(applications.get('app1')).toEqual( expect.objectContaining({ id: 'app1', - legacy: false, navLinkStatus: AppNavLinkStatus.disabled, status: AppStatus.inaccessible, }) @@ -374,10 +308,7 @@ describe('#setup()', () => { setup.registerAppUpdater(statusUpdater); const start = await service.start(startDeps); - let latestValue: ReadonlyMap = new Map< - string, - PublicAppInfo | PublicLegacyAppInfo - >(); + let latestValue: ReadonlyMap = new Map(); start.applications$.subscribe((apps) => { latestValue = apps; }); @@ -385,7 +316,6 @@ describe('#setup()', () => { expect(latestValue.get('app1')).toEqual( expect.objectContaining({ id: 'app1', - legacy: false, status: AppStatus.inaccessible, navLinkStatus: AppNavLinkStatus.disabled, }) @@ -401,43 +331,12 @@ describe('#setup()', () => { expect(latestValue.get('app1')).toEqual( expect.objectContaining({ id: 'app1', - legacy: false, status: AppStatus.accessible, navLinkStatus: AppNavLinkStatus.hidden, }) ); }); - it('also updates legacy apps', async () => { - const setup = service.setup(setupDeps); - - setup.registerLegacyApp(createLegacyApp({ id: 'app1' })); - - setup.registerAppUpdater( - new BehaviorSubject((app) => { - return { - status: AppStatus.inaccessible, - navLinkStatus: AppNavLinkStatus.hidden, - tooltip: 'App inaccessible due to reason', - }; - }) - ); - - const start = await service.start(startDeps); - const applications = await start.applications$.pipe(take(1)).toPromise(); - - expect(applications.size).toEqual(1); - expect(applications.get('app1')).toEqual( - expect.objectContaining({ - id: 'app1', - legacy: true, - status: AppStatus.inaccessible, - navLinkStatus: AppNavLinkStatus.hidden, - tooltip: 'App inaccessible due to reason', - }) - ); - }); - it('allows to update the basePath', async () => { const setup = service.setup(setupDeps); @@ -486,10 +385,8 @@ describe('#start()', () => { setupDeps = { http, context: contextServiceMock.createSetupContract(), - injectedMetadata: injectedMetadataServiceMock.createSetupContract(), redirectTo: jest.fn(), }; - setupDeps.injectedMetadata.getLegacyMode.mockReturnValue(false); startDeps = { http, overlays: overlayServiceMock.createStartContract() }; service = new ApplicationService(); }); @@ -507,11 +404,10 @@ describe('#start()', () => { }); it('exposes available apps', async () => { - setupDeps.injectedMetadata.getLegacyMode.mockReturnValue(true); - const { register, registerLegacyApp } = service.setup(setupDeps); + const { register } = service.setup(setupDeps); register(Symbol(), createApp({ id: 'app1' })); - registerLegacyApp(createLegacyApp({ id: 'app2' })); + register(Symbol(), createApp({ id: 'app2' })); const { applications$ } = await service.start(startDeps); const availableApps = await applications$.pipe(take(1)).toPromise(); @@ -522,16 +418,14 @@ describe('#start()', () => { expect.objectContaining({ appRoute: '/app/app1', id: 'app1', - legacy: false, navLinkStatus: AppNavLinkStatus.visible, status: AppStatus.accessible, }) ); expect(availableApps.get('app2')).toEqual( expect.objectContaining({ - appUrl: '/my-url', + appRoute: '/app/app2', id: 'app2', - legacy: true, navLinkStatus: AppNavLinkStatus.visible, status: AppStatus.accessible, }) @@ -558,39 +452,19 @@ describe('#start()', () => { navLinks: { app1: true, app2: false, - legacyApp1: true, - legacyApp2: false, }, }, } as any); - const { register, registerLegacyApp } = service.setup(setupDeps); + const { register } = service.setup(setupDeps); register(Symbol(), createApp({ id: 'app1' })); - registerLegacyApp(createLegacyApp({ id: 'legacyApp1' })); register(Symbol(), createApp({ id: 'app2' })); - registerLegacyApp(createLegacyApp({ id: 'legacyApp2' })); const { applications$ } = await service.start(startDeps); const availableApps = await applications$.pipe(take(1)).toPromise(); - expect([...availableApps.keys()]).toEqual(['app1', 'legacyApp1']); - }); - - describe('currentAppId$', () => { - it('emits the legacy app id when in legacy mode', async () => { - setupDeps.injectedMetadata.getLegacyMode.mockReturnValue(true); - setupDeps.injectedMetadata.getLegacyMetadata.mockReturnValue({ - app: { - id: 'legacy', - title: 'Legacy App', - }, - } as any); - await service.setup(setupDeps); - const { currentAppId$ } = await service.start(startDeps); - - expect(await currentAppId$.pipe(take(1)).toPromise()).toEqual('legacy'); - }); + expect([...availableApps.keys()]).toEqual(['app1']); }); describe('getComponent', () => { @@ -602,16 +476,6 @@ describe('#start()', () => { expect(() => shallow(createElement(getComponent))).not.toThrow(); expect(getComponent()).toMatchSnapshot(); }); - - it('renders null when in legacy mode', async () => { - setupDeps.injectedMetadata.getLegacyMode.mockReturnValue(true); - service.setup(setupDeps); - - const { getComponent } = await service.start(startDeps); - - expect(() => shallow(createElement(getComponent))).not.toThrow(); - expect(getComponent()).toBe(null); - }); }); describe('getUrlForApp', () => { @@ -624,16 +488,14 @@ describe('#start()', () => { }); it('creates URL for registered appId', async () => { - const { register, registerLegacyApp } = service.setup(setupDeps); + const { register } = service.setup(setupDeps); register(Symbol(), createApp({ id: 'app1' })); - registerLegacyApp(createLegacyApp({ id: 'legacyApp1' })); register(Symbol(), createApp({ id: 'app2', appRoute: '/custom/path' })); const { getUrlForApp } = await service.start(startDeps); expect(getUrlForApp('app1')).toBe('/base-path/app/app1'); - expect(getUrlForApp('legacyApp1')).toBe('/base-path/app/legacyApp1'); expect(getUrlForApp('app2')).toBe('/base-path/custom/path'); }); @@ -800,16 +662,6 @@ describe('#start()', () => { expect(MockHistory.push).toHaveBeenCalledWith('/custom/path', 'my-state'); }); - it('redirects when in legacyMode', async () => { - setupDeps.injectedMetadata.getLegacyMode.mockReturnValue(true); - service.setup(setupDeps); - - const { navigateToApp } = await service.start(startDeps); - - await navigateToApp('myTestApp'); - expect(setupDeps.redirectTo).toHaveBeenCalledWith('/base-path/app/myTestApp'); - }); - it('updates currentApp$ after mounting', async () => { service.setup(setupDeps); @@ -903,31 +755,6 @@ describe('#start()', () => { `); }); - it('sets window.location.href when navigating to legacy apps', async () => { - setupDeps.http = httpServiceMock.createSetupContract({ basePath: '/test' }); - setupDeps.injectedMetadata.getLegacyMode.mockReturnValue(true); - service.setup(setupDeps); - - const { navigateToApp } = await service.start(startDeps); - - await navigateToApp('alpha'); - expect(setupDeps.redirectTo).toHaveBeenCalledWith('/test/app/alpha'); - }); - - it('handles legacy apps with subapps', async () => { - setupDeps.http = httpServiceMock.createSetupContract({ basePath: '/test' }); - setupDeps.injectedMetadata.getLegacyMode.mockReturnValue(true); - - const { registerLegacyApp } = service.setup(setupDeps); - - registerLegacyApp(createLegacyApp({ id: 'baseApp:legacyApp1' })); - - const { navigateToApp } = await service.start(startDeps); - - await navigateToApp('baseApp:legacyApp1'); - expect(setupDeps.redirectTo).toHaveBeenCalledWith('/test/app/baseApp'); - }); - describe('when `replace` option is true', () => { it('use `history.replace` instead of `history.push`', async () => { service.setup(setupDeps); @@ -973,16 +800,6 @@ describe('#start()', () => { undefined ); }); - it('do not change the behavior when in legacy mode', async () => { - setupDeps.http = httpServiceMock.createSetupContract({ basePath: '/test' }); - setupDeps.injectedMetadata.getLegacyMode.mockReturnValue(true); - service.setup(setupDeps); - - const { navigateToApp } = await service.start(startDeps); - - await navigateToApp('alpha', { replace: true }); - expect(setupDeps.redirectTo).toHaveBeenCalledWith('/test/app/alpha'); - }); }); describe('when `replace` option is false', () => { @@ -1040,9 +857,7 @@ describe('#stop()', () => { setupDeps = { http, context: contextServiceMock.createSetupContract(), - injectedMetadata: injectedMetadataServiceMock.createSetupContract(), }; - setupDeps.injectedMetadata.getLegacyMode.mockReturnValue(false); startDeps = { http, overlays: overlayServiceMock.createStartContract() }; service = new ApplicationService(); }); diff --git a/src/core/public/application/application_service.tsx b/src/core/public/application/application_service.tsx index df0f74c1914e9..0d08f6f3007b0 100644 --- a/src/core/public/application/application_service.tsx +++ b/src/core/public/application/application_service.tsx @@ -23,7 +23,6 @@ import { map, shareReplay, takeUntil, distinctUntilChanged, filter } from 'rxjs/ import { createBrowserHistory, History } from 'history'; import { MountPoint } from '../types'; -import { InjectedMetadataSetup } from '../injected_metadata'; import { HttpSetup, HttpStart } from '../http'; import { OverlayStart } from '../overlays'; import { ContextSetup, IContextContainer } from '../context'; @@ -32,7 +31,6 @@ import { AppRouter } from './ui'; import { Capabilities, CapabilitiesService } from './capabilities'; import { App, - AppBase, AppLeaveHandler, AppMount, AppMountDeprecated, @@ -42,8 +40,6 @@ import { AppUpdater, InternalApplicationSetup, InternalApplicationStart, - LegacyApp, - LegacyAppMounter, Mounter, NavigateToAppOptions, } from './types'; @@ -53,9 +49,8 @@ import { appendAppPath, parseAppUrl, relativeToAbsolute, getAppInfo } from './ut interface SetupDeps { context: ContextSetup; http: HttpSetup; - injectedMetadata: InjectedMetadataSetup; history?: History; - /** Used to redirect to external urls (and legacy apps) */ + /** Used to redirect to external urls */ redirectTo?: (path: string) => void; } @@ -101,7 +96,7 @@ interface AppInternalState { * @internal */ export class ApplicationService { - private readonly apps = new Map | LegacyApp>(); + private readonly apps = new Map>(); private readonly mounters = new Map(); private readonly capabilities = new CapabilitiesService(); private readonly appInternalStates = new Map(); @@ -119,28 +114,17 @@ export class ApplicationService { public setup({ context, http: { basePath }, - injectedMetadata, redirectTo = (path: string) => { window.location.assign(path); }, history, }: SetupDeps): InternalApplicationSetup { const basename = basePath.get(); - if (injectedMetadata.getLegacyMode()) { - this.currentAppId$.next(injectedMetadata.getLegacyMetadata().app.id); - } else { - // Only setup history if we're not in legacy mode - this.history = history || createBrowserHistory({ basename }); - } + this.history = history || createBrowserHistory({ basename }); this.navigate = (url, state, replace) => { - if (this.history) { - // basePath not needed here because `history` is configured with basename - return replace ? this.history.replace(url, state) : this.history.push(url, state); - } else { - // If we do not have history available (legacy mode), use redirectTo to do a full page refresh. - return redirectTo(basePath.prepend(url)); - } + // basePath not needed here because `history` is configured with basename + return replace ? this.history!.replace(url, state) : this.history!.push(url, state); }; this.redirectTo = redirectTo; @@ -200,7 +184,6 @@ export class ApplicationService { ...appProps, status: app.status ?? AppStatus.accessible, navLinkStatus: app.navLinkStatus ?? AppNavLinkStatus.default, - legacy: false, }); if (updater$) { registerStatusUpdater(app.id, updater$); @@ -211,43 +194,6 @@ export class ApplicationService { exactRoute: app.exactRoute ?? false, mount: wrapMount(plugin, app), unmountBeforeMounting: false, - legacy: false, - }); - }, - registerLegacyApp: (app) => { - const appRoute = `/app/${app.id.split(':')[0]}`; - - if (this.registrationClosed) { - throw new Error('Applications cannot be registered after "setup"'); - } else if (this.apps.has(app.id)) { - throw new Error(`An application is already registered with the id "${app.id}"`); - } else if (basename && appRoute!.startsWith(`${basename}/`)) { - throw new Error('Cannot register an application route that includes HTTP base path'); - } - - const appBasePath = basePath.prepend(appRoute); - const mount: LegacyAppMounter = ({ history: appHistory }) => { - redirectTo(appHistory.createHref(appHistory.location)); - window.location.reload(); - }; - - const { updater$, ...appProps } = app; - this.apps.set(app.id, { - ...appProps, - status: app.status ?? AppStatus.accessible, - navLinkStatus: app.navLinkStatus ?? AppNavLinkStatus.default, - legacy: true, - }); - if (updater$) { - registerStatusUpdater(app.id, updater$); - } - this.mounters.set(app.id, { - appRoute, - appBasePath, - exactRoute: false, - mount, - unmountBeforeMounting: true, - legacy: true, }); }, registerAppUpdater: (appUpdater$: Observable) => @@ -323,7 +269,7 @@ export class ApplicationService { distinctUntilChanged(), takeUntil(this.stop$) ), - history: this.history, + history: this.history!, registerMountContext: this.mountContext.registerContext, getUrlForApp: ( appId, @@ -421,7 +367,7 @@ export class ApplicationService { } } -const updateStatus = (app: T, statusUpdaters: AppUpdaterWrapper[]): T => { +const updateStatus = (app: App, statusUpdaters: AppUpdaterWrapper[]): App => { let changes: Partial = {}; statusUpdaters.forEach((wrapper) => { if (wrapper.application !== allApplicationsFilter && wrapper.application !== app.id) { diff --git a/src/core/public/application/index.ts b/src/core/public/application/index.ts index 121f0c7ac07d6..4f3b113a29c9b 100644 --- a/src/core/public/application/index.ts +++ b/src/core/public/application/index.ts @@ -22,7 +22,6 @@ export { Capabilities } from './capabilities'; export { ScopedHistory } from './scoped_history'; export { App, - AppBase, AppMount, AppMountDeprecated, AppUnmount, @@ -39,10 +38,8 @@ export { AppLeaveAction, AppLeaveDefaultAction, AppLeaveConfirmAction, - LegacyApp, NavigateToAppOptions, PublicAppInfo, - PublicLegacyAppInfo, // Internal types InternalApplicationSetup, InternalApplicationStart, diff --git a/src/core/public/application/integration_tests/application_service.test.tsx b/src/core/public/application/integration_tests/application_service.test.tsx index 9eafddd6a61fe..d28486928b7e2 100644 --- a/src/core/public/application/integration_tests/application_service.test.tsx +++ b/src/core/public/application/integration_tests/application_service.test.tsx @@ -25,11 +25,9 @@ import { createRenderer } from './utils'; import { ApplicationService } from '../application_service'; import { httpServiceMock } from '../../http/http_service.mock'; import { contextServiceMock } from '../../context/context_service.mock'; -import { injectedMetadataServiceMock } from '../../injected_metadata/injected_metadata_service.mock'; import { MockLifecycle } from '../test_types'; import { overlayServiceMock } from '../../overlays/overlay_service.mock'; import { AppMountParameters } from '../types'; -import { ScopedHistory } from '../scoped_history'; import { Observable } from 'rxjs'; import { MountPoint } from 'kibana/public'; @@ -56,10 +54,8 @@ describe('ApplicationService', () => { setupDeps = { http, context: contextServiceMock.createSetupContract(), - injectedMetadata: injectedMetadataServiceMock.createSetupContract(), history: history as any, }; - setupDeps.injectedMetadata.getLegacyMode.mockReturnValue(false); startDeps = { http, overlays: overlayServiceMock.createStartContract() }; service = new ApplicationService(); }); @@ -149,54 +145,6 @@ describe('ApplicationService', () => { }); }); - describe('redirects', () => { - beforeAll(() => { - Object.defineProperty(window, 'location', { - value: { - reload: jest.fn(), - }, - }); - }); - - it('to full path when navigating to legacy app', async () => { - const redirectTo = jest.fn(); - - // In the real application, we use a BrowserHistory instance configured with `basename`. However, in tests we must - // use MemoryHistory which does not support `basename`. In order to emulate this behavior, we will wrap this - // instance with a ScopedHistory configured with a basepath. - history.push(setupDeps.http.basePath.get()); // ScopedHistory constructor will fail if underlying history is not currently at basePath. - const { register, registerLegacyApp } = service.setup({ - ...setupDeps, - redirectTo, - history: new ScopedHistory(history, setupDeps.http.basePath.get()), - }); - - register(Symbol(), { - id: 'app1', - title: 'App1', - mount: ({ onAppLeave }: AppMountParameters) => { - onAppLeave((actions) => actions.default()); - return () => undefined; - }, - }); - registerLegacyApp({ - id: 'myLegacyTestApp', - appUrl: '/app/myLegacyTestApp', - title: 'My Legacy Test App', - }); - - const { navigateToApp, getComponent } = await service.start(startDeps); - - update = createRenderer(getComponent()); - - await navigate('/test/app/app1'); - await act(() => navigateToApp('myLegacyTestApp', { path: '#/some-path' })); - - expect(redirectTo).toHaveBeenCalledWith('/test/app/myLegacyTestApp#/some-path'); - expect(window.location.reload).toHaveBeenCalled(); - }); - }); - describe('leaving an application that registered an app leave handler', () => { it('navigates to the new app if action is default', async () => { startDeps.overlays.openConfirm.mockResolvedValue(true); diff --git a/src/core/public/application/integration_tests/router.test.tsx b/src/core/public/application/integration_tests/router.test.tsx index 6408b8123365e..e3f992990f9f9 100644 --- a/src/core/public/application/integration_tests/router.test.tsx +++ b/src/core/public/application/integration_tests/router.test.tsx @@ -22,13 +22,12 @@ import { BehaviorSubject } from 'rxjs'; import { createMemoryHistory, History, createHashHistory } from 'history'; import { AppRouter, AppNotFound } from '../ui'; -import { EitherApp, MockedMounterMap, MockedMounterTuple } from '../test_types'; -import { createRenderer, createAppMounter, createLegacyAppMounter, getUnmounter } from './utils'; +import { MockedMounterMap, MockedMounterTuple } from '../test_types'; +import { createRenderer, createAppMounter, getUnmounter } from './utils'; import { AppStatus } from '../types'; -import { ScopedHistory } from '../scoped_history'; describe('AppRouter', () => { - let mounters: MockedMounterMap; + let mounters: MockedMounterMap; let globalHistory: History; let update: ReturnType; let scopedAppHistory: History; @@ -67,9 +66,7 @@ describe('AppRouter', () => { beforeEach(() => { mounters = new Map([ createAppMounter({ appId: 'app1', html: 'App 1' }), - createLegacyAppMounter('legacyApp1', jest.fn()), createAppMounter({ appId: 'app2', html: '
App 2
' }), - createLegacyAppMounter('baseApp:legacyApp2', jest.fn()), createAppMounter({ appId: 'app3', html: '
Chromeless A
', @@ -81,7 +78,6 @@ describe('AppRouter', () => { appRoute: '/chromeless-b/path', }), createAppMounter({ appId: 'disabledApp', html: '
Disabled app
' }), - createLegacyAppMounter('disabledLegacyApp', jest.fn()), createAppMounter({ appId: 'scopedApp', extraMountHook: ({ history }) => { @@ -99,7 +95,7 @@ describe('AppRouter', () => { html: '
App 6
', appRoute: '/app/my-app/app6', }), - ] as Array>); + ] as MockedMounterTuple[]); globalHistory = createMemoryHistory(); update = createMountersRenderer(); }); @@ -384,26 +380,6 @@ describe('AppRouter', () => { expect(globalHistory.location.pathname).toEqual('/app/scopedApp/subpath'); }); - it('calls legacy mount handler', async () => { - await navigate('/app/legacyApp1'); - expect(mounters.get('legacyApp1')!.mounter.mount.mock.calls[0][0]).toMatchObject({ - appBasePath: '/app/legacyApp1', - element: expect.any(HTMLDivElement), - onAppLeave: expect.any(Function), - history: expect.any(ScopedHistory), - }); - }); - - it('handles legacy apps with subapps', async () => { - await navigate('/app/baseApp'); - expect(mounters.get('baseApp:legacyApp2')!.mounter.mount.mock.calls[0][0]).toMatchObject({ - appBasePath: '/app/baseApp', - element: expect.any(HTMLDivElement), - onAppLeave: expect.any(Function), - history: expect.any(ScopedHistory), - }); - }); - it('displays error page if no app is found', async () => { const dom = await navigate('/app/unknown'); @@ -415,10 +391,4 @@ describe('AppRouter', () => { expect(dom?.exists(AppNotFound)).toBe(true); }); - - it('displays error page if legacy app is inaccessible', async () => { - const dom = await navigate('/app/disabledLegacyApp'); - - expect(dom?.exists(AppNotFound)).toBe(true); - }); }); diff --git a/src/core/public/application/integration_tests/utils.tsx b/src/core/public/application/integration_tests/utils.tsx index 80a7fc2c2cad6..2ed9e0c495fb9 100644 --- a/src/core/public/application/integration_tests/utils.tsx +++ b/src/core/public/application/integration_tests/utils.tsx @@ -20,11 +20,10 @@ import React, { ReactElement } from 'react'; import { act } from 'react-dom/test-utils'; import { mount } from 'enzyme'; - import { I18nProvider } from '@kbn/i18n/react'; -import { App, LegacyApp, AppMountParameters } from '../types'; -import { EitherApp, MockedMounter, MockedMounterTuple, Mountable } from '../test_types'; +import { AppMountParameters } from '../types'; +import { MockedMounterTuple, Mountable } from '../test_types'; type Dom = ReturnType | null; type Renderer = () => Dom | Promise; @@ -55,7 +54,7 @@ export const createAppMounter = ({ appRoute?: string; exactRoute?: boolean; extraMountHook?: (params: AppMountParameters) => void; -}): MockedMounterTuple => { +}): MockedMounterTuple => { const unmount = jest.fn(); return [ appId, @@ -63,7 +62,6 @@ export const createAppMounter = ({ mounter: { appRoute, appBasePath: appRoute, - legacy: false, exactRoute, mount: jest.fn(async (params: AppMountParameters) => { const { appBasePath: basename, element } = params; @@ -82,24 +80,6 @@ export const createAppMounter = ({ ]; }; -export const createLegacyAppMounter = ( - appId: string, - legacyMount: MockedMounter['mount'] -): MockedMounterTuple => [ - appId, - { - mounter: { - appRoute: `/app/${appId.split(':')[0]}`, - appBasePath: `/app/${appId.split(':')[0]}`, - unmountBeforeMounting: true, - legacy: true, - exactRoute: false, - mount: legacyMount, - }, - unmount: jest.fn(), - }, -]; - -export function getUnmounter(app: Mountable) { +export function getUnmounter(app: Mountable) { return app.mounter.mount.mock.results[0].value; } diff --git a/src/core/public/application/test_types.ts b/src/core/public/application/test_types.ts index b822597e510cb..64012f0c0b6c1 100644 --- a/src/core/public/application/test_types.ts +++ b/src/core/public/application/test_types.ts @@ -17,28 +17,26 @@ * under the License. */ -import { App, LegacyApp, Mounter, AppUnmount } from './types'; +import { AppUnmount, Mounter } from './types'; import { ApplicationService } from './application_service'; /** @internal */ export type ApplicationServiceContract = PublicMethodsOf; /** @internal */ -export type EitherApp = App | LegacyApp; -/** @internal */ export type MockedUnmount = jest.Mocked; /** @internal */ -export interface Mountable { - mounter: MockedMounter; +export interface Mountable { + mounter: MockedMounter; unmount: MockedUnmount; } /** @internal */ -export type MockedMounter = jest.Mocked>>; +export type MockedMounter = jest.Mocked; /** @internal */ -export type MockedMounterTuple = [string, Mountable]; +export type MockedMounterTuple = [string, Mountable]; /** @internal */ -export type MockedMounterMap = Map>; +export type MockedMounterMap = Map; /** @internal */ export type MockLifecycle< T extends keyof ApplicationService, diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index 320416a8c2379..df83b6e932aad 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -36,8 +36,64 @@ import { SavedObjectsStart } from '../saved_objects'; import { AppCategory } from '../../types'; import { ScopedHistory } from './scoped_history'; -/** @public */ -export interface AppBase { +/** + * Accessibility status of an application. + * + * @public + */ +export enum AppStatus { + /** + * Application is accessible. + */ + accessible = 0, + /** + * Application is not accessible. + */ + inaccessible = 1, +} + +/** + * Status of the application's navLink. + * + * @public + */ +export enum AppNavLinkStatus { + /** + * The application navLink will be `visible` if the application's {@link AppStatus} is set to `accessible` + * and `hidden` if the application status is set to `inaccessible`. + */ + default = 0, + /** + * The application navLink is visible and clickable in the navigation bar. + */ + visible = 1, + /** + * The application navLink is visible but inactive and not clickable in the navigation bar. + */ + disabled = 2, + /** + * The application navLink does not appear in the navigation bar. + */ + hidden = 3, +} + +/** + * Defines the list of fields that can be updated via an {@link AppUpdater}. + * @public + */ +export type AppUpdatableFields = Pick; + +/** + * Updater for applications. + * see {@link ApplicationSetup} + * @public + */ +export type AppUpdater = (app: App) => Partial | undefined; + +/** + * @public + */ +export interface App { /** * The unique identifier of the application */ @@ -136,83 +192,12 @@ export interface AppBase { */ capabilities?: Partial; - /** - * Flag to keep track of legacy applications. - * For internal use only. any value will be overridden when registering an App. - * - * @internal - */ - legacy?: boolean; - /** * Hide the UI chrome when the application is mounted. Defaults to `false`. * Takes precedence over chrome service visibility settings. */ chromeless?: boolean; -} -/** - * Accessibility status of an application. - * - * @public - */ -export enum AppStatus { - /** - * Application is accessible. - */ - accessible = 0, - /** - * Application is not accessible. - */ - inaccessible = 1, -} - -/** - * Status of the application's navLink. - * - * @public - */ -export enum AppNavLinkStatus { - /** - * The application navLink will be `visible` if the application's {@link AppStatus} is set to `accessible` - * and `hidden` if the application status is set to `inaccessible`. - */ - default = 0, - /** - * The application navLink is visible and clickable in the navigation bar. - */ - visible = 1, - /** - * The application navLink is visible but inactive and not clickable in the navigation bar. - */ - disabled = 2, - /** - * The application navLink does not appear in the navigation bar. - */ - hidden = 3, -} - -/** - * Defines the list of fields that can be updated via an {@link AppUpdater}. - * @public - */ -export type AppUpdatableFields = Pick< - AppBase, - 'status' | 'navLinkStatus' | 'tooltip' | 'defaultPath' ->; - -/** - * Updater for applications. - * see {@link ApplicationSetup} - * @public - */ -export type AppUpdater = (app: AppBase) => Partial | undefined; - -/** - * Extension of {@link AppBase | common app properties} with the mount function. - * @public - */ -export interface App extends AppBase { /** * A mount function called when the user navigates to this app's route. May have signature of {@link AppMount} or * {@link AppMountDeprecated}. @@ -223,12 +208,6 @@ export interface App extends AppBase { */ mount: AppMount | AppMountDeprecated; - /** - * Hide the UI chrome when the application is mounted. Defaults to `false`. - * Takes precedence over chrome service visibility settings. - */ - chromeless?: boolean; - /** * Override the application's routing path from `/app/${id}`. * Must be unique across registered applications. Should not include the @@ -255,39 +234,18 @@ export interface App extends AppBase { exactRoute?: boolean; } -/** @public */ -export interface LegacyApp extends AppBase { - appUrl: string; - subUrlBase?: string; - linkToLastSubUrl?: boolean; - disableSubUrlTracking?: boolean; -} - /** * Public information about a registered {@link App | application} * * @public */ export type PublicAppInfo = Omit & { - legacy: false; // remove optional on fields populated with default values status: AppStatus; navLinkStatus: AppNavLinkStatus; appRoute: string; }; -/** - * Information about a registered {@link LegacyApp | legacy application} - * - * @public - */ -export type PublicLegacyAppInfo = Omit & { - legacy: true; - // remove optional on fields populated with default values - status: AppStatus; - navLinkStatus: AppNavLinkStatus; -}; - /** * A mount function called when the user navigates to this app's route. * @@ -300,6 +258,12 @@ export type AppMount = ( params: AppMountParameters ) => AppUnmount | Promise; +/** + * A function called when an application should be unmounted from the page. This function should be synchronous. + * @public + */ +export type AppUnmount = () => void; + /** * A mount function called when the user navigates to this app's route. * @@ -607,30 +571,14 @@ export interface AppLeaveActionFactory { default(): AppLeaveDefaultAction; } -/** - * A function called when an application should be unmounted from the page. This function should be synchronous. - * @public - */ -export type AppUnmount = () => void; - -/** @internal */ -export type AppMounter = (params: AppMountParameters) => Promise; - -/** @internal */ -export type LegacyAppMounter = (params: AppMountParameters) => void; - /** @internal */ -export type Mounter = SelectivePartial< - { - appRoute: string; - appBasePath: string; - mount: T extends LegacyApp ? LegacyAppMounter : AppMounter; - legacy: boolean; - exactRoute: boolean; - unmountBeforeMounting: T extends LegacyApp ? true : boolean; - }, - T extends LegacyApp ? never : 'unmountBeforeMounting' ->; +export interface Mounter { + appRoute: string; + appBasePath: string; + mount: AppMount; + exactRoute: boolean; + unmountBeforeMounting?: boolean; +} /** @internal */ export interface ParsedAppUrl { @@ -702,13 +650,6 @@ export interface InternalApplicationSetup extends Pick ): void; - /** - * Register metadata about legacy applications. Legacy apps will not be mounted when navigated to. - * @param app - * @internal - */ - registerLegacyApp(app: LegacyApp): void; - /** * Register a context provider for application mounting. Will only be available to applications that depend on the * plugin that registered this context. Deprecated, use {@link CoreSetup.getStartServices}. @@ -731,7 +672,7 @@ export interface InternalApplicationSetup extends Pick>; + applications$: Observable>; /** * Navigate to a given app @@ -861,14 +799,8 @@ export interface InternalApplicationStart extends Omit; /** - * The global history instance, exposed only to Core. Undefined when rendering a legacy application. + * The global history instance, exposed only to Core. * @internal */ - history: History | undefined; + history: History; } - -/** @internal */ -type SelectivePartial = Partial> & - Required>> extends infer U - ? { [P in keyof U]: U[P] } - : never; diff --git a/src/core/public/application/ui/app_container.test.tsx b/src/core/public/application/ui/app_container.test.tsx index e26fe7e59fd04..f6cde54e6f502 100644 --- a/src/core/public/application/ui/app_container.test.tsx +++ b/src/core/public/application/ui/app_container.test.tsx @@ -55,7 +55,6 @@ describe('AppContainer', () => { appBasePath: '/base-path', appRoute: '/some-route', unmountBeforeMounting: false, - legacy: false, exactRoute: false, mount: async ({ element }: AppMountParameters) => { await promise; @@ -146,7 +145,6 @@ describe('AppContainer', () => { appBasePath: '/base-path/some-route', appRoute: '/some-route', unmountBeforeMounting: false, - legacy: false, exactRoute: false, mount: async ({ element }: AppMountParameters) => { await waitPromise; diff --git a/src/core/public/application/ui/app_container.tsx b/src/core/public/application/ui/app_container.tsx index f668cf851da55..089d1cf3f3ced 100644 --- a/src/core/public/application/ui/app_container.tsx +++ b/src/core/public/application/ui/app_container.tsx @@ -94,8 +94,10 @@ export const AppContainer: FunctionComponent = ({ // eslint-disable-next-line no-console console.error(e); } finally { - setShowSpinner(false); - setIsMounting(false); + if (elementRef.current) { + setShowSpinner(false); + setIsMounting(false); + } } }; diff --git a/src/core/public/application/ui/app_router.tsx b/src/core/public/application/ui/app_router.tsx index 5021dd3ae765a..42bc9a53aee2d 100644 --- a/src/core/public/application/ui/app_router.tsx +++ b/src/core/public/application/ui/app_router.tsx @@ -58,25 +58,21 @@ export const AppRouter: FunctionComponent = ({ return ( - {[...mounters] - // legacy apps can have multiple sub-apps registered with the same route - // which needs additional logic that is handled in the catch-all route below - .filter(([_, mounter]) => !mounter.legacy) - .map(([appId, mounter]) => ( - ( - - )} - /> - ))} + {[...mounters].map(([appId, mounter]) => ( + ( + + )} + /> + ))} {/* handler for legacy apps and used as a catch-all to display 404 page on not existing /app/appId apps*/} = ({ url, }, }: RouteComponentProps) => { - // Find the mounter including legacy mounters with subapps: - const [id, mounter] = mounters.has(appId) - ? [appId, mounters.get(appId)] - : [...mounters].filter(([key]) => key.split(':')[0] === appId)[0] ?? []; - + // the id/mounter retrieval can be removed once #76348 is addressed + const [id, mounter] = mounters.has(appId) ? [appId, mounters.get(appId)] : []; return ( diff --git a/src/core/public/application/utils.test.ts b/src/core/public/application/utils.test.ts index 4663ca2db21e7..ee1d82a7a872e 100644 --- a/src/core/public/application/utils.test.ts +++ b/src/core/public/application/utils.test.ts @@ -18,16 +18,9 @@ */ import { of } from 'rxjs'; -import { App, AppNavLinkStatus, AppStatus, LegacyApp } from './types'; +import { App, AppNavLinkStatus, AppStatus } from './types'; import { BasePath } from '../http/base_path'; -import { - appendAppPath, - getAppInfo, - isLegacyApp, - parseAppUrl, - relativeToAbsolute, - removeSlashes, -} from './utils'; +import { appendAppPath, getAppInfo, parseAppUrl, relativeToAbsolute, removeSlashes } from './utils'; describe('removeSlashes', () => { it('only removes duplicates by default', () => { @@ -70,9 +63,11 @@ describe('appendAppPath', () => { expect(appendAppPath('/app/my-app', '')).toEqual('/app/my-app'); }); - it('preserves the trailing slash only if included in the hash', () => { + it('preserves the trailing slash only if included in the hash or appPath', () => { expect(appendAppPath('/app/my-app', '/some-path/')).toEqual('/app/my-app/some-path'); expect(appendAppPath('/app/my-app', '/some-path#/')).toEqual('/app/my-app/some-path#/'); + expect(appendAppPath('/app/my-app#/', '')).toEqual('/app/my-app#/'); + expect(appendAppPath('/app/my-app#', '/')).toEqual('/app/my-app#/'); expect(appendAppPath('/app/my-app', '/some-path#/hash/')).toEqual( '/app/my-app/some-path#/hash/' ); @@ -80,29 +75,6 @@ describe('appendAppPath', () => { }); }); -describe('isLegacyApp', () => { - it('returns true for legacy apps', () => { - expect( - isLegacyApp({ - id: 'legacy', - title: 'Legacy App', - appUrl: '/some-url', - legacy: true, - }) - ).toEqual(true); - }); - it('returns false for non-legacy apps', () => { - expect( - isLegacyApp({ - id: 'legacy', - title: 'Legacy App', - mount: () => () => undefined, - legacy: false, - }) - ).toEqual(false); - }); -}); - describe('relativeToAbsolute', () => { it('converts a relative path to an absolute url', () => { const origin = window.location.origin; @@ -113,7 +85,7 @@ describe('relativeToAbsolute', () => { }); describe('parseAppUrl', () => { - let apps: Map | LegacyApp>; + let apps: Map>; let basePath: BasePath; const getOrigin = () => 'https://kibana.local:8080'; @@ -124,19 +96,6 @@ describe('parseAppUrl', () => { title: 'some-title', mount: () => () => undefined, ...props, - legacy: false, - }; - apps.set(app.id, app); - return app; - }; - - const createLegacyApp = (props: Partial): LegacyApp => { - const app: LegacyApp = { - id: 'some-id', - title: 'some-title', - appUrl: '/my-url', - ...props, - legacy: true, }; apps.set(app.id, app); return app; @@ -153,10 +112,6 @@ describe('parseAppUrl', () => { id: 'bar', appRoute: '/custom-bar', }); - createLegacyApp({ - id: 'legacy', - appUrl: '/app/legacy', - }); }); describe('with relative paths', () => { @@ -236,18 +191,6 @@ describe('parseAppUrl', () => { path: '/path#hash/bang?hello=dolly', }); }); - it('works with legacy apps', () => { - expect(parseAppUrl('/base-path/app/legacy', basePath, apps, getOrigin)).toEqual({ - app: 'legacy', - path: undefined, - }); - expect( - parseAppUrl('/base-path/app/legacy/path#hash?query=bar', basePath, apps, getOrigin) - ).toEqual({ - app: 'legacy', - path: '/path#hash?query=bar', - }); - }); it('returns undefined when the app is not known', () => { expect(parseAppUrl('/base-path/app/non-registered', basePath, apps, getOrigin)).toEqual( undefined @@ -409,25 +352,6 @@ describe('parseAppUrl', () => { path: '/path#hash/bang?hello=dolly', }); }); - it('works with legacy apps', () => { - expect( - parseAppUrl('https://kibana.local:8080/base-path/app/legacy', basePath, apps, getOrigin) - ).toEqual({ - app: 'legacy', - path: undefined, - }); - expect( - parseAppUrl( - 'https://kibana.local:8080/base-path/app/legacy/path#hash?query=bar', - basePath, - apps, - getOrigin - ) - ).toEqual({ - app: 'legacy', - path: '/path#hash?query=bar', - }); - }); it('returns undefined when the app is not known', () => { expect( parseAppUrl( @@ -471,18 +395,6 @@ describe('getAppInfo', () => { status: AppStatus.accessible, navLinkStatus: AppNavLinkStatus.default, appRoute: `/app/some-id`, - legacy: false, - ...props, - }); - - const createLegacyApp = (props: Partial = {}): LegacyApp => ({ - appUrl: '/my-app-url', - updater$: of(() => undefined), - id: 'some-id', - title: 'some-title', - status: AppStatus.accessible, - navLinkStatus: AppNavLinkStatus.default, - legacy: true, ...props, }); @@ -496,21 +408,6 @@ describe('getAppInfo', () => { status: AppStatus.accessible, navLinkStatus: AppNavLinkStatus.visible, appRoute: `/app/some-id`, - legacy: false, - }); - }); - - it('converts a legacy application and remove sensitive properties', () => { - const app = createLegacyApp(); - const info = getAppInfo(app); - - expect(info).toEqual({ - appUrl: '/my-app-url', - id: 'some-id', - title: 'some-title', - status: AppStatus.accessible, - navLinkStatus: AppNavLinkStatus.visible, - legacy: true, }); }); diff --git a/src/core/public/application/utils.ts b/src/core/public/application/utils.ts index c5ed7b659f3ae..85760526bf544 100644 --- a/src/core/public/application/utils.ts +++ b/src/core/public/application/utils.ts @@ -18,15 +18,7 @@ */ import { IBasePath } from '../http'; -import { - App, - AppNavLinkStatus, - AppStatus, - LegacyApp, - ParsedAppUrl, - PublicAppInfo, - PublicLegacyAppInfo, -} from './types'; +import { App, AppNavLinkStatus, AppStatus, ParsedAppUrl, PublicAppInfo } from './types'; /** * Utility to remove trailing, leading or duplicate slashes. @@ -55,8 +47,8 @@ export const removeSlashes = ( export const appendAppPath = (appBasePath: string, path: string = '') => { // Only prepend slash if not a hash or query path path = path === '' || path.startsWith('#') || path.startsWith('?') ? path : `/${path}`; - // Do not remove trailing slash when in hashbang - const removeTrailing = path.indexOf('#') === -1; + // Do not remove trailing slash when in hashbang or basePath + const removeTrailing = path.indexOf('#') === -1 && appBasePath.indexOf('#') === -1; return removeSlashes(`${appBasePath}${path}`, { trailing: removeTrailing, duplicates: true, @@ -64,10 +56,6 @@ export const appendAppPath = (appBasePath: string, path: string = '') => { }); }; -export function isLegacyApp(app: App | LegacyApp): app is LegacyApp { - return app.legacy === true; -} - /** * Converts a relative path to an absolute url. * Implementation is based on a specified behavior of the browser to automatically convert @@ -95,7 +83,7 @@ export const relativeToAbsolute = (url: string): string => { export const parseAppUrl = ( url: string, basePath: IBasePath, - apps: Map | LegacyApp>, + apps: Map>, getOrigin: () => string = () => window.location.origin ): ParsedAppUrl | undefined => { url = removeBasePath(url, basePath, getOrigin()); @@ -104,7 +92,7 @@ export const parseAppUrl = ( } for (const app of apps.values()) { - const appPath = isLegacyApp(app) ? app.appUrl : app.appRoute || `/app/${app.id}`; + const appPath = app.appRoute || `/app/${app.id}`; if (url.startsWith(appPath)) { const path = url.substr(appPath.length); @@ -123,29 +111,18 @@ const removeBasePath = (url: string, basePath: IBasePath, origin: string): strin return basePath.remove(url); }; -export function getAppInfo(app: App | LegacyApp): PublicAppInfo | PublicLegacyAppInfo { +export function getAppInfo(app: App): PublicAppInfo { const navLinkStatus = app.navLinkStatus === AppNavLinkStatus.default ? app.status === AppStatus.inaccessible ? AppNavLinkStatus.hidden : AppNavLinkStatus.visible : app.navLinkStatus!; - if (isLegacyApp(app)) { - const { updater$, ...infos } = app; - return { - ...infos, - status: app.status!, - navLinkStatus, - legacy: true, - }; - } else { - const { updater$, mount, ...infos } = app; - return { - ...infos, - status: app.status!, - navLinkStatus, - appRoute: app.appRoute!, - legacy: false, - }; - } + const { updater$, mount, ...infos } = app; + return { + ...infos, + status: app.status!, + navLinkStatus, + appRoute: app.appRoute!, + }; } diff --git a/src/core/public/chrome/chrome_service.mock.ts b/src/core/public/chrome/chrome_service.mock.ts index c9a05ff4e08fe..0ae8b132f1d86 100644 --- a/src/core/public/chrome/chrome_service.mock.ts +++ b/src/core/public/chrome/chrome_service.mock.ts @@ -17,14 +17,7 @@ * under the License. */ import { BehaviorSubject } from 'rxjs'; -import { - ChromeBadge, - ChromeBrand, - ChromeBreadcrumb, - ChromeService, - InternalChromeStart, - NavType, -} from './'; +import { ChromeBadge, ChromeBrand, ChromeBreadcrumb, ChromeService, InternalChromeStart } from './'; const createStartContractMock = () => { const startContract: DeeplyMockedKeys = { @@ -47,14 +40,13 @@ const createStartContractMock = () => { docTitle: { change: jest.fn(), reset: jest.fn(), - __legacy: { - setBaseTitle: jest.fn(), - }, }, navControls: { registerLeft: jest.fn(), + registerCenter: jest.fn(), registerRight: jest.fn(), getLeft$: jest.fn(), + getCenter$: jest.fn(), getRight$: jest.fn(), }, setAppTitle: jest.fn(), @@ -73,7 +65,6 @@ const createStartContractMock = () => { setHelpExtension: jest.fn(), setHelpSupportUrl: jest.fn(), getIsNavDrawerLocked$: jest.fn(), - getNavType$: jest.fn(), getCustomNavLink$: jest.fn(), setCustomNavLink: jest.fn(), }; @@ -86,7 +77,6 @@ const createStartContractMock = () => { startContract.getCustomNavLink$.mockReturnValue(new BehaviorSubject(undefined)); startContract.getHelpExtension$.mockReturnValue(new BehaviorSubject(undefined)); startContract.getIsNavDrawerLocked$.mockReturnValue(new BehaviorSubject(false)); - startContract.getNavType$.mockReturnValue(new BehaviorSubject('modern' as NavType)); return startContract; }; diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index ef9a682d609ec..b01f120b81305 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -37,7 +37,6 @@ import { ChromeNavControls, NavControlsService } from './nav_controls'; import { ChromeNavLinks, NavLinksService, ChromeNavLink } from './nav_links'; import { ChromeRecentlyAccessed, RecentlyAccessedService } from './recently_accessed'; import { Header } from './ui'; -import { NavType } from './ui/header'; import { ChromeHelpExtensionMenuLink } from './ui/header/header_help_menu'; export { ChromeNavControls, ChromeRecentlyAccessed, ChromeDocTitle }; @@ -172,10 +171,6 @@ export class ChromeService { const getIsNavDrawerLocked$ = isNavDrawerLocked$.pipe(takeUntil(this.stop$)); - // TODO #64541 - // Can delete - const getNavType$ = uiSettings.get$('pageNavigation').pipe(takeUntil(this.stop$)); - const isIE = () => { const ua = window.navigator.userAgent; const msie = ua.indexOf('MSIE '); // IE 10 or older @@ -238,14 +233,13 @@ export class ChromeService { homeHref={http.basePath.prepend('/app/home')} isVisible$={this.isVisible$} kibanaVersion={injectedMetadata.getKibanaVersion()} - legacyMode={injectedMetadata.getLegacyMode()} navLinks$={navLinks.getNavLinks$()} recentlyAccessed$={recentlyAccessed.get$()} navControlsLeft$={navControls.getLeft$()} + navControlsCenter$={navControls.getCenter$()} navControlsRight$={navControls.getRight$()} onIsLockedUpdate={setIsNavDrawerLocked} isLocked$={getIsNavDrawerLocked$} - navType$={getNavType$} /> ), @@ -306,8 +300,6 @@ export class ChromeService { getIsNavDrawerLocked$: () => getIsNavDrawerLocked$, - getNavType$: () => getNavType$, - getCustomNavLink$: () => customNavLink$.pipe(takeUntil(this.stop$)), setCustomNavLink: (customNavLink?: ChromeNavLink) => { @@ -469,13 +461,6 @@ export interface ChromeStart { * Get an observable of the current locked state of the nav drawer. */ getIsNavDrawerLocked$(): Observable; - - /** - * Get the navigation type - * TODO #64541 - * Can delete - */ - getNavType$(): Observable; } /** @internal */ diff --git a/src/core/public/chrome/doc_title/doc_title_service.test.ts b/src/core/public/chrome/doc_title/doc_title_service.test.ts index 763e8c9ebd74a..953baf7e7d1d5 100644 --- a/src/core/public/chrome/doc_title/doc_title_service.test.ts +++ b/src/core/public/chrome/doc_title/doc_title_service.test.ts @@ -64,17 +64,4 @@ describe('DocTitleService', () => { expect(document.title).toEqual('InitialTitle'); }); }); - - describe('#__legacy.setBaseTitle()', () => { - it('allows to change the baseTitle after startup', async () => { - const start = getStart('InitialTitle'); - start.change('WithInitial'); - expect(document.title).toEqual('WithInitial - InitialTitle'); - start.__legacy.setBaseTitle('NewBaseTitle'); - start.change('WithNew'); - expect(document.title).toEqual('WithNew - NewBaseTitle'); - start.reset(); - expect(document.title).toEqual('NewBaseTitle'); - }); - }); }); diff --git a/src/core/public/chrome/doc_title/doc_title_service.ts b/src/core/public/chrome/doc_title/doc_title_service.ts index c6e9ec7a40b77..817a460acaf3f 100644 --- a/src/core/public/chrome/doc_title/doc_title_service.ts +++ b/src/core/public/chrome/doc_title/doc_title_service.ts @@ -59,11 +59,6 @@ export interface ChromeDocTitle { * (meaning the one present in the title meta at application load.) */ reset(): void; - - /** @internal */ - __legacy: { - setBaseTitle(baseTitle: string): void; - }; } const defaultTitle: string[] = []; @@ -85,11 +80,6 @@ export class DocTitleService { reset: () => { this.applyTitle(defaultTitle); }, - __legacy: { - setBaseTitle: (baseTitle) => { - this.baseTitle = baseTitle; - }, - }, }; } 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 167948e01cb36..2638f40c77dc4 100644 --- a/src/core/public/chrome/nav_controls/nav_controls_service.ts +++ b/src/core/public/chrome/nav_controls/nav_controls_service.ts @@ -45,14 +45,18 @@ export interface ChromeNavControl { * @public */ export interface ChromeNavControls { - /** Register a nav control to be presented on the left side of the chrome header. */ + /** Register a nav control to be presented on the bottom-left side of the chrome header. */ registerLeft(navControl: ChromeNavControl): void; - /** Register a nav control to be presented on the right side of the chrome header. */ + /** Register a nav control to be presented on the top-right side of the chrome header. */ registerRight(navControl: ChromeNavControl): void; + /** Register a nav control to be presented on the top-center side of the chrome header. */ + registerCenter(navControl: ChromeNavControl): void; /** @internal */ getLeft$(): Observable; /** @internal */ getRight$(): Observable; + /** @internal */ + getCenter$(): Observable; } /** @internal */ @@ -62,6 +66,7 @@ export class NavControlsService { public start() { const navControlsLeft$ = new BehaviorSubject>(new Set()); const navControlsRight$ = new BehaviorSubject>(new Set()); + const navControlsCenter$ = new BehaviorSubject>(new Set()); return { // In the future, registration should be moved to the setup phase. This @@ -72,6 +77,9 @@ export class NavControlsService { registerRight: (navControl: ChromeNavControl) => navControlsRight$.next(new Set([...navControlsRight$.value.values(), navControl])), + registerCenter: (navControl: ChromeNavControl) => + navControlsCenter$.next(new Set([...navControlsCenter$.value.values(), navControl])), + getLeft$: () => navControlsLeft$.pipe( map((controls) => sortBy([...controls.values()], 'order')), @@ -82,6 +90,11 @@ export class NavControlsService { map((controls) => sortBy([...controls.values()], 'order')), takeUntil(this.stop$) ), + getCenter$: () => + navControlsCenter$.pipe( + map((controls) => sortBy([...controls.values()], 'order')), + takeUntil(this.stop$) + ), }; } diff --git a/src/core/public/chrome/nav_links/nav_link.ts b/src/core/public/chrome/nav_links/nav_link.ts index 55b5c80526bab..4b82e0ced4505 100644 --- a/src/core/public/chrome/nav_links/nav_link.ts +++ b/src/core/public/chrome/nav_links/nav_link.ts @@ -74,54 +74,8 @@ export interface ChromeNavLink { /** * Settled state between `url`, `baseUrl`, and `active` - * - * @internalRemarks - * This should be required once legacy apps are gone. - */ - readonly href?: string; - - /** LEGACY FIELDS */ - - /** - * A url base that legacy apps can set to match deep URLs to an application. - * - * @internalRemarks - * This should be removed once legacy apps are gone. - * - * @deprecated */ - readonly subUrlBase?: string; - - /** - * A flag that tells legacy chrome to ignore the link when - * tracking sub-urls - * - * @internalRemarks - * This should be removed once legacy apps are gone. - * - * @deprecated - */ - readonly disableSubUrlTracking?: boolean; - - /** - * Whether or not the subUrl feature should be enabled. - * - * @internalRemarks - * Only read by legacy platform. - * - * @deprecated - */ - readonly linkToLastSubUrl?: boolean; - - /** - * Indicates whether or not this app is currently on the screen. - * - * @internalRemarks - * Remove this when ApplicationService is implemented and managing apps. - * - * @deprecated - */ - readonly active?: boolean; + readonly href: string; /** * Disables a link from being clickable. @@ -129,30 +83,18 @@ export interface ChromeNavLink { * @internalRemarks * This is only used by the ML and Graph plugins currently. They use this field * to disable the nav link when the license is expired. - * - * @deprecated */ readonly disabled?: boolean; /** * Hides a link from the navigation. - * - * @internalRemarks - * Remove this when ApplicationService is implemented. Instead, plugins should only - * register an Application if needed. */ readonly hidden?: boolean; - - /** - * Used to separate links to legacy applications from NP applications - * @internal - */ - readonly legacy: boolean; } /** @public */ export type ChromeNavLinkUpdateableFields = Partial< - Pick + Pick >; export class NavLinkWrapper { @@ -170,7 +112,7 @@ export class NavLinkWrapper { public update(newProps: ChromeNavLinkUpdateableFields) { // Enforce limited properties at runtime for JS code - newProps = pick(newProps, ['active', 'disabled', 'hidden', 'url', 'subUrlBase', 'href']); + newProps = pick(newProps, ['disabled', 'hidden', 'url', 'href']); return new NavLinkWrapper({ ...this.properties, ...newProps }); } } diff --git a/src/core/public/chrome/nav_links/nav_links_service.test.ts b/src/core/public/chrome/nav_links/nav_links_service.test.ts index 8f610e238b0fd..a8413ed5b546a 100644 --- a/src/core/public/chrome/nav_links/nav_links_service.test.ts +++ b/src/core/public/chrome/nav_links/nav_links_service.test.ts @@ -19,7 +19,7 @@ import { NavLinksService } from './nav_links_service'; import { take, map, takeLast } from 'rxjs/operators'; -import { App, LegacyApp } from '../../application'; +import { App } from '../../application'; import { BehaviorSubject } from 'rxjs'; const availableApps = new Map([ @@ -34,32 +34,6 @@ const availableApps = new Map([ }, ], ['chromelessApp', { id: 'chromelessApp', order: 20, title: 'Chromless App', chromeless: true }], - [ - 'legacyApp1', - { - id: 'legacyApp1', - order: 5, - title: 'Legacy App 1', - icon: 'legacyApp1', - appUrl: '/app1', - legacy: true, - }, - ], - [ - 'legacyApp2', - { - id: 'legacyApp2', - order: -10, - title: 'Legacy App 2', - euiIconType: 'canvasApp', - appUrl: '/app2', - legacy: true, - }, - ], - [ - 'legacyApp3', - { id: 'legacyApp3', order: 20, title: 'Legacy App 3', appUrl: '/app3', legacy: true }, - ], ]); const mockHttp = { @@ -76,9 +50,7 @@ describe('NavLinksService', () => { beforeEach(() => { service = new NavLinksService(); mockAppService = { - applications$: new BehaviorSubject>( - availableApps as any - ), + applications$: new BehaviorSubject>(availableApps as any), }; start = service.start({ application: mockAppService, http: mockHttp }); }); @@ -105,19 +77,19 @@ describe('NavLinksService', () => { map((links) => links.map((l) => l.id)) ) .toPromise() - ).toEqual(['app2', 'legacyApp2', 'app1', 'legacyApp1', 'legacyApp3']); + ).toEqual(['app2', 'app1']); }); it('emits multiple values', async () => { const navLinkIds$ = start.getNavLinks$().pipe(map((links) => links.map((l) => l.id))); const emittedLinks: string[][] = []; navLinkIds$.subscribe((r) => emittedLinks.push(r)); - start.update('legacyApp1', { active: true }); + start.update('app1', { href: '/foo' }); service.stop(); expect(emittedLinks).toEqual([ - ['app2', 'legacyApp2', 'app1', 'legacyApp1', 'legacyApp3'], - ['app2', 'legacyApp2', 'app1', 'legacyApp1', 'legacyApp3'], + ['app2', 'app1'], + ['app2', 'app1'], ]); }); @@ -130,7 +102,7 @@ describe('NavLinksService', () => { describe('#get()', () => { it('returns link if exists', () => { - expect(start.get('legacyApp1')!.title).toEqual('Legacy App 1'); + expect(start.get('app2')!.title).toEqual('App 2'); }); it('returns undefined if it does not exist', () => { @@ -140,19 +112,13 @@ describe('NavLinksService', () => { describe('#getAll()', () => { it('returns a sorted array of navlinks', () => { - expect(start.getAll().map((l) => l.id)).toEqual([ - 'app2', - 'legacyApp2', - 'app1', - 'legacyApp1', - 'legacyApp3', - ]); + expect(start.getAll().map((l) => l.id)).toEqual(['app2', 'app1']); }); }); describe('#has()', () => { it('returns true if exists', () => { - expect(start.has('legacyApp1')).toBe(true); + expect(start.has('app2')).toBe(true); }); it('returns false if it does not exist', () => { @@ -171,7 +137,7 @@ describe('NavLinksService', () => { map((links) => links.map((l) => l.id)) ) .toPromise() - ).toEqual(['app2', 'legacyApp2', 'app1', 'legacyApp1', 'legacyApp3']); + ).toEqual(['app2', 'app1']); }); it('does nothing on chromeless applications', async () => { @@ -184,11 +150,11 @@ describe('NavLinksService', () => { map((links) => links.map((l) => l.id)) ) .toPromise() - ).toEqual(['app2', 'legacyApp2', 'app1', 'legacyApp1', 'legacyApp3']); + ).toEqual(['app2', 'app1']); }); it('removes all other links', async () => { - start.showOnly('legacyApp1'); + start.showOnly('app2'); expect( await start .getNavLinks$() @@ -197,11 +163,11 @@ describe('NavLinksService', () => { map((links) => links.map((l) => l.id)) ) .toPromise() - ).toEqual(['legacyApp1']); + ).toEqual(['app2']); }); it('still removes all other links when availableApps are re-emitted', async () => { - start.showOnly('legacyApp2'); + start.showOnly('app2'); mockAppService.applications$.next(mockAppService.applications$.value); expect( await start @@ -211,22 +177,19 @@ describe('NavLinksService', () => { map((links) => links.map((l) => l.id)) ) .toPromise() - ).toEqual(['legacyApp2']); + ).toEqual(['app2']); }); }); describe('#update()', () => { it('updates the navlinks and returns the updated link', async () => { - expect(start.update('legacyApp1', { hidden: true })).toEqual( + expect(start.update('app2', { hidden: true })).toEqual( expect.objectContaining({ - appUrl: '/app1', - disabled: false, hidden: true, - icon: 'legacyApp1', - id: 'legacyApp1', - legacy: true, - order: 5, - title: 'Legacy App 1', + id: 'app2', + order: -10, + title: 'App 2', + euiIconType: 'canvasApp', }) ); const hiddenLinkIds = await start @@ -236,7 +199,7 @@ describe('NavLinksService', () => { map((links) => links.filter((l) => l.hidden).map((l) => l.id)) ) .toPromise(); - expect(hiddenLinkIds).toEqual(['legacyApp1']); + expect(hiddenLinkIds).toEqual(['app2']); }); it('returns undefined if link does not exist', () => { @@ -244,7 +207,7 @@ describe('NavLinksService', () => { }); it('keeps the updated link when availableApps are re-emitted', async () => { - start.update('legacyApp1', { hidden: true }); + start.update('app2', { hidden: true }); mockAppService.applications$.next(mockAppService.applications$.value); const hiddenLinkIds = await start .getNavLinks$() @@ -253,7 +216,7 @@ describe('NavLinksService', () => { map((links) => links.filter((l) => l.hidden).map((l) => l.id)) ) .toPromise(); - expect(hiddenLinkIds).toEqual(['legacyApp1']); + expect(hiddenLinkIds).toEqual(['app2']); }); }); diff --git a/src/core/public/chrome/nav_links/to_nav_link.test.ts b/src/core/public/chrome/nav_links/to_nav_link.test.ts index ba04dbed49cd4..7e2c1fc1f89f8 100644 --- a/src/core/public/chrome/nav_links/to_nav_link.test.ts +++ b/src/core/public/chrome/nav_links/to_nav_link.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { PublicAppInfo, AppNavLinkStatus, AppStatus, PublicLegacyAppInfo } from '../../application'; +import { PublicAppInfo, AppNavLinkStatus, AppStatus } from '../../application'; import { toNavLink } from './to_nav_link'; import { httpServiceMock } from '../../mocks'; @@ -28,17 +28,6 @@ const app = (props: Partial = {}): PublicAppInfo => ({ status: AppStatus.accessible, navLinkStatus: AppNavLinkStatus.default, appRoute: `/app/some-id`, - legacy: false, - ...props, -}); - -const legacyApp = (props: Partial = {}): PublicLegacyAppInfo => ({ - appUrl: '/my-app-url', - id: 'some-id', - title: 'some-title', - status: AppStatus.accessible, - navLinkStatus: AppNavLinkStatus.default, - legacy: true, ...props, }); @@ -67,11 +56,6 @@ describe('toNavLink', () => { ); }); - it('flags legacy apps when converting to navLink', () => { - expect(toNavLink(app({}), basePath).properties.legacy).toEqual(false); - expect(toNavLink(legacyApp({}), basePath).properties.legacy).toEqual(true); - }); - it('handles applications with custom app route', () => { const link = toNavLink( app({ @@ -103,32 +87,6 @@ describe('toNavLink', () => { ); }); - it('does not generate `url` for legacy app', () => { - const link = toNavLink( - legacyApp({ - appUrl: '/my-legacy-app/#foo', - defaultPath: '/some/default/path', - }), - basePath - ); - expect(link.properties.url).toBeUndefined(); - }); - - it('uses appUrl when converting legacy applications', () => { - expect( - toNavLink( - legacyApp({ - appUrl: '/my-legacy-app/#foo', - }), - basePath - ).properties - ).toEqual( - expect.objectContaining({ - baseUrl: 'http://localhost/base-path/my-legacy-app/#foo', - }) - ); - }); - it('uses the application status when the navLinkStatus is set to default', () => { expect( toNavLink( diff --git a/src/core/public/chrome/nav_links/to_nav_link.ts b/src/core/public/chrome/nav_links/to_nav_link.ts index 2dedbfd5f36ac..703c1798b6fb8 100644 --- a/src/core/public/chrome/nav_links/to_nav_link.ts +++ b/src/core/public/chrome/nav_links/to_nav_link.ts @@ -17,19 +17,14 @@ * under the License. */ -import { PublicAppInfo, AppNavLinkStatus, AppStatus, PublicLegacyAppInfo } from '../../application'; +import { PublicAppInfo, AppNavLinkStatus, AppStatus } from '../../application'; import { IBasePath } from '../../http'; import { NavLinkWrapper } from './nav_link'; import { appendAppPath } from '../../application/utils'; -export function toNavLink( - app: PublicAppInfo | PublicLegacyAppInfo, - basePath: IBasePath -): NavLinkWrapper { +export function toNavLink(app: PublicAppInfo, basePath: IBasePath): NavLinkWrapper { const useAppStatus = app.navLinkStatus === AppNavLinkStatus.default; - const relativeBaseUrl = isLegacyApp(app) - ? basePath.prepend(app.appUrl) - : basePath.prepend(app.appRoute!); + const relativeBaseUrl = basePath.prepend(app.appRoute!); const url = relativeToAbsolute(appendAppPath(relativeBaseUrl, app.defaultPath)); const baseUrl = relativeToAbsolute(relativeBaseUrl); @@ -39,14 +34,9 @@ export function toNavLink( ? app.status === AppStatus.inaccessible : app.navLinkStatus === AppNavLinkStatus.hidden, disabled: useAppStatus ? false : app.navLinkStatus === AppNavLinkStatus.disabled, - legacy: isLegacyApp(app), baseUrl, - ...(isLegacyApp(app) - ? {} - : { - href: url, - url, - }), + href: url, + url, }); } @@ -63,7 +53,3 @@ export function relativeToAbsolute(url: string) { a.setAttribute('href', url); return a.href; } - -function isLegacyApp(app: PublicAppInfo | PublicLegacyAppInfo): app is PublicLegacyAppInfo { - return app.legacy === true; -} diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap index 79beaf79068ef..86cacfe98f767 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap @@ -71,7 +71,6 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` "href": "Custom link", "id": "Custom link", "isActive": true, - "legacy": false, "title": "Custom link", }, "closed": false, @@ -122,8 +121,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` homeHref="/" id="collapsibe-nav" isLocked={false} - isOpen={true} - legacyMode={false} + isNavOpen={true} navLinks$={ BehaviorSubject { "_isScalar": false, @@ -140,7 +138,6 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` "href": "discover", "id": "discover", "isActive": true, - "legacy": false, "title": "discover", }, Object { @@ -155,7 +152,6 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` "href": "siem", "id": "siem", "isActive": true, - "legacy": false, "title": "siem", }, Object { @@ -170,7 +166,6 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` "href": "metrics", "id": "metrics", "isActive": true, - "legacy": false, "title": "metrics", }, Object { @@ -184,7 +179,6 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` "href": "monitoring", "id": "monitoring", "isActive": true, - "legacy": false, "title": "monitoring", }, Object { @@ -199,7 +193,6 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` "href": "visualize", "id": "visualize", "isActive": true, - "legacy": false, "title": "visualize", }, Object { @@ -214,7 +207,6 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` "href": "dashboard", "id": "dashboard", "isActive": true, - "legacy": false, "title": "dashboard", }, Object { @@ -224,7 +216,6 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` "href": "canvas", "id": "canvas", "isActive": true, - "legacy": false, "title": "canvas", }, Object { @@ -239,7 +230,6 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` "href": "logs", "id": "logs", "isActive": true, - "legacy": false, "title": "logs", }, ], @@ -2115,8 +2105,7 @@ exports[`CollapsibleNav renders the default nav 1`] = ` homeHref="/" id="collapsibe-nav" isLocked={false} - isOpen={false} - legacyMode={false} + isNavOpen={false} navLinks$={ BehaviorSubject { "_isScalar": false, @@ -2350,8 +2339,8 @@ exports[`CollapsibleNav renders the default nav 2`] = ` homeHref="/" id="collapsibe-nav" isLocked={false} + isNavOpen={false} isOpen={true} - legacyMode={false} navLinks$={ BehaviorSubject { "_isScalar": false, @@ -2466,461 +2455,9 @@ exports[`CollapsibleNav renders the default nav 2`] = ` data-test-subj="collapsibleNav" id="collapsibe-nav" isDocked={false} - isOpen={true} + isOpen={false} onClose={[Function]} - > - - - -
- -
-
- + /> `; @@ -3037,8 +2574,8 @@ exports[`CollapsibleNav renders the default nav 3`] = ` homeHref="/" id="collapsibe-nav" isLocked={true} + isNavOpen={false} isOpen={true} - legacyMode={false} navLinks$={ BehaviorSubject { "_isScalar": false, @@ -3153,7 +2690,7 @@ exports[`CollapsibleNav renders the default nav 3`] = ` data-test-subj="collapsibleNav" id="collapsibe-nav" isDocked={true} - isOpen={true} + isOpen={false} onClose={[Function]} > - -
-
-
- - -`; - -exports[`Header renders 2`] = ` -
+ -
- -
- -
- -
- - - -
-
- -
+ - - - -
- - - - -
- - , + ], + }, + Object { + "borders": "none", + "items": Array [ + -
- - - - - - - - - , ], - "thrownError": null, - } - } - /> - -
- -
+ }, + Object { + "borders": "none", + "items": Array [ , + , + ], + }, + ] + } + theme="dark" + > +
+ +
+ + + + +
+ +
+ +
+
+
+
+ +
+ +
+ - - - - } - closePopover={[Function]} - data-test-subj="helpMenuButton" - display="inlineBlock" - hasArrow={true} - id="headerHelpMenu" - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - repositionOnScroll={true} - > - -
-
- - - -
-
-
-
- -
-
-
- -
-
-
- - - - -
-
-`; - -exports[`Header renders 3`] = ` -
- -
-
-
- -
- -
- -
- -
- - - -
-
- -
- - - - -
- - - - -
- - -
- - - - - - - - - - -
- -
- - - - - - } - closePopover={[Function]} - data-test-subj="helpMenuButton" - display="inlineBlock" - hasArrow={true} - id="headerHelpMenu" - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - repositionOnScroll={true} - > - -
-
- - - -
-
-
-
-
-
-
-
- -
-
-
- - - - - -
- -
-
-
-
-
-
-`; - -exports[`Header renders 4`] = ` -
- -
-
-
- -
- -
- -
- - -
- - - -
-
-
- -
- - - - -
- - - - -
- - -
- - - - - - - - - - -
- -
- - - - - + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": Symbol(react.fragment), + "timeZone": null, + } } - closePopover={[Function]} - data-test-subj="helpMenuButton" - display="inlineBlock" - hasArrow={true} - id="headerHelpMenu" - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - repositionOnScroll={true} + kibanaDocLink="/docs" + kibanaVersion="1.0.0" + useDefaultContent={true} > - + + + } + closePopover={[Function]} + data-test-subj="helpMenuButton" + display="inlineBlock" + hasArrow={true} + id="headerHelpMenu" + isOpen={false} + ownFocus={true} + panelPaddingSize="m" + repositionOnScroll={true} > -
- - - + type="help" + > +
+ + + +
-
-
-
-
-
-
-
- + + + +
+
+ +
+ +
+
+
+
+
+
+ +
+ +
+ +
+ + + +
+
+ +
+ + +
+ + + + + + + + + -
-
-
-
- + +
+ +
+ +
+ +
+ +
+ +
+ +
+ - - + - - - - + close + + + + + + + + +
+ + + `; diff --git a/src/core/public/chrome/ui/header/_index.scss b/src/core/public/chrome/ui/header/_index.scss index e3b73abbcabc2..44cd864278325 100644 --- a/src/core/public/chrome/ui/header/_index.scss +++ b/src/core/public/chrome/ui/header/_index.scss @@ -1,14 +1,5 @@ @include euiHeaderAffordForFixed; -// TODO #64541 -// Delete this block -.chrHeaderWrapper:not(.headerWrapper) { - width: 100%; - position: fixed; - top: 0; - z-index: 10; -} - .chrHeaderHelpMenu__version { text-transform: none; } diff --git a/src/core/public/chrome/ui/header/collapsible_nav.test.tsx b/src/core/public/chrome/ui/header/collapsible_nav.test.tsx index 97dd229cb1ead..e33e76a45580e 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.test.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.test.tsx @@ -40,7 +40,6 @@ function mockLink({ title = 'discover', category }: Partial) { id: title, href: title, baseUrl: '/', - legacy: false, isActive: true, 'data-test-subj': title, }; @@ -60,9 +59,8 @@ function mockProps() { basePath: httpServiceMock.createSetupContract({ basePath: '/test' }).basePath, id: 'collapsibe-nav', isLocked: false, - isOpen: false, + isNavOpen: false, homeHref: '/', - legacyMode: false, navLinks$: new BehaviorSubject([]), recentlyAccessed$: new BehaviorSubject([]), storage: new StubBrowserStorage(), @@ -125,7 +123,7 @@ describe('CollapsibleNav', () => { const component = mount( { const component = mount( @@ -149,9 +147,9 @@ describe('CollapsibleNav', () => { clickGroup(component, 'kibana'); clickGroup(component, 'recentlyViewed'); expectShownNavLinksCount(component, 1); - component.setProps({ isOpen: false }); + component.setProps({ isNavOpen: false }); expectNavIsClosed(component); - component.setProps({ isOpen: true }); + component.setProps({ isNavOpen: true }); expectShownNavLinksCount(component, 1); }); @@ -162,14 +160,14 @@ describe('CollapsibleNav', () => { const component = mount( ); component.setProps({ closeNav: () => { - component.setProps({ isOpen: false }); + component.setProps({ isNavOpen: false }); onClose(); }, }); @@ -177,11 +175,11 @@ describe('CollapsibleNav', () => { component.find('[data-test-subj="collapsibleNavGroup-recentlyViewed"] a').simulate('click'); expect(onClose.callCount).toEqual(1); expectNavIsClosed(component); - component.setProps({ isOpen: true }); + component.setProps({ isNavOpen: true }); component.find('[data-test-subj="collapsibleNavGroup-kibana"] a').simulate('click'); expect(onClose.callCount).toEqual(2); expectNavIsClosed(component); - component.setProps({ isOpen: true }); + component.setProps({ isNavOpen: true }); component.find('[data-test-subj="collapsibleNavGroup-noCategory"] a').simulate('click'); expect(onClose.callCount).toEqual(3); expectNavIsClosed(component); diff --git a/src/core/public/chrome/ui/header/collapsible_nav.tsx b/src/core/public/chrome/ui/header/collapsible_nav.tsx index 5abd14312f4a6..01cdb9c38881a 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.tsx @@ -79,9 +79,8 @@ interface Props { basePath: HttpStart['basePath']; id: string; isLocked: boolean; - isOpen: boolean; + isNavOpen: boolean; homeHref: string; - legacyMode: boolean; navLinks$: Rx.Observable; recentlyAccessed$: Rx.Observable; storage?: Storage; @@ -95,9 +94,8 @@ export function CollapsibleNav({ basePath, id, isLocked, - isOpen, + isNavOpen, homeHref, - legacyMode, storage = window.localStorage, onIsLockedUpdate, closeNav, @@ -116,7 +114,6 @@ export function CollapsibleNav({ const readyForEUI = (link: ChromeNavLink, needsIcon: boolean = false) => { return createEuiListItem({ link, - legacyMode, appId, dataTestSubj: 'collapsibleNavAppLink', navigateToApp, @@ -132,7 +129,7 @@ export function CollapsibleNav({ aria-label={i18n.translate('core.ui.primaryNav.screenReaderLabel', { defaultMessage: 'Primary', })} - isOpen={isOpen} + isOpen={isNavOpen} isDocked={isLocked} onClose={closeNav} > @@ -148,7 +145,6 @@ export function CollapsibleNav({ listItems={[ createEuiListItem({ link: customNavLink, - legacyMode, basePath, navigateToApp, dataTestSubj: 'collapsibleNavCustomNavLink', diff --git a/src/core/public/chrome/ui/header/header.test.tsx b/src/core/public/chrome/ui/header/header.test.tsx index a9fa15d43182b..7309b9af49388 100644 --- a/src/core/public/chrome/ui/header/header.test.tsx +++ b/src/core/public/chrome/ui/header/header.test.tsx @@ -21,7 +21,6 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { BehaviorSubject } from 'rxjs'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { NavType } from '.'; import { httpServiceMock } from '../../../http/http_service.mock'; import { applicationServiceMock } from '../../../mocks'; import { Header } from './header'; @@ -50,12 +49,11 @@ function mockProps() { forceAppSwitcherNavigation$: new BehaviorSubject(false), helpExtension$: new BehaviorSubject(undefined), helpSupportUrl$: new BehaviorSubject(''), - legacyMode: false, navControlsLeft$: new BehaviorSubject([]), + navControlsCenter$: new BehaviorSubject([]), navControlsRight$: new BehaviorSubject([]), basePath: http.basePath, isLocked$: new BehaviorSubject(false), - navType$: new BehaviorSubject('modern' as NavType), loadingCount$: new BehaviorSubject(0), onIsLockedUpdate: () => {}, }; @@ -72,15 +70,14 @@ describe('Header', () => { const isVisible$ = new BehaviorSubject(false); const breadcrumbs$ = new BehaviorSubject([{ text: 'test' }]); const isLocked$ = new BehaviorSubject(false); - const navType$ = new BehaviorSubject('modern' as NavType); const navLinks$ = new BehaviorSubject([ - { id: 'kibana', title: 'kibana', baseUrl: '', legacy: false }, + { id: 'kibana', title: 'kibana', baseUrl: '', href: '' }, ]); const customNavLink$ = new BehaviorSubject({ id: 'cloud-deployment-link', title: 'Manage cloud deployment', baseUrl: '', - legacy: false, + href: '', }); const recentlyAccessed$ = new BehaviorSubject([ { link: '', label: 'dashboard', id: 'dashboard' }, @@ -93,22 +90,19 @@ describe('Header', () => { navLinks$={navLinks$} recentlyAccessed$={recentlyAccessed$} isLocked$={isLocked$} - navType$={navType$} customNavLink$={customNavLink$} /> ); - expect(component).toMatchSnapshot(); + expect(component.find('EuiHeader').exists()).toBeFalsy(); act(() => isVisible$.next(true)); component.update(); - expect(component).toMatchSnapshot(); + expect(component.find('EuiHeader').exists()).toBeTruthy(); + expect(component.find('nav[aria-label="Primary"]').exists()).toBeFalsy(); act(() => isLocked$.next(true)); component.update(); - expect(component).toMatchSnapshot(); - - act(() => navType$.next('legacy' as NavType)); - component.update(); + expect(component.find('nav[aria-label="Primary"]').exists()).toBeTruthy(); expect(component).toMatchSnapshot(); }); }); diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index 0624d66a9598b..7ec03ea4c6da6 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -23,8 +23,6 @@ import { EuiHeaderSectionItem, EuiHeaderSectionItemButton, EuiIcon, - EuiNavDrawer, - EuiShowFor, htmlIdGenerator, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -43,14 +41,14 @@ import { import { InternalApplicationStart } from '../../../application/types'; import { HttpStart } from '../../../http'; import { ChromeHelpExtension } from '../../chrome_service'; -import { NavType, OnIsLockedUpdate } from './'; +import { OnIsLockedUpdate } from './'; import { CollapsibleNav } from './collapsible_nav'; import { HeaderBadge } from './header_badge'; import { HeaderBreadcrumbs } from './header_breadcrumbs'; import { HeaderHelpMenu } from './header_help_menu'; import { HeaderLogo } from './header_logo'; import { HeaderNavControls } from './header_nav_controls'; -import { NavDrawer } from './nav_drawer'; +import { HeaderActionMenu } from './header_action_menu'; export interface HeaderProps { kibanaVersion: string; @@ -67,160 +65,134 @@ export interface HeaderProps { forceAppSwitcherNavigation$: Observable; helpExtension$: Observable; helpSupportUrl$: Observable; - legacyMode: boolean; navControlsLeft$: Observable; + navControlsCenter$: Observable; navControlsRight$: Observable; basePath: HttpStart['basePath']; isLocked$: Observable; - navType$: Observable; loadingCount$: ReturnType; onIsLockedUpdate: OnIsLockedUpdate; } -function renderMenuTrigger(toggleOpen: () => void) { - return ( - - - - ); -} - export function Header({ kibanaVersion, kibanaDocLink, - legacyMode, application, basePath, onIsLockedUpdate, homeHref, ...observables }: HeaderProps) { - const isVisible = useObservable(observables.isVisible$, true); - const navType = useObservable(observables.navType$, 'modern'); + const isVisible = useObservable(observables.isVisible$, false); const isLocked = useObservable(observables.isLocked$, false); - const [isOpen, setIsOpen] = useState(false); + const [isNavOpen, setIsNavOpen] = useState(false); if (!isVisible) { return ; } - const navDrawerRef = createRef(); const toggleCollapsibleNavRef = createRef(); const navId = htmlIdGenerator()(); - const className = classnames( - 'chrHeaderWrapper', // TODO #64541 - delete this - 'hide-for-sharing', - { - 'chrHeaderWrapper--navIsLocked': isLocked, - headerWrapper: navType === 'modern', - } - ); + const className = classnames('hide-for-sharing', 'headerGlobalNav'); return ( <>
- - - {navType === 'modern' ? ( +
+ , + ], + borders: 'none', + }, + { + ...(observables.navControlsCenter$ && { + items: [], + }), + borders: 'none', + }, + { + items: [ + , + , + ], + borders: 'none', + }, + ]} + /> + + + setIsOpen(!isOpen)} - aria-expanded={isOpen} - aria-pressed={isOpen} + onClick={() => setIsNavOpen(!isNavOpen)} + aria-expanded={isNavOpen} + aria-pressed={isNavOpen} aria-controls={navId} ref={toggleCollapsibleNavRef} > - ) : ( - // TODO #64541 - // Delete this block - - - {renderMenuTrigger(() => navDrawerRef.current?.toggleOpen())} - - - )} - - - + - - + + - + - + - - - - + + + + + + +
- -
-
- {navType === 'modern' ? ( - { - setIsOpen(false); - if (toggleCollapsibleNavRef.current) { - toggleCollapsibleNavRef.current.focus(); - } - }} - customNavLink$={observables.customNavLink$} - /> - ) : ( - // TODO #64541 - // Delete this block - - )} + { + setIsNavOpen(false); + if (toggleCollapsibleNavRef.current) { + toggleCollapsibleNavRef.current.focus(); + } + }} + customNavLink$={observables.customNavLink$} + />
); diff --git a/src/core/public/chrome/ui/header/header_action_menu.test.tsx b/src/core/public/chrome/ui/header/header_action_menu.test.tsx new file mode 100644 index 0000000000000..a124c8ab66969 --- /dev/null +++ b/src/core/public/chrome/ui/header/header_action_menu.test.tsx @@ -0,0 +1,139 @@ +/* + * 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, ReactWrapper } from 'enzyme'; +import { act } from 'react-dom/test-utils'; +import { BehaviorSubject } from 'rxjs'; +import { HeaderActionMenu } from './header_action_menu'; +import { MountPoint, UnmountCallback } from '../../../types'; + +type MockedUnmount = jest.MockedFunction; + +describe('HeaderActionMenu', () => { + let component: ReactWrapper; + let menuMount$: BehaviorSubject; + let unmounts: Record; + + beforeEach(() => { + menuMount$ = new BehaviorSubject(undefined); + unmounts = {}; + }); + + const refresh = () => { + new Promise(async (resolve) => { + if (component) { + act(() => { + component.update(); + }); + } + setImmediate(() => resolve(component)); // flushes any pending promises + }); + }; + + const createMountPoint = (id: string, content: string = id): MountPoint => ( + root + ): MockedUnmount => { + const container = document.createElement('DIV'); + // eslint-disable-next-line no-unsanitized/property + container.innerHTML = content; + root.appendChild(container); + const unmount = jest.fn(() => container.remove()); + unmounts[id] = unmount; + return unmount; + }; + + it('mounts the current value of the provided observable', async () => { + component = mount(); + + act(() => { + menuMount$.next(createMountPoint('FOO')); + }); + await refresh(); + + expect(component.html()).toMatchInlineSnapshot( + `"
FOO
"` + ); + }); + + it('clears the content of the component when emitting undefined', async () => { + component = mount(); + + act(() => { + menuMount$.next(createMountPoint('FOO')); + }); + await refresh(); + + expect(component.html()).toMatchInlineSnapshot( + `"
FOO
"` + ); + + act(() => { + menuMount$.next(undefined); + }); + await refresh(); + + expect(component.html()).toMatchInlineSnapshot( + `"
"` + ); + }); + + it('updates the dom when a new mount point is emitted', async () => { + component = mount(); + + act(() => { + menuMount$.next(createMountPoint('FOO')); + }); + await refresh(); + + expect(component.html()).toMatchInlineSnapshot( + `"
FOO
"` + ); + + act(() => { + menuMount$.next(createMountPoint('BAR')); + }); + await refresh(); + + expect(component.html()).toMatchInlineSnapshot( + `"
BAR
"` + ); + }); + + it('calls the previous mount point `unmount` when mounting a new mount point', async () => { + component = mount(); + + act(() => { + menuMount$.next(createMountPoint('FOO')); + }); + await refresh(); + + expect(Object.keys(unmounts)).toEqual(['FOO']); + expect(unmounts.FOO).not.toHaveBeenCalled(); + + act(() => { + menuMount$.next(createMountPoint('BAR')); + }); + await refresh(); + + expect(Object.keys(unmounts)).toEqual(['FOO', 'BAR']); + expect(unmounts.FOO).toHaveBeenCalledTimes(1); + expect(unmounts.BAR).not.toHaveBeenCalled(); + }); +}); diff --git a/src/core/public/chrome/ui/header/header_action_menu.tsx b/src/core/public/chrome/ui/header/header_action_menu.tsx new file mode 100644 index 0000000000000..3a7a09608ba66 --- /dev/null +++ b/src/core/public/chrome/ui/header/header_action_menu.tsx @@ -0,0 +1,64 @@ +/* + * 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, { FC, useRef, useLayoutEffect, useState } from 'react'; +import { Observable } from 'rxjs'; +import { MountPoint, UnmountCallback } from '../../../types'; + +interface HeaderActionMenuProps { + actionMenu$: Observable; +} + +export const HeaderActionMenu: FC = ({ actionMenu$ }) => { + // useObservable relies on useState under the hood. The signature is type SetStateAction = S | ((prevState: S) => S); + // As we got a Observable here, React's setState setter assume he's getting a `(prevState: S) => S` signature, + // therefore executing the mount method, causing everything to crash. + // piping the observable before calling `useObservable` causes the effect to always having a new reference, as + // the piped observable is a new instance on every render, causing infinite loops. + // this is why we use `useLayoutEffect` manually here. + const [mounter, setMounter] = useState<{ mount: MountPoint | undefined }>({ mount: undefined }); + useLayoutEffect(() => { + const s = actionMenu$.subscribe((value) => { + setMounter({ mount: value }); + }); + return () => s.unsubscribe(); + }, [actionMenu$]); + + const elementRef = useRef(null); + const unmountRef = useRef(null); + + useLayoutEffect(() => { + if (unmountRef.current) { + unmountRef.current(); + unmountRef.current = null; + } + + if (mounter.mount && elementRef.current) { + try { + unmountRef.current = mounter.mount(elementRef.current); + } catch (e) { + // TODO: use client-side logger when feature is implemented + // eslint-disable-next-line no-console + console.error(e); + } + } + }, [mounter]); + + return
; +}; diff --git a/src/core/public/chrome/ui/header/header_logo.tsx b/src/core/public/chrome/ui/header/header_logo.tsx index 9bec946b6b76e..dee93ecb1a804 100644 --- a/src/core/public/chrome/ui/header/header_logo.tsx +++ b/src/core/public/chrome/ui/header/header_logo.tsx @@ -105,6 +105,8 @@ export function HeaderLogo({ href, navigateToApp, ...observables }: Props) { aria-label={i18n.translate('core.ui.chrome.headerGlobalNav.goHomePageIconAriaLabel', { defaultMessage: 'Go to home page', })} - /> + > + Elastic + ); } diff --git a/src/core/public/chrome/ui/header/header_nav_controls.tsx b/src/core/public/chrome/ui/header/header_nav_controls.tsx index 0941f7b27b662..8d9d8097fd8e3 100644 --- a/src/core/public/chrome/ui/header/header_nav_controls.tsx +++ b/src/core/public/chrome/ui/header/header_nav_controls.tsx @@ -26,7 +26,7 @@ import { HeaderExtension } from './header_extension'; interface Props { navControls$: Observable; - side: 'left' | 'right'; + side?: 'left' | 'right'; } export function HeaderNavControls({ navControls$, side }: Props) { @@ -41,7 +41,10 @@ export function HeaderNavControls({ navControls$, side }: Props) { return ( <> {navControls.map((navControl: ChromeNavControl, index: number) => ( - + ))} diff --git a/src/core/public/chrome/ui/header/nav_drawer.tsx b/src/core/public/chrome/ui/header/nav_drawer.tsx deleted file mode 100644 index ee4bff6cc0ac4..0000000000000 --- a/src/core/public/chrome/ui/header/nav_drawer.tsx +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { EuiHorizontalRule, EuiNavDrawer, EuiNavDrawerGroup } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { useObservable } from 'react-use'; -import { Observable } from 'rxjs'; -import { ChromeNavLink, ChromeRecentlyAccessedHistoryItem, CoreStart } from '../../..'; -import { InternalApplicationStart } from '../../../application/types'; -import { HttpStart } from '../../../http'; -import { OnIsLockedUpdate } from './'; -import { createEuiListItem, createRecentNavLink } from './nav_link'; -import { RecentLinks } from './recent_links'; - -export interface Props { - appId$: InternalApplicationStart['currentAppId$']; - basePath: HttpStart['basePath']; - isLocked?: boolean; - legacyMode: boolean; - navLinks$: Observable; - recentlyAccessed$: Observable; - navigateToApp: CoreStart['application']['navigateToApp']; - onIsLockedUpdate?: OnIsLockedUpdate; -} - -function NavDrawerRenderer( - { isLocked, onIsLockedUpdate, basePath, legacyMode, navigateToApp, ...observables }: Props, - ref: React.Ref -) { - const appId = useObservable(observables.appId$, ''); - const navLinks = useObservable(observables.navLinks$, []).filter((link) => !link.hidden); - const recentNavLinks = useObservable(observables.recentlyAccessed$, []).map((link) => - createRecentNavLink(link, navLinks, basePath) - ); - - return ( - - {RecentLinks({ recentNavLinks })} - - - createEuiListItem({ - link, - legacyMode, - appId, - basePath, - navigateToApp, - dataTestSubj: 'navDrawerAppsMenuLink', - }) - )} - aria-label={i18n.translate('core.ui.primaryNavList.screenReaderLabel', { - defaultMessage: 'Primary navigation links', - })} - /> - - ); -} - -export const NavDrawer = React.forwardRef(NavDrawerRenderer); diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx index c70a40f49643e..04d9c5bf7a10a 100644 --- a/src/core/public/chrome/ui/header/nav_link.tsx +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -29,7 +29,6 @@ export const isModifiedOrPrevented = (event: React.MouseEvent {}, @@ -52,12 +50,7 @@ export function createEuiListItem({ dataTestSubj, externalLink = false, }: Props) { - const { legacy, active, id, title, disabled, euiIconType, icon, tooltip } = link; - let { href } = link; - - if (legacy) { - href = link.url && !active ? link.url : link.baseUrl; - } + const { href, id, title, disabled, euiIconType, icon, tooltip } = link; return { label: tooltip ?? title, @@ -70,8 +63,6 @@ export function createEuiListItem({ if ( !externalLink && // ignore external links - !legacyMode && // ignore when in legacy mode - !legacy && // ignore links to legacy apps event.button === 0 && // ignore everything but left clicks !isModifiedOrPrevented(event) ) { @@ -79,8 +70,7 @@ export function createEuiListItem({ navigateToApp(id); } }, - // Legacy apps use `active` property, NP apps should match the current app - isActive: active || appId === id, + isActive: appId === id, isDisabled: disabled, 'data-test-subj': dataTestSubj, ...(basePath && { @@ -116,7 +106,7 @@ export function createRecentNavLink( ) { const { link, label } = recentLink; const href = relativeToAbsolute(basePath.prepend(link)); - const navLink = navLinks.find((nl) => href.startsWith(nl.baseUrl ?? nl.subUrlBase)); + const navLink = navLinks.find((nl) => href.startsWith(nl.baseUrl)); let titleAndAriaLabel = label; if (navLink) { diff --git a/src/core/public/core_app/status/lib/load_status.test.ts b/src/core/public/core_app/status/lib/load_status.test.ts index 3a444a4448467..5a9f982e106a7 100644 --- a/src/core/public/core_app/status/lib/load_status.test.ts +++ b/src/core/public/core_app/status/lib/load_status.test.ts @@ -57,6 +57,7 @@ const mockedResponse: StatusResponse = { ], }, metrics: { + collected_at: new Date('2020-01-01 01:00:00'), collection_interval_in_millis: 1000, os: { platform: 'darwin' as const, diff --git a/src/core/public/core_app/styles/_globals_v7dark.scss b/src/core/public/core_app/styles/_globals_v7dark.scss index 8ac841aab8469..9a4a965d63a38 100644 --- a/src/core/public/core_app/styles/_globals_v7dark.scss +++ b/src/core/public/core_app/styles/_globals_v7dark.scss @@ -3,9 +3,6 @@ // prepended to all .scss imports (from JS, when v7dark theme selected) @import '@elastic/eui/src/themes/eui/eui_colors_dark'; - -@import '@elastic/eui/src/global_styling/functions/index'; -@import '@elastic/eui/src/global_styling/variables/index'; -@import '@elastic/eui/src/global_styling/mixins/index'; +@import '@elastic/eui/src/themes/eui/eui_globals'; @import './mixins'; diff --git a/src/core/public/core_app/styles/_globals_v7light.scss b/src/core/public/core_app/styles/_globals_v7light.scss index 701bbdfe03662..ddb4b5b31fa1f 100644 --- a/src/core/public/core_app/styles/_globals_v7light.scss +++ b/src/core/public/core_app/styles/_globals_v7light.scss @@ -3,9 +3,6 @@ // prepended to all .scss imports (from JS, when v7light theme selected) @import '@elastic/eui/src/themes/eui/eui_colors_light'; - -@import '@elastic/eui/src/global_styling/functions/index'; -@import '@elastic/eui/src/global_styling/variables/index'; -@import '@elastic/eui/src/global_styling/mixins/index'; +@import '@elastic/eui/src/themes/eui/eui_globals'; @import './mixins'; diff --git a/src/core/public/core_app/styles/_globals_v8dark.scss b/src/core/public/core_app/styles/_globals_v8dark.scss index 972365e9e9d0e..9ad9108f350ff 100644 --- a/src/core/public/core_app/styles/_globals_v8dark.scss +++ b/src/core/public/core_app/styles/_globals_v8dark.scss @@ -3,14 +3,6 @@ // prepended to all .scss imports (from JS, when v8dark theme selected) @import '@elastic/eui/src/themes/eui-amsterdam/eui_amsterdam_colors_dark'; - -@import '@elastic/eui/src/global_styling/functions/index'; -@import '@elastic/eui/src/themes/eui-amsterdam/global_styling/functions/index'; - -@import '@elastic/eui/src/global_styling/variables/index'; -@import '@elastic/eui/src/themes/eui-amsterdam/global_styling/variables/index'; - -@import '@elastic/eui/src/global_styling/mixins/index'; -@import '@elastic/eui/src/themes/eui-amsterdam/global_styling/mixins/index'; +@import '@elastic/eui/src/themes/eui-amsterdam/eui_amsterdam_globals'; @import './mixins'; diff --git a/src/core/public/core_app/styles/_globals_v8light.scss b/src/core/public/core_app/styles/_globals_v8light.scss index dc99f4d45082e..a6b2cb84c2062 100644 --- a/src/core/public/core_app/styles/_globals_v8light.scss +++ b/src/core/public/core_app/styles/_globals_v8light.scss @@ -3,14 +3,6 @@ // prepended to all .scss imports (from JS, when v8light theme selected) @import '@elastic/eui/src/themes/eui-amsterdam/eui_amsterdam_colors_light'; - -@import '@elastic/eui/src/global_styling/functions/index'; -@import '@elastic/eui/src/themes/eui-amsterdam/global_styling/functions/index'; - -@import '@elastic/eui/src/global_styling/variables/index'; -@import '@elastic/eui/src/themes/eui-amsterdam/global_styling/variables/index'; - -@import '@elastic/eui/src/global_styling/mixins/index'; -@import '@elastic/eui/src/themes/eui-amsterdam/global_styling/mixins/index'; +@import '@elastic/eui/src/themes/eui-amsterdam/eui_amsterdam_globals'; @import './mixins'; diff --git a/src/core/public/core_system.test.mocks.ts b/src/core/public/core_system.test.mocks.ts index b5b99418b44b4..d0e457386ffca 100644 --- a/src/core/public/core_system.test.mocks.ts +++ b/src/core/public/core_system.test.mocks.ts @@ -23,7 +23,6 @@ import { fatalErrorsServiceMock } from './fatal_errors/fatal_errors_service.mock import { httpServiceMock } from './http/http_service.mock'; import { i18nServiceMock } from './i18n/i18n_service.mock'; import { injectedMetadataServiceMock } from './injected_metadata/injected_metadata_service.mock'; -import { legacyPlatformServiceMock } from './legacy/legacy_service.mock'; import { notificationServiceMock } from './notifications/notifications_service.mock'; import { overlayServiceMock } from './overlays/overlay_service.mock'; import { pluginsServiceMock } from './plugins/plugins_service.mock'; @@ -34,14 +33,6 @@ import { contextServiceMock } from './context/context_service.mock'; import { integrationsServiceMock } from './integrations/integrations_service.mock'; import { coreAppMock } from './core_app/core_app.mock'; -export const MockLegacyPlatformService = legacyPlatformServiceMock.create(); -export const LegacyPlatformServiceConstructor = jest - .fn() - .mockImplementation(() => MockLegacyPlatformService); -jest.doMock('./legacy', () => ({ - LegacyPlatformService: LegacyPlatformServiceConstructor, -})); - export const MockInjectedMetadataService = injectedMetadataServiceMock.create(); export const InjectedMetadataServiceConstructor = jest .fn() diff --git a/src/core/public/core_system.test.ts b/src/core/public/core_system.test.ts index 4c1993c90a2e1..213237309c30b 100644 --- a/src/core/public/core_system.test.ts +++ b/src/core/public/core_system.test.ts @@ -23,13 +23,11 @@ import { HttpServiceConstructor, I18nServiceConstructor, InjectedMetadataServiceConstructor, - LegacyPlatformServiceConstructor, MockChromeService, MockFatalErrorsService, MockHttpService, MockI18nService, MockInjectedMetadataService, - MockLegacyPlatformService, MockNotificationsService, MockOverlayService, MockPluginsService, @@ -80,7 +78,6 @@ describe('constructor', () => { createCoreSystem(); expect(InjectedMetadataServiceConstructor).toHaveBeenCalledTimes(1); - expect(LegacyPlatformServiceConstructor).toHaveBeenCalledTimes(1); expect(I18nServiceConstructor).toHaveBeenCalledTimes(1); expect(FatalErrorsServiceConstructor).toHaveBeenCalledTimes(1); expect(NotificationServiceConstructor).toHaveBeenCalledTimes(1); @@ -106,25 +103,6 @@ describe('constructor', () => { }); }); - it('passes required params to LegacyPlatformService', () => { - const requireLegacyFiles = { requireLegacyFiles: true }; - const requireLegacyBootstrapModule = { requireLegacyBootstrapModule: true }; - const requireNewPlatformShimModule = { requireNewPlatformShimModule: true }; - - createCoreSystem({ - requireLegacyFiles, - requireLegacyBootstrapModule, - requireNewPlatformShimModule, - }); - - expect(LegacyPlatformServiceConstructor).toHaveBeenCalledTimes(1); - expect(LegacyPlatformServiceConstructor).toHaveBeenCalledWith({ - requireLegacyFiles, - requireLegacyBootstrapModule, - requireNewPlatformShimModule, - }); - }); - it('passes browserSupportsCsp to ChromeService', () => { createCoreSystem(); @@ -190,7 +168,6 @@ describe('#setup()', () => { pluginDependencies: new Map([ [pluginA, []], [pluginB, [pluginA]], - [MockLegacyPlatformService.legacyId, [pluginA, pluginB]], ]), }); }); @@ -301,11 +278,6 @@ describe('#start()', () => { expect(MockPluginsService.start).toHaveBeenCalledTimes(1); }); - it('calls legacyPlatform#start()', async () => { - await startCore(); - expect(MockLegacyPlatformService.start).toHaveBeenCalledTimes(1); - }); - it('calls overlays#start()', async () => { await startCore(); expect(MockOverlayService.start).toHaveBeenCalledTimes(1); @@ -317,7 +289,6 @@ describe('#start()', () => { expect(MockRenderingService.start).toHaveBeenCalledWith({ application: expect.any(Object), chrome: expect.any(Object), - injectedMetadata: expect.any(Object), overlays: expect.any(Object), targetDomElement: expect.any(HTMLElement), }); @@ -335,14 +306,6 @@ describe('#start()', () => { }); describe('#stop()', () => { - it('calls legacyPlatform.stop()', () => { - const coreSystem = createCoreSystem(); - - expect(MockLegacyPlatformService.stop).not.toHaveBeenCalled(); - coreSystem.stop(); - expect(MockLegacyPlatformService.stop).toHaveBeenCalled(); - }); - it('calls notifications.stop()', () => { const coreSystem = createCoreSystem(); @@ -422,7 +385,6 @@ describe('RenderingService targetDomElement', () => { let targetDomElementParentInStart: HTMLElement | null; MockRenderingService.start.mockImplementation(({ targetDomElement }) => { targetDomElementParentInStart = targetDomElement.parentElement; - return { legacyTargetDomElement: document.createElement('div') }; }); // Starting the core system should pass the targetDomElement as a child of the rootDomElement @@ -432,24 +394,6 @@ describe('RenderingService targetDomElement', () => { }); }); -describe('LegacyPlatformService targetDomElement', () => { - it('only mounts the element when start, after setting up the legacyPlatformService', async () => { - const core = createCoreSystem(); - - let targetDomElementInStart: HTMLElement | undefined; - MockLegacyPlatformService.start.mockImplementation(({ targetDomElement }) => { - targetDomElementInStart = targetDomElement; - }); - - await core.setup(); - await core.start(); - // Starting the core system should pass the legacyTargetDomElement to the LegacyPlatformService - const renderingLegacyTargetDomElement = - MockRenderingService.start.mock.results[0].value.legacyTargetDomElement; - expect(targetDomElementInStart!).toBe(renderingLegacyTargetDomElement); - }); -}); - describe('Notifications targetDomElement', () => { it('only mounts the element when started, after setting up the notificationsService', async () => { const rootDomElement = document.createElement('div'); diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index e08841b0271d9..006d0036f7a12 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -30,13 +30,12 @@ import { InjectedMetadataSetup, InjectedMetadataStart, } from './injected_metadata'; -import { LegacyPlatformParams, LegacyPlatformService } from './legacy'; import { NotificationsService } from './notifications'; import { OverlayService } from './overlays'; import { PluginsService } from './plugins'; import { UiSettingsService } from './ui_settings'; import { ApplicationService } from './application'; -import { mapToObject, pick } from '../utils/'; +import { pick } from '../utils/'; import { DocLinksService } from './doc_links'; import { RenderingService } from './rendering'; import { SavedObjectsService } from './saved_objects'; @@ -49,9 +48,6 @@ interface Params { rootDomElement: HTMLElement; browserSupportsCsp: boolean; injectedMetadata: InjectedMetadataParams['injectedMetadata']; - requireLegacyFiles?: LegacyPlatformParams['requireLegacyFiles']; - requireLegacyBootstrapModule?: LegacyPlatformParams['requireLegacyBootstrapModule']; - requireNewPlatformShimModule?: LegacyPlatformParams['requireNewPlatformShimModule']; } /** @internal */ @@ -86,7 +82,6 @@ export interface InternalCoreStart extends Omit { export class CoreSystem { private readonly fatalErrors: FatalErrorsService; private readonly injectedMetadata: InjectedMetadataService; - private readonly legacy: LegacyPlatformService; private readonly notifications: NotificationsService; private readonly http: HttpService; private readonly savedObjects: SavedObjectsService; @@ -107,14 +102,7 @@ export class CoreSystem { private fatalErrorsSetup: FatalErrorsSetup | null = null; constructor(params: Params) { - const { - rootDomElement, - browserSupportsCsp, - injectedMetadata, - requireLegacyFiles, - requireLegacyBootstrapModule, - requireNewPlatformShimModule, - } = params; + const { rootDomElement, browserSupportsCsp, injectedMetadata } = params; this.rootDomElement = rootDomElement; @@ -145,12 +133,6 @@ export class CoreSystem { this.context = new ContextService(this.coreContext); this.plugins = new PluginsService(this.coreContext, injectedMetadata.uiPlugins); this.coreApp = new CoreApp(this.coreContext); - - this.legacy = new LegacyPlatformService({ - requireLegacyFiles, - requireLegacyBootstrapModule, - requireNewPlatformShimModule, - }); } public async setup() { @@ -170,16 +152,9 @@ export class CoreSystem { const pluginDependencies = this.plugins.getOpaqueIds(); const context = this.context.setup({ - // We inject a fake "legacy plugin" with dependencies on every plugin so that legacy plugins: - // 1) Can access context from any NP plugin - // 2) Can register context providers that will only be available to other legacy plugins and will not leak into - // New Platform plugins. - pluginDependencies: new Map([ - ...pluginDependencies, - [this.legacy.legacyId, [...pluginDependencies.keys()]], - ]), + pluginDependencies: new Map([...pluginDependencies]), }); - const application = this.application.setup({ context, http, injectedMetadata }); + const application = this.application.setup({ context, http }); this.coreApp.setup({ application, http, injectedMetadata, notifications }); const core: InternalCoreSetup = { @@ -193,12 +168,7 @@ export class CoreSystem { }; // Services that do not expose contracts at setup - const plugins = await this.plugins.setup(core); - - await this.legacy.setup({ - core, - plugins: mapToObject(plugins.contracts), - }); + await this.plugins.setup(core); return { fatalErrors: this.fatalErrorsSetup }; } catch (error) { @@ -277,7 +247,7 @@ export class CoreSystem { fatalErrors, }; - const plugins = await this.plugins.start(core); + await this.plugins.start(core); // ensure the rootDomElement is empty this.rootDomElement.textContent = ''; @@ -286,20 +256,13 @@ export class CoreSystem { this.rootDomElement.appendChild(notificationsTargetDomElement); this.rootDomElement.appendChild(overlayTargetDomElement); - const rendering = this.rendering.start({ + this.rendering.start({ application, chrome, - injectedMetadata, overlays, targetDomElement: coreUiTargetDomElement, }); - await this.legacy.start({ - core, - plugins: mapToObject(plugins.contracts), - targetDomElement: rendering.legacyTargetDomElement, - }); - return { application, }; @@ -315,7 +278,6 @@ export class CoreSystem { } public stop() { - this.legacy.stop(); this.plugins.stop(); this.coreApp.stop(); this.notifications.stop(); diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index fc753517fd940..fae7a272c9635 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -38,6 +38,9 @@ export class DocLinksService { links: { dashboard: { drilldowns: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/drilldowns.html`, + drilldownsTriggerPicker: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url-drilldown.html#trigger-picker`, + urlDrilldownTemplateSyntax: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url-drilldown.html#templating`, + urlDrilldownVariables: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url-drilldown.html#variables`, }, filebeat: { base: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}`, @@ -129,7 +132,7 @@ export class DocLinksService { }, visualize: { guide: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/visualize.html`, - timelionDeprecation: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/timelion.html#timelion-deprecation`, + timelionDeprecation: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/dashboard.html#timelion-deprecation`, }, }, }); @@ -143,6 +146,9 @@ export interface DocLinksStart { readonly links: { readonly dashboard: { readonly drilldowns: string; + readonly drilldownsTriggerPicker: string; + readonly urlDrilldownTemplateSyntax: string; + readonly urlDrilldownVariables: string; }; readonly filebeat: { readonly base: string; diff --git a/src/core/public/index.scss b/src/core/public/index.scss index c2ad2841d5a77..6ba9254e5d381 100644 --- a/src/core/public/index.scss +++ b/src/core/public/index.scss @@ -1,6 +1,6 @@ +@import './variables'; @import './core'; @import './chrome/index'; @import './overlays/index'; @import './rendering/index'; @import './styles/index'; - diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 9176a277b3f43..a9774dafd2340 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -61,7 +61,6 @@ import { import { FatalErrorsSetup, FatalErrorsStart, FatalErrorInfo } from './fatal_errors'; import { HttpSetup, HttpStart } from './http'; import { I18nStart } from './i18n'; -import { InjectedMetadataSetup, InjectedMetadataStart, LegacyNavLink } from './injected_metadata'; import { NotificationsSetup, NotificationsStart } from './notifications'; import { OverlayStart } from './overlays'; import { Plugin, PluginInitializer, PluginInitializerContext, PluginOpaqueId } from './plugins'; @@ -106,7 +105,6 @@ export { ApplicationStart, App, PublicAppInfo, - AppBase, AppMount, AppMountDeprecated, AppUnmount, @@ -122,8 +120,6 @@ export { AppUpdatableFields, AppUpdater, ScopedHistory, - LegacyApp, - PublicLegacyAppInfo, NavigateToAppOptions, } from './application'; @@ -230,7 +226,7 @@ export interface CoreSetup { - /** @deprecated */ - injectedMetadata: InjectedMetadataSetup; -} - -/** - * Start interface exposed to the legacy platform via the `ui/new_platform` module. - * - * @remarks - * Some methods are not supported in the legacy platform and while present to make this type compatibile with - * {@link CoreStart}, unsupported methods will throw exceptions when called. - * - * @public - * @deprecated - */ -export interface LegacyCoreStart extends CoreStart { - /** @deprecated */ - injectedMetadata: InjectedMetadataStart; -} - export { Capabilities, ChromeBadge, @@ -356,7 +322,6 @@ export { HttpSetup, HttpStart, I18nStart, - LegacyNavLink, NotificationsSetup, NotificationsStart, Plugin, diff --git a/src/core/public/injected_metadata/index.ts b/src/core/public/injected_metadata/index.ts index cebd0f017de69..925eeab187535 100644 --- a/src/core/public/injected_metadata/index.ts +++ b/src/core/public/injected_metadata/index.ts @@ -23,5 +23,4 @@ export { InjectedMetadataSetup, InjectedMetadataStart, InjectedPluginMetadata, - LegacyNavLink, } from './injected_metadata_service'; diff --git a/src/core/public/injected_metadata/injected_metadata_service.mock.ts b/src/core/public/injected_metadata/injected_metadata_service.mock.ts index e6b1c440519bd..3bb4358406246 100644 --- a/src/core/public/injected_metadata/injected_metadata_service.mock.ts +++ b/src/core/public/injected_metadata/injected_metadata_service.mock.ts @@ -25,7 +25,6 @@ const createSetupContractMock = () => { getKibanaVersion: jest.fn(), getKibanaBranch: jest.fn(), getCspConfig: jest.fn(), - getLegacyMode: jest.fn(), getAnonymousStatusPage: jest.fn(), getLegacyMetadata: jest.fn(), getPlugins: jest.fn(), @@ -35,7 +34,6 @@ const createSetupContractMock = () => { }; setupContract.getCspConfig.mockReturnValue({ warnLegacyBrowsers: true }); setupContract.getKibanaVersion.mockReturnValue('kibanaVersion'); - setupContract.getLegacyMode.mockReturnValue(true); setupContract.getAnonymousStatusPage.mockReturnValue(false); setupContract.getLegacyMetadata.mockReturnValue({ app: { diff --git a/src/core/public/injected_metadata/injected_metadata_service.ts b/src/core/public/injected_metadata/injected_metadata_service.ts index db4bfdf415bcc..23630a5bcf228 100644 --- a/src/core/public/injected_metadata/injected_metadata_service.ts +++ b/src/core/public/injected_metadata/injected_metadata_service.ts @@ -28,17 +28,6 @@ import { import { deepFreeze } from '../../utils/'; import { AppCategory } from '../'; -/** @public */ -export interface LegacyNavLink { - id: string; - category?: AppCategory; - title: string; - order: number; - url: string; - icon?: string; - euiIconType?: string; -} - export interface InjectedPluginMetadata { id: PluginName; plugin: DiscoveredPlugin; @@ -67,7 +56,6 @@ export interface InjectedMetadataParams { packageInfo: Readonly; }; uiPlugins: InjectedPluginMetadata[]; - legacyMode: boolean; anonymousStatusPage: boolean; legacyMetadata: { app: { @@ -75,7 +63,6 @@ export interface InjectedMetadataParams { title: string; }; bundleId: string; - nav: LegacyNavLink[]; version: string; branch: string; buildNum: number; @@ -137,10 +124,6 @@ export class InjectedMetadataService { return this.state.uiPlugins; }, - getLegacyMode: () => { - return this.state.legacyMode; - }, - getLegacyMetadata: () => { return this.state.legacyMetadata; }, @@ -182,8 +165,6 @@ export interface InjectedMetadataSetup { * An array of frontend plugins in topological order. */ getPlugins: () => InjectedPluginMetadata[]; - /** Indicates whether or not we are rendering a known legacy app. */ - getLegacyMode: () => boolean; getAnonymousStatusPage: () => boolean; getLegacyMetadata: () => { app: { @@ -191,7 +172,6 @@ export interface InjectedMetadataSetup { title: string; }; bundleId: string; - nav: LegacyNavLink[]; version: string; branch: string; buildNum: number; diff --git a/src/core/public/legacy/index.ts b/src/core/public/legacy/index.ts deleted file mode 100644 index 1ea43d8deebbc..0000000000000 --- a/src/core/public/legacy/index.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 { LegacyPlatformService, LegacyPlatformParams } from './legacy_service'; diff --git a/src/core/public/legacy/legacy_service.mock.ts b/src/core/public/legacy/legacy_service.mock.ts deleted file mode 100644 index 0c8d9682185d5..0000000000000 --- a/src/core/public/legacy/legacy_service.mock.ts +++ /dev/null @@ -1,35 +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 { LegacyPlatformService } from './legacy_service'; - -// Use Required to get only public properties -type LegacyPlatformServiceContract = Required; -const createMock = () => { - const mocked: jest.Mocked = { - legacyId: Symbol(), - setup: jest.fn(), - start: jest.fn(), - stop: jest.fn(), - }; - return mocked; -}; - -export const legacyPlatformServiceMock = { - create: createMock, -}; diff --git a/src/core/public/legacy/legacy_service.test.ts b/src/core/public/legacy/legacy_service.test.ts deleted file mode 100644 index cb29abc9b0ccc..0000000000000 --- a/src/core/public/legacy/legacy_service.test.ts +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import angular from 'angular'; - -import { chromeServiceMock } from '../chrome/chrome_service.mock'; -import { fatalErrorsServiceMock } from '../fatal_errors/fatal_errors_service.mock'; -import { httpServiceMock } from '../http/http_service.mock'; -import { i18nServiceMock } from '../i18n/i18n_service.mock'; -import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock'; -import { notificationServiceMock } from '../notifications/notifications_service.mock'; -import { overlayServiceMock } from '../overlays/overlay_service.mock'; -import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock'; -import { LegacyPlatformService } from './legacy_service'; -import { applicationServiceMock } from '../application/application_service.mock'; -import { docLinksServiceMock } from '../doc_links/doc_links_service.mock'; -import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.mock'; -import { contextServiceMock } from '../context/context_service.mock'; - -const applicationSetup = applicationServiceMock.createInternalSetupContract(); -const contextSetup = contextServiceMock.createSetupContract(); -const docLinksSetup = docLinksServiceMock.createSetupContract(); -const fatalErrorsSetup = fatalErrorsServiceMock.createSetupContract(); -const httpSetup = httpServiceMock.createSetupContract(); -const injectedMetadataSetup = injectedMetadataServiceMock.createSetupContract(); -const notificationsSetup = notificationServiceMock.createSetupContract(); -const uiSettingsSetup = uiSettingsServiceMock.createSetupContract(); - -const mockLoadOrder: string[] = []; -const mockUiNewPlatformSetup = jest.fn(); -const mockUiNewPlatformStart = jest.fn(); -const mockUiChromeBootstrap = jest.fn(); -const defaultParams = { - requireLegacyFiles: jest.fn(() => { - mockLoadOrder.push('legacy files'); - }), - requireLegacyBootstrapModule: jest.fn(() => { - mockLoadOrder.push('ui/chrome'); - return { - bootstrap: mockUiChromeBootstrap, - }; - }), - requireNewPlatformShimModule: jest.fn(() => ({ - __setup__: mockUiNewPlatformSetup, - __start__: mockUiNewPlatformStart, - })), -}; - -const defaultSetupDeps = { - core: { - application: applicationSetup, - context: contextSetup, - docLinks: docLinksSetup, - fatalErrors: fatalErrorsSetup, - injectedMetadata: injectedMetadataSetup, - notifications: notificationsSetup, - http: httpSetup, - uiSettings: uiSettingsSetup, - }, - plugins: {}, -}; - -const applicationStart = applicationServiceMock.createInternalStartContract(); -const docLinksStart = docLinksServiceMock.createStartContract(); -const httpStart = httpServiceMock.createStartContract(); -const chromeStart = chromeServiceMock.createStartContract(); -const i18nStart = i18nServiceMock.createStartContract(); -const injectedMetadataStart = injectedMetadataServiceMock.createStartContract(); -const notificationsStart = notificationServiceMock.createStartContract(); -const overlayStart = overlayServiceMock.createStartContract(); -const uiSettingsStart = uiSettingsServiceMock.createStartContract(); -const savedObjectsStart = savedObjectsServiceMock.createStartContract(); -const fatalErrorsStart = fatalErrorsServiceMock.createStartContract(); -const mockStorage = { getItem: jest.fn() } as any; - -const defaultStartDeps = { - core: { - application: applicationStart, - docLinks: docLinksStart, - http: httpStart, - chrome: chromeStart, - i18n: i18nStart, - injectedMetadata: injectedMetadataStart, - notifications: notificationsStart, - overlays: overlayStart, - uiSettings: uiSettingsStart, - savedObjects: savedObjectsStart, - fatalErrors: fatalErrorsStart, - }, - lastSubUrlStorage: mockStorage, - targetDomElement: document.createElement('div'), - plugins: {}, -}; - -afterEach(() => { - jest.clearAllMocks(); - jest.resetModules(); - mockLoadOrder.length = 0; -}); - -describe('#setup()', () => { - describe('default', () => { - it('initializes new platform shim module with core APIs', () => { - const legacyPlatform = new LegacyPlatformService({ - ...defaultParams, - }); - - legacyPlatform.setup(defaultSetupDeps); - - expect(mockUiNewPlatformSetup).toHaveBeenCalledTimes(1); - expect(mockUiNewPlatformSetup).toHaveBeenCalledWith(expect.any(Object), {}); - }); - - it('throws error if requireNewPlatformShimModule is undefined', () => { - const legacyPlatform = new LegacyPlatformService({ - ...defaultParams, - requireNewPlatformShimModule: undefined, - }); - - expect(() => { - legacyPlatform.setup(defaultSetupDeps); - }).toThrowErrorMatchingInlineSnapshot( - `"requireNewPlatformShimModule must be specified when rendering a legacy application"` - ); - - expect(mockUiNewPlatformSetup).not.toHaveBeenCalled(); - }); - }); -}); - -describe('#start()', () => { - it('fetches and sets legacy lastSubUrls', () => { - chromeStart.navLinks.getAll.mockReturnValue([ - { id: 'link1', baseUrl: 'http://wowza.com/app1', legacy: true } as any, - ]); - mockStorage.getItem.mockReturnValue('http://wowza.com/app1/subUrl'); - const legacyPlatform = new LegacyPlatformService({ - ...defaultParams, - }); - - legacyPlatform.setup(defaultSetupDeps); - legacyPlatform.start({ ...defaultStartDeps, lastSubUrlStorage: mockStorage }); - - expect(chromeStart.navLinks.update).toHaveBeenCalledWith('link1', { - url: 'http://wowza.com/app1/subUrl', - }); - }); - - it('initializes ui/new_platform with core APIs', () => { - const legacyPlatform = new LegacyPlatformService({ - ...defaultParams, - }); - - legacyPlatform.setup(defaultSetupDeps); - legacyPlatform.start(defaultStartDeps); - - expect(mockUiNewPlatformStart).toHaveBeenCalledTimes(1); - expect(mockUiNewPlatformStart).toHaveBeenCalledWith(expect.any(Object), {}); - }); - - it('throws error if requireNewPlatformShimeModule is undefined', () => { - const legacyPlatform = new LegacyPlatformService({ - ...defaultParams, - requireNewPlatformShimModule: undefined, - }); - - expect(() => { - legacyPlatform.start(defaultStartDeps); - }).toThrowErrorMatchingInlineSnapshot( - `"requireNewPlatformShimModule must be specified when rendering a legacy application"` - ); - - expect(mockUiNewPlatformStart).not.toHaveBeenCalled(); - }); - - it('resolves getStartServices with core and plugin APIs', async () => { - const legacyPlatform = new LegacyPlatformService({ - ...defaultParams, - }); - - legacyPlatform.setup(defaultSetupDeps); - legacyPlatform.start(defaultStartDeps); - - const { getStartServices } = mockUiNewPlatformSetup.mock.calls[0][0]; - const [coreStart, pluginsStart] = await getStartServices(); - expect(coreStart).toEqual(expect.any(Object)); - expect(pluginsStart).toBe(defaultStartDeps.plugins); - }); - - it('passes the targetDomElement to legacy bootstrap module', () => { - const legacyPlatform = new LegacyPlatformService({ - ...defaultParams, - }); - - legacyPlatform.setup(defaultSetupDeps); - legacyPlatform.start(defaultStartDeps); - - expect(mockUiChromeBootstrap).toHaveBeenCalledTimes(1); - expect(mockUiChromeBootstrap).toHaveBeenCalledWith(defaultStartDeps.targetDomElement); - }); - - describe('load order', () => { - it('loads ui/modules before ui/chrome, and both before legacy files', () => { - const legacyPlatform = new LegacyPlatformService({ - ...defaultParams, - }); - - expect(mockLoadOrder).toEqual([]); - - legacyPlatform.setup(defaultSetupDeps); - legacyPlatform.start(defaultStartDeps); - - expect(mockLoadOrder).toMatchInlineSnapshot(` - Array [ - "ui/chrome", - "legacy files", - ] - `); - }); - }); -}); - -describe('#stop()', () => { - it('does nothing if angular was not bootstrapped to targetDomElement', () => { - const targetDomElement = document.createElement('div'); - targetDomElement.innerHTML = ` -

this should not be removed

- `; - - const legacyPlatform = new LegacyPlatformService({ - ...defaultParams, - }); - - legacyPlatform.stop(); - expect(targetDomElement).toMatchInlineSnapshot(` -
- - -

- this should not be removed -

- - -
- `); - }); - - it('destroys the angular scope and empties the targetDomElement if angular is bootstrapped to targetDomElement', async () => { - const targetDomElement = document.createElement('div'); - const scopeDestroySpy = jest.fn(); - - const legacyPlatform = new LegacyPlatformService({ - ...defaultParams, - }); - - // simulate bootstrapping with a module "foo" - angular.module('foo', []).directive('bar', () => ({ - restrict: 'E', - link($scope) { - $scope.$on('$destroy', scopeDestroySpy); - }, - })); - - targetDomElement.innerHTML = ` - - `; - - angular.bootstrap(targetDomElement, ['foo']); - - await legacyPlatform.setup(defaultSetupDeps); - legacyPlatform.start({ ...defaultStartDeps, targetDomElement }); - legacyPlatform.stop(); - - expect(targetDomElement).toMatchInlineSnapshot(` -
- `); - expect(scopeDestroySpy).toHaveBeenCalledTimes(1); - }); -}); diff --git a/src/core/public/legacy/legacy_service.ts b/src/core/public/legacy/legacy_service.ts deleted file mode 100644 index 78a9219f3d694..0000000000000 --- a/src/core/public/legacy/legacy_service.ts +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import angular from 'angular'; -import { first } from 'rxjs/operators'; -import { Subject } from 'rxjs'; -import { InternalCoreSetup, InternalCoreStart } from '../core_system'; -import { LegacyCoreSetup, LegacyCoreStart, MountPoint } from '../'; - -/** @internal */ -export interface LegacyPlatformParams { - requireLegacyFiles?: () => void; - requireLegacyBootstrapModule?: () => BootstrapModule; - requireNewPlatformShimModule?: () => { - __setup__: (legacyCore: LegacyCoreSetup, plugins: Record) => void; - __start__: (legacyCore: LegacyCoreStart, plugins: Record) => void; - }; -} - -interface SetupDeps { - core: InternalCoreSetup; - plugins: Record; -} - -interface StartDeps { - core: InternalCoreStart; - plugins: Record; - lastSubUrlStorage?: Storage; - targetDomElement?: HTMLElement; -} - -interface BootstrapModule { - bootstrap: MountPoint; -} - -/** - * The LegacyPlatformService is responsible for initializing - * the legacy platform by injecting parts of the new platform - * services into the legacy platform modules, like ui/modules, - * and then bootstrapping the ui/chrome or ~~ui/test_harness~~ to - * setup either the app or browser tests. - */ -export class LegacyPlatformService { - /** Symbol to represent the legacy platform as a fake "plugin". Used by the ContextService */ - public readonly legacyId = Symbol(); - private bootstrapModule?: BootstrapModule; - private targetDomElement?: HTMLElement; - private readonly startDependencies$ = new Subject<[LegacyCoreStart, object, {}]>(); - private readonly startDependencies = this.startDependencies$.pipe(first()).toPromise(); - - constructor(private readonly params: LegacyPlatformParams) {} - - public setup({ core, plugins }: SetupDeps) { - // Always register legacy apps, even if not in legacy mode. - core.injectedMetadata.getLegacyMetadata().nav.forEach((navLink: any) => - core.application.registerLegacyApp({ - id: navLink.id, - order: navLink.order, - title: navLink.title, - euiIconType: navLink.euiIconType, - icon: navLink.icon, - appUrl: navLink.url, - subUrlBase: navLink.subUrlBase, - linkToLastSubUrl: navLink.linkToLastSubUrl, - category: navLink.category, - disableSubUrlTracking: navLink.disableSubUrlTracking, - }) - ); - - const legacyCore: LegacyCoreSetup = { - ...core, - getStartServices: () => this.startDependencies, - application: { - ...core.application, - register: notSupported(`core.application.register()`), - registerMountContext: notSupported(`core.application.registerMountContext()`), - }, - }; - - // Inject parts of the new platform into parts of the legacy platform - // so that legacy APIs/modules can mimic their new platform counterparts - if (core.injectedMetadata.getLegacyMode()) { - if (!this.params.requireNewPlatformShimModule) { - throw new Error( - `requireNewPlatformShimModule must be specified when rendering a legacy application` - ); - } - - this.params.requireNewPlatformShimModule().__setup__(legacyCore, plugins); - } - } - - public start({ - core, - targetDomElement, - plugins, - lastSubUrlStorage = window.sessionStorage, - }: StartDeps) { - // Initialize legacy sub urls - core.chrome.navLinks - .getAll() - .filter((link) => link.legacy) - .forEach((navLink) => { - const lastSubUrl = lastSubUrlStorage.getItem(`lastSubUrl:${navLink.baseUrl}`); - core.chrome.navLinks.update(navLink.id, { - url: lastSubUrl || navLink.url || navLink.baseUrl, - }); - }); - - // Only import and bootstrap legacy platform if we're in legacy mode. - if (!core.injectedMetadata.getLegacyMode()) { - return; - } - - const legacyCore: LegacyCoreStart = { - ...core, - application: { - applications$: core.application.applications$, - currentAppId$: core.application.currentAppId$, - capabilities: core.application.capabilities, - getUrlForApp: core.application.getUrlForApp, - navigateToApp: core.application.navigateToApp, - navigateToUrl: core.application.navigateToUrl, - registerMountContext: notSupported(`core.application.registerMountContext()`), - }, - }; - - this.startDependencies$.next([legacyCore, plugins, {}]); - - if (!this.params.requireNewPlatformShimModule) { - throw new Error( - `requireNewPlatformShimModule must be specified when rendering a legacy application` - ); - } - if (!this.params.requireLegacyBootstrapModule) { - throw new Error( - `requireLegacyBootstrapModule must be specified when rendering a legacy application` - ); - } - - // Inject parts of the new platform into parts of the legacy platform - // so that legacy APIs/modules can mimic their new platform counterparts - this.params.requireNewPlatformShimModule().__start__(legacyCore, plugins); - - // Load the bootstrap module before loading the legacy platform files so that - // the bootstrap module can modify the environment a bit first - this.bootstrapModule = this.params.requireLegacyBootstrapModule(); - - // require the files that will tie into the legacy platform - if (this.params.requireLegacyFiles) { - this.params.requireLegacyFiles(); - } - - if (!this.bootstrapModule) { - throw new Error('Bootstrap module must be loaded before `start`'); - } - - this.targetDomElement = targetDomElement; - - // `targetDomElement` is always defined when in legacy mode - this.bootstrapModule.bootstrap(this.targetDomElement!); - } - - public stop() { - if (!this.targetDomElement) { - return; - } - - const angularRoot = angular.element(this.targetDomElement); - const injector$ = angularRoot.injector(); - - // if we haven't gotten to the point of bootstrapping - // angular, injector$ won't be defined - if (!injector$) { - return; - } - - // destroy the root angular scope - injector$.get('$rootScope').$destroy(); - - // clear the inner html of the root angular element - this.targetDomElement.textContent = ''; - } -} - -const notSupported = (methodName: string) => (...args: any[]) => { - throw new Error(`${methodName} is not supported in the legacy platform.`); -}; diff --git a/src/core/public/mocks.ts b/src/core/public/mocks.ts index aefcb830d40bf..8ed415c09806c 100644 --- a/src/core/public/mocks.ts +++ b/src/core/public/mocks.ts @@ -42,7 +42,6 @@ export { fatalErrorsServiceMock } from './fatal_errors/fatal_errors_service.mock export { httpServiceMock } from './http/http_service.mock'; export { i18nServiceMock } from './i18n/i18n_service.mock'; export { injectedMetadataServiceMock } from './injected_metadata/injected_metadata_service.mock'; -export { legacyPlatformServiceMock } from './legacy/legacy_service.mock'; export { notificationServiceMock } from './notifications/notifications_service.mock'; export { overlayServiceMock } from './overlays/overlay_service.mock'; export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index bacbd6e757114..a9bea7bcfdef1 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -7,6 +7,7 @@ import { Action } from 'history'; import { ApiResponse } from '@elastic/elasticsearch/lib/Transport'; import Boom from 'boom'; +import { ErrorToastOptions as ErrorToastOptions_2 } from 'src/core/public/notifications'; import { EuiBreadcrumb } from '@elastic/eui'; import { EuiButtonEmptyProps } from '@elastic/eui'; import { EuiConfirmModalProps } from '@elastic/eui'; @@ -27,7 +28,9 @@ import { PublicUiSettingsParams as PublicUiSettingsParams_2 } from 'src/core/ser import React from 'react'; import { RecursiveReadonly } from '@kbn/utility-types'; import * as Rx from 'rxjs'; +import { SavedObject as SavedObject_2 } from 'src/core/server'; import { ShallowPromise } from '@kbn/utility-types'; +import { ToastInputFields as ToastInputFields_2 } from 'src/core/public/notifications'; import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; @@ -39,25 +42,18 @@ import { UserProvidedValues as UserProvidedValues_2 } from 'src/core/server/type // @internal (undocumented) export function __kbnBootstrap__(): void; -// @public -export interface App extends AppBase { - appRoute?: string; - chromeless?: boolean; - exactRoute?: boolean; - mount: AppMount | AppMountDeprecated; -} - // @public (undocumented) -export interface AppBase { +export interface App { + appRoute?: string; capabilities?: Partial; category?: AppCategory; chromeless?: boolean; defaultPath?: string; euiIconType?: string; + exactRoute?: boolean; icon?: string; id: string; - // @internal - legacy?: boolean; + mount: AppMount | AppMountDeprecated; navLinkStatus?: AppNavLinkStatus; order?: number; status?: AppStatus; @@ -121,7 +117,7 @@ export interface ApplicationSetup { // @public (undocumented) export interface ApplicationStart { - applications$: Observable>; + applications$: Observable>; capabilities: RecursiveReadonly; currentAppId$: Observable; getUrlForApp(appId: string, options?: { @@ -186,10 +182,10 @@ export enum AppStatus { export type AppUnmount = () => void; // @public -export type AppUpdatableFields = Pick; +export type AppUpdatableFields = Pick; // @public -export type AppUpdater = (app: AppBase) => Partial | undefined; +export type AppUpdater = (app: App) => Partial | undefined; // @public export function assertNever(x: never): never; @@ -227,10 +223,6 @@ export type ChromeBreadcrumb = EuiBreadcrumb; // @public export interface ChromeDocTitle { - // @internal (undocumented) - __legacy: { - setBaseTitle(baseTitle: string): void; - }; change(newTitle: string | string[]): void; reset(): void; } @@ -280,38 +272,31 @@ export interface ChromeNavControl { // @public export interface ChromeNavControls { + // @internal (undocumented) + getCenter$(): Observable; // @internal (undocumented) getLeft$(): Observable; // @internal (undocumented) getRight$(): Observable; + registerCenter(navControl: ChromeNavControl): void; registerLeft(navControl: ChromeNavControl): void; registerRight(navControl: ChromeNavControl): void; } // @public (undocumented) export interface ChromeNavLink { - // @deprecated - readonly active?: boolean; readonly baseUrl: string; readonly category?: AppCategory; - // @deprecated readonly disabled?: boolean; - // @deprecated - readonly disableSubUrlTracking?: boolean; readonly euiIconType?: string; readonly hidden?: boolean; - readonly href?: string; + readonly href: string; readonly icon?: string; readonly id: string; - // @internal - readonly legacy: boolean; - // @deprecated - readonly linkToLastSubUrl?: boolean; readonly order?: number; - // @deprecated - readonly subUrlBase?: string; readonly title: string; readonly tooltip?: string; + // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "AppBase" readonly url?: string; } @@ -324,12 +309,14 @@ export interface ChromeNavLinks { getNavLinks$(): Observable>>; has(id: string): boolean; showOnly(id: string): void; + // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "AppBase" + // // @deprecated update(id: string, values: ChromeNavLinkUpdateableFields): ChromeNavLink | undefined; } // @public (undocumented) -export type ChromeNavLinkUpdateableFields = Partial>; +export type ChromeNavLinkUpdateableFields = Partial>; // @public export interface ChromeRecentlyAccessed { @@ -361,7 +348,6 @@ export interface ChromeStart { getHelpExtension$(): Observable; getIsNavDrawerLocked$(): Observable; getIsVisible$(): Observable; - getNavType$(): Observable; navControls: ChromeNavControls; navLinks: ChromeNavLinks; recentlyAccessed: ChromeRecentlyAccessed; @@ -506,6 +492,9 @@ export interface DocLinksStart { readonly links: { readonly dashboard: { readonly drilldowns: string; + readonly drilldownsTriggerPicker: string; + readonly urlDrilldownTemplateSyntax: string; + readonly urlDrilldownVariables: string; }; readonly filebeat: { readonly base: string; @@ -881,52 +870,6 @@ export interface IUiSettingsClient { set: (key: string, value: any) => Promise; } -// @public (undocumented) -export interface LegacyApp extends AppBase { - // (undocumented) - appUrl: string; - // (undocumented) - disableSubUrlTracking?: boolean; - // (undocumented) - linkToLastSubUrl?: boolean; - // (undocumented) - subUrlBase?: string; -} - -// @public @deprecated -export interface LegacyCoreSetup extends CoreSetup { - // Warning: (ae-forgotten-export) The symbol "InjectedMetadataSetup" needs to be exported by the entry point index.d.ts - // - // @deprecated (undocumented) - injectedMetadata: InjectedMetadataSetup; -} - -// @public @deprecated -export interface LegacyCoreStart extends CoreStart { - // Warning: (ae-forgotten-export) The symbol "InjectedMetadataStart" needs to be exported by the entry point index.d.ts - // - // @deprecated (undocumented) - injectedMetadata: InjectedMetadataStart; -} - -// @public (undocumented) -export interface LegacyNavLink { - // (undocumented) - category?: AppCategory; - // (undocumented) - euiIconType?: string; - // (undocumented) - icon?: string; - // (undocumented) - id: string; - // (undocumented) - order: number; - // (undocumented) - title: string; - // (undocumented) - url: string; -} - // @public export function modifyUrl(url: string, urlModifier: (urlParts: URLMeaningfulParts) => Partial | void): string; @@ -1040,19 +983,11 @@ export type PluginOpaqueId = symbol; // @public export type PublicAppInfo = Omit & { - legacy: false; status: AppStatus; navLinkStatus: AppNavLinkStatus; appRoute: string; }; -// @public -export type PublicLegacyAppInfo = Omit & { - legacy: true; - status: AppStatus; - navLinkStatus: AppNavLinkStatus; -}; - // @public export type PublicUiSettingsParams = Omit; @@ -1545,6 +1480,6 @@ export interface UserProvidedValues { // Warnings were encountered during analysis: // -// src/core/public/core_system.ts:215:21 - (ae-forgotten-export) The symbol "InternalApplicationStart" needs to be exported by the entry point index.d.ts +// src/core/public/core_system.ts:185:21 - (ae-forgotten-export) The symbol "InternalApplicationStart" needs to be exported by the entry point index.d.ts ``` diff --git a/src/core/public/rendering/_base.scss b/src/core/public/rendering/_base.scss index 211e9c03beea5..b806ac270331d 100644 --- a/src/core/public/rendering/_base.scss +++ b/src/core/public/rendering/_base.scss @@ -1,5 +1,4 @@ -@import '@elastic/eui/src/global_styling/variables/header'; -@import '@elastic/eui/src/components/nav_drawer/variables'; +@include euiHeaderAffordForFixed($kbnHeaderOffset); /** * stretch the root element of the Kibana application to set the base-size that @@ -12,74 +11,11 @@ min-height: 100%; } -// TODO #64541 -// Delete this block -.chrHeaderWrapper:not(.headerWrapper) ~ .app-wrapper { +.app-wrapper { display: flex; flex-flow: column nowrap; - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - z-index: 5; margin: 0 auto; - - &:not(.hidden-chrome) { - top: $euiHeaderChildSize; - left: $euiHeaderChildSize; - - // HOTFIX: Temporary fix for flyouts not inside portals - // SASSTODO: Find an actual solution - .euiFlyout { - top: $euiHeaderChildSize; - height: calc(100% - #{$euiHeaderChildSize}); - } - - @include euiBreakpoint('xs', 's') { - left: 0; - } - } - - /** - * 1. Dirty, but we need to override the .kbnGlobalNav-isOpen state - * when we're looking at the log-in screen. - */ - &.hidden-chrome { - left: 0 !important; /* 1 */ - } - - .navbar-right { - margin-right: 0; - } -} - -// TODO #64541 -// Delete this block -@include euiBreakpoint('xl') { - .chrHeaderWrapper--navIsLocked:not(.headerWrapper) { - ~ .app-wrapper:not(.hidden-chrome) { - // Shrink the content from the left so it's no longer overlapped by the nav drawer (ALWAYS) - left: $euiNavDrawerWidthExpanded !important; // sass-lint:disable-line no-important - transition: left $euiAnimSpeedFast $euiAnimSlightResistance; - } - } -} - -// TODO #64541 -// Remove .headerWrapper and header conditionals -.headerWrapper ~ .app-wrapper, -:not(header) ~ .app-wrapper { - display: flex; - flex-flow: column nowrap; - margin: 0 auto; - min-height: calc(100vh - #{$euiHeaderHeightCompensation}); - - @include internetExplorerOnly { - // IE specific bug with min-height in flex container, described in the next link - // https://github.com/philipwalton/flexbugs#3-min-height-on-a-flex-container-wont-apply-to-its-flex-items - height: calc(100vh - #{$euiHeaderHeightCompensation}); - } + min-height: calc(100vh - #{$kbnHeaderOffset}); &.hidden-chrome { min-height: 100vh; diff --git a/src/core/public/rendering/index.ts b/src/core/public/rendering/index.ts index 7c1ea7031b763..1de82a50a36b5 100644 --- a/src/core/public/rendering/index.ts +++ b/src/core/public/rendering/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { RenderingService, RenderingStart } from './rendering_service'; +export { RenderingService } from './rendering_service'; diff --git a/src/core/public/rendering/rendering_service.mock.ts b/src/core/public/rendering/rendering_service.mock.ts index bb4e7cb49f150..bb4723e69ab5e 100644 --- a/src/core/public/rendering/rendering_service.mock.ts +++ b/src/core/public/rendering/rendering_service.mock.ts @@ -17,25 +17,16 @@ * under the License. */ -import { RenderingStart, RenderingService } from './rendering_service'; - -const createStartContractMock = () => { - const setupContract: jest.Mocked = { - legacyTargetDomElement: document.createElement('div'), - }; - return setupContract; -}; +import { RenderingService } from './rendering_service'; type RenderingServiceContract = PublicMethodsOf; const createMock = () => { const mocked: jest.Mocked = { start: jest.fn(), }; - mocked.start.mockReturnValue(createStartContractMock()); return mocked; }; export const renderingServiceMock = { create: createMock, - createStartContract: createStartContractMock, }; diff --git a/src/core/public/rendering/rendering_service.test.tsx b/src/core/public/rendering/rendering_service.test.tsx index 437a602a3d447..37658cb51c46f 100644 --- a/src/core/public/rendering/rendering_service.test.tsx +++ b/src/core/public/rendering/rendering_service.test.tsx @@ -23,7 +23,6 @@ import { act } from 'react-dom/test-utils'; import { RenderingService } from './rendering_service'; import { applicationServiceMock } from '../application/application_service.mock'; import { chromeServiceMock } from '../chrome/chrome_service.mock'; -import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock'; import { overlayServiceMock } from '../overlays/overlay_service.mock'; import { BehaviorSubject } from 'rxjs'; @@ -31,7 +30,6 @@ describe('RenderingService#start', () => { let application: ReturnType; let chrome: ReturnType; let overlays: ReturnType; - let injectedMetadata: ReturnType; let targetDomElement: HTMLDivElement; let rendering: RenderingService; @@ -45,8 +43,6 @@ describe('RenderingService#start', () => { overlays = overlayServiceMock.createStartContract(); overlays.banners.getComponent.mockReturnValue(
I'm a banner!
); - injectedMetadata = injectedMetadataServiceMock.createStartContract(); - targetDomElement = document.createElement('div'); rendering = new RenderingService(); @@ -56,20 +52,14 @@ describe('RenderingService#start', () => { return rendering.start({ application, chrome, - injectedMetadata, overlays, targetDomElement, }); }; - describe('standard mode', () => { - beforeEach(() => { - injectedMetadata.getLegacyMode.mockReturnValue(false); - }); - - it('renders application service into provided DOM element', () => { - startService(); - expect(targetDomElement.querySelector('div.application')).toMatchInlineSnapshot(` + it('renders application service into provided DOM element', () => { + startService(); + expect(targetDomElement.querySelector('div.application')).toMatchInlineSnapshot(`
@@ -78,50 +68,50 @@ describe('RenderingService#start', () => {
`); - }); + }); - it('adds the `chrome-hidden` class to the AppWrapper when chrome is hidden', () => { - const isVisible$ = new BehaviorSubject(true); - chrome.getIsVisible$.mockReturnValue(isVisible$); - startService(); + it('adds the `chrome-hidden` class to the AppWrapper when chrome is hidden', () => { + const isVisible$ = new BehaviorSubject(true); + chrome.getIsVisible$.mockReturnValue(isVisible$); + startService(); - const appWrapper = targetDomElement.querySelector('div.app-wrapper')!; - expect(appWrapper.className).toEqual('app-wrapper'); + const appWrapper = targetDomElement.querySelector('div.app-wrapper')!; + expect(appWrapper.className).toEqual('app-wrapper'); - act(() => isVisible$.next(false)); - expect(appWrapper.className).toEqual('app-wrapper hidden-chrome'); + act(() => isVisible$.next(false)); + expect(appWrapper.className).toEqual('app-wrapper hidden-chrome'); - act(() => isVisible$.next(true)); - expect(appWrapper.className).toEqual('app-wrapper'); - }); + act(() => isVisible$.next(true)); + expect(appWrapper.className).toEqual('app-wrapper'); + }); - it('adds the application classes to the AppContainer', () => { - const applicationClasses$ = new BehaviorSubject([]); - chrome.getApplicationClasses$.mockReturnValue(applicationClasses$); - startService(); + it('adds the application classes to the AppContainer', () => { + const applicationClasses$ = new BehaviorSubject([]); + chrome.getApplicationClasses$.mockReturnValue(applicationClasses$); + startService(); - const appContainer = targetDomElement.querySelector('div.application')!; - expect(appContainer.className).toEqual('application'); + const appContainer = targetDomElement.querySelector('div.application')!; + expect(appContainer.className).toEqual('application'); - act(() => applicationClasses$.next(['classA', 'classB'])); - expect(appContainer.className).toEqual('application classA classB'); + act(() => applicationClasses$.next(['classA', 'classB'])); + expect(appContainer.className).toEqual('application classA classB'); - act(() => applicationClasses$.next(['classC'])); - expect(appContainer.className).toEqual('application classC'); + act(() => applicationClasses$.next(['classC'])); + expect(appContainer.className).toEqual('application classC'); - act(() => applicationClasses$.next([])); - expect(appContainer.className).toEqual('application'); - }); + act(() => applicationClasses$.next([])); + expect(appContainer.className).toEqual('application'); + }); - it('contains wrapper divs', () => { - startService(); - expect(targetDomElement.querySelector('div.app-wrapper')).toBeDefined(); - expect(targetDomElement.querySelector('div.app-wrapper-pannel')).toBeDefined(); - }); + it('contains wrapper divs', () => { + startService(); + expect(targetDomElement.querySelector('div.app-wrapper')).toBeDefined(); + expect(targetDomElement.querySelector('div.app-wrapper-pannel')).toBeDefined(); + }); - it('renders the banner UI', () => { - startService(); - expect(targetDomElement.querySelector('#globalBannerList')).toMatchInlineSnapshot(` + it('renders the banner UI', () => { + startService(); + expect(targetDomElement.querySelector('#globalBannerList')).toMatchInlineSnapshot(`
@@ -130,36 +120,5 @@ describe('RenderingService#start', () => {
`); - }); - }); - - describe('legacy mode', () => { - beforeEach(() => { - injectedMetadata.getLegacyMode.mockReturnValue(true); - }); - - it('renders into provided DOM element', () => { - startService(); - - expect(targetDomElement).toMatchInlineSnapshot(` -
-
-
- Hello chrome! -
-
-
-
- `); - }); - - it('returns a div for the legacy service to render into', () => { - const { legacyTargetDomElement } = startService(); - - expect(targetDomElement.contains(legacyTargetDomElement!)).toBe(true); - }); }); }); diff --git a/src/core/public/rendering/rendering_service.tsx b/src/core/public/rendering/rendering_service.tsx index 58b8c1921e333..a20e14dbf61c5 100644 --- a/src/core/public/rendering/rendering_service.tsx +++ b/src/core/public/rendering/rendering_service.tsx @@ -23,14 +23,12 @@ import { I18nProvider } from '@kbn/i18n/react'; import { InternalChromeStart } from '../chrome'; import { InternalApplicationStart } from '../application'; -import { InjectedMetadataStart } from '../injected_metadata'; import { OverlayStart } from '../overlays'; import { AppWrapper, AppContainer } from './app_containers'; interface StartDeps { application: InternalApplicationStart; chrome: InternalChromeStart; - injectedMetadata: InjectedMetadataStart; overlays: OverlayStart; targetDomElement: HTMLDivElement; } @@ -41,53 +39,28 @@ interface StartDeps { * @internalRemarks Currently this only renders Chrome UI. Notifications and * Overlays UI should be moved here as well. * - * @returns a DOM element for the legacy platform to render into. - * * @internal */ export class RenderingService { - start({ - application, - chrome, - injectedMetadata, - overlays, - targetDomElement, - }: StartDeps): RenderingStart { + start({ application, chrome, overlays, targetDomElement }: StartDeps) { const chromeUi = chrome.getHeaderComponent(); const appUi = application.getComponent(); const bannerUi = overlays.banners.getComponent(); - const legacyMode = injectedMetadata.getLegacyMode(); - const legacyRef = legacyMode ? React.createRef() : null; - ReactDOM.render(
{chromeUi} - {!legacyMode && ( - -
-
{bannerUi}
- {appUi} -
-
- )} - - {legacyMode &&
} + +
+
{bannerUi}
+ {appUi} +
+
, targetDomElement ); - - return { - // When in legacy mode, return legacy div, otherwise undefined. - legacyTargetDomElement: legacyRef ? legacyRef.current! : undefined, - }; } } - -/** @internal */ -export interface RenderingStart { - legacyTargetDomElement?: HTMLDivElement; -} diff --git a/src/core/public/styles/_base.scss b/src/core/public/styles/_base.scss index 9b06b526fc7dd..bfb07c1b51427 100644 --- a/src/core/public/styles/_base.scss +++ b/src/core/public/styles/_base.scss @@ -1,16 +1,11 @@ +// Charts themes available app-wide +@import '@elastic/charts/dist/theme'; +@import '@elastic/eui/src/themes/charts/theme'; + +// Grab some nav-specific EUI vars @import '@elastic/eui/src/components/collapsible_nav/variables'; -// Application Layout -// chrome-context -// TODO #64541 -// Delete this block -.chrHeaderWrapper:not(.headerWrapper) .content { - display: flex; - flex-flow: row nowrap; - width: 100%; - height: 100%; - overflow: hidden; -} +// Application Layout .application, .app-container { diff --git a/src/core/public/ui_settings/ui_settings_api.test.ts b/src/core/public/ui_settings/ui_settings_api.test.ts index 14791407d2550..b15754e5f1383 100644 --- a/src/core/public/ui_settings/ui_settings_api.test.ts +++ b/src/core/public/ui_settings/ui_settings_api.test.ts @@ -22,7 +22,7 @@ import fetchMock from 'fetch-mock/es5/client'; import * as Rx from 'rxjs'; import { takeUntil, toArray } from 'rxjs/operators'; -import { setup as httpSetup } from '../../../test_utils/public/http_test_setup'; +import { setup as httpSetup } from '../../test_helpers/http_test_setup'; import { UiSettingsApi } from './ui_settings_api'; function setup() { diff --git a/src/core/server/config/deprecation/core_deprecations.ts b/src/core/server/config/deprecation/core_deprecations.ts index e4e881ab24372..2b8b8e383da24 100644 --- a/src/core/server/config/deprecation/core_deprecations.ts +++ b/src/core/server/config/deprecation/core_deprecations.ts @@ -113,7 +113,7 @@ const mapManifestServiceUrlDeprecation: ConfigDeprecation = (settings, fromPath, return settings; }; -export const coreDeprecationProvider: ConfigDeprecationProvider = ({ unusedFromRoot }) => [ +export const coreDeprecationProvider: ConfigDeprecationProvider = ({ rename, unusedFromRoot }) => [ unusedFromRoot('savedObjects.indexCheckTimeout'), unusedFromRoot('server.xsrf.token'), unusedFromRoot('maps.manifestServiceUrl'), @@ -136,6 +136,8 @@ export const coreDeprecationProvider: ConfigDeprecationProvider = ({ unusedFromR unusedFromRoot('optimize.workers'), unusedFromRoot('optimize.profile'), unusedFromRoot('optimize.validateSyntaxOfNodeModules'), + rename('cpu.cgroup.path.override', 'ops.cGroupOverrides.cpuPath'), + rename('cpuacct.cgroup.path.override', 'ops.cGroupOverrides.cpuAcctPath'), configPathDeprecation, dataPathDeprecation, rewriteBasePathDeprecation, diff --git a/src/core/server/config/integration_tests/config_deprecation.test.ts b/src/core/server/config/integration_tests/config_deprecation.test.ts index 65f5bbdac5248..3ebf6d507a2fd 100644 --- a/src/core/server/config/integration_tests/config_deprecation.test.ts +++ b/src/core/server/config/integration_tests/config_deprecation.test.ts @@ -19,7 +19,7 @@ import { mockLoggingSystem } from './config_deprecation.test.mocks'; import { loggingSystemMock } from '../../logging/logging_system.mock'; -import * as kbnTestServer from '../../../../test_utils/kbn_server'; +import * as kbnTestServer from '../../../test_helpers/kbn_server'; describe('configuration deprecations', () => { let root: ReturnType; diff --git a/src/core/server/core_app/integration_tests/default_route_provider_config.test.ts b/src/core/server/core_app/integration_tests/default_route_provider_config.test.ts index 3284be5ba4750..340f45a0a2c18 100644 --- a/src/core/server/core_app/integration_tests/default_route_provider_config.test.ts +++ b/src/core/server/core_app/integration_tests/default_route_provider_config.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import * as kbnTestServer from '../../../../test_utils/kbn_server'; +import * as kbnTestServer from '../../../test_helpers/kbn_server'; import { Root } from '../../root'; const { startES } = kbnTestServer.createTestServers({ diff --git a/src/core/server/core_app/integration_tests/static_assets.test.ts b/src/core/server/core_app/integration_tests/static_assets.test.ts index 160ef064a14d9..ca03c4228221f 100644 --- a/src/core/server/core_app/integration_tests/static_assets.test.ts +++ b/src/core/server/core_app/integration_tests/static_assets.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import * as kbnTestServer from '../../../../test_utils/kbn_server'; +import * as kbnTestServer from '../../../test_helpers/kbn_server'; import { Root } from '../../root'; describe('Platform assets', function () { diff --git a/src/core/server/http/integration_tests/core_services.test.ts b/src/core/server/http/integration_tests/core_services.test.ts index 2b9193a280aec..f30ff66ed803a 100644 --- a/src/core/server/http/integration_tests/core_services.test.ts +++ b/src/core/server/http/integration_tests/core_services.test.ts @@ -30,7 +30,7 @@ import { LegacyElasticsearchErrorHelpers } from '../../elasticsearch/legacy'; import { elasticsearchClientMock } from '../../elasticsearch/client/mocks'; import { ResponseError } from '@elastic/elasticsearch/lib/errors'; -import * as kbnTestServer from '../../../../test_utils/kbn_server'; +import * as kbnTestServer from '../../../test_helpers/kbn_server'; import { InternalElasticsearchServiceStart } from '../../elasticsearch'; interface User { diff --git a/src/core/server/http_resources/integration_tests/http_resources_service.test.ts b/src/core/server/http_resources/integration_tests/http_resources_service.test.ts index eee7dc2786076..624cdbb7f9655 100644 --- a/src/core/server/http_resources/integration_tests/http_resources_service.test.ts +++ b/src/core/server/http_resources/integration_tests/http_resources_service.test.ts @@ -17,7 +17,7 @@ * under the License. */ import { schema } from '@kbn/config-schema'; -import * as kbnTestServer from '../../../../test_utils/kbn_server'; +import * as kbnTestServer from '../../../test_helpers/kbn_server'; describe('http resources service', () => { describe('register', () => { diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 5422cbc2180ef..d127471348d9f 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -39,6 +39,7 @@ * @packageDocumentation */ +import { Type } from '@kbn/config-schema'; import { ElasticsearchServiceSetup, ILegacyScopedClusterClient, @@ -46,7 +47,6 @@ import { ElasticsearchServiceStart, IScopedClusterClient, } from './elasticsearch'; - import { HttpServiceSetup, HttpServiceStart } from './http'; import { HttpResources } from './http_resources'; @@ -63,12 +63,7 @@ import { CapabilitiesSetup, CapabilitiesStart } from './capabilities'; import { MetricsServiceStart } from './metrics'; import { StatusServiceSetup } from './status'; import { Auditor, AuditTrailSetup, AuditTrailStart } from './audit_trail'; -import { - LoggingServiceSetup, - appendersSchema, - loggerContextConfigSchema, - loggerSchema, -} from './logging'; +import { AppenderConfigType, appendersSchema, LoggingServiceSetup } from './logging'; export { AuditableEvent, Auditor, AuditorFactory, AuditTrailSetup } from './audit_trail'; export { bootstrap } from './bootstrap'; @@ -271,9 +266,7 @@ export { SavedObjectUnsanitizedDoc, SavedObjectsRepositoryFactory, SavedObjectsResolveImportErrorsOptions, - SavedObjectsSchema, SavedObjectsSerializer, - SavedObjectsLegacyService, SavedObjectsUpdateOptions, SavedObjectsUpdateResponse, SavedObjectsAddToNamespacesOptions, @@ -300,6 +293,7 @@ export { SavedObjectsTypeManagementDefinition, SavedObjectMigrationMap, SavedObjectMigrationFn, + SavedObjectsUtils, exportSavedObjectsToStream, importSavedObjectsFromStream, resolveSavedObjectsImportErrors, @@ -497,8 +491,6 @@ export const config = { schema: elasticsearchConfigSchema, }, logging: { - appenders: appendersSchema, - loggers: loggerSchema, - loggerContext: loggerContextConfigSchema, + appenders: appendersSchema as Type, }, }; diff --git a/src/core/server/legacy/config/get_unused_config_keys.test.ts b/src/core/server/legacy/config/get_unused_config_keys.test.ts index 2106a0748d814..f8506b5744030 100644 --- a/src/core/server/legacy/config/get_unused_config_keys.test.ts +++ b/src/core/server/legacy/config/get_unused_config_keys.test.ts @@ -217,34 +217,4 @@ describe('getUnusedConfigKeys', () => { }) ).toEqual([]); }); - - describe('using deprecation', () => { - it('should use the plugin deprecations provider', async () => { - expect( - await getUnusedConfigKeys({ - coreHandledConfigPaths: [], - pluginSpecs: [ - ({ - getDeprecationsProvider() { - return async ({ rename }: any) => [rename('foo1', 'foo2')]; - }, - getConfigPrefix: () => 'foo', - } as unknown) as LegacyPluginSpec, - ], - disabledPluginSpecs: [], - settings: { - foo: { - foo: 'dolly', - foo1: 'bar', - }, - }, - legacyConfig: getConfig({ - foo: { - foo2: 'bar', - }, - }), - }) - ).toEqual(['foo.foo']); - }); - }); }); diff --git a/src/core/server/legacy/config/get_unused_config_keys.ts b/src/core/server/legacy/config/get_unused_config_keys.ts index 354bf9af042cf..d10c574f04974 100644 --- a/src/core/server/legacy/config/get_unused_config_keys.ts +++ b/src/core/server/legacy/config/get_unused_config_keys.ts @@ -17,10 +17,7 @@ * under the License. */ -import { set } from '@elastic/safer-lodash-set'; -import { difference, get } from 'lodash'; -// @ts-expect-error -import { getTransform } from '../../../../legacy/deprecation/index'; +import { difference } from 'lodash'; import { unset } from '../../../../legacy/utils'; import { getFlattenedObject } from '../../../utils'; import { hasConfigPathIntersection } from '../../config'; @@ -41,21 +38,6 @@ export async function getUnusedConfigKeys({ settings: LegacyVars; legacyConfig: LegacyConfig; }) { - // transform deprecated plugin settings - for (let i = 0; i < pluginSpecs.length; i++) { - const spec = pluginSpecs[i]; - const transform = await getTransform(spec); - const prefix = spec.getConfigPrefix(); - - // nested plugin prefixes (a.b) translate to nested objects - const pluginSettings = get(settings, prefix); - if (pluginSettings) { - // flattened settings are expected to be converted to nested objects - // a.b = true => { a: { b: true }} - set(settings, prefix, transform(pluginSettings)); - } - } - // remove config values from disabled plugins for (const spec of disabledPluginSpecs) { unset(settings, spec.getConfigPrefix()); diff --git a/src/core/server/legacy/config/index.ts b/src/core/server/legacy/config/index.ts index f10e3f22d53c5..b56b83ca324cb 100644 --- a/src/core/server/legacy/config/index.ts +++ b/src/core/server/legacy/config/index.ts @@ -19,4 +19,3 @@ export { ensureValidConfiguration } from './ensure_valid_configuration'; export { LegacyObjectToConfigAdapter } from './legacy_object_to_config_adapter'; -export { convertLegacyDeprecationProvider } from './legacy_deprecation_adapters'; diff --git a/src/core/server/legacy/config/legacy_deprecation_adapters.test.ts b/src/core/server/legacy/config/legacy_deprecation_adapters.test.ts deleted file mode 100644 index b09f9d00b3bed..0000000000000 --- a/src/core/server/legacy/config/legacy_deprecation_adapters.test.ts +++ /dev/null @@ -1,106 +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 { ConfigDeprecation } from '../../config'; -import { configDeprecationFactory } from '../../config/deprecation/deprecation_factory'; -import { applyDeprecations } from '../../config/deprecation/apply_deprecations'; -import { LegacyConfigDeprecationProvider } from '../types'; -import { convertLegacyDeprecationProvider } from './legacy_deprecation_adapters'; - -jest.spyOn(configDeprecationFactory, 'unusedFromRoot'); -jest.spyOn(configDeprecationFactory, 'renameFromRoot'); - -const executeHandlers = (handlers: ConfigDeprecation[]) => { - handlers.forEach((handler) => { - handler({}, '', () => null); - }); -}; - -describe('convertLegacyDeprecationProvider', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('returns the same number of handlers', async () => { - const legacyProvider: LegacyConfigDeprecationProvider = ({ rename, unused }) => [ - rename('a', 'b'), - unused('c'), - unused('d'), - ]; - - const migrated = await convertLegacyDeprecationProvider(legacyProvider); - const handlers = migrated(configDeprecationFactory); - expect(handlers).toHaveLength(3); - }); - - it('invokes the factory "unusedFromRoot" when using legacy "unused"', async () => { - const legacyProvider: LegacyConfigDeprecationProvider = ({ rename, unused }) => [ - rename('a', 'b'), - unused('c'), - unused('d'), - ]; - - const migrated = await convertLegacyDeprecationProvider(legacyProvider); - const handlers = migrated(configDeprecationFactory); - executeHandlers(handlers); - - expect(configDeprecationFactory.unusedFromRoot).toHaveBeenCalledTimes(2); - expect(configDeprecationFactory.unusedFromRoot).toHaveBeenCalledWith('c'); - expect(configDeprecationFactory.unusedFromRoot).toHaveBeenCalledWith('d'); - }); - - it('invokes the factory "renameFromRoot" when using legacy "rename"', async () => { - const legacyProvider: LegacyConfigDeprecationProvider = ({ rename, unused }) => [ - rename('a', 'b'), - unused('c'), - rename('d', 'e'), - ]; - - const migrated = await convertLegacyDeprecationProvider(legacyProvider); - const handlers = migrated(configDeprecationFactory); - executeHandlers(handlers); - - expect(configDeprecationFactory.renameFromRoot).toHaveBeenCalledTimes(2); - expect(configDeprecationFactory.renameFromRoot).toHaveBeenCalledWith('a', 'b'); - expect(configDeprecationFactory.renameFromRoot).toHaveBeenCalledWith('d', 'e'); - }); - - it('properly works in a real use case', async () => { - const legacyProvider: LegacyConfigDeprecationProvider = ({ rename, unused }) => [ - rename('old', 'new'), - unused('unused'), - unused('notpresent'), - ]; - - const convertedProvider = await convertLegacyDeprecationProvider(legacyProvider); - const handlers = convertedProvider(configDeprecationFactory); - - const rawConfig = { - old: 'oldvalue', - unused: 'unused', - goodValue: 'good', - }; - - const migrated = applyDeprecations( - rawConfig, - handlers.map((handler) => ({ deprecation: handler, path: '' })) - ); - expect(migrated).toEqual({ new: 'oldvalue', goodValue: 'good' }); - }); -}); diff --git a/src/core/server/legacy/config/legacy_deprecation_adapters.ts b/src/core/server/legacy/config/legacy_deprecation_adapters.ts deleted file mode 100644 index 1e0733969e662..0000000000000 --- a/src/core/server/legacy/config/legacy_deprecation_adapters.ts +++ /dev/null @@ -1,57 +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 { ConfigDeprecation, ConfigDeprecationProvider } from '../../config/deprecation'; -import { configDeprecationFactory } from '../../config/deprecation/deprecation_factory'; -import { LegacyConfigDeprecation, LegacyConfigDeprecationProvider } from '../types'; - -const convertLegacyDeprecation = ( - legacyDeprecation: LegacyConfigDeprecation -): ConfigDeprecation => (config, fromPath, logger) => { - legacyDeprecation(config, logger); - return config; -}; - -const legacyUnused = (unusedKey: string): LegacyConfigDeprecation => (settings, log) => { - const deprecation = configDeprecationFactory.unusedFromRoot(unusedKey); - deprecation(settings, '', log); -}; - -const legacyRename = (oldKey: string, newKey: string): LegacyConfigDeprecation => ( - settings, - log -) => { - const deprecation = configDeprecationFactory.renameFromRoot(oldKey, newKey); - deprecation(settings, '', log); -}; - -/** - * Async deprecation provider converter for legacy deprecation implementation - * - * @internal - */ -export const convertLegacyDeprecationProvider = async ( - legacyProvider: LegacyConfigDeprecationProvider -): Promise => { - const legacyDeprecations = await legacyProvider({ - rename: legacyRename, - unused: legacyUnused, - }); - return () => legacyDeprecations.map(convertLegacyDeprecation); -}; diff --git a/src/core/server/legacy/integration_tests/legacy_service.test.ts b/src/core/server/legacy/integration_tests/legacy_service.test.ts index 1dc8d53e7c3d6..ca3573e730d3f 100644 --- a/src/core/server/legacy/integration_tests/legacy_service.test.ts +++ b/src/core/server/legacy/integration_tests/legacy_service.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import * as kbnTestServer from '../../../../test_utils/kbn_server'; +import * as kbnTestServer from '../../../test_helpers/kbn_server'; describe('legacy service', () => { describe('http server', () => { diff --git a/src/core/server/legacy/integration_tests/logging.test.ts b/src/core/server/legacy/integration_tests/logging.test.ts index 2581c85debf26..2ebe17ea92978 100644 --- a/src/core/server/legacy/integration_tests/logging.test.ts +++ b/src/core/server/legacy/integration_tests/logging.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import * as kbnTestServer from '../../../../test_utils/kbn_server'; +import * as kbnTestServer from '../../../test_helpers/kbn_server'; import { getPlatformLogsFromMock, diff --git a/src/core/server/legacy/legacy_service.mock.ts b/src/core/server/legacy/legacy_service.mock.ts index 26ec52185a5d8..c27f5be04d965 100644 --- a/src/core/server/legacy/legacy_service.mock.ts +++ b/src/core/server/legacy/legacy_service.mock.ts @@ -24,13 +24,7 @@ type LegacyServiceMock = jest.Mocked & { legacyId const createDiscoverPluginsMock = (): LegacyServiceDiscoverPlugins => ({ pluginSpecs: [], - uiExports: { - savedObjectSchemas: {}, - savedObjectMappings: [], - savedObjectMigrations: {}, - savedObjectValidations: {}, - savedObjectsManagement: {}, - }, + uiExports: {}, navLinks: [], pluginExtendedConfig: { get: jest.fn(), diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index 45869fd12d2b4..d0492ea88c5e8 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -19,9 +19,7 @@ jest.mock('../../../legacy/server/kbn_server'); jest.mock('../../../cli/cluster/cluster_manager'); -jest.mock('./config/legacy_deprecation_adapters', () => ({ - convertLegacyDeprecationProvider: (provider: any) => Promise.resolve(provider), -})); + import { findLegacyPluginSpecsMock, logLegacyThirdPartyPluginDeprecationWarningMock, @@ -446,46 +444,8 @@ describe('#discoverPlugins()', () => { expect(findLegacyPluginSpecs).toHaveBeenCalledWith(expect.any(Object), logger, env.packageInfo); }); - it(`register legacy plugin's deprecation providers`, async () => { - findLegacyPluginSpecsMock.mockImplementation( - (settings) => - Promise.resolve({ - pluginSpecs: [ - { - getDeprecationsProvider: () => undefined, - }, - { - getDeprecationsProvider: () => 'providerA', - }, - { - getDeprecationsProvider: () => 'providerB', - }, - ], - pluginExtendedConfig: settings, - disabledPluginSpecs: [], - uiExports: {}, - navLinks: [], - }) as any - ); - - const legacyService = new LegacyService({ - coreId, - env, - logger, - configService: configService as any, - }); - - await legacyService.discoverPlugins(); - expect(configService.addDeprecationProvider).toHaveBeenCalledTimes(2); - expect(configService.addDeprecationProvider).toHaveBeenCalledWith('', 'providerA'); - expect(configService.addDeprecationProvider).toHaveBeenCalledWith('', 'providerB'); - }); - it(`logs deprecations for legacy third party plugins`, async () => { - const pluginSpecs = [ - { getId: () => 'pluginA', getDeprecationsProvider: () => undefined }, - { getId: () => 'pluginB', getDeprecationsProvider: () => undefined }, - ]; + const pluginSpecs = [{ getId: () => 'pluginA' }, { getId: () => 'pluginB' }]; findLegacyPluginSpecsMock.mockImplementation( (settings) => Promise.resolve({ diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 7d5557be92b30..6e6d5cfc24340 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -21,7 +21,7 @@ import { combineLatest, ConnectableObservable, EMPTY, Observable, Subscription } import { first, map, publishReplay, tap } from 'rxjs/operators'; import { CoreService } from '../../types'; -import { Config, ConfigDeprecationProvider } from '../config'; +import { Config } from '../config'; import { CoreContext } from '../core_context'; import { CspConfigType, config as cspConfig } from '../csp'; import { DevConfig, DevConfigType, config as devConfig } from '../dev'; @@ -29,7 +29,6 @@ import { BasePathProxyServer, HttpConfig, HttpConfigType, config as httpConfig } import { Logger } from '../logging'; import { PathConfigType } from '../path'; import { findLegacyPluginSpecs, logLegacyThirdPartyPluginDeprecationWarning } from './plugins'; -import { convertLegacyDeprecationProvider } from './config'; import { ILegacyInternals, LegacyServiceSetupDeps, @@ -145,18 +144,6 @@ export class LegacyService implements CoreService { navLinks, }; - const deprecationProviders = await pluginSpecs - .map((spec) => spec.getDeprecationsProvider()) - .reduce(async (providers, current) => { - if (current) { - return [...(await providers), await convertLegacyDeprecationProvider(current)]; - } - return providers; - }, Promise.resolve([] as ConfigDeprecationProvider[])); - deprecationProviders.forEach((provider) => - this.coreContext.configService.addDeprecationProvider('', provider) - ); - this.legacyRawConfig = pluginExtendedConfig; // check for unknown uiExport types @@ -277,6 +264,7 @@ export class LegacyService implements CoreService { getTypeRegistry: startDeps.core.savedObjects.getTypeRegistry, }, metrics: { + collectionInterval: startDeps.core.metrics.collectionInterval, getOpsMetrics$: startDeps.core.metrics.getOpsMetrics$, }, uiSettings: { asScopedToClient: startDeps.core.uiSettings.asScopedToClient }, @@ -323,9 +311,17 @@ export class LegacyService implements CoreService { status: { core$: setupDeps.core.status.core$, overall$: setupDeps.core.status.overall$, - set: setupDeps.core.status.plugins.set.bind(null, 'legacy'), - dependencies$: setupDeps.core.status.plugins.getDependenciesStatus$('legacy'), - derivedStatus$: setupDeps.core.status.plugins.getDerivedStatus$('legacy'), + set: () => { + throw new Error(`core.status.set is unsupported in legacy`); + }, + // @ts-expect-error + get dependencies$() { + throw new Error(`core.status.dependencies$ is unsupported in legacy`); + }, + // @ts-expect-error + get derivedStatus$() { + throw new Error(`core.status.derivedStatus$ is unsupported in legacy`); + }, }, uiSettings: { register: setupDeps.core.uiSettings.register, @@ -357,11 +353,9 @@ export class LegacyService implements CoreService { registerStaticDir: setupDeps.core.http.registerStaticDir, }, hapiServer: setupDeps.core.http.server, - kibanaMigrator: startDeps.core.savedObjects.migrator, uiPlugins: setupDeps.uiPlugins, elasticsearch: setupDeps.core.elasticsearch, rendering: setupDeps.core.rendering, - savedObjectsClientProvider: startDeps.core.savedObjects.clientProvider, legacy: this.legacyInternals, }, logger: this.coreContext.logger, diff --git a/src/core/server/legacy/logging/appenders/legacy_appender.ts b/src/core/server/legacy/logging/appenders/legacy_appender.ts index 0c2f4ce93c3b8..a5d36423ba4c6 100644 --- a/src/core/server/legacy/logging/appenders/legacy_appender.ts +++ b/src/core/server/legacy/logging/appenders/legacy_appender.ts @@ -23,6 +23,11 @@ import { LogRecord } from '../../../logging/log_record'; import { LegacyLoggingServer } from '../legacy_logging_server'; import { LegacyVars } from '../../types'; +export interface LegacyAppenderConfig { + kind: 'legacy-appender'; + legacyLoggingConfig?: any; +} + /** * Simple appender that just forwards `LogRecord` to the legacy KbnServer log. * @internal diff --git a/src/core/server/legacy/plugins/__snapshots__/get_nav_links.test.ts.snap b/src/core/server/legacy/plugins/__snapshots__/get_nav_links.test.ts.snap deleted file mode 100644 index c1b7164908ed6..0000000000000 --- a/src/core/server/legacy/plugins/__snapshots__/get_nav_links.test.ts.snap +++ /dev/null @@ -1,56 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` 1`] = ` -Array [ - Object { - "category": undefined, - "disableSubUrlTracking": undefined, - "disabled": false, - "euiIconType": undefined, - "hidden": false, - "icon": undefined, - "id": "link-a", - "linkToLastSubUrl": true, - "order": 0, - "subUrlBase": "/some-custom-url", - "title": "AppA", - "tooltip": "", - "url": "/some-custom-url", - }, - Object { - "category": undefined, - "disableSubUrlTracking": true, - "disabled": false, - "euiIconType": undefined, - "hidden": false, - "icon": undefined, - "id": "link-b", - "linkToLastSubUrl": true, - "order": 0, - "subUrlBase": "/url-b", - "title": "AppB", - "tooltip": "", - "url": "/url-b", - }, - Object { - "category": undefined, - "euiIconType": undefined, - "icon": undefined, - "id": "app-a", - "linkToLastSubUrl": true, - "order": 0, - "title": "AppA", - "url": "/app/app-a", - }, - Object { - "category": undefined, - "euiIconType": undefined, - "icon": undefined, - "id": "app-b", - "linkToLastSubUrl": true, - "order": 0, - "title": "AppB", - "url": "/app/app-b", - }, -] -`; diff --git a/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts b/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts index f3ec2ed8335c5..82e04496ffc3e 100644 --- a/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts +++ b/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts @@ -31,7 +31,6 @@ import { collectUiExports as collectLegacyUiExports } from '../../../../legacy/u import { LoggerFactory } from '../../logging'; import { PackageInfo } from '../../config'; import { LegacyPluginSpec, LegacyPluginPack, LegacyConfig } from '../types'; -import { getNavLinks } from './get_nav_links'; export async function findLegacyPluginSpecs( settings: unknown, @@ -125,13 +124,12 @@ export async function findLegacyPluginSpecs( log$.pipe(toArray()) ).toPromise(); const uiExports = collectLegacyUiExports(pluginSpecs); - const navLinks = getNavLinks(uiExports, pluginSpecs); return { disabledPluginSpecs, pluginSpecs, pluginExtendedConfig: configToMutate, uiExports, - navLinks, + navLinks: [], }; } diff --git a/src/core/server/legacy/plugins/get_nav_links.test.ts b/src/core/server/legacy/plugins/get_nav_links.test.ts deleted file mode 100644 index af10706d0ea08..0000000000000 --- a/src/core/server/legacy/plugins/get_nav_links.test.ts +++ /dev/null @@ -1,288 +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 { LegacyUiExports, LegacyPluginSpec, LegacyAppSpec, LegacyNavLinkSpec } from '../types'; -import { getNavLinks } from './get_nav_links'; - -const createLegacyExports = ({ - uiAppSpecs = [], - navLinkSpecs = [], -}: { - uiAppSpecs?: LegacyAppSpec[]; - navLinkSpecs?: LegacyNavLinkSpec[]; -}): LegacyUiExports => ({ - uiAppSpecs, - navLinkSpecs, - injectedVarsReplacers: [], - defaultInjectedVarProviders: [], - savedObjectMappings: [], - savedObjectSchemas: {}, - savedObjectMigrations: {}, - savedObjectValidations: {}, - savedObjectsManagement: {}, -}); - -const createPluginSpecs = (...ids: string[]): LegacyPluginSpec[] => - ids.map( - (id) => - ({ - getId: () => id, - } as LegacyPluginSpec) - ); - -describe('getNavLinks', () => { - describe('generating from uiAppSpecs', () => { - it('generates navlinks from legacy app specs', () => { - const navlinks = getNavLinks( - createLegacyExports({ - uiAppSpecs: [ - { - id: 'app-a', - title: 'AppA', - pluginId: 'pluginA', - }, - { - id: 'app-b', - title: 'AppB', - pluginId: 'pluginA', - }, - ], - }), - createPluginSpecs('pluginA') - ); - - expect(navlinks.length).toEqual(2); - expect(navlinks[0]).toEqual( - expect.objectContaining({ - id: 'app-a', - title: 'AppA', - url: '/app/app-a', - }) - ); - expect(navlinks[1]).toEqual( - expect.objectContaining({ - id: 'app-b', - title: 'AppB', - url: '/app/app-b', - }) - ); - }); - - it('uses the app id to generates the navlink id even if pluginId is specified', () => { - const navlinks = getNavLinks( - createLegacyExports({ - uiAppSpecs: [ - { - id: 'app-a', - title: 'AppA', - pluginId: 'pluginA', - }, - { - id: 'app-b', - title: 'AppB', - pluginId: 'pluginA', - }, - ], - }), - createPluginSpecs('pluginA') - ); - - expect(navlinks.length).toEqual(2); - expect(navlinks[0].id).toEqual('app-a'); - expect(navlinks[1].id).toEqual('app-b'); - }); - - it('throws if an app reference a missing plugin', () => { - expect(() => { - getNavLinks( - createLegacyExports({ - uiAppSpecs: [ - { - id: 'app-a', - title: 'AppA', - pluginId: 'notExistingPlugin', - }, - ], - }), - createPluginSpecs('pluginA') - ); - }).toThrowErrorMatchingInlineSnapshot(`"Unknown plugin id \\"notExistingPlugin\\""`); - }); - - it('uses all known properties of the navlink', () => { - const navlinks = getNavLinks( - createLegacyExports({ - uiAppSpecs: [ - { - id: 'app-a', - title: 'AppA', - category: { - id: 'foo', - label: 'My Category', - }, - order: 42, - url: '/some-custom-url', - icon: 'fa-snowflake', - euiIconType: 'euiIcon', - linkToLastSubUrl: true, - hidden: false, - }, - ], - }), - [] - ); - expect(navlinks.length).toBe(1); - expect(navlinks[0]).toEqual({ - id: 'app-a', - title: 'AppA', - category: { - id: 'foo', - label: 'My Category', - }, - order: 42, - url: '/some-custom-url', - icon: 'fa-snowflake', - euiIconType: 'euiIcon', - linkToLastSubUrl: true, - }); - }); - }); - - describe('generating from navLinkSpecs', () => { - it('generates navlinks from legacy navLink specs', () => { - const navlinks = getNavLinks( - createLegacyExports({ - navLinkSpecs: [ - { - id: 'link-a', - title: 'AppA', - url: '/some-custom-url', - }, - { - id: 'link-b', - title: 'AppB', - url: '/some-other-url', - disableSubUrlTracking: true, - }, - ], - }), - createPluginSpecs('pluginA') - ); - - expect(navlinks.length).toEqual(2); - expect(navlinks[0]).toEqual( - expect.objectContaining({ - id: 'link-a', - title: 'AppA', - url: '/some-custom-url', - hidden: false, - disabled: false, - }) - ); - expect(navlinks[1]).toEqual( - expect.objectContaining({ - id: 'link-b', - title: 'AppB', - url: '/some-other-url', - disableSubUrlTracking: true, - }) - ); - }); - - it('only uses known properties to create the navlink', () => { - const navlinks = getNavLinks( - createLegacyExports({ - navLinkSpecs: [ - { - id: 'link-a', - title: 'AppA', - category: { - id: 'foo', - label: 'My Second Cat', - }, - order: 72, - url: '/some-other-custom', - subUrlBase: '/some-other-custom/sub', - disableSubUrlTracking: true, - icon: 'fa-corn', - euiIconType: 'euiIconBis', - linkToLastSubUrl: false, - hidden: false, - tooltip: 'My other tooltip', - }, - ], - }), - [] - ); - expect(navlinks.length).toBe(1); - expect(navlinks[0]).toEqual({ - id: 'link-a', - title: 'AppA', - category: { - id: 'foo', - label: 'My Second Cat', - }, - order: 72, - url: '/some-other-custom', - subUrlBase: '/some-other-custom/sub', - disableSubUrlTracking: true, - icon: 'fa-corn', - euiIconType: 'euiIconBis', - linkToLastSubUrl: false, - hidden: false, - disabled: false, - tooltip: 'My other tooltip', - }); - }); - }); - - describe('generating from both apps and navlinks', () => { - const navlinks = getNavLinks( - createLegacyExports({ - uiAppSpecs: [ - { - id: 'app-a', - title: 'AppA', - }, - { - id: 'app-b', - title: 'AppB', - }, - ], - navLinkSpecs: [ - { - id: 'link-a', - title: 'AppA', - url: '/some-custom-url', - }, - { - id: 'link-b', - title: 'AppB', - url: '/url-b', - disableSubUrlTracking: true, - }, - ], - }), - [] - ); - - expect(navlinks.length).toBe(4); - expect(navlinks).toMatchSnapshot(); - }); -}); diff --git a/src/core/server/legacy/plugins/get_nav_links.ts b/src/core/server/legacy/plugins/get_nav_links.ts deleted file mode 100644 index b1d22df41e345..0000000000000 --- a/src/core/server/legacy/plugins/get_nav_links.ts +++ /dev/null @@ -1,82 +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 { - LegacyUiExports, - LegacyNavLink, - LegacyPluginSpec, - LegacyNavLinkSpec, - LegacyAppSpec, -} from '../types'; - -function legacyAppToNavLink(spec: LegacyAppSpec): LegacyNavLink { - if (!spec.id) { - throw new Error('Every app must specify an id'); - } - return { - id: spec.id, - category: spec.category, - title: spec.title ?? spec.id, - order: typeof spec.order === 'number' ? spec.order : 0, - icon: spec.icon, - euiIconType: spec.euiIconType, - url: spec.url || `/app/${spec.id}`, - linkToLastSubUrl: spec.linkToLastSubUrl ?? true, - }; -} - -function legacyLinkToNavLink(spec: LegacyNavLinkSpec): LegacyNavLink { - return { - id: spec.id, - category: spec.category, - title: spec.title, - order: typeof spec.order === 'number' ? spec.order : 0, - url: spec.url, - subUrlBase: spec.subUrlBase || spec.url, - disableSubUrlTracking: spec.disableSubUrlTracking, - icon: spec.icon, - euiIconType: spec.euiIconType, - linkToLastSubUrl: spec.linkToLastSubUrl ?? true, - hidden: spec.hidden ?? false, - disabled: spec.disabled ?? false, - tooltip: spec.tooltip ?? '', - }; -} - -function isHidden(app: LegacyAppSpec) { - return app.listed === false || app.hidden === true; -} - -export function getNavLinks(uiExports: LegacyUiExports, pluginSpecs: LegacyPluginSpec[]) { - const navLinkSpecs = uiExports.navLinkSpecs || []; - const appSpecs = (uiExports.uiAppSpecs || []).filter( - (app) => app !== undefined && !isHidden(app) - ) as LegacyAppSpec[]; - - const pluginIds = (pluginSpecs || []).map((spec) => spec.getId()); - appSpecs.forEach((spec) => { - if (spec.pluginId && !pluginIds.includes(spec.pluginId)) { - throw new Error(`Unknown plugin id "${spec.pluginId}"`); - } - }); - - return [...navLinkSpecs.map(legacyLinkToNavLink), ...appSpecs.map(legacyAppToNavLink)].sort( - (a, b) => a.order - b.order - ); -} diff --git a/src/core/server/legacy/plugins/log_legacy_plugins_warning.test.ts b/src/core/server/legacy/plugins/log_legacy_plugins_warning.test.ts index dfa2396d5904b..2317f1036ce42 100644 --- a/src/core/server/legacy/plugins/log_legacy_plugins_warning.test.ts +++ b/src/core/server/legacy/plugins/log_legacy_plugins_warning.test.ts @@ -26,7 +26,6 @@ const createPluginSpec = ({ id, path }: { id: string; path: string }): LegacyPlu getId: () => id, getExpectedKibanaVersion: () => 'kibana', getConfigPrefix: () => 'plugin.config', - getDeprecationsProvider: () => undefined, getPack: () => ({ getPath: () => path, }), diff --git a/src/core/server/legacy/types.ts b/src/core/server/legacy/types.ts index 98f8d874c7088..1105308fd44cf 100644 --- a/src/core/server/legacy/types.ts +++ b/src/core/server/legacy/types.ts @@ -24,7 +24,6 @@ import { KibanaRequest, LegacyRequest } from '../http'; import { InternalCoreSetup, InternalCoreStart } from '../internal_types'; import { PluginsServiceSetup, PluginsServiceStart, UiPlugins } from '../plugins'; import { InternalRenderingServiceSetup } from '../rendering'; -import { SavedObjectsLegacyUiExports } from '../types'; /** * @internal @@ -51,36 +50,6 @@ export interface LegacyConfig { set(config: LegacyVars): void; } -/** - * Representation of a legacy configuration deprecation factory used for - * legacy plugin deprecations. - * - * @internal - * @deprecated - */ -export interface LegacyConfigDeprecationFactory { - rename(oldKey: string, newKey: string): LegacyConfigDeprecation; - unused(unusedKey: string): LegacyConfigDeprecation; -} - -/** - * Representation of a legacy configuration deprecation. - * - * @internal - * @deprecated - */ -export type LegacyConfigDeprecation = (settings: LegacyVars, log: (msg: string) => void) => void; - -/** - * Representation of a legacy configuration deprecation provider. - * - * @internal - * @deprecated - */ -export type LegacyConfigDeprecationProvider = ( - factory: LegacyConfigDeprecationFactory -) => LegacyConfigDeprecation[] | Promise; - /** * @internal * @deprecated @@ -97,7 +66,6 @@ export interface LegacyPluginSpec { getId: () => unknown; getExpectedKibanaVersion: () => string; getConfigPrefix: () => string; - getDeprecationsProvider: () => LegacyConfigDeprecationProvider | undefined; getPack: () => LegacyPluginPack; } @@ -159,13 +127,13 @@ export type LegacyNavLink = Omit; unknown?: [{ pluginSpec: LegacyPluginSpec; type: unknown }]; -}; +} /** * @public diff --git a/src/core/server/logging/appenders/appenders.ts b/src/core/server/logging/appenders/appenders.ts index 3b90a10a1a76c..edfce4988275a 100644 --- a/src/core/server/logging/appenders/appenders.ts +++ b/src/core/server/logging/appenders/appenders.ts @@ -17,14 +17,17 @@ * under the License. */ -import { schema, TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; import { assertNever } from '../../../utils'; -import { LegacyAppender } from '../../legacy/logging/appenders/legacy_appender'; +import { + LegacyAppender, + LegacyAppenderConfig, +} from '../../legacy/logging/appenders/legacy_appender'; import { Layouts } from '../layouts/layouts'; import { LogRecord } from '../log_record'; -import { ConsoleAppender } from './console/console_appender'; -import { FileAppender } from './file/file_appender'; +import { ConsoleAppender, ConsoleAppenderConfig } from './console/console_appender'; +import { FileAppender, FileAppenderConfig } from './file/file_appender'; /** * Config schema for validting the shape of the `appenders` key in in {@link LoggerContextConfigType} or @@ -39,7 +42,7 @@ export const appendersSchema = schema.oneOf([ ]); /** @public */ -export type AppenderConfigType = TypeOf; +export type AppenderConfigType = ConsoleAppenderConfig | FileAppenderConfig | LegacyAppenderConfig; /** * Entity that can append `LogRecord` instances to file, stdout, memory or whatever diff --git a/src/core/server/logging/appenders/console/console_appender.ts b/src/core/server/logging/appenders/console/console_appender.ts index b4420c12a23ca..a54674b1d347c 100644 --- a/src/core/server/logging/appenders/console/console_appender.ts +++ b/src/core/server/logging/appenders/console/console_appender.ts @@ -19,13 +19,19 @@ import { schema } from '@kbn/config-schema'; -import { Layout, Layouts } from '../../layouts/layouts'; +import { Layout, Layouts, LayoutConfigType } from '../../layouts/layouts'; import { LogRecord } from '../../log_record'; import { DisposableAppender } from '../appenders'; const { literal, object } = schema; +export interface ConsoleAppenderConfig { + kind: 'console'; + layout: LayoutConfigType; +} + /** + * * Appender that formats all the `LogRecord` instances it receives and logs them via built-in `console`. * @internal */ diff --git a/src/core/server/logging/appenders/file/file_appender.ts b/src/core/server/logging/appenders/file/file_appender.ts index 728e82ebcec9a..a0e484cd87c8f 100644 --- a/src/core/server/logging/appenders/file/file_appender.ts +++ b/src/core/server/logging/appenders/file/file_appender.ts @@ -20,10 +20,16 @@ import { schema } from '@kbn/config-schema'; import { createWriteStream, WriteStream } from 'fs'; -import { Layout, Layouts } from '../../layouts/layouts'; +import { Layout, Layouts, LayoutConfigType } from '../../layouts/layouts'; import { LogRecord } from '../../log_record'; import { DisposableAppender } from '../appenders'; +export interface FileAppenderConfig { + kind: 'file'; + layout: LayoutConfigType; + path: string; +} + /** * Appender that formats all the `LogRecord` instances it receives and writes them to the specified file. * @internal diff --git a/src/core/server/logging/integration_tests/logging.test.ts b/src/core/server/logging/integration_tests/logging.test.ts index 841c1ce15af47..7f6059567c46e 100644 --- a/src/core/server/logging/integration_tests/logging.test.ts +++ b/src/core/server/logging/integration_tests/logging.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import * as kbnTestServer from '../../../../test_utils/kbn_server'; +import * as kbnTestServer from '../../../test_helpers/kbn_server'; import { InternalCoreSetup } from '../../internal_types'; import { LoggerContextConfigInput } from '../logging_config'; import { Subject } from 'rxjs'; diff --git a/src/core/server/logging/layouts/json_layout.ts b/src/core/server/logging/layouts/json_layout.ts index 04416184a5957..37eb6b8c4806e 100644 --- a/src/core/server/logging/layouts/json_layout.ts +++ b/src/core/server/logging/layouts/json_layout.ts @@ -19,7 +19,7 @@ import moment from 'moment-timezone'; import { merge } from 'lodash'; -import { schema, TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; import { LogRecord } from '../log_record'; import { Layout } from './layouts'; @@ -31,7 +31,9 @@ const jsonLayoutSchema = object({ }); /** @internal */ -export type JsonLayoutConfigType = TypeOf; +export interface JsonLayoutConfigType { + kind: 'json'; +} /** * Layout that just converts `LogRecord` into JSON string. diff --git a/src/core/server/logging/layouts/layouts.ts b/src/core/server/logging/layouts/layouts.ts index 0e6a6360cab2e..124c007bae104 100644 --- a/src/core/server/logging/layouts/layouts.ts +++ b/src/core/server/logging/layouts/layouts.ts @@ -26,7 +26,7 @@ import { PatternLayout, PatternLayoutConfigType } from './pattern_layout'; const { oneOf } = schema; -type LayoutConfigType = PatternLayoutConfigType | JsonLayoutConfigType; +export type LayoutConfigType = PatternLayoutConfigType | JsonLayoutConfigType; /** * Entity that can format `LogRecord` instance into a string. diff --git a/src/core/server/logging/layouts/pattern_layout.ts b/src/core/server/logging/layouts/pattern_layout.ts index 7839345a3703b..5dfc8aca77f18 100644 --- a/src/core/server/logging/layouts/pattern_layout.ts +++ b/src/core/server/logging/layouts/pattern_layout.ts @@ -17,7 +17,7 @@ * under the License. */ -import { schema, TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; import { LogRecord } from '../log_record'; import { Layout } from './layouts'; @@ -58,7 +58,11 @@ const conversions: Conversion[] = [ ]; /** @internal */ -export type PatternLayoutConfigType = TypeOf; +export interface PatternLayoutConfigType { + kind: 'pattern'; + highlight?: boolean; + pattern?: string; +} /** * Layout that formats `LogRecord` using the `pattern` string with optional diff --git a/src/core/server/logging/logging_config.ts b/src/core/server/logging/logging_config.ts index a6aafabeb970c..a6ab15dc29fdf 100644 --- a/src/core/server/logging/logging_config.ts +++ b/src/core/server/logging/logging_config.ts @@ -96,7 +96,9 @@ export const config = { }), }; -export type LoggingConfigType = TypeOf; +export type LoggingConfigType = Omit, 'appenders'> & { + appenders: Map; +}; /** * Config schema for validating the inputs to the {@link LoggingServiceStart.configure} API. diff --git a/src/core/server/metrics/collectors/cgroup.test.ts b/src/core/server/metrics/collectors/cgroup.test.ts new file mode 100644 index 0000000000000..39f917b9f0ba1 --- /dev/null +++ b/src/core/server/metrics/collectors/cgroup.test.ts @@ -0,0 +1,115 @@ +/* + * 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 mockFs from 'mock-fs'; +import { OsCgroupMetricsCollector } from './cgroup'; + +describe('OsCgroupMetricsCollector', () => { + afterEach(() => mockFs.restore()); + + it('returns empty object when no cgroup file present', async () => { + mockFs({ + '/proc/self': { + /** empty directory */ + }, + }); + + const collector = new OsCgroupMetricsCollector({}); + expect(await collector.collect()).toEqual({}); + }); + + it('collects default cgroup data', async () => { + mockFs({ + '/proc/self/cgroup': ` +123:memory:/groupname +123:cpu:/groupname +123:cpuacct:/groupname + `, + '/sys/fs/cgroup/cpuacct/groupname/cpuacct.usage': '111', + '/sys/fs/cgroup/cpu/groupname/cpu.cfs_period_us': '222', + '/sys/fs/cgroup/cpu/groupname/cpu.cfs_quota_us': '333', + '/sys/fs/cgroup/cpu/groupname/cpu.stat': ` +nr_periods 444 +nr_throttled 555 +throttled_time 666 + `, + }); + + const collector = new OsCgroupMetricsCollector({}); + expect(await collector.collect()).toMatchInlineSnapshot(` + Object { + "cpu": Object { + "cfs_period_micros": 222, + "cfs_quota_micros": 333, + "control_group": "/groupname", + "stat": Object { + "number_of_elapsed_periods": 444, + "number_of_times_throttled": 555, + "time_throttled_nanos": 666, + }, + }, + "cpuacct": Object { + "control_group": "/groupname", + "usage_nanos": 111, + }, + } + `); + }); + + it('collects override cgroup data', async () => { + mockFs({ + '/proc/self/cgroup': ` +123:memory:/groupname +123:cpu:/groupname +123:cpuacct:/groupname + `, + '/sys/fs/cgroup/cpuacct/xxcustomcpuacctxx/cpuacct.usage': '111', + '/sys/fs/cgroup/cpu/xxcustomcpuxx/cpu.cfs_period_us': '222', + '/sys/fs/cgroup/cpu/xxcustomcpuxx/cpu.cfs_quota_us': '333', + '/sys/fs/cgroup/cpu/xxcustomcpuxx/cpu.stat': ` +nr_periods 444 +nr_throttled 555 +throttled_time 666 + `, + }); + + const collector = new OsCgroupMetricsCollector({ + cpuAcctPath: 'xxcustomcpuacctxx', + cpuPath: 'xxcustomcpuxx', + }); + expect(await collector.collect()).toMatchInlineSnapshot(` + Object { + "cpu": Object { + "cfs_period_micros": 222, + "cfs_quota_micros": 333, + "control_group": "xxcustomcpuxx", + "stat": Object { + "number_of_elapsed_periods": 444, + "number_of_times_throttled": 555, + "time_throttled_nanos": 666, + }, + }, + "cpuacct": Object { + "control_group": "xxcustomcpuacctxx", + "usage_nanos": 111, + }, + } + `); + }); +}); diff --git a/src/core/server/metrics/collectors/cgroup.ts b/src/core/server/metrics/collectors/cgroup.ts new file mode 100644 index 0000000000000..867ea44dff1ae --- /dev/null +++ b/src/core/server/metrics/collectors/cgroup.ts @@ -0,0 +1,194 @@ +/* + * 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 fs from 'fs'; +import { join as joinPath } from 'path'; +import { MetricsCollector, OpsOsMetrics } from './types'; + +type OsCgroupMetrics = Pick; + +interface OsCgroupMetricsCollectorOptions { + cpuPath?: string; + cpuAcctPath?: string; +} + +export class OsCgroupMetricsCollector implements MetricsCollector { + /** Used to prevent unnecessary file reads on systems not using cgroups. */ + private noCgroupPresent = false; + private cpuPath?: string; + private cpuAcctPath?: string; + + constructor(private readonly options: OsCgroupMetricsCollectorOptions) {} + + public async collect(): Promise { + try { + await this.initializePaths(); + if (this.noCgroupPresent || !this.cpuAcctPath || !this.cpuPath) { + return {}; + } + + const [cpuAcctUsage, cpuFsPeriod, cpuFsQuota, cpuStat] = await Promise.all([ + readCPUAcctUsage(this.cpuAcctPath), + readCPUFsPeriod(this.cpuPath), + readCPUFsQuota(this.cpuPath), + readCPUStat(this.cpuPath), + ]); + + return { + cpuacct: { + control_group: this.cpuAcctPath, + usage_nanos: cpuAcctUsage, + }, + + cpu: { + control_group: this.cpuPath, + cfs_period_micros: cpuFsPeriod, + cfs_quota_micros: cpuFsQuota, + stat: cpuStat, + }, + }; + } catch (err) { + if (err.code === 'ENOENT') { + this.noCgroupPresent = true; + return {}; + } else { + throw err; + } + } + } + + public reset() {} + + private async initializePaths() { + // Perform this setup lazily on the first collect call and then memoize the results. + // Makes the assumption this data doesn't change while the process is running. + if (this.cpuPath && this.cpuAcctPath) { + return; + } + + // Only read the file if both options are undefined. + if (!this.options.cpuPath || !this.options.cpuAcctPath) { + const cgroups = await readControlGroups(); + this.cpuPath = this.options.cpuPath || cgroups[GROUP_CPU]; + this.cpuAcctPath = this.options.cpuAcctPath || cgroups[GROUP_CPUACCT]; + } else { + this.cpuPath = this.options.cpuPath; + this.cpuAcctPath = this.options.cpuAcctPath; + } + + // prevents undefined cgroup paths + if (!this.cpuPath || !this.cpuAcctPath) { + this.noCgroupPresent = true; + } + } +} + +const CONTROL_GROUP_RE = new RegExp('\\d+:([^:]+):(/.*)'); +const CONTROLLER_SEPARATOR_RE = ','; + +const PROC_SELF_CGROUP_FILE = '/proc/self/cgroup'; +const PROC_CGROUP_CPU_DIR = '/sys/fs/cgroup/cpu'; +const PROC_CGROUP_CPUACCT_DIR = '/sys/fs/cgroup/cpuacct'; + +const GROUP_CPUACCT = 'cpuacct'; +const CPUACCT_USAGE_FILE = 'cpuacct.usage'; + +const GROUP_CPU = 'cpu'; +const CPU_FS_PERIOD_US_FILE = 'cpu.cfs_period_us'; +const CPU_FS_QUOTA_US_FILE = 'cpu.cfs_quota_us'; +const CPU_STATS_FILE = 'cpu.stat'; + +async function readControlGroups() { + const data = await fs.promises.readFile(PROC_SELF_CGROUP_FILE); + + return data + .toString() + .split(/\n/) + .reduce((acc, line) => { + const matches = line.match(CONTROL_GROUP_RE); + + if (matches !== null) { + const controllers = matches[1].split(CONTROLLER_SEPARATOR_RE); + controllers.forEach((controller) => { + acc[controller] = matches[2]; + }); + } + + return acc; + }, {} as Record); +} + +async function fileContentsToInteger(path: string) { + const data = await fs.promises.readFile(path); + return parseInt(data.toString(), 10); +} + +function readCPUAcctUsage(controlGroup: string) { + return fileContentsToInteger(joinPath(PROC_CGROUP_CPUACCT_DIR, controlGroup, CPUACCT_USAGE_FILE)); +} + +function readCPUFsPeriod(controlGroup: string) { + return fileContentsToInteger(joinPath(PROC_CGROUP_CPU_DIR, controlGroup, CPU_FS_PERIOD_US_FILE)); +} + +function readCPUFsQuota(controlGroup: string) { + return fileContentsToInteger(joinPath(PROC_CGROUP_CPU_DIR, controlGroup, CPU_FS_QUOTA_US_FILE)); +} + +async function readCPUStat(controlGroup: string) { + const stat = { + number_of_elapsed_periods: -1, + number_of_times_throttled: -1, + time_throttled_nanos: -1, + }; + + try { + const data = await fs.promises.readFile( + joinPath(PROC_CGROUP_CPU_DIR, controlGroup, CPU_STATS_FILE) + ); + return data + .toString() + .split(/\n/) + .reduce((acc, line) => { + const fields = line.split(/\s+/); + + switch (fields[0]) { + case 'nr_periods': + acc.number_of_elapsed_periods = parseInt(fields[1], 10); + break; + + case 'nr_throttled': + acc.number_of_times_throttled = parseInt(fields[1], 10); + break; + + case 'throttled_time': + acc.time_throttled_nanos = parseInt(fields[1], 10); + break; + } + + return acc; + }, stat); + } catch (err) { + if (err.code === 'ENOENT') { + return stat; + } + + throw err; + } +} diff --git a/src/core/server/metrics/collectors/collector.mock.ts b/src/core/server/metrics/collectors/collector.mock.ts new file mode 100644 index 0000000000000..2a942e1fafe63 --- /dev/null +++ b/src/core/server/metrics/collectors/collector.mock.ts @@ -0,0 +1,33 @@ +/* + * 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 { MetricsCollector } from './types'; + +const createCollector = (collectReturnValue: any = {}): jest.Mocked> => { + const collector: jest.Mocked> = { + collect: jest.fn().mockResolvedValue(collectReturnValue), + reset: jest.fn(), + }; + + return collector; +}; + +export const metricsCollectorMock = { + create: createCollector, +}; diff --git a/src/core/server/metrics/collectors/index.ts b/src/core/server/metrics/collectors/index.ts index f58ab02e63881..4540cb79be74b 100644 --- a/src/core/server/metrics/collectors/index.ts +++ b/src/core/server/metrics/collectors/index.ts @@ -18,6 +18,6 @@ */ export { OpsProcessMetrics, OpsOsMetrics, OpsServerMetrics, MetricsCollector } from './types'; -export { OsMetricsCollector } from './os'; +export { OsMetricsCollector, OpsMetricsCollectorOptions } from './os'; export { ProcessMetricsCollector } from './process'; export { ServerMetricsCollector } from './server'; diff --git a/src/core/server/metrics/collectors/os.test.mocks.ts b/src/core/server/metrics/collectors/os.test.mocks.ts new file mode 100644 index 0000000000000..ee02b8c802151 --- /dev/null +++ b/src/core/server/metrics/collectors/os.test.mocks.ts @@ -0,0 +1,25 @@ +/* + * 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 { metricsCollectorMock } from './collector.mock'; + +export const cgroupCollectorMock = metricsCollectorMock.create(); +jest.doMock('./cgroup', () => ({ + OsCgroupMetricsCollector: jest.fn(() => cgroupCollectorMock), +})); diff --git a/src/core/server/metrics/collectors/os.test.ts b/src/core/server/metrics/collectors/os.test.ts index 7d5a6da90b7d6..5e52cecb76be3 100644 --- a/src/core/server/metrics/collectors/os.test.ts +++ b/src/core/server/metrics/collectors/os.test.ts @@ -20,6 +20,7 @@ jest.mock('getos', () => (cb: Function) => cb(null, { dist: 'distrib', release: 'release' })); import os from 'os'; +import { cgroupCollectorMock } from './os.test.mocks'; import { OsMetricsCollector } from './os'; describe('OsMetricsCollector', () => { @@ -27,6 +28,8 @@ describe('OsMetricsCollector', () => { beforeEach(() => { collector = new OsMetricsCollector(); + cgroupCollectorMock.collect.mockReset(); + cgroupCollectorMock.reset.mockReset(); }); afterEach(() => { @@ -96,4 +99,9 @@ describe('OsMetricsCollector', () => { '15m': fifteenMinLoad, }); }); + + it('calls the cgroup sub-collector', async () => { + await collector.collect(); + expect(cgroupCollectorMock.collect).toHaveBeenCalled(); + }); }); diff --git a/src/core/server/metrics/collectors/os.ts b/src/core/server/metrics/collectors/os.ts index 59bef9d8ddd2b..eae49278405a9 100644 --- a/src/core/server/metrics/collectors/os.ts +++ b/src/core/server/metrics/collectors/os.ts @@ -21,10 +21,22 @@ import os from 'os'; import getosAsync, { LinuxOs } from 'getos'; import { promisify } from 'util'; import { OpsOsMetrics, MetricsCollector } from './types'; +import { OsCgroupMetricsCollector } from './cgroup'; const getos = promisify(getosAsync); +export interface OpsMetricsCollectorOptions { + cpuPath?: string; + cpuAcctPath?: string; +} + export class OsMetricsCollector implements MetricsCollector { + private readonly cgroupCollector: OsCgroupMetricsCollector; + + constructor(options: OpsMetricsCollectorOptions = {}) { + this.cgroupCollector = new OsCgroupMetricsCollector(options); + } + public async collect(): Promise { const platform = os.platform(); const load = os.loadavg(); @@ -43,20 +55,30 @@ export class OsMetricsCollector implements MetricsCollector { used_in_bytes: os.totalmem() - os.freemem(), }, uptime_in_millis: os.uptime() * 1000, + ...(await this.getDistroStats(platform)), + ...(await this.cgroupCollector.collect()), }; + return metrics; + } + + public reset() {} + + private async getDistroStats( + platform: string + ): Promise> { if (platform === 'linux') { try { const distro = (await getos()) as LinuxOs; - metrics.distro = distro.dist; - metrics.distroRelease = `${distro.dist}-${distro.release}`; + return { + distro: distro.dist, + distroRelease: `${distro.dist}-${distro.release}`, + }; } catch (e) { // ignore errors } } - return metrics; + return {}; } - - public reset() {} } diff --git a/src/core/server/metrics/collectors/types.ts b/src/core/server/metrics/collectors/types.ts index 73e8975a6b362..77ea13a1f0787 100644 --- a/src/core/server/metrics/collectors/types.ts +++ b/src/core/server/metrics/collectors/types.ts @@ -85,6 +85,33 @@ export interface OpsOsMetrics { }; /** the OS uptime */ uptime_in_millis: number; + + /** cpu accounting metrics, undefined when not running in a cgroup */ + cpuacct?: { + /** name of this process's cgroup */ + control_group: string; + /** cpu time used by this process's cgroup */ + usage_nanos: number; + }; + + /** cpu cgroup metrics, undefined when not running in a cgroup */ + cpu?: { + /** name of this process's cgroup */ + control_group: string; + /** the length of the cfs period */ + cfs_period_micros: number; + /** total available run-time within a cfs period */ + cfs_quota_micros: number; + /** current stats on the cfs periods */ + stat: { + /** number of cfs periods that elapsed */ + number_of_elapsed_periods: number; + /** number of times the cgroup has been throttled */ + number_of_times_throttled: number; + /** total amount of time the cgroup has been throttled for */ + time_throttled_nanos: number; + }; + }; } /** diff --git a/src/core/server/metrics/metrics_service.mock.ts b/src/core/server/metrics/metrics_service.mock.ts index 769f6ee2a549a..2af653004a479 100644 --- a/src/core/server/metrics/metrics_service.mock.ts +++ b/src/core/server/metrics/metrics_service.mock.ts @@ -21,20 +21,18 @@ import { MetricsService } from './metrics_service'; import { InternalMetricsServiceSetup, InternalMetricsServiceStart, + MetricsServiceSetup, MetricsServiceStart, } from './types'; const createInternalSetupContractMock = () => { - const setupContract: jest.Mocked = {}; - return setupContract; -}; - -const createStartContractMock = () => { - const startContract: jest.Mocked = { + const setupContract: jest.Mocked = { + collectionInterval: 30000, getOpsMetrics$: jest.fn(), }; - startContract.getOpsMetrics$.mockReturnValue( + setupContract.getOpsMetrics$.mockReturnValue( new BehaviorSubject({ + collected_at: new Date('2020-01-01 01:00:00'), process: { memory: { heap: { total_in_bytes: 1, used_in_bytes: 1, size_limit: 1 }, @@ -56,11 +54,21 @@ const createStartContractMock = () => { concurrent_connections: 1, }) ); + return setupContract; +}; + +const createSetupContractMock = () => { + const startContract: jest.Mocked = createInternalSetupContractMock(); return startContract; }; const createInternalStartContractMock = () => { - const startContract: jest.Mocked = createStartContractMock(); + const startContract: jest.Mocked = createInternalSetupContractMock(); + return startContract; +}; + +const createStartContractMock = () => { + const startContract: jest.Mocked = createInternalSetupContractMock(); return startContract; }; @@ -77,7 +85,7 @@ const createMock = () => { export const metricsServiceMock = { create: createMock, - createSetupContract: createStartContractMock, + createSetupContract: createSetupContractMock, createStartContract: createStartContractMock, createInternalSetupContract: createInternalSetupContractMock, createInternalStartContract: createInternalStartContractMock, diff --git a/src/core/server/metrics/metrics_service.ts b/src/core/server/metrics/metrics_service.ts index f28fb21aaac0d..d4696b3aa9aaf 100644 --- a/src/core/server/metrics/metrics_service.ts +++ b/src/core/server/metrics/metrics_service.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Subject } from 'rxjs'; +import { ReplaySubject } from 'rxjs'; import { first } from 'rxjs/operators'; import { CoreService } from '../../types'; import { CoreContext } from '../core_context'; @@ -37,26 +37,21 @@ export class MetricsService private readonly logger: Logger; private metricsCollector?: OpsMetricsCollector; private collectInterval?: NodeJS.Timeout; - private metrics$ = new Subject(); + private metrics$ = new ReplaySubject(); + private service?: InternalMetricsServiceSetup; constructor(private readonly coreContext: CoreContext) { this.logger = coreContext.logger.get('metrics'); } public async setup({ http }: MetricsServiceSetupDeps): Promise { - this.metricsCollector = new OpsMetricsCollector(http.server); - return {}; - } - - public async start(): Promise { - if (!this.metricsCollector) { - throw new Error('#setup() needs to be run first'); - } const config = await this.coreContext.configService .atPath(opsConfig.path) .pipe(first()) .toPromise(); + this.metricsCollector = new OpsMetricsCollector(http.server, config.cGroupOverrides); + await this.refreshMetrics(); this.collectInterval = setInterval(() => { @@ -65,9 +60,20 @@ export class MetricsService const metricsObservable = this.metrics$.asObservable(); - return { + this.service = { + collectionInterval: config.interval.asMilliseconds(), getOpsMetrics$: () => metricsObservable, }; + + return this.service; + } + + public async start(): Promise { + if (!this.service) { + throw new Error('#setup() needs to be run first'); + } + + return this.service; } private async refreshMetrics() { diff --git a/src/core/server/metrics/ops_config.ts b/src/core/server/metrics/ops_config.ts index bd6ae5cc5474d..5f3f67e931c38 100644 --- a/src/core/server/metrics/ops_config.ts +++ b/src/core/server/metrics/ops_config.ts @@ -23,6 +23,10 @@ export const opsConfig = { path: 'ops', schema: schema.object({ interval: schema.duration({ defaultValue: '5s' }), + cGroupOverrides: schema.object({ + cpuPath: schema.maybe(schema.string()), + cpuAcctPath: schema.maybe(schema.string()), + }), }), }; diff --git a/src/core/server/metrics/ops_metrics_collector.test.ts b/src/core/server/metrics/ops_metrics_collector.test.ts index 9e76895b14578..7aa3f7cd3baf0 100644 --- a/src/core/server/metrics/ops_metrics_collector.test.ts +++ b/src/core/server/metrics/ops_metrics_collector.test.ts @@ -30,7 +30,7 @@ describe('OpsMetricsCollector', () => { beforeEach(() => { const hapiServer = httpServiceMock.createInternalSetupContract().server; - collector = new OpsMetricsCollector(hapiServer); + collector = new OpsMetricsCollector(hapiServer, {}); mockOsCollector.collect.mockResolvedValue('osMetrics'); }); @@ -51,6 +51,7 @@ describe('OpsMetricsCollector', () => { expect(mockServerCollector.collect).toHaveBeenCalledTimes(1); expect(metrics).toEqual({ + collected_at: expect.any(Date), process: 'processMetrics', os: 'osMetrics', requests: 'serverRequestsMetrics', diff --git a/src/core/server/metrics/ops_metrics_collector.ts b/src/core/server/metrics/ops_metrics_collector.ts index 525515dba1457..af74caa6cb386 100644 --- a/src/core/server/metrics/ops_metrics_collector.ts +++ b/src/core/server/metrics/ops_metrics_collector.ts @@ -21,6 +21,7 @@ import { Server as HapiServer } from 'hapi'; import { ProcessMetricsCollector, OsMetricsCollector, + OpsMetricsCollectorOptions, ServerMetricsCollector, MetricsCollector, } from './collectors'; @@ -31,9 +32,9 @@ export class OpsMetricsCollector implements MetricsCollector { private readonly osCollector: OsMetricsCollector; private readonly serverCollector: ServerMetricsCollector; - constructor(server: HapiServer) { + constructor(server: HapiServer, opsOptions: OpsMetricsCollectorOptions) { this.processCollector = new ProcessMetricsCollector(); - this.osCollector = new OsMetricsCollector(); + this.osCollector = new OsMetricsCollector(opsOptions); this.serverCollector = new ServerMetricsCollector(server); } @@ -44,6 +45,7 @@ export class OpsMetricsCollector implements MetricsCollector { this.serverCollector.collect(), ]); return { + collected_at: new Date(), process, os, ...server, diff --git a/src/core/server/metrics/types.ts b/src/core/server/metrics/types.ts index cbf0acacd6bab..c177b3ed25115 100644 --- a/src/core/server/metrics/types.ts +++ b/src/core/server/metrics/types.ts @@ -20,14 +20,15 @@ import { Observable } from 'rxjs'; import { OpsProcessMetrics, OpsOsMetrics, OpsServerMetrics } from './collectors'; -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface MetricsServiceSetup {} /** * APIs to retrieves metrics gathered and exposed by the core platform. * * @public */ -export interface MetricsServiceStart { +export interface MetricsServiceSetup { + /** Interval metrics are collected in milliseconds */ + readonly collectionInterval: number; + /** * Retrieve an observable emitting the {@link OpsMetrics} gathered. * The observable will emit an initial value during core's `start` phase, and a new value every fixed interval of time, @@ -42,6 +43,12 @@ export interface MetricsServiceStart { */ getOpsMetrics$: () => Observable; } +/** + * {@inheritdoc MetricsServiceSetup} + * + * @public + */ +export type MetricsServiceStart = MetricsServiceSetup; export type InternalMetricsServiceSetup = MetricsServiceSetup; export type InternalMetricsServiceStart = MetricsServiceStart; @@ -53,6 +60,8 @@ export type InternalMetricsServiceStart = MetricsServiceStart; * @public */ export interface OpsMetrics { + /** Time metrics were recorded at. */ + collected_at: Date; /** Process related metrics */ process: OpsProcessMetrics; /** OS related metrics */ diff --git a/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts b/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts index 64d1256be2f30..836aabf881474 100644 --- a/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts +++ b/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts @@ -116,6 +116,16 @@ test('logs warning if pluginId is not in camelCase format', async () => { `); }); +test('does not log pluginId format warning in dist mode', async () => { + mockReadFile.mockImplementation((path, cb) => { + cb(null, Buffer.from(JSON.stringify({ id: 'some_name', version: 'kibana', server: true }))); + }); + + expect(loggingSystemMock.collect(logger).warn).toHaveLength(0); + await parseManifest(pluginPath, { ...packageInfo, dist: true }, logger); + expect(loggingSystemMock.collect(logger).warn.length).toBe(0); +}); + test('return error when plugin version is missing', async () => { mockReadFile.mockImplementation((path, cb) => { cb(null, Buffer.from(JSON.stringify({ id: 'someId' }))); diff --git a/src/core/server/plugins/discovery/plugin_manifest_parser.ts b/src/core/server/plugins/discovery/plugin_manifest_parser.ts index 0d33e266c37db..cfc412cb60b50 100644 --- a/src/core/server/plugins/discovery/plugin_manifest_parser.ts +++ b/src/core/server/plugins/discovery/plugin_manifest_parser.ts @@ -116,7 +116,7 @@ export async function parseManifest( ); } - if (!isCamelCase(manifest.id)) { + if (!packageInfo.dist && !isCamelCase(manifest.id)) { log.warn(`Expect plugin "id" in camelCase, but found: ${manifest.id}`); } diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index eb31b2380d177..af0b0e19b3227 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -236,6 +236,7 @@ export function createPluginStartContext( getTypeRegistry: deps.savedObjects.getTypeRegistry, }, metrics: { + collectionInterval: deps.metrics.collectionInterval, getOpsMetrics$: deps.metrics.getOpsMetrics$, }, uiSettings: { diff --git a/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap b/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap index 5ff5d69f96f70..ab828a1780425 100644 --- a/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap +++ b/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap @@ -46,7 +46,6 @@ Object { }, "version": Any, }, - "legacyMode": false, "serverBasePath": "/mock-server-basepath", "uiPlugins": Array [], "vars": Object {}, @@ -100,7 +99,6 @@ Object { }, "version": Any, }, - "legacyMode": false, "serverBasePath": "/mock-server-basepath", "uiPlugins": Array [], "vars": Object {}, @@ -158,7 +156,6 @@ Object { }, "version": Any, }, - "legacyMode": false, "serverBasePath": "/mock-server-basepath", "uiPlugins": Array [], "vars": Object {}, @@ -212,7 +209,6 @@ Object { }, "version": Any, }, - "legacyMode": false, "serverBasePath": "/mock-server-basepath", "uiPlugins": Array [], "vars": Object {}, @@ -266,7 +262,6 @@ Object { }, "version": Any, }, - "legacyMode": false, "serverBasePath": "/mock-server-basepath", "uiPlugins": Array [], "vars": Object {}, diff --git a/src/core/server/rendering/rendering_service.tsx b/src/core/server/rendering/rendering_service.tsx index e7ee0b16fce08..7761c89044f6f 100644 --- a/src/core/server/rendering/rendering_service.tsx +++ b/src/core/server/rendering/rendering_service.tsx @@ -82,7 +82,6 @@ export class RenderingService implements CoreService { diff --git a/src/core/server/saved_objects/index.ts b/src/core/server/saved_objects/index.ts index a294b28753f7b..f2bae29c4743b 100644 --- a/src/core/server/saved_objects/index.ts +++ b/src/core/server/saved_objects/index.ts @@ -19,8 +19,6 @@ export * from './service'; -export { SavedObjectsSchema } from './schema'; - export * from './import'; export { diff --git a/src/core/server/saved_objects/migrations/core/document_migrator.test.ts b/src/core/server/saved_objects/migrations/core/document_migrator.test.ts index 4fc94d1992869..4cc4f696d307c 100644 --- a/src/core/server/saved_objects/migrations/core/document_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/core/document_migrator.test.ts @@ -48,7 +48,6 @@ describe('DocumentMigrator', () => { return { kibanaVersion: '25.2.3', typeRegistry: createRegistry(), - validateDoc: _.noop, log: mockLogger, }; } @@ -60,7 +59,6 @@ describe('DocumentMigrator', () => { name: 'foo', migrations: _.noop as any, }), - validateDoc: _.noop, log: mockLogger, }; expect(() => new DocumentMigrator(invalidDefinition)).toThrow( @@ -77,7 +75,6 @@ describe('DocumentMigrator', () => { bar: (doc) => doc, }, }), - validateDoc: _.noop, log: mockLogger, }; expect(() => new DocumentMigrator(invalidDefinition)).toThrow( @@ -94,7 +91,6 @@ describe('DocumentMigrator', () => { '1.2.3': 23 as any, }, }), - validateDoc: _.noop, log: mockLogger, }; expect(() => new DocumentMigrator(invalidDefinition)).toThrow( @@ -633,27 +629,6 @@ describe('DocumentMigrator', () => { bbb: '3.2.3', }); }); - - test('fails if the validate doc throws', () => { - const migrator = new DocumentMigrator({ - ...testOpts(), - typeRegistry: createRegistry({ - name: 'aaa', - migrations: { - '2.3.4': (d) => set(d, 'attributes.counter', 42), - }, - }), - validateDoc: (d) => { - if ((d.attributes as any).counter === 42) { - throw new Error('Meaningful!'); - } - }, - }); - - const doc = { id: '1', type: 'foo', attributes: {}, migrationVersion: {}, aaa: {} }; - - expect(() => migrator.migrate(doc)).toThrow(/Meaningful/); - }); }); function renameAttr(path: string, newPath: string) { diff --git a/src/core/server/saved_objects/migrations/core/document_migrator.ts b/src/core/server/saved_objects/migrations/core/document_migrator.ts index c50f755fda994..345704fbfd783 100644 --- a/src/core/server/saved_objects/migrations/core/document_migrator.ts +++ b/src/core/server/saved_objects/migrations/core/document_migrator.ts @@ -73,12 +73,9 @@ import { SavedObjectMigrationFn } from '../types'; export type TransformFn = (doc: SavedObjectUnsanitizedDoc) => SavedObjectUnsanitizedDoc; -type ValidateDoc = (doc: SavedObjectUnsanitizedDoc) => void; - interface DocumentMigratorOptions { kibanaVersion: string; typeRegistry: ISavedObjectTypeRegistry; - validateDoc: ValidateDoc; log: Logger; } @@ -113,19 +110,16 @@ export class DocumentMigrator implements VersionedTransformer { * @param {DocumentMigratorOptions} opts * @prop {string} kibanaVersion - The current version of Kibana * @prop {SavedObjectTypeRegistry} typeRegistry - The type registry to get type migrations from - * @prop {ValidateDoc} validateDoc - A function which, given a document throws an error if it is - * not up to date. This is used to ensure we don't let unmigrated documents slip through. * @prop {Logger} log - The migration logger * @memberof DocumentMigrator */ - constructor({ typeRegistry, kibanaVersion, log, validateDoc }: DocumentMigratorOptions) { + constructor({ typeRegistry, kibanaVersion, log }: DocumentMigratorOptions) { validateMigrationDefinition(typeRegistry); this.migrations = buildActiveMigrations(typeRegistry, log); this.transformDoc = buildDocumentTransform({ kibanaVersion, migrations: this.migrations, - validateDoc, }); } @@ -231,21 +225,16 @@ function buildActiveMigrations( * Creates a function which migrates and validates any document that is passed to it. */ function buildDocumentTransform({ - kibanaVersion, migrations, - validateDoc, }: { kibanaVersion: string; migrations: ActiveMigrations; - validateDoc: ValidateDoc; }): TransformFn { return function transformAndValidate(doc: SavedObjectUnsanitizedDoc) { const result = doc.migrationVersion ? applyMigrations(doc, migrations) : markAsUpToDate(doc, migrations); - validateDoc(result); - // In order to keep tests a bit more stable, we won't // tack on an empy migrationVersion to docs that have // no migrations defined. diff --git a/src/core/server/saved_objects/migrations/core/index_migrator.test.ts b/src/core/server/saved_objects/migrations/core/index_migrator.test.ts index df89137a1d798..13f771c16bc67 100644 --- a/src/core/server/saved_objects/migrations/core/index_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/core/index_migrator.test.ts @@ -369,6 +369,30 @@ describe('IndexMigrator', () => { ], }); }); + + test('rejects when the migration function throws an error', async () => { + const { client } = testOpts; + const migrateDoc = jest.fn((doc: SavedObjectUnsanitizedDoc) => { + throw new Error('error migrating document'); + }); + + testOpts.documentMigrator = { + migrationVersion: { foo: '1.2.3' }, + migrate: migrateDoc, + }; + + withIndex(client, { + numOutOfDate: 1, + docs: [ + [{ _id: 'foo:1', _source: { type: 'foo', foo: { name: 'Bar' } } }], + [{ _id: 'foo:2', _source: { type: 'foo', foo: { name: 'Baz' } } }], + ], + }); + + await expect(new IndexMigrator(testOpts).migrate()).rejects.toThrowErrorMatchingInlineSnapshot( + `"error migrating document"` + ); + }); }); function withIndex( diff --git a/src/core/server/saved_objects/migrations/core/migrate_raw_docs.test.ts b/src/core/server/saved_objects/migrations/core/migrate_raw_docs.test.ts index 4c9d2e870a7bb..83dc042d2b96b 100644 --- a/src/core/server/saved_objects/migrations/core/migrate_raw_docs.test.ts +++ b/src/core/server/saved_objects/migrations/core/migrate_raw_docs.test.ts @@ -90,4 +90,18 @@ describe('migrateRawDocs', () => { expect(logger.error).toBeCalledTimes(1); }); + + test('rejects when the transform function throws an error', async () => { + const transform = jest.fn((doc: any) => { + throw new Error('error during transform'); + }); + await expect( + migrateRawDocs( + new SavedObjectsSerializer(new SavedObjectTypeRegistry()), + transform, + [{ _id: 'a:b', _source: { type: 'a', a: { name: 'AAA' } } }], + createSavedObjectsMigrationLoggerMock() + ) + ).rejects.toThrowErrorMatchingInlineSnapshot(`"error during transform"`); + }); }); diff --git a/src/core/server/saved_objects/migrations/core/migrate_raw_docs.ts b/src/core/server/saved_objects/migrations/core/migrate_raw_docs.ts index 2bdf59d25dc74..5a5048d8ad88f 100644 --- a/src/core/server/saved_objects/migrations/core/migrate_raw_docs.ts +++ b/src/core/server/saved_objects/migrations/core/migrate_raw_docs.ts @@ -78,10 +78,14 @@ function transformNonBlocking( ): (doc: SavedObjectUnsanitizedDoc) => Promise { // promises aren't enough to unblock the event loop return (doc: SavedObjectUnsanitizedDoc) => - new Promise((resolve) => { + new Promise((resolve, reject) => { // set immediate is though setImmediate(() => { - resolve(transform(doc)); + try { + resolve(transform(doc)); + } catch (e) { + reject(e); + } }); }); } diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts index cc443093e30a3..7eb2cfefe4620 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts @@ -134,7 +134,6 @@ const mockOptions = () => { const options: MockedOptions = { logger: loggingSystemMock.create().get(), kibanaVersion: '8.2.3', - savedObjectValidations: {}, typeRegistry: createRegistry([ { name: 'testtype', diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts index 85b9099308807..18a385c6994b8 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts @@ -28,7 +28,6 @@ import { BehaviorSubject } from 'rxjs'; import { Logger } from '../../../logging'; import { IndexMapping, SavedObjectsTypeMappingDefinitions } from '../../mappings'; import { SavedObjectUnsanitizedDoc, SavedObjectsSerializer } from '../../serialization'; -import { docValidator, PropertyValidators } from '../../validation'; import { buildActiveMappings, IndexMigrator, MigrationResult, MigrationStatus } from '../core'; import { DocumentMigrator, VersionedTransformer } from '../core/document_migrator'; import { MigrationEsClient } from '../core/'; @@ -44,7 +43,6 @@ export interface KibanaMigratorOptions { kibanaConfig: KibanaConfigType; kibanaVersion: string; logger: Logger; - savedObjectValidations: PropertyValidators; } export type IKibanaMigrator = Pick; @@ -80,7 +78,6 @@ export class KibanaMigrator { typeRegistry, kibanaConfig, savedObjectsConfig, - savedObjectValidations, kibanaVersion, logger, }: KibanaMigratorOptions) { @@ -94,7 +91,6 @@ export class KibanaMigrator { this.documentMigrator = new DocumentMigrator({ kibanaVersion, typeRegistry, - validateDoc: docValidator(savedObjectValidations || {}), log: this.log, }); // Building the active mappings (and associated md5sums) is an expensive @@ -124,9 +120,17 @@ export class KibanaMigrator { Array<{ status: string }> > { if (this.migrationResult === undefined || rerun) { - this.status$.next({ status: 'running' }); + // Reruns are only used by CI / EsArchiver. Publishing status updates on reruns results in slowing down CI + // unnecessarily, so we skip it in this case. + if (!rerun) { + this.status$.next({ status: 'running' }); + } + this.migrationResult = this.runMigrationsInternal().then((result) => { - this.status$.next({ status: 'completed', result }); + // Similar to above, don't publish status updates when rerunning in CI. + if (!rerun) { + this.status$.next({ status: 'completed', result }); + } return result; }); } diff --git a/src/core/server/saved_objects/routes/bulk_update.ts b/src/core/server/saved_objects/routes/bulk_update.ts index c112833b29f3f..882213644146a 100644 --- a/src/core/server/saved_objects/routes/bulk_update.ts +++ b/src/core/server/saved_objects/routes/bulk_update.ts @@ -40,6 +40,7 @@ export const registerBulkUpdateRoute = (router: IRouter) => { }) ) ), + namespace: schema.maybe(schema.string({ minLength: 1 })), }) ), }, diff --git a/src/core/server/saved_objects/routes/export.ts b/src/core/server/saved_objects/routes/export.ts index 9445c144ecda4..35a65d8d9651f 100644 --- a/src/core/server/saved_objects/routes/export.ts +++ b/src/core/server/saved_objects/routes/export.ts @@ -19,11 +19,7 @@ import { schema } from '@kbn/config-schema'; import stringify from 'json-stable-stringify'; -import { - createPromiseFromStreams, - createMapStream, - createConcatStream, -} from '../../../../legacy/utils/streams'; +import { createPromiseFromStreams, createMapStream, createConcatStream } from '../../utils/streams'; import { IRouter } from '../../http'; import { SavedObjectConfig } from '../saved_objects_config'; import { exportSavedObjectsToStream } from '../export'; diff --git a/src/core/server/saved_objects/routes/integration_tests/export.test.ts b/src/core/server/saved_objects/routes/integration_tests/export.test.ts index d47f7c6050d8f..a3891712fd22b 100644 --- a/src/core/server/saved_objects/routes/integration_tests/export.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/export.test.ts @@ -22,7 +22,7 @@ jest.mock('../../export', () => ({ })); import * as exportMock from '../../export'; -import { createListStream } from '../../../../../legacy/utils/streams'; +import { createListStream } from '../../../utils/streams'; import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { SavedObjectConfig } from '../../saved_objects_config'; diff --git a/src/core/server/saved_objects/routes/integration_tests/migrate.test.ts b/src/core/server/saved_objects/routes/integration_tests/migrate.test.ts index 7a0e39b71afb8..e003d564c1ea2 100644 --- a/src/core/server/saved_objects/routes/integration_tests/migrate.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/migrate.test.ts @@ -18,7 +18,7 @@ */ import { migratorInstanceMock } from './migrate.test.mocks'; -import * as kbnTestServer from '../../../../../test_utils/kbn_server'; +import * as kbnTestServer from '../../../../test_helpers/kbn_server'; describe('SavedObjects /_migrate endpoint', () => { let root: ReturnType; diff --git a/src/core/server/saved_objects/routes/utils.test.ts b/src/core/server/saved_objects/routes/utils.test.ts index 24719724785af..fd3bdad8606ed 100644 --- a/src/core/server/saved_objects/routes/utils.test.ts +++ b/src/core/server/saved_objects/routes/utils.test.ts @@ -19,7 +19,7 @@ import { createSavedObjectsStreamFromNdJson, validateTypes, validateObjects } from './utils'; import { Readable } from 'stream'; -import { createPromiseFromStreams, createConcatStream } from '../../../../legacy/utils/streams'; +import { createPromiseFromStreams, createConcatStream } from '../../utils/streams'; async function readStreamToCompletion(stream: Readable) { return createPromiseFromStreams([stream, createConcatStream([])]); diff --git a/src/core/server/saved_objects/routes/utils.ts b/src/core/server/saved_objects/routes/utils.ts index 3963833a9c718..f16a6e471257d 100644 --- a/src/core/server/saved_objects/routes/utils.ts +++ b/src/core/server/saved_objects/routes/utils.ts @@ -19,11 +19,7 @@ import { Readable } from 'stream'; import { SavedObject, SavedObjectsExportResultDetails } from 'src/core/server'; -import { - createSplitStream, - createMapStream, - createFilterStream, -} from '../../../../legacy/utils/streams'; +import { createSplitStream, createMapStream, createFilterStream } from '../../utils/streams'; export function createSavedObjectsStreamFromNdJson(ndJsonStream: Readable) { return ndJsonStream diff --git a/src/core/server/saved_objects/saved_objects_service.mock.ts b/src/core/server/saved_objects/saved_objects_service.mock.ts index 6f5ecb1eb464b..e3d44c20dd190 100644 --- a/src/core/server/saved_objects/saved_objects_service.mock.ts +++ b/src/core/server/saved_objects/saved_objects_service.mock.ts @@ -26,8 +26,7 @@ import { SavedObjectsServiceSetup, SavedObjectsServiceStart, } from './saved_objects_service'; -import { mockKibanaMigrator } from './migrations/kibana/kibana_migrator.mock'; -import { savedObjectsClientProviderMock } from './service/lib/scoped_client_provider.mock'; + import { savedObjectsRepositoryMock } from './service/lib/repository.mock'; import { savedObjectsClientMock } from './service/saved_objects_client.mock'; import { typeRegistryMock } from './saved_objects_type_registry.mock'; @@ -54,11 +53,7 @@ const createStartContractMock = () => { }; const createInternalStartContractMock = () => { - const internalStartContract: jest.Mocked = { - ...createStartContractMock(), - clientProvider: savedObjectsClientProviderMock.create(), - migrator: mockKibanaMigrator.create(), - }; + const internalStartContract: jest.Mocked = createStartContractMock(); return internalStartContract; }; diff --git a/src/core/server/saved_objects/saved_objects_service.test.ts b/src/core/server/saved_objects/saved_objects_service.test.ts index 8df6a07318c45..d6b30889eba5f 100644 --- a/src/core/server/saved_objects/saved_objects_service.test.ts +++ b/src/core/server/saved_objects/saved_objects_service.test.ts @@ -33,7 +33,6 @@ import { Env } from '../config'; import { configServiceMock } from '../mocks'; import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock'; import { elasticsearchClientMock } from '../elasticsearch/client/mocks'; -import { legacyServiceMock } from '../legacy/legacy_service.mock'; import { httpServiceMock } from '../http/http_service.mock'; import { httpServerMock } from '../http/http_server.mocks'; import { SavedObjectsClientFactoryProvider } from './service/lib'; @@ -65,7 +64,6 @@ describe('SavedObjectsService', () => { return { http: httpServiceMock.createInternalSetupContract(), elasticsearch: elasticsearchMock, - legacyPlugins: legacyServiceMock.createDiscoverPlugins(), }; }; @@ -239,8 +237,7 @@ describe('SavedObjectsService', () => { await soService.setup(createSetupDeps()); expect(migratorInstanceMock.runMigrations).toHaveBeenCalledTimes(0); - const startContract = await soService.start(createStartDeps()); - expect(startContract.migrator).toBe(migratorInstanceMock); + await soService.start(createStartDeps()); expect(migratorInstanceMock.runMigrations).toHaveBeenCalledTimes(1); }); diff --git a/src/core/server/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts index f05e912b12ad8..5cc59d55a254e 100644 --- a/src/core/server/saved_objects/saved_objects_service.ts +++ b/src/core/server/saved_objects/saved_objects_service.ts @@ -23,12 +23,10 @@ import { CoreService } from '../../types'; import { SavedObjectsClient, SavedObjectsClientProvider, - ISavedObjectsClientProvider, SavedObjectsClientProviderOptions, } from './'; import { KibanaMigrator, IKibanaMigrator } from './migrations'; import { CoreContext } from '../core_context'; -import { LegacyServiceDiscoverPlugins } from '../legacy'; import { ElasticsearchClient, IClusterClient, @@ -49,9 +47,7 @@ import { SavedObjectsClientWrapperFactory, } from './service/lib/scoped_client_provider'; import { Logger } from '../logging'; -import { convertLegacyTypes } from './utils'; import { SavedObjectTypeRegistry, ISavedObjectTypeRegistry } from './saved_objects_type_registry'; -import { PropertyValidators } from './validation'; import { SavedObjectsSerializer } from './serialization'; import { registerRoutes } from './routes'; import { ServiceStatus } from '../status'; @@ -67,9 +63,6 @@ import { createMigrationEsClient } from './migrations/core/'; * the factory provided to `setClientFactory` and wrapped by all wrappers * registered through `addClientWrapper`. * - * All the setup APIs will throw if called after the service has started, and therefor cannot be used - * from legacy plugin code. Legacy plugins should use the legacy savedObject service until migrated. - * * @example * ```ts * import { SavedObjectsClient, CoreSetup } from 'src/core/server'; @@ -155,9 +148,6 @@ export interface SavedObjectsServiceSetup { * } * } * ``` - * - * @remarks The type definition is an aggregation of the legacy savedObjects `schema`, `mappings` and `migration` concepts. - * This API is the single entry point to register saved object types in the new platform. */ registerType: (type: SavedObjectsType) => void; @@ -230,16 +220,7 @@ export interface SavedObjectsServiceStart { getTypeRegistry: () => ISavedObjectTypeRegistry; } -export interface InternalSavedObjectsServiceStart extends SavedObjectsServiceStart { - /** - * @deprecated Exposed only for injecting into Legacy - */ - migrator: IKibanaMigrator; - /** - * @deprecated Exposed only for injecting into Legacy - */ - clientProvider: ISavedObjectsClientProvider; -} +export type InternalSavedObjectsServiceStart = SavedObjectsServiceStart; /** * Factory provided when invoking a {@link SavedObjectsClientFactoryProvider | client factory provider} @@ -271,7 +252,6 @@ export interface SavedObjectsRepositoryFactory { /** @internal */ export interface SavedObjectsSetupDeps { http: InternalHttpServiceSetup; - legacyPlugins: LegacyServiceDiscoverPlugins; elasticsearch: InternalElasticsearchServiceSetup; } @@ -296,9 +276,8 @@ export class SavedObjectsService private clientFactoryProvider?: SavedObjectsClientFactoryProvider; private clientFactoryWrappers: WrappedClientFactoryWrapper[] = []; - private migrator$ = new Subject(); + private migrator$ = new Subject(); private typeRegistry = new SavedObjectTypeRegistry(); - private validations: PropertyValidators = {}; private started = false; constructor(private readonly coreContext: CoreContext) { @@ -310,13 +289,6 @@ export class SavedObjectsService this.setupDeps = setupDeps; - const legacyTypes = convertLegacyTypes( - setupDeps.legacyPlugins.uiExports, - setupDeps.legacyPlugins.pluginExtendedConfig - ); - legacyTypes.forEach((type) => this.typeRegistry.registerType(type)); - this.validations = setupDeps.legacyPlugins.uiExports.savedObjectValidations || {}; - const savedObjectsConfig = await this.coreContext.configService .atPath('savedObjects') .pipe(first()) @@ -471,8 +443,6 @@ export class SavedObjectsService this.started = true; return { - migrator, - clientProvider, getScopedClient: clientProvider.getClient.bind(clientProvider), createScopedRepository: repositoryFactory.createScopedRepository, createInternalRepository: repositoryFactory.createInternalRepository, @@ -488,13 +458,12 @@ export class SavedObjectsService savedObjectsConfig: SavedObjectsMigrationConfigType, client: IClusterClient, migrationsRetryDelay?: number - ): KibanaMigrator { + ): IKibanaMigrator { return new KibanaMigrator({ typeRegistry: this.typeRegistry, logger: this.logger, kibanaVersion: this.coreContext.env.packageInfo.version, savedObjectsConfig, - savedObjectValidations: this.validations, kibanaConfig, client: createMigrationEsClient(client.asInternalUser, this.logger, migrationsRetryDelay), }); diff --git a/src/core/server/saved_objects/schema/index.ts b/src/core/server/saved_objects/schema/index.ts deleted file mode 100644 index d30bbb8d34cd3..0000000000000 --- a/src/core/server/saved_objects/schema/index.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 { SavedObjectsSchema, SavedObjectsSchemaDefinition } from './schema'; diff --git a/src/core/server/saved_objects/schema/schema.test.ts b/src/core/server/saved_objects/schema/schema.test.ts deleted file mode 100644 index f2daa13e43fce..0000000000000 --- a/src/core/server/saved_objects/schema/schema.test.ts +++ /dev/null @@ -1,106 +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 { SavedObjectsSchema, SavedObjectsSchemaDefinition } from './schema'; - -describe('#isNamespaceAgnostic', () => { - const expectResult = (expected: boolean, schemaDefinition?: SavedObjectsSchemaDefinition) => { - const schema = new SavedObjectsSchema(schemaDefinition); - const result = schema.isNamespaceAgnostic('foo'); - expect(result).toBe(expected); - }; - - it(`returns false when no schema is defined`, () => { - expectResult(false); - }); - - it(`returns false for unknown types`, () => { - expectResult(false, { bar: {} }); - }); - - it(`returns false for non-namespace-agnostic type`, () => { - expectResult(false, { foo: { isNamespaceAgnostic: false } }); - expectResult(false, { foo: { isNamespaceAgnostic: undefined } }); - }); - - it(`returns true for explicitly namespace-agnostic type`, () => { - expectResult(true, { foo: { isNamespaceAgnostic: true } }); - }); -}); - -describe('#isSingleNamespace', () => { - const expectResult = (expected: boolean, schemaDefinition?: SavedObjectsSchemaDefinition) => { - const schema = new SavedObjectsSchema(schemaDefinition); - const result = schema.isSingleNamespace('foo'); - expect(result).toBe(expected); - }; - - it(`returns true when no schema is defined`, () => { - expectResult(true); - }); - - it(`returns true for unknown types`, () => { - expectResult(true, { bar: {} }); - }); - - it(`returns false for explicitly namespace-agnostic type`, () => { - expectResult(false, { foo: { isNamespaceAgnostic: true } }); - }); - - it(`returns false for explicitly multi-namespace type`, () => { - expectResult(false, { foo: { multiNamespace: true } }); - }); - - it(`returns true for non-namespace-agnostic and non-multi-namespace type`, () => { - expectResult(true, { foo: { isNamespaceAgnostic: false, multiNamespace: false } }); - expectResult(true, { foo: { isNamespaceAgnostic: false, multiNamespace: undefined } }); - expectResult(true, { foo: { isNamespaceAgnostic: undefined, multiNamespace: false } }); - expectResult(true, { foo: { isNamespaceAgnostic: undefined, multiNamespace: undefined } }); - }); -}); - -describe('#isMultiNamespace', () => { - const expectResult = (expected: boolean, schemaDefinition?: SavedObjectsSchemaDefinition) => { - const schema = new SavedObjectsSchema(schemaDefinition); - const result = schema.isMultiNamespace('foo'); - expect(result).toBe(expected); - }; - - it(`returns false when no schema is defined`, () => { - expectResult(false); - }); - - it(`returns false for unknown types`, () => { - expectResult(false, { bar: {} }); - }); - - it(`returns false for explicitly namespace-agnostic type`, () => { - expectResult(false, { foo: { isNamespaceAgnostic: true } }); - }); - - it(`returns false for non-multi-namespace type`, () => { - expectResult(false, { foo: { multiNamespace: false } }); - expectResult(false, { foo: { multiNamespace: undefined } }); - }); - - it(`returns true for non-namespace-agnostic and explicitly multi-namespace type`, () => { - expectResult(true, { foo: { isNamespaceAgnostic: false, multiNamespace: true } }); - expectResult(true, { foo: { isNamespaceAgnostic: undefined, multiNamespace: true } }); - }); -}); diff --git a/src/core/server/saved_objects/schema/schema.ts b/src/core/server/saved_objects/schema/schema.ts deleted file mode 100644 index ba1905158e822..0000000000000 --- a/src/core/server/saved_objects/schema/schema.ts +++ /dev/null @@ -1,116 +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 { LegacyConfig } from '../../legacy'; - -/** - * @deprecated - * @internal - **/ -interface SavedObjectsSchemaTypeDefinition { - isNamespaceAgnostic?: boolean; - multiNamespace?: boolean; - hidden?: boolean; - indexPattern?: ((config: LegacyConfig) => string) | string; - convertToAliasScript?: string; -} - -/** - * @deprecated - * @internal - **/ -export interface SavedObjectsSchemaDefinition { - [type: string]: SavedObjectsSchemaTypeDefinition; -} - -/** - * @deprecated This is only used by the {@link SavedObjectsLegacyService | legacy savedObjects service} - * @internal - **/ -export class SavedObjectsSchema { - private readonly definition?: SavedObjectsSchemaDefinition; - constructor(schemaDefinition?: SavedObjectsSchemaDefinition) { - this.definition = schemaDefinition; - } - - public isHiddenType(type: string) { - if (this.definition && this.definition.hasOwnProperty(type)) { - return Boolean(this.definition[type].hidden); - } - - return false; - } - - public getIndexForType(config: LegacyConfig, type: string): string | undefined { - if (this.definition != null && this.definition.hasOwnProperty(type)) { - const { indexPattern } = this.definition[type]; - return typeof indexPattern === 'function' ? indexPattern(config) : indexPattern; - } else { - return undefined; - } - } - - public getConvertToAliasScript(type: string): string | undefined { - if (this.definition != null && this.definition.hasOwnProperty(type)) { - return this.definition[type].convertToAliasScript; - } - } - - public isNamespaceAgnostic(type: string) { - // if no plugins have registered a Saved Objects Schema, - // this.schema will be undefined, and no types are namespace agnostic - if (!this.definition) { - return false; - } - - const typeSchema = this.definition[type]; - if (!typeSchema) { - return false; - } - return Boolean(typeSchema.isNamespaceAgnostic); - } - - public isSingleNamespace(type: string) { - // if no plugins have registered a Saved Objects Schema, - // this.schema will be undefined, and all types are namespace isolated - if (!this.definition) { - return true; - } - - const typeSchema = this.definition[type]; - if (!typeSchema) { - return true; - } - return !Boolean(typeSchema.isNamespaceAgnostic) && !Boolean(typeSchema.multiNamespace); - } - - public isMultiNamespace(type: string) { - // if no plugins have registered a Saved Objects Schema, - // this.schema will be undefined, and no types are multi-namespace - if (!this.definition) { - return false; - } - - const typeSchema = this.definition[type]; - if (!typeSchema) { - return false; - } - return !Boolean(typeSchema.isNamespaceAgnostic) && Boolean(typeSchema.multiNamespace); - } -} diff --git a/src/core/server/saved_objects/service/index.ts b/src/core/server/saved_objects/service/index.ts index 9f625b4732e26..c33a9f2f3b157 100644 --- a/src/core/server/saved_objects/service/index.ts +++ b/src/core/server/saved_objects/service/index.ts @@ -17,37 +17,6 @@ * under the License. */ -import { Readable } from 'stream'; -import { SavedObjectsClientProvider } from './lib'; -import { SavedObjectsClient } from './saved_objects_client'; -import { SavedObjectsExportOptions } from '../export'; -import { SavedObjectsImportOptions, SavedObjectsImportResponse } from '../import'; -import { SavedObjectsSchema } from '../schema'; -import { SavedObjectsResolveImportErrorsOptions } from '../import/types'; - -/** - * @internal - * @deprecated - */ -export interface SavedObjectsLegacyService { - // ATTENTION: these types are incomplete - addScopedSavedObjectsClientWrapperFactory: SavedObjectsClientProvider['addClientWrapperFactory']; - setScopedSavedObjectsClientFactory: SavedObjectsClientProvider['setClientFactory']; - getScopedSavedObjectsClient: SavedObjectsClientProvider['getClient']; - SavedObjectsClient: typeof SavedObjectsClient; - types: string[]; - schema: SavedObjectsSchema; - getSavedObjectsRepository(...rest: any[]): any; - importExport: { - objectLimit: number; - importSavedObjects(options: SavedObjectsImportOptions): Promise; - resolveImportErrors( - options: SavedObjectsResolveImportErrorsOptions - ): Promise; - getSortedObjectsForExport(options: SavedObjectsExportOptions): Promise; - }; -} - export { SavedObjectsRepository, SavedObjectsClientProvider, @@ -58,6 +27,7 @@ export { SavedObjectsErrorHelpers, SavedObjectsClientFactory, SavedObjectsClientFactoryProvider, + SavedObjectsUtils, } from './lib'; export * from './saved_objects_client'; diff --git a/src/core/server/saved_objects/service/lib/index.ts b/src/core/server/saved_objects/service/lib/index.ts index e103120388e35..eae8c5ef2e10c 100644 --- a/src/core/server/saved_objects/service/lib/index.ts +++ b/src/core/server/saved_objects/service/lib/index.ts @@ -30,3 +30,5 @@ export { } from './scoped_client_provider'; export { SavedObjectsErrorHelpers } from './errors'; + +export { SavedObjectsUtils } from './utils'; diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js index b1d6028465713..7d30875b90796 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -153,30 +153,35 @@ describe('SavedObjectsRepository', () => { typeRegistry: registry, kibanaVersion: '2.0.0', log: {}, - validateDoc: jest.fn(), }); - const getMockGetResponse = ({ type, id, references, namespace, originId }) => ({ - // NOTE: Elasticsearch returns more fields (_index, _type) but the SavedObjectsRepository method ignores these - found: true, - _id: `${registry.isSingleNamespace(type) && namespace ? `${namespace}:` : ''}${type}:${id}`, - ...mockVersionProps, - _source: { - ...(registry.isSingleNamespace(type) && { namespace }), - ...(registry.isMultiNamespace(type) && { namespaces: [namespace ?? 'default'] }), - ...(originId && { originId }), - type, - [type]: { title: 'Testing' }, - references, - specialProperty: 'specialValue', - ...mockTimestampFields, - }, - }); + const getMockGetResponse = ( + { type, id, references, namespace: objectNamespace, originId }, + namespace + ) => { + const namespaceId = objectNamespace === 'default' ? undefined : objectNamespace ?? namespace; + return { + // NOTE: Elasticsearch returns more fields (_index, _type) but the SavedObjectsRepository method ignores these + found: true, + _id: `${ + registry.isSingleNamespace(type) && namespaceId ? `${namespaceId}:` : '' + }${type}:${id}`, + ...mockVersionProps, + _source: { + ...(registry.isSingleNamespace(type) && { namespace: namespaceId }), + ...(registry.isMultiNamespace(type) && { namespaces: [namespaceId ?? 'default'] }), + ...(originId && { originId }), + type, + [type]: { title: 'Testing' }, + references, + specialProperty: 'specialValue', + ...mockTimestampFields, + }, + }; + }; const getMockMgetResponse = (objects, namespace) => ({ - docs: objects.map((obj) => - obj.found === false ? obj : getMockGetResponse({ ...obj, namespace }) - ), + docs: objects.map((obj) => (obj.found === false ? obj : getMockGetResponse(obj, namespace))), }); expect.extend({ @@ -587,6 +592,16 @@ describe('SavedObjectsRepository', () => { ); }); + it(`normalizes options.namespace from 'default' to undefined`, async () => { + await bulkCreateSuccess([obj1, obj2], { namespace: 'default' }); + const expected = expect.not.objectContaining({ namespace: 'default' }); + const body = [expect.any(Object), expected, expect.any(Object), expected]; + expect(client.bulk).toHaveBeenCalledWith( + expect.objectContaining({ body }), + expect.anything() + ); + }); + it(`doesn't add namespace to request body for any types that are not single-namespace`, async () => { const objects = [ { ...obj1, type: NAMESPACE_AGNOSTIC_TYPE }, @@ -654,19 +669,19 @@ describe('SavedObjectsRepository', () => { }); it(`prepends namespace to the id when providing namespace for single-namespace type`, async () => { - const getId = (type, id) => `${namespace}:${type}:${id}`; + const getId = (type, id) => `${namespace}:${type}:${id}`; // test that the raw document ID equals this (e.g., has a namespace prefix) await bulkCreateSuccess([obj1, obj2], { namespace }); expectClientCallArgsAction([obj1, obj2], { method: 'create', getId }); }); it(`doesn't prepend namespace to the id when providing no namespace for single-namespace type`, async () => { - const getId = (type, id) => `${type}:${id}`; + const getId = (type, id) => `${type}:${id}`; // test that the raw document ID equals this (e.g., does not have a namespace prefix) await bulkCreateSuccess([obj1, obj2]); expectClientCallArgsAction([obj1, obj2], { method: 'create', getId }); }); it(`doesn't prepend namespace to the id when not using single-namespace type`, async () => { - const getId = (type, id) => `${type}:${id}`; + const getId = (type, id) => `${type}:${id}`; // test that the raw document ID equals this (e.g., does not have a namespace prefix) const objects = [ { ...obj1, type: NAMESPACE_AGNOSTIC_TYPE }, { ...obj2, type: MULTI_NAMESPACE_TYPE }, @@ -973,19 +988,25 @@ describe('SavedObjectsRepository', () => { describe('client calls', () => { it(`prepends namespace to the id when providing namespace for single-namespace type`, async () => { - const getId = (type, id) => `${namespace}:${type}:${id}`; + const getId = (type, id) => `${namespace}:${type}:${id}`; // test that the raw document ID equals this (e.g., has a namespace prefix) await bulkGetSuccess([obj1, obj2], { namespace }); _expectClientCallArgs([obj1, obj2], { getId }); }); it(`doesn't prepend namespace to the id when providing no namespace for single-namespace type`, async () => { - const getId = (type, id) => `${type}:${id}`; + const getId = (type, id) => `${type}:${id}`; // test that the raw document ID equals this (e.g., does not have a namespace prefix) await bulkGetSuccess([obj1, obj2]); _expectClientCallArgs([obj1, obj2], { getId }); }); + it(`normalizes options.namespace from 'default' to undefined`, async () => { + const getId = (type, id) => `${type}:${id}`; // test that the raw document ID equals this (e.g., does not have a namespace prefix) + await bulkGetSuccess([obj1, obj2], { namespace: 'default' }); + _expectClientCallArgs([obj1, obj2], { getId }); + }); + it(`doesn't prepend namespace to the id when not using single-namespace type`, async () => { - const getId = (type, id) => `${type}:${id}`; + const getId = (type, id) => `${type}:${id}`; // test that the raw document ID equals this (e.g., does not have a namespace prefix) let objects = [obj1, obj2].map((obj) => ({ ...obj, type: NAMESPACE_AGNOSTIC_TYPE })); await bulkGetSuccess(objects, { namespace }); _expectClientCallArgs(objects, { getId }); @@ -1328,32 +1349,66 @@ describe('SavedObjectsRepository', () => { }); it(`prepends namespace to the id when providing namespace for single-namespace type`, async () => { - const getId = (type, id) => `${namespace}:${type}:${id}`; + const getId = (type, id) => `${namespace}:${type}:${id}`; // test that the raw document ID equals this (e.g., has a namespace prefix) await bulkUpdateSuccess([obj1, obj2], { namespace }); expectClientCallArgsAction([obj1, obj2], { method: 'update', getId }); + + jest.clearAllMocks(); + // test again with object namespace string that supersedes the operation's namespace ID + await bulkUpdateSuccess([ + { ...obj1, namespace }, + { ...obj2, namespace }, + ]); + expectClientCallArgsAction([obj1, obj2], { method: 'update', getId }); }); it(`doesn't prepend namespace to the id when providing no namespace for single-namespace type`, async () => { - const getId = (type, id) => `${type}:${id}`; + const getId = (type, id) => `${type}:${id}`; // test that the raw document ID equals this (e.g., does not have a namespace prefix) await bulkUpdateSuccess([obj1, obj2]); expectClientCallArgsAction([obj1, obj2], { method: 'update', getId }); + + jest.clearAllMocks(); + // test again with object namespace string that supersedes the operation's namespace ID + await bulkUpdateSuccess( + [ + { ...obj1, namespace: 'default' }, + { ...obj2, namespace: 'default' }, + ], + { namespace } + ); + expectClientCallArgsAction([obj1, obj2], { method: 'update', getId }); }); - it(`doesn't prepend namespace to the id when not using single-namespace type`, async () => { + it(`normalizes options.namespace from 'default' to undefined`, async () => { const getId = (type, id) => `${type}:${id}`; - const objects1 = [{ ...obj1, type: NAMESPACE_AGNOSTIC_TYPE }]; - await bulkUpdateSuccess(objects1, { namespace }); - expectClientCallArgsAction(objects1, { method: 'update', getId }); - client.bulk.mockClear(); + await bulkUpdateSuccess([obj1, obj2], { namespace: 'default' }); + expectClientCallArgsAction([obj1, obj2], { method: 'update', getId }); + }); + + it(`doesn't prepend namespace to the id when not using single-namespace type`, async () => { + const getId = (type, id) => `${type}:${id}`; // test that the raw document ID equals this (e.g., does not have a namespace prefix) const overrides = { // bulkUpdate uses a preflight `get` request for multi-namespace saved objects, and specifies that version on `update` // we aren't testing for this here, but we need to include Jest assertions so this test doesn't fail if_primary_term: expect.any(Number), if_seq_no: expect.any(Number), }; - const objects2 = [{ ...obj2, type: MULTI_NAMESPACE_TYPE }]; - await bulkUpdateSuccess(objects2, { namespace }); - expectClientCallArgsAction(objects2, { method: 'update', getId, overrides }, 2); + const _obj1 = { ...obj1, type: NAMESPACE_AGNOSTIC_TYPE }; + const _obj2 = { ...obj2, type: MULTI_NAMESPACE_TYPE }; + + await bulkUpdateSuccess([_obj1], { namespace }); + expectClientCallArgsAction([_obj1], { method: 'update', getId }); + client.bulk.mockClear(); + await bulkUpdateSuccess([_obj2], { namespace }); + expectClientCallArgsAction([_obj2], { method: 'update', getId, overrides }, 2); + + jest.clearAllMocks(); + // test again with object namespace string that supersedes the operation's namespace ID + await bulkUpdateSuccess([{ ..._obj1, namespace }]); + expectClientCallArgsAction([_obj1], { method: 'update', getId }); + client.bulk.mockClear(); + await bulkUpdateSuccess([{ ..._obj2, namespace }]); + expectClientCallArgsAction([_obj2], { method: 'update', getId, overrides }, 2); }); }); @@ -1582,19 +1637,25 @@ describe('SavedObjectsRepository', () => { }); it(`prepends namespace to the id when providing namespace for single-namespace type`, async () => { - const getId = (type, id) => `${namespace}:${type}:${id}`; + const getId = (type, id) => `${namespace}:${type}:${id}`; // test that the raw document ID equals this (e.g., has a namespace prefix) await checkConflictsSuccess([obj1, obj2], { namespace }); _expectClientCallArgs([obj1, obj2], { getId }); }); it(`doesn't prepend namespace to the id when providing no namespace for single-namespace type`, async () => { - const getId = (type, id) => `${type}:${id}`; + const getId = (type, id) => `${type}:${id}`; // test that the raw document ID equals this (e.g., does not have a namespace prefix) await checkConflictsSuccess([obj1, obj2]); _expectClientCallArgs([obj1, obj2], { getId }); }); + it(`normalizes options.namespace from 'default' to undefined`, async () => { + const getId = (type, id) => `${type}:${id}`; // test that the raw document ID equals this (e.g., does not have a namespace prefix) + await checkConflictsSuccess([obj1, obj2], { namespace: 'default' }); + _expectClientCallArgs([obj1, obj2], { getId }); + }); + it(`doesn't prepend namespace to the id when not using single-namespace type`, async () => { - const getId = (type, id) => `${type}:${id}`; + const getId = (type, id) => `${type}:${id}`; // test that the raw document ID equals this (e.g., does not have a namespace prefix) // obj3 is multi-namespace, and obj6 is namespace-agnostic await checkConflictsSuccess([obj3, obj6], { namespace }); _expectClientCallArgs([obj3, obj6], { getId }); @@ -1817,6 +1878,16 @@ describe('SavedObjectsRepository', () => { ); }); + it(`normalizes options.namespace from 'default' to undefined`, async () => { + await createSuccess(type, attributes, { id, namespace: 'default' }); + expect(client.create).toHaveBeenCalledWith( + expect.objectContaining({ + id: `${type}:${id}`, + }), + expect.anything() + ); + }); + it(`doesn't prepend namespace to the id when not using single-namespace type`, async () => { await createSuccess(NAMESPACE_AGNOSTIC_TYPE, attributes, { id, namespace }); expect(client.create).toHaveBeenCalledWith( @@ -1853,11 +1924,7 @@ describe('SavedObjectsRepository', () => { }); it(`throws when there is a conflict with an existing multi-namespace saved object (get)`, async () => { - const response = getMockGetResponse({ - type: MULTI_NAMESPACE_TYPE, - id, - namespace: 'bar-namespace', - }); + const response = getMockGetResponse({ type: MULTI_NAMESPACE_TYPE, id }, 'bar-namespace'); client.get.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise(response) ); @@ -1960,7 +2027,7 @@ describe('SavedObjectsRepository', () => { const deleteSuccess = async (type, id, options) => { if (registry.isMultiNamespace(type)) { - const mockGetResponse = getMockGetResponse({ type, id, namespace: options?.namespace }); + const mockGetResponse = getMockGetResponse({ type, id }, options?.namespace); client.get.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise(mockGetResponse) ); @@ -2036,6 +2103,14 @@ describe('SavedObjectsRepository', () => { ); }); + it(`normalizes options.namespace from 'default' to undefined`, async () => { + await deleteSuccess(type, id, { namespace: 'default' }); + expect(client.delete).toHaveBeenCalledWith( + expect.objectContaining({ id: `${type}:${id}` }), + expect.anything() + ); + }); + it(`doesn't prepend namespace to the id when not using single-namespace type`, async () => { await deleteSuccess(NAMESPACE_AGNOSTIC_TYPE, id, { namespace }); expect(client.delete).toHaveBeenCalledWith( @@ -2086,7 +2161,7 @@ describe('SavedObjectsRepository', () => { }); it(`throws when the type is multi-namespace and the document exists, but not in this namespace`, async () => { - const response = getMockGetResponse({ type: MULTI_NAMESPACE_TYPE, id, namespace }); + const response = getMockGetResponse({ type: MULTI_NAMESPACE_TYPE, id }, namespace); client.get.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise(response) ); @@ -2661,14 +2736,16 @@ describe('SavedObjectsRepository', () => { const originId = 'some-origin-id'; const getSuccess = async (type, id, options, includeOriginId) => { - const response = getMockGetResponse({ - type, - id, - namespace: options?.namespace, - // "includeOriginId" is not an option for the operation; however, if the existing saved object contains an originId attribute, the - // operation will return it in the result. This flag is just used for test purposes to modify the mock cluster call response. - ...(includeOriginId && { originId }), - }); + const response = getMockGetResponse( + { + type, + id, + // "includeOriginId" is not an option for the operation; however, if the existing saved object contains an originId attribute, the + // operation will return it in the result. This flag is just used for test purposes to modify the mock cluster call response. + ...(includeOriginId && { originId }), + }, + options?.namespace + ); client.get.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise(response) ); @@ -2703,6 +2780,16 @@ describe('SavedObjectsRepository', () => { ); }); + it(`normalizes options.namespace from 'default' to undefined`, async () => { + await getSuccess(type, id, { namespace: 'default' }); + expect(client.get).toHaveBeenCalledWith( + expect.objectContaining({ + id: `${type}:${id}`, + }), + expect.anything() + ); + }); + it(`doesn't prepend namespace to the id when not using single-namespace type`, async () => { await getSuccess(NAMESPACE_AGNOSTIC_TYPE, id, { namespace }); expect(client.get).toHaveBeenCalledWith( @@ -2757,7 +2844,7 @@ describe('SavedObjectsRepository', () => { }); it(`throws when type is multi-namespace and the document exists, but not in this namespace`, async () => { - const response = getMockGetResponse({ type: MULTI_NAMESPACE_TYPE, id, namespace }); + const response = getMockGetResponse({ type: MULTI_NAMESPACE_TYPE, id }, namespace); client.get.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise(response) ); @@ -2813,7 +2900,7 @@ describe('SavedObjectsRepository', () => { const incrementCounterSuccess = async (type, id, field, options) => { const isMultiNamespace = registry.isMultiNamespace(type); if (isMultiNamespace) { - const response = getMockGetResponse({ type, id, namespace: options?.namespace }); + const response = getMockGetResponse({ type, id }, options?.namespace); client.get.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise(response) ); @@ -2884,6 +2971,16 @@ describe('SavedObjectsRepository', () => { ); }); + it(`normalizes options.namespace from 'default' to undefined`, async () => { + await incrementCounterSuccess(type, id, field, { namespace: 'default' }); + expect(client.update).toHaveBeenCalledWith( + expect.objectContaining({ + id: `${type}:${id}`, + }), + expect.anything() + ); + }); + it(`doesn't prepend namespace to the id when not using single-namespace type`, async () => { await incrementCounterSuccess(NAMESPACE_AGNOSTIC_TYPE, id, field, { namespace }); expect(client.update).toHaveBeenCalledWith( @@ -2950,11 +3047,7 @@ describe('SavedObjectsRepository', () => { }); it(`throws when there is a conflict with an existing multi-namespace saved object (get)`, async () => { - const response = getMockGetResponse({ - type: MULTI_NAMESPACE_TYPE, - id, - namespace: 'bar-namespace', - }); + const response = getMockGetResponse({ type: MULTI_NAMESPACE_TYPE, id }, 'bar-namespace'); client.get.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise(response) ); @@ -3247,7 +3340,7 @@ describe('SavedObjectsRepository', () => { expect(client.update).not.toHaveBeenCalled(); }); - it(`throws when type is not namespace-agnostic`, async () => { + it(`throws when type is not multi-namespace`, async () => { const test = async (type) => { const message = `${type} doesn't support multiple namespaces`; await expectBadRequestError(type, id, [namespace1, namespace2], message); @@ -3389,7 +3482,7 @@ describe('SavedObjectsRepository', () => { const updateSuccess = async (type, id, attributes, options, includeOriginId) => { if (registry.isMultiNamespace(type)) { - const mockGetResponse = getMockGetResponse({ type, id, namespace: options?.namespace }); + const mockGetResponse = getMockGetResponse({ type, id }, options?.namespace); client.get.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise(mockGetResponse) ); @@ -3520,6 +3613,14 @@ describe('SavedObjectsRepository', () => { ); }); + it(`normalizes options.namespace from 'default' to undefined`, async () => { + await updateSuccess(type, id, attributes, { references, namespace: 'default' }); + expect(client.update).toHaveBeenCalledWith( + expect.objectContaining({ id: expect.stringMatching(`${type}:${id}`) }), + expect.anything() + ); + }); + it(`doesn't prepend namespace to the id when not using single-namespace type`, async () => { await updateSuccess(NAMESPACE_AGNOSTIC_TYPE, id, attributes, { namespace }); expect(client.update).toHaveBeenCalledWith( @@ -3590,7 +3691,7 @@ describe('SavedObjectsRepository', () => { }); it(`throws when type is multi-namespace and the document exists, but not in this namespace`, async () => { - const response = getMockGetResponse({ type: MULTI_NAMESPACE_TYPE, id, namespace }); + const response = getMockGetResponse({ type: MULTI_NAMESPACE_TYPE, id }, namespace); client.get.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise(response) ); diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index dd25989725f3e..125f97e7feb11 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -31,7 +31,7 @@ import { getSearchDsl } from './search_dsl'; import { includedFields } from './included_fields'; import { SavedObjectsErrorHelpers, DecoratedError } from './errors'; import { decodeRequestVersion, encodeVersion, encodeHitVersion } from '../../version'; -import { KibanaMigrator } from '../../migrations'; +import { IKibanaMigrator } from '../../migrations'; import { SavedObjectsSerializer, SavedObjectSanitizedDoc, @@ -67,6 +67,7 @@ import { } from '../../types'; import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry'; import { validateConvertFilterToKueryNode } from './filter_utils'; +import { SavedObjectsUtils } from './utils'; // BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository // so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient. @@ -85,7 +86,7 @@ export interface SavedObjectsRepositoryOptions { client: ElasticsearchClient; typeRegistry: SavedObjectTypeRegistry; serializer: SavedObjectsSerializer; - migrator: KibanaMigrator; + migrator: IKibanaMigrator; allowedTypes: string[]; } @@ -120,7 +121,7 @@ export type ISavedObjectsRepository = Pick>, options: SavedObjectsCreateOptions = {} ): Promise> { - const { namespace, overwrite = false, refresh = DEFAULT_REFRESH_SETTING } = options; + const { overwrite = false, refresh = DEFAULT_REFRESH_SETTING } = options; + const namespace = normalizeNamespace(options.namespace); const time = this._getCurrentTime(); let bulkGetRequestIndexCounter = 0; @@ -468,7 +470,7 @@ export class SavedObjectsRepository { return { errors: [] }; } - const { namespace } = options; + const namespace = normalizeNamespace(options.namespace); let bulkGetRequestIndexCounter = 0; const expectedBulkGetResults: Either[] = objects.map((object) => { @@ -551,7 +553,8 @@ export class SavedObjectsRepository { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } - const { namespace, refresh = DEFAULT_REFRESH_SETTING } = options; + const { refresh = DEFAULT_REFRESH_SETTING } = options; + const namespace = normalizeNamespace(options.namespace); const rawId = this._serializer.generateRawId(namespace, type, id); let preflightResult: SavedObjectsRawDoc | undefined; @@ -560,7 +563,7 @@ export class SavedObjectsRepository { preflightResult = await this.preflightCheckIncludesNamespace(type, id, namespace); const existingNamespaces = getSavedObjectNamespaces(undefined, preflightResult); const remainingNamespaces = existingNamespaces?.filter( - (x) => x !== getNamespaceString(namespace) + (x) => x !== SavedObjectsUtils.namespaceIdToString(namespace) ); if (remainingNamespaces?.length) { @@ -658,7 +661,7 @@ export class SavedObjectsRepository { } `, lang: 'painless', - params: { namespace: getNamespaceString(namespace) }, + params: { namespace }, }, conflicts: 'proceed', ...getSearchDsl(this._mappings, this._registry, { @@ -814,7 +817,7 @@ export class SavedObjectsRepository { objects: SavedObjectsBulkGetObject[] = [], options: SavedObjectsBaseOptions = {} ): Promise> { - const { namespace } = options; + const namespace = normalizeNamespace(options.namespace); if (objects.length === 0) { return { saved_objects: [] }; @@ -884,7 +887,9 @@ export class SavedObjectsRepository { const { originId, updated_at: updatedAt } = doc._source; let namespaces = []; if (!this._registry.isNamespaceAgnostic(type)) { - namespaces = doc._source.namespaces ?? [getNamespaceString(doc._source.namespace)]; + namespaces = doc._source.namespaces ?? [ + SavedObjectsUtils.namespaceIdToString(doc._source.namespace), + ]; } return { @@ -920,7 +925,7 @@ export class SavedObjectsRepository { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } - const { namespace } = options; + const namespace = normalizeNamespace(options.namespace); const { body, statusCode } = await this.client.get>( { @@ -941,7 +946,9 @@ export class SavedObjectsRepository { let namespaces: string[] = []; if (!this._registry.isNamespaceAgnostic(type)) { - namespaces = body._source.namespaces ?? [getNamespaceString(body._source.namespace)]; + namespaces = body._source.namespaces ?? [ + SavedObjectsUtils.namespaceIdToString(body._source.namespace), + ]; } return { @@ -978,7 +985,8 @@ export class SavedObjectsRepository { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } - const { version, namespace, references, refresh = DEFAULT_REFRESH_SETTING } = options; + const { version, references, refresh = DEFAULT_REFRESH_SETTING } = options; + const namespace = normalizeNamespace(options.namespace); let preflightResult: SavedObjectsRawDoc | undefined; if (this._registry.isMultiNamespace(type)) { @@ -1016,7 +1024,9 @@ export class SavedObjectsRepository { const { originId } = body.get._source; let namespaces = []; if (!this._registry.isNamespaceAgnostic(type)) { - namespaces = body.get._source.namespaces ?? [getNamespaceString(body.get._source.namespace)]; + namespaces = body.get._source.namespaces ?? [ + SavedObjectsUtils.namespaceIdToString(body.get._source.namespace), + ]; } return { @@ -1060,6 +1070,7 @@ export class SavedObjectsRepository { } const { version, namespace, refresh = DEFAULT_REFRESH_SETTING } = options; + // we do not need to normalize the namespace to its ID format, since it will be converted to a namespace string before being used const rawId = this._serializer.generateRawId(undefined, type, id); const preflightResult = await this.preflightCheckIncludesNamespace(type, id, namespace); @@ -1122,6 +1133,7 @@ export class SavedObjectsRepository { } const { namespace, refresh = DEFAULT_REFRESH_SETTING } = options; + // we do not need to normalize the namespace to its ID format, since it will be converted to a namespace string before being used const rawId = this._serializer.generateRawId(undefined, type, id); const preflightResult = await this.preflightCheckIncludesNamespace(type, id, namespace); @@ -1208,7 +1220,7 @@ export class SavedObjectsRepository { options: SavedObjectsBulkUpdateOptions = {} ): Promise> { const time = this._getCurrentTime(); - const { namespace } = options; + const namespace = normalizeNamespace(options.namespace); let bulkGetRequestIndexCounter = 0; const expectedBulkGetResults: Either[] = objects.map((object) => { @@ -1225,7 +1237,9 @@ export class SavedObjectsRepository { }; } - const { attributes, references, version } = object; + const { attributes, references, version, namespace: objectNamespace } = object; + // `objectNamespace` is a namespace string, while `namespace` is a namespace ID. + // The object namespace string, if defined, will supersede the operation's namespace ID. const documentToSave = { [type]: attributes, @@ -1242,16 +1256,24 @@ export class SavedObjectsRepository { id, version, documentToSave, + objectNamespace, ...(requiresNamespacesCheck && { esRequestIndex: bulkGetRequestIndexCounter++ }), }, }; }); + const getNamespaceId = (objectNamespace?: string) => + objectNamespace !== undefined + ? SavedObjectsUtils.namespaceStringToId(objectNamespace) + : namespace; + const getNamespaceString = (objectNamespace?: string) => + objectNamespace ?? SavedObjectsUtils.namespaceIdToString(namespace); + const bulkGetDocs = expectedBulkGetResults .filter(isRight) .filter(({ value }) => value.esRequestIndex !== undefined) - .map(({ value: { type, id } }) => ({ - _id: this._serializer.generateRawId(namespace, type, id), + .map(({ value: { type, id, objectNamespace } }) => ({ + _id: this._serializer.generateRawId(getNamespaceId(objectNamespace), type, id), _index: this.getIndexForType(type), _source: ['type', 'namespaces'], })); @@ -1276,14 +1298,25 @@ export class SavedObjectsRepository { return expectedBulkGetResult; } - const { esRequestIndex, id, type, version, documentToSave } = expectedBulkGetResult.value; + const { + esRequestIndex, + id, + type, + version, + documentToSave, + objectNamespace, + } = expectedBulkGetResult.value; + let namespaces; let versionProperties; if (esRequestIndex !== undefined) { const indexFound = bulkGetResponse?.statusCode !== 404; const actualResult = indexFound ? bulkGetResponse?.body.docs[esRequestIndex] : undefined; const docFound = indexFound && actualResult.found === true; - if (!docFound || !this.rawDocExistsInNamespace(actualResult, namespace)) { + if ( + !docFound || + !this.rawDocExistsInNamespace(actualResult, getNamespaceId(objectNamespace)) + ) { return { tag: 'Left' as 'Left', error: { @@ -1294,12 +1327,13 @@ export class SavedObjectsRepository { }; } namespaces = actualResult._source.namespaces ?? [ - getNamespaceString(actualResult._source.namespace), + SavedObjectsUtils.namespaceIdToString(actualResult._source.namespace), ]; versionProperties = getExpectedVersionProperties(version, actualResult); } else { if (this._registry.isSingleNamespace(type)) { - namespaces = [getNamespaceString(namespace)]; + // if `objectNamespace` is undefined, fall back to `options.namespace` + namespaces = [getNamespaceString(objectNamespace)]; } versionProperties = getExpectedVersionProperties(version); } @@ -1315,7 +1349,7 @@ export class SavedObjectsRepository { bulkUpdateParams.push( { update: { - _id: this._serializer.generateRawId(namespace, type, id), + _id: this._serializer.generateRawId(getNamespaceId(objectNamespace), type, id), _index: this.getIndexForType(type), ...versionProperties, }, @@ -1401,7 +1435,8 @@ export class SavedObjectsRepository { throw SavedObjectsErrorHelpers.createUnsupportedTypeError(type); } - const { migrationVersion, namespace, refresh = DEFAULT_REFRESH_SETTING } = options; + const { migrationVersion, refresh = DEFAULT_REFRESH_SETTING } = options; + const namespace = normalizeNamespace(options.namespace); const time = this._getCurrentTime(); let savedObjectNamespace; @@ -1495,7 +1530,7 @@ export class SavedObjectsRepository { const savedObject = this._serializer.rawToSavedObject(raw); const { namespace, type } = savedObject; if (this._registry.isSingleNamespace(type)) { - savedObject.namespaces = [getNamespaceString(namespace)]; + savedObject.namespaces = [SavedObjectsUtils.namespaceIdToString(namespace)]; } return omit(savedObject, 'namespace') as SavedObject; } @@ -1518,7 +1553,7 @@ export class SavedObjectsRepository { } const namespaces = raw._source.namespaces; - return namespaces?.includes(getNamespaceString(namespace)) ?? false; + return namespaces?.includes(SavedObjectsUtils.namespaceIdToString(namespace)) ?? false; } /** @@ -1623,14 +1658,6 @@ function getExpectedVersionProperties(version?: string, document?: SavedObjectsR return {}; } -/** - * Returns the string representation of a namespace. - * The default namespace is undefined, and is represented by the string 'default'. - */ -function getNamespaceString(namespace?: string) { - return namespace ?? 'default'; -} - /** * Returns a string array of namespaces for a given saved object. If the saved object is undefined, the result is an array that contains the * current namespace. Value may be undefined if an existing saved object has no namespaces attribute; this should not happen in normal @@ -1646,9 +1673,16 @@ function getSavedObjectNamespaces( if (document) { return document._source?.namespaces; } - return [getNamespaceString(namespace)]; + return [SavedObjectsUtils.namespaceIdToString(namespace)]; } +/** + * Ensure that a namespace is always in its namespace ID representation. + * This allows `'default'` to be used interchangeably with `undefined`. + */ +const normalizeNamespace = (namespace?: string) => + namespace === undefined ? namespace : SavedObjectsUtils.namespaceStringToId(namespace); + /** * Extracts the contents of a decorated error to return the attributes for bulk operations. */ diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts index ad1a08187dc32..3ff72a86c2f89 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts @@ -21,6 +21,7 @@ import { esKuery, KueryNode } from '../../../../../../plugins/data/server'; import { getRootPropertiesObjects, IndexMapping } from '../../../mappings'; import { ISavedObjectTypeRegistry } from '../../../saved_objects_type_registry'; +import { DEFAULT_NAMESPACE_STRING } from '../utils'; /** * Gets the types based on the type. Uses mappings to support @@ -73,7 +74,7 @@ function getFieldsForTypes( */ function getClauseForType( registry: ISavedObjectTypeRegistry, - namespaces: string[] = ['default'], + namespaces: string[] = [DEFAULT_NAMESPACE_STRING], type: string ) { if (namespaces.length === 0) { @@ -88,11 +89,11 @@ function getClauseForType( }; } else if (registry.isSingleNamespace(type)) { const should: Array> = []; - const eligibleNamespaces = namespaces.filter((namespace) => namespace !== 'default'); + const eligibleNamespaces = namespaces.filter((x) => x !== DEFAULT_NAMESPACE_STRING); if (eligibleNamespaces.length > 0) { should.push({ terms: { namespace: eligibleNamespaces } }); } - if (namespaces.includes('default')) { + if (namespaces.includes(DEFAULT_NAMESPACE_STRING)) { should.push({ bool: { must_not: [{ exists: { field: 'namespace' } }] } }); } if (should.length === 0) { @@ -162,9 +163,7 @@ export function getQueryParams({ // would result in no results being returned, as the wildcard is treated as a literal, and not _actually_ as a wildcard. // We had a good discussion around the tradeoffs here: https://github.com/elastic/kibana/pull/67644#discussion_r441055716 const normalizedNamespaces = namespaces - ? Array.from( - new Set(namespaces.map((namespace) => (namespace === '*' ? 'default' : namespace))) - ) + ? Array.from(new Set(namespaces.map((x) => (x === '*' ? DEFAULT_NAMESPACE_STRING : x)))) : undefined; const bool: any = { diff --git a/src/core/server/saved_objects/service/lib/utils.test.ts b/src/core/server/saved_objects/service/lib/utils.test.ts new file mode 100644 index 0000000000000..ea4fa68242bea --- /dev/null +++ b/src/core/server/saved_objects/service/lib/utils.test.ts @@ -0,0 +1,57 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SavedObjectsUtils } from './utils'; + +describe('SavedObjectsUtils', () => { + const { namespaceIdToString, namespaceStringToId } = SavedObjectsUtils; + + describe('#namespaceIdToString', () => { + it('converts `undefined` to default namespace string', () => { + expect(namespaceIdToString(undefined)).toEqual('default'); + }); + + it('leaves other namespace IDs as-is', () => { + expect(namespaceIdToString('foo')).toEqual('foo'); + }); + + it('throws an error when a namespace ID is an empty string', () => { + expect(() => namespaceIdToString('')).toThrowError('namespace cannot be an empty string'); + }); + }); + + describe('#namespaceStringToId', () => { + it('converts default namespace string to `undefined`', () => { + expect(namespaceStringToId('default')).toBeUndefined(); + }); + + it('leaves other namespace strings as-is', () => { + expect(namespaceStringToId('foo')).toEqual('foo'); + }); + + it('throws an error when a namespace string is falsy', () => { + const test = (arg: any) => + expect(() => namespaceStringToId(arg)).toThrowError('namespace must be a non-empty string'); + + test(undefined); + test(null); + test(''); + }); + }); +}); diff --git a/src/core/server/saved_objects/service/lib/utils.ts b/src/core/server/saved_objects/service/lib/utils.ts new file mode 100644 index 0000000000000..6101ad57cc401 --- /dev/null +++ b/src/core/server/saved_objects/service/lib/utils.ts @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const DEFAULT_NAMESPACE_STRING = 'default'; + +/** + * @public + */ +export class SavedObjectsUtils { + /** + * Converts a given saved object namespace ID to its string representation. All namespace IDs have an identical string representation, with + * the exception of the `undefined` namespace ID (which has a namespace string of `'default'`). + * + * @param namespace The namespace ID, which must be either a non-empty string or `undefined`. + */ + public static namespaceIdToString = (namespace?: string) => { + if (namespace === '') { + throw new TypeError('namespace cannot be an empty string'); + } + + return namespace ?? DEFAULT_NAMESPACE_STRING; + }; + + /** + * Converts a given saved object namespace string to its ID representation. All namespace strings have an identical ID representation, with + * the exception of the `'default'` namespace string (which has a namespace ID of `undefined`). + * + * @param namespace The namespace string, which must be non-empty. + */ + public static namespaceStringToId = (namespace: string) => { + if (!namespace) { + throw new TypeError('namespace must be a non-empty string'); + } + + return namespace !== DEFAULT_NAMESPACE_STRING ? namespace : undefined; + }; +} diff --git a/src/core/server/saved_objects/service/saved_objects_client.ts b/src/core/server/saved_objects/service/saved_objects_client.ts index 347c760f841bc..8c96116de49cb 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.ts +++ b/src/core/server/saved_objects/service/saved_objects_client.ts @@ -80,6 +80,13 @@ export interface SavedObjectsBulkUpdateObject type: string; /** {@inheritdoc SavedObjectAttributes} */ attributes: Partial; + /** + * Optional namespace string to use when searching for this object. If this is defined, it will supersede the namespace ID that is in + * {@link SavedObjectsBulkUpdateOptions}. + * + * Note: the default namespace's string representation is `'default'`, and its ID representation is `undefined`. + **/ + namespace?: string; } /** diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts index 000153cd542fa..50c118ca64ffb 100644 --- a/src/core/server/saved_objects/types.ts +++ b/src/core/server/saved_objects/types.ts @@ -18,9 +18,8 @@ */ import { SavedObjectsClient } from './service/saved_objects_client'; -import { SavedObjectsTypeMappingDefinition, SavedObjectsTypeMappingDefinitions } from './mappings'; +import { SavedObjectsTypeMappingDefinition } from './mappings'; import { SavedObjectMigrationMap } from './migrations'; -import { PropertyValidators } from './validation'; export { SavedObjectsImportResponse, @@ -34,9 +33,6 @@ export { SavedObjectsImportRetry, } from './import/types'; -import { LegacyConfig } from '../legacy'; -import { SavedObjectUnsanitizedDoc } from './serialization'; -import { SavedObjectsMigrationLogger } from './migrations/core/migration_logger'; import { SavedObject } from '../../types'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths @@ -269,92 +265,3 @@ export interface SavedObjectsTypeManagementDefinition { */ getInAppUrl?: (savedObject: SavedObject) => { path: string; uiCapabilitiesPath: string }; } - -/** - * @internal - * @deprecated - */ -export interface SavedObjectsLegacyUiExports { - savedObjectMappings: SavedObjectsLegacyMapping[]; - savedObjectMigrations: SavedObjectsLegacyMigrationDefinitions; - savedObjectSchemas: SavedObjectsLegacySchemaDefinitions; - savedObjectValidations: PropertyValidators; - savedObjectsManagement: SavedObjectsLegacyManagementDefinition; -} - -/** - * @internal - * @deprecated - */ -export interface SavedObjectsLegacyMapping { - pluginId: string; - properties: SavedObjectsTypeMappingDefinitions; -} - -/** - * @internal - * @deprecated Use {@link SavedObjectsTypeManagementDefinition | management definition} when registering - * from new platform plugins - */ -export interface SavedObjectsLegacyManagementDefinition { - [key: string]: SavedObjectsLegacyManagementTypeDefinition; -} - -/** - * @internal - * @deprecated - */ -export interface SavedObjectsLegacyManagementTypeDefinition { - isImportableAndExportable?: boolean; - defaultSearchField?: string; - icon?: string; - getTitle?: (savedObject: SavedObject) => string; - getEditUrl?: (savedObject: SavedObject) => string; - getInAppUrl?: (savedObject: SavedObject) => { path: string; uiCapabilitiesPath: string }; -} - -/** - * @internal - * @deprecated - */ -export interface SavedObjectsLegacyMigrationDefinitions { - [type: string]: SavedObjectLegacyMigrationMap; -} - -/** - * @internal - * @deprecated - */ -export interface SavedObjectLegacyMigrationMap { - [version: string]: SavedObjectLegacyMigrationFn; -} - -/** - * @internal - * @deprecated - */ -export type SavedObjectLegacyMigrationFn = ( - doc: SavedObjectUnsanitizedDoc, - log: SavedObjectsMigrationLogger -) => SavedObjectUnsanitizedDoc; - -/** - * @internal - * @deprecated - */ -interface SavedObjectsLegacyTypeSchema { - isNamespaceAgnostic?: boolean; - /** Cannot be used in conjunction with `isNamespaceAgnostic` */ - multiNamespace?: boolean; - hidden?: boolean; - indexPattern?: ((config: LegacyConfig) => string) | string; - convertToAliasScript?: string; -} - -/** - * @internal - * @deprecated - */ -export interface SavedObjectsLegacySchemaDefinitions { - [type: string]: SavedObjectsLegacyTypeSchema; -} diff --git a/src/core/server/saved_objects/utils.test.ts b/src/core/server/saved_objects/utils.test.ts deleted file mode 100644 index 21229bee489c2..0000000000000 --- a/src/core/server/saved_objects/utils.test.ts +++ /dev/null @@ -1,445 +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 { legacyServiceMock } from '../legacy/legacy_service.mock'; -import { convertLegacyTypes, convertTypesToLegacySchema } from './utils'; -import { SavedObjectsLegacyUiExports, SavedObjectsType } from './types'; -import { LegacyConfig, SavedObjectMigrationContext } from 'kibana/server'; -import { SavedObjectUnsanitizedDoc } from './serialization'; - -describe('convertLegacyTypes', () => { - let legacyConfig: ReturnType; - - beforeEach(() => { - legacyConfig = legacyServiceMock.createLegacyConfig(); - }); - - it('converts the legacy mappings using default values if no schemas are specified', () => { - const uiExports: SavedObjectsLegacyUiExports = { - savedObjectMappings: [ - { - pluginId: 'pluginA', - properties: { - typeA: { - properties: { - fieldA: { type: 'text' }, - }, - }, - typeB: { - properties: { - fieldB: { type: 'text' }, - }, - }, - }, - }, - { - pluginId: 'pluginB', - properties: { - typeC: { - properties: { - fieldC: { type: 'text' }, - }, - }, - }, - }, - ], - savedObjectMigrations: {}, - savedObjectSchemas: {}, - savedObjectValidations: {}, - savedObjectsManagement: {}, - }; - - const converted = convertLegacyTypes(uiExports, legacyConfig); - expect(converted).toMatchSnapshot(); - }); - - it('merges the mappings and the schema to create the type when schema exists for the type', () => { - const uiExports: SavedObjectsLegacyUiExports = { - savedObjectMappings: [ - { - pluginId: 'pluginA', - properties: { - typeA: { - properties: { - fieldA: { type: 'text' }, - }, - }, - }, - }, - { - pluginId: 'pluginB', - properties: { - typeB: { - properties: { - fieldB: { type: 'text' }, - }, - }, - }, - }, - { - pluginId: 'pluginC', - properties: { - typeC: { - properties: { - fieldC: { type: 'text' }, - }, - }, - }, - }, - { - pluginId: 'pluginD', - properties: { - typeD: { - properties: { - fieldD: { type: 'text' }, - }, - }, - }, - }, - ], - savedObjectMigrations: {}, - savedObjectSchemas: { - typeA: { - indexPattern: 'fooBar', - hidden: true, - isNamespaceAgnostic: true, - }, - typeB: { - indexPattern: 'barBaz', - hidden: false, - multiNamespace: true, - }, - typeD: { - indexPattern: 'bazQux', - hidden: false, - // if both isNamespaceAgnostic and multiNamespace are true, the resulting namespaceType is 'agnostic' - isNamespaceAgnostic: true, - multiNamespace: true, - }, - }, - savedObjectValidations: {}, - savedObjectsManagement: {}, - }; - - const converted = convertLegacyTypes(uiExports, legacyConfig); - expect(converted).toMatchSnapshot(); - }); - - it('invokes indexPattern to retrieve the index when it is a function', () => { - const indexPatternAccessor: (config: LegacyConfig) => string = jest.fn((config) => { - config.get('foo.bar'); - return 'myIndex'; - }); - - const uiExports: SavedObjectsLegacyUiExports = { - savedObjectMappings: [ - { - pluginId: 'pluginA', - properties: { - typeA: { - properties: { - fieldA: { type: 'text' }, - }, - }, - }, - }, - ], - savedObjectMigrations: {}, - savedObjectSchemas: { - typeA: { - indexPattern: indexPatternAccessor, - hidden: true, - isNamespaceAgnostic: true, - }, - }, - savedObjectValidations: {}, - savedObjectsManagement: {}, - }; - - const converted = convertLegacyTypes(uiExports, legacyConfig); - - expect(indexPatternAccessor).toHaveBeenCalledWith(legacyConfig); - expect(legacyConfig.get).toHaveBeenCalledWith('foo.bar'); - expect(converted.length).toEqual(1); - expect(converted[0].indexPattern).toEqual('myIndex'); - }); - - it('import migrations from the uiExports', () => { - const migrationsA = { - '1.0.0': jest.fn(), - '2.0.4': jest.fn(), - }; - const migrationsB = { - '1.5.3': jest.fn(), - }; - - const uiExports: SavedObjectsLegacyUiExports = { - savedObjectMappings: [ - { - pluginId: 'pluginA', - properties: { - typeA: { - properties: { - fieldA: { type: 'text' }, - }, - }, - }, - }, - { - pluginId: 'pluginB', - properties: { - typeB: { - properties: { - fieldC: { type: 'text' }, - }, - }, - }, - }, - ], - savedObjectMigrations: { - typeA: migrationsA, - typeB: migrationsB, - }, - savedObjectSchemas: {}, - savedObjectValidations: {}, - savedObjectsManagement: {}, - }; - - const converted = convertLegacyTypes(uiExports, legacyConfig); - expect(converted.length).toEqual(2); - expect(Object.keys(converted[0]!.migrations!)).toEqual(Object.keys(migrationsA)); - expect(Object.keys(converted[1]!.migrations!)).toEqual(Object.keys(migrationsB)); - }); - - it('converts the migration to the new format', () => { - const legacyMigration = jest.fn(); - const migrationsA = { - '1.0.0': legacyMigration, - }; - - const uiExports: SavedObjectsLegacyUiExports = { - savedObjectMappings: [ - { - pluginId: 'pluginA', - properties: { - typeA: { - properties: { - fieldA: { type: 'text' }, - }, - }, - }, - }, - ], - savedObjectMigrations: { - typeA: migrationsA, - }, - savedObjectSchemas: {}, - savedObjectValidations: {}, - savedObjectsManagement: {}, - }; - - const converted = convertLegacyTypes(uiExports, legacyConfig); - expect(Object.keys(converted[0]!.migrations!)).toEqual(['1.0.0']); - - const migration = converted[0]!.migrations!['1.0.0']!; - - const doc = {} as SavedObjectUnsanitizedDoc; - const context = { log: {} } as SavedObjectMigrationContext; - migration(doc, context); - - expect(legacyMigration).toHaveBeenCalledTimes(1); - expect(legacyMigration).toHaveBeenCalledWith(doc, context.log); - }); - - it('imports type management information', () => { - const uiExports: SavedObjectsLegacyUiExports = { - savedObjectMappings: [ - { - pluginId: 'pluginA', - properties: { - typeA: { - properties: { - fieldA: { type: 'text' }, - }, - }, - }, - }, - { - pluginId: 'pluginB', - properties: { - typeB: { - properties: { - fieldB: { type: 'text' }, - }, - }, - typeC: { - properties: { - fieldC: { type: 'text' }, - }, - }, - }, - }, - ], - savedObjectsManagement: { - typeA: { - isImportableAndExportable: true, - icon: 'iconA', - defaultSearchField: 'searchFieldA', - getTitle: (savedObject) => savedObject.id, - }, - typeB: { - isImportableAndExportable: false, - icon: 'iconB', - getEditUrl: (savedObject) => `/some-url/${savedObject.id}`, - getInAppUrl: (savedObject) => ({ path: 'path', uiCapabilitiesPath: 'ui-path' }), - }, - }, - savedObjectMigrations: {}, - savedObjectSchemas: {}, - savedObjectValidations: {}, - }; - - const converted = convertLegacyTypes(uiExports, legacyConfig); - expect(converted.length).toEqual(3); - const [typeA, typeB, typeC] = converted; - - expect(typeA.management).toEqual({ - importableAndExportable: true, - icon: 'iconA', - defaultSearchField: 'searchFieldA', - getTitle: uiExports.savedObjectsManagement.typeA.getTitle, - }); - - expect(typeB.management).toEqual({ - importableAndExportable: false, - icon: 'iconB', - getEditUrl: uiExports.savedObjectsManagement.typeB.getEditUrl, - getInAppUrl: uiExports.savedObjectsManagement.typeB.getInAppUrl, - }); - - expect(typeC.management).toBeUndefined(); - }); - - it('merges everything when all are present', () => { - const uiExports: SavedObjectsLegacyUiExports = { - savedObjectMappings: [ - { - pluginId: 'pluginA', - properties: { - typeA: { - properties: { - fieldA: { type: 'text' }, - }, - }, - typeB: { - properties: { - fieldB: { type: 'text' }, - anotherFieldB: { type: 'boolean' }, - }, - }, - }, - }, - { - pluginId: 'pluginB', - properties: { - typeC: { - properties: { - fieldC: { type: 'text' }, - }, - }, - }, - }, - ], - savedObjectMigrations: { - typeA: { - '1.0.0': jest.fn(), - '2.0.4': jest.fn(), - }, - typeC: { - '1.5.3': jest.fn(), - }, - }, - savedObjectSchemas: { - typeA: { - indexPattern: jest.fn((config) => { - config.get('foo.bar'); - return 'myIndex'; - }), - hidden: true, - isNamespaceAgnostic: true, - }, - typeB: { - convertToAliasScript: 'some alias script', - hidden: false, - }, - }, - savedObjectValidations: {}, - savedObjectsManagement: {}, - }; - - const converted = convertLegacyTypes(uiExports, legacyConfig); - expect(converted).toMatchSnapshot(); - }); -}); - -describe('convertTypesToLegacySchema', () => { - it('converts types to the legacy schema format', () => { - const types: SavedObjectsType[] = [ - { - name: 'typeA', - hidden: false, - namespaceType: 'agnostic', - mappings: { properties: {} }, - convertToAliasScript: 'some script', - }, - { - name: 'typeB', - hidden: true, - namespaceType: 'single', - indexPattern: 'myIndex', - mappings: { properties: {} }, - }, - { - name: 'typeC', - hidden: false, - namespaceType: 'multiple', - mappings: { properties: {} }, - }, - ]; - expect(convertTypesToLegacySchema(types)).toEqual({ - typeA: { - hidden: false, - isNamespaceAgnostic: true, - multiNamespace: false, - convertToAliasScript: 'some script', - }, - typeB: { - hidden: true, - isNamespaceAgnostic: false, - multiNamespace: false, - indexPattern: 'myIndex', - }, - typeC: { - hidden: false, - isNamespaceAgnostic: false, - multiNamespace: true, - }, - }); - }); -}); diff --git a/src/core/server/saved_objects/utils.ts b/src/core/server/saved_objects/utils.ts deleted file mode 100644 index af7c08d1fbfcc..0000000000000 --- a/src/core/server/saved_objects/utils.ts +++ /dev/null @@ -1,117 +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 { LegacyConfig } from '../legacy'; -import { SavedObjectMigrationMap } from './migrations'; -import { - SavedObjectsNamespaceType, - SavedObjectsType, - SavedObjectsLegacyUiExports, - SavedObjectLegacyMigrationMap, - SavedObjectsLegacyManagementTypeDefinition, - SavedObjectsTypeManagementDefinition, -} from './types'; -import { SavedObjectsSchemaDefinition } from './schema'; - -/** - * Converts the legacy savedObjects mappings, schema, and migrations - * to actual {@link SavedObjectsType | saved object types} - */ -export const convertLegacyTypes = ( - { - savedObjectMappings = [], - savedObjectMigrations = {}, - savedObjectSchemas = {}, - savedObjectsManagement = {}, - }: SavedObjectsLegacyUiExports, - legacyConfig: LegacyConfig -): SavedObjectsType[] => { - return savedObjectMappings.reduce((types, { properties }) => { - return [ - ...types, - ...Object.entries(properties).map(([type, mappings]) => { - const schema = savedObjectSchemas[type]; - const migrations = savedObjectMigrations[type]; - const management = savedObjectsManagement[type]; - const namespaceType = (schema?.isNamespaceAgnostic - ? 'agnostic' - : schema?.multiNamespace - ? 'multiple' - : 'single') as SavedObjectsNamespaceType; - return { - name: type, - hidden: schema?.hidden ?? false, - namespaceType, - mappings, - indexPattern: - typeof schema?.indexPattern === 'function' - ? schema.indexPattern(legacyConfig) - : schema?.indexPattern, - convertToAliasScript: schema?.convertToAliasScript, - migrations: convertLegacyMigrations(migrations ?? {}), - management: management ? convertLegacyTypeManagement(management) : undefined, - }; - }), - ]; - }, [] as SavedObjectsType[]); -}; - -/** - * Convert {@link SavedObjectsType | saved object types} to the legacy {@link SavedObjectsSchemaDefinition | schema} format - */ -export const convertTypesToLegacySchema = ( - types: SavedObjectsType[] -): SavedObjectsSchemaDefinition => { - return types.reduce((schema, type) => { - return { - ...schema, - [type.name]: { - isNamespaceAgnostic: type.namespaceType === 'agnostic', - multiNamespace: type.namespaceType === 'multiple', - hidden: type.hidden, - indexPattern: type.indexPattern, - convertToAliasScript: type.convertToAliasScript, - }, - }; - }, {} as SavedObjectsSchemaDefinition); -}; - -const convertLegacyMigrations = ( - legacyMigrations: SavedObjectLegacyMigrationMap -): SavedObjectMigrationMap => { - return Object.entries(legacyMigrations).reduce((migrated, [version, migrationFn]) => { - return { - ...migrated, - [version]: (doc, context) => migrationFn(doc, context.log), - }; - }, {} as SavedObjectMigrationMap); -}; - -const convertLegacyTypeManagement = ( - legacyTypeManagement: SavedObjectsLegacyManagementTypeDefinition -): SavedObjectsTypeManagementDefinition => { - return { - importableAndExportable: legacyTypeManagement.isImportableAndExportable, - defaultSearchField: legacyTypeManagement.defaultSearchField, - icon: legacyTypeManagement.icon, - getTitle: legacyTypeManagement.getTitle, - getEditUrl: legacyTypeManagement.getEditUrl, - getInAppUrl: legacyTypeManagement.getInAppUrl, - }; -}; diff --git a/src/core/server/saved_objects/validation/index.ts b/src/core/server/saved_objects/validation/index.ts deleted file mode 100644 index b1b33f91d3fd4..0000000000000 --- a/src/core/server/saved_objects/validation/index.ts +++ /dev/null @@ -1,67 +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. - */ - -/* - * This is the core logic for validating saved object properties. The saved object client - * and migrations consume this in order to validate saved object documents prior to - * persisting them. - */ - -interface SavedObjectDoc { - type: string; - [prop: string]: any; -} - -/** - * A dictionary of property name -> validation function. The property name - * is generally the document's type (e.g. "dashboard"), but will also - * match other properties. - * - * For example, the "acl" and "dashboard" validators both apply to the - * following saved object: { type: "dashboard", attributes: {}, acl: "sdlaj3w" } - * - * @export - * @interface Validators - */ -export interface PropertyValidators { - [prop: string]: ValidateDoc; -} - -export type ValidateDoc = (doc: SavedObjectDoc) => void; - -/** - * Creates a function which uses a dictionary of property validators to validate - * individual saved object documents. - * - * @export - * @param {Validators} validators - * @param {SavedObjectDoc} doc - */ -export function docValidator(validators: PropertyValidators = {}): ValidateDoc { - return function validateDoc(doc: SavedObjectDoc) { - Object.keys(doc) - .concat(doc.type) - .forEach((prop) => { - const validator = validators[prop]; - if (validator) { - validator(doc); - } - }); - }; -} diff --git a/src/core/server/saved_objects/validation/readme.md b/src/core/server/saved_objects/validation/readme.md deleted file mode 100644 index 3b9f17c37fd0b..0000000000000 --- a/src/core/server/saved_objects/validation/readme.md +++ /dev/null @@ -1,63 +0,0 @@ -# Saved Object Validations - -The saved object client supports validation of documents during create / bulkCreate operations. - -This allows us tighter control over what documents get written to the saved object index, and helps us keep the index in a healthy state. - -## Creating validations - -Plugin authors can write their own validations by adding a `validations` property to their uiExports. A validation is nothing more than a dictionary of `{[prop: string]: validationFunction}` where: - -* `prop` - a root-property on a saved object document -* `validationFunction` - a function that takes a document and throws an error if it does not meet expectations. - -## Example - -```js -// In myFanciPlugin... -uiExports: { - validations: { - myProperty(doc) { - if (doc.attributes.someField === undefined) { - throw new Error(`Document ${doc.id} did not define "someField"`); - } - }, - - someOtherProp(doc) { - if (doc.attributes.counter < 0) { - throw new Error(`Document ${doc.id} cannot have a negative counter.`); - } - }, - }, -}, -``` - -In this example, `myFanciPlugin` defines validations for two properties: `myProperty` and `someOtherProp`. - -This means that no other plugin can define validations for myProperty or someOtherProp. - -The `myProperty` validation would run for any doc that has a `type="myProperty"` or for any doc that has a root-level property of `myProperty`. e.g. it would apply to all documents in the following array: - -```js -[ - { - type: 'foo', - attributes: { stuff: 'here' }, - myProperty: 'shazm!', - }, - { - type: 'myProperty', - attributes: { shazm: true }, - }, -]; -``` - -Validating properties other than just 'type' allows us to support potential future saved object scenarios in which plugins might want to annotate other plugin documents, such as a security plugin adding an acl to another document: - -```js -{ - type: 'dashboard', - attributes: { stuff: 'here' }, - acl: '342343', -} -``` diff --git a/src/core/server/saved_objects/validation/validation.test.ts b/src/core/server/saved_objects/validation/validation.test.ts deleted file mode 100644 index 71e220280ba5f..0000000000000 --- a/src/core/server/saved_objects/validation/validation.test.ts +++ /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 { docValidator } from './index'; - -describe('docValidator', () => { - test('does not run validators that have no application to the doc', () => { - const validators = { - foo: () => { - throw new Error('Boom!'); - }, - }; - expect(() => docValidator(validators)({ type: 'shoo', bar: 'hi' })).not.toThrow(); - }); - - test('validates the doc type', () => { - const validators = { - foo: () => { - throw new Error('Boom!'); - }, - }; - expect(() => docValidator(validators)({ type: 'foo' })).toThrow(/Boom!/); - }); - - test('validates various props', () => { - const validators = { - a: jest.fn(), - b: jest.fn(), - c: jest.fn(), - }; - docValidator(validators)({ type: 'a', b: 'foo' }); - - expect(validators.c).not.toHaveBeenCalled(); - - expect(validators.a.mock.calls).toEqual([[{ type: 'a', b: 'foo' }]]); - expect(validators.b.mock.calls).toEqual([[{ type: 'a', b: 'foo' }]]); - }); -}); diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 05afad5a4f7a4..ec457704e89c7 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -40,6 +40,7 @@ import { DeleteScriptParams } from 'elasticsearch'; import { DeleteTemplateParams } from 'elasticsearch'; import { DetailedPeerCertificate } from 'tls'; import { Duration } from 'moment'; +import { ErrorToastOptions } from 'src/core/public/notifications'; import { ExistsParams } from 'elasticsearch'; import { ExplainParams } from 'elasticsearch'; import { FieldStatsParams } from 'elasticsearch'; @@ -118,6 +119,7 @@ import { RenderSearchTemplateParams } from 'elasticsearch'; import { Request } from 'hapi'; import { ResponseObject } from 'hapi'; import { ResponseToolkit } from 'hapi'; +import { SavedObject as SavedObject_2 } from 'src/core/server'; import { SchemaTypeError } from '@kbn/config-schema'; import { ScrollParams } from 'elasticsearch'; import { SearchParams } from 'elasticsearch'; @@ -141,6 +143,7 @@ import { TasksCancelParams } from 'elasticsearch'; import { TasksGetParams } from 'elasticsearch'; import { TasksListParams } from 'elasticsearch'; import { TermvectorsParams } from 'elasticsearch'; +import { ToastInputFields } from 'src/core/public/notifications'; import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; @@ -150,10 +153,12 @@ import { UpdateDocumentByQueryParams } from 'elasticsearch'; import { UpdateDocumentParams } from 'elasticsearch'; import { Url } from 'url'; -// Warning: (ae-forgotten-export) The symbol "appendersSchema" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "ConsoleAppenderConfig" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "FileAppenderConfig" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "LegacyAppenderConfig" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export type AppenderConfigType = TypeOf; +export type AppenderConfigType = ConsoleAppenderConfig | FileAppenderConfig | LegacyAppenderConfig; // @public export function assertNever(x: never): never; @@ -322,108 +327,45 @@ export type CapabilitiesSwitcher = (request: KibanaRequest, uiCapabilities: Capa export const config: { elasticsearch: { schema: import("@kbn/config-schema").ObjectType<{ - sniffOnStart: import("@kbn/config-schema").Type; - sniffInterval: import("@kbn/config-schema").Type; - sniffOnConnectionFault: import("@kbn/config-schema").Type; - hosts: import("@kbn/config-schema").Type; - preserveHost: import("@kbn/config-schema").Type; - username: import("@kbn/config-schema").Type; - password: import("@kbn/config-schema").Type; - requestHeadersWhitelist: import("@kbn/config-schema").Type; - customHeaders: import("@kbn/config-schema").Type>; - shardTimeout: import("@kbn/config-schema").Type; - requestTimeout: import("@kbn/config-schema").Type; - pingTimeout: import("@kbn/config-schema").Type; - startupTimeout: import("@kbn/config-schema").Type; - logQueries: import("@kbn/config-schema").Type; + sniffOnStart: Type; + sniffInterval: Type; + sniffOnConnectionFault: Type; + hosts: Type; + preserveHost: Type; + username: Type; + password: Type; + requestHeadersWhitelist: Type; + customHeaders: Type>; + shardTimeout: Type; + requestTimeout: Type; + pingTimeout: Type; + startupTimeout: Type; + logQueries: Type; ssl: import("@kbn/config-schema").ObjectType<{ - verificationMode: import("@kbn/config-schema").Type<"none" | "certificate" | "full">; - certificateAuthorities: import("@kbn/config-schema").Type; - certificate: import("@kbn/config-schema").Type; - key: import("@kbn/config-schema").Type; - keyPassphrase: import("@kbn/config-schema").Type; + verificationMode: Type<"none" | "certificate" | "full">; + certificateAuthorities: Type; + certificate: Type; + key: Type; + keyPassphrase: Type; keystore: import("@kbn/config-schema").ObjectType<{ - path: import("@kbn/config-schema").Type; - password: import("@kbn/config-schema").Type; + path: Type; + password: Type; }>; truststore: import("@kbn/config-schema").ObjectType<{ - path: import("@kbn/config-schema").Type; - password: import("@kbn/config-schema").Type; + path: Type; + password: Type; }>; - alwaysPresentCertificate: import("@kbn/config-schema").Type; + alwaysPresentCertificate: Type; }>; - apiVersion: import("@kbn/config-schema").Type; + apiVersion: Type; healthCheck: import("@kbn/config-schema").ObjectType<{ - delay: import("@kbn/config-schema").Type; + delay: Type; }>; ignoreVersionMismatch: import("@kbn/config-schema/target/types/types").ConditionalType; }>; }; logging: { - appenders: import("@kbn/config-schema").Type | Readonly<{ - pattern?: string | undefined; - highlight?: boolean | undefined; - } & { - kind: "pattern"; - }>; - kind: "console"; - }> | Readonly<{} & { - path: string; - layout: Readonly<{} & { - kind: "json"; - }> | Readonly<{ - pattern?: string | undefined; - highlight?: boolean | undefined; - } & { - kind: "pattern"; - }>; - kind: "file"; - }> | Readonly<{ - legacyLoggingConfig?: any; - } & { - kind: "legacy-appender"; - }>>; - loggers: import("@kbn/config-schema").ObjectType<{ - appenders: import("@kbn/config-schema").Type; - context: import("@kbn/config-schema").Type; - level: import("@kbn/config-schema").Type; - }>; - loggerContext: import("@kbn/config-schema").ObjectType<{ - appenders: import("@kbn/config-schema").Type | Readonly<{ - pattern?: string | undefined; - highlight?: boolean | undefined; - } & { - kind: "pattern"; - }>; - kind: "console"; - }> | Readonly<{} & { - path: string; - layout: Readonly<{} & { - kind: "json"; - }> | Readonly<{ - pattern?: string | undefined; - highlight?: boolean | undefined; - } & { - kind: "pattern"; - }>; - kind: "file"; - }> | Readonly<{ - legacyLoggingConfig?: any; - } & { - kind: "legacy-appender"; - }>>>; - loggers: import("@kbn/config-schema").Type[]>; - }>; + appenders: Type; }; }; @@ -1469,19 +1411,30 @@ export interface LegacyServiceStartDeps { plugins: Record; } -// Warning: (ae-forgotten-export) The symbol "SavedObjectsLegacyUiExports" needs to be exported by the entry point index.d.ts -// // @internal @deprecated (undocumented) -export type LegacyUiExports = SavedObjectsLegacyUiExports & { +export interface LegacyUiExports { + // Warning: (ae-forgotten-export) The symbol "VarsProvider" needs to be exported by the entry point index.d.ts + // + // (undocumented) defaultInjectedVarProviders?: VarsProvider[]; + // Warning: (ae-forgotten-export) The symbol "VarsReplacer" needs to be exported by the entry point index.d.ts + // + // (undocumented) injectedVarsReplacers?: VarsReplacer[]; + // Warning: (ae-forgotten-export) The symbol "LegacyNavLinkSpec" needs to be exported by the entry point index.d.ts + // + // (undocumented) navLinkSpecs?: LegacyNavLinkSpec[] | null; + // Warning: (ae-forgotten-export) The symbol "LegacyAppSpec" needs to be exported by the entry point index.d.ts + // + // (undocumented) uiAppSpecs?: Array; + // (undocumented) unknown?: [{ pluginSpec: LegacyPluginSpec; type: unknown; }]; -}; +} // Warning: (ae-forgotten-export) The symbol "lifecycleResponseFactory" needs to be exported by the entry point index.d.ts // @@ -1578,10 +1531,10 @@ export interface LogRecord { timestamp: Date; } -// Warning: (ae-missing-release-tag) "MetricsServiceSetup" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export interface MetricsServiceSetup { + readonly collectionInterval: number; + getOpsMetrics$: () => Observable; } // @public @deprecated (undocumented) @@ -1668,6 +1621,7 @@ export interface OnPreRoutingToolkit { // @public export interface OpsMetrics { + collected_at: Date; concurrent_connections: OpsServerMetrics['concurrent_connections']; os: OpsOsMetrics; process: OpsProcessMetrics; @@ -1677,6 +1631,20 @@ export interface OpsMetrics { // @public export interface OpsOsMetrics { + cpu?: { + control_group: string; + cfs_period_micros: number; + cfs_quota_micros: number; + stat: { + number_of_elapsed_periods: number; + number_of_times_throttled: number; + time_throttled_nanos: number; + }; + }; + cpuacct?: { + control_group: string; + usage_nanos: number; + }; distro?: string; distroRelease?: string; load: { @@ -2079,6 +2047,7 @@ export interface SavedObjectsBulkResponse { export interface SavedObjectsBulkUpdateObject extends Pick { attributes: Partial; id: string; + namespace?: string; type: string; } @@ -2495,33 +2464,6 @@ export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOpt refresh?: MutatingOperationRefreshSetting; } -// @internal @deprecated (undocumented) -export interface SavedObjectsLegacyService { - // Warning: (ae-forgotten-export) The symbol "SavedObjectsClientProvider" needs to be exported by the entry point index.d.ts - // - // (undocumented) - addScopedSavedObjectsClientWrapperFactory: SavedObjectsClientProvider['addClientWrapperFactory']; - // (undocumented) - getSavedObjectsRepository(...rest: any[]): any; - // (undocumented) - getScopedSavedObjectsClient: SavedObjectsClientProvider['getClient']; - // (undocumented) - importExport: { - objectLimit: number; - importSavedObjects(options: SavedObjectsImportOptions): Promise; - resolveImportErrors(options: SavedObjectsResolveImportErrorsOptions): Promise; - getSortedObjectsForExport(options: SavedObjectsExportOptions): Promise; - }; - // (undocumented) - SavedObjectsClient: typeof SavedObjectsClient; - // (undocumented) - schema: SavedObjectsSchema; - // (undocumented) - setScopedSavedObjectsClientFactory: SavedObjectsClientProvider['setClientFactory']; - // (undocumented) - types: string[]; -} - // @public export interface SavedObjectsMappingProperties { // (undocumented) @@ -2575,10 +2517,10 @@ export class SavedObjectsRepository { bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; checkConflicts(objects?: SavedObjectsCheckConflictsObject[], options?: SavedObjectsBaseOptions): Promise; create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; - // Warning: (ae-forgotten-export) The symbol "KibanaMigrator" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "IKibanaMigrator" needs to be exported by the entry point index.d.ts // // @internal - static createRepository(migrator: KibanaMigrator, typeRegistry: SavedObjectTypeRegistry, indexName: string, client: ElasticsearchClient, includedHiddenTypes?: string[], injectedConstructor?: any): ISavedObjectsRepository; + static createRepository(migrator: IKibanaMigrator, typeRegistry: SavedObjectTypeRegistry, indexName: string, client: ElasticsearchClient, includedHiddenTypes?: string[], injectedConstructor?: any): ISavedObjectsRepository; delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise; deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise; @@ -2606,24 +2548,6 @@ export interface SavedObjectsResolveImportErrorsOptions { typeRegistry: ISavedObjectTypeRegistry; } -// @internal @deprecated (undocumented) -export class SavedObjectsSchema { - // Warning: (ae-forgotten-export) The symbol "SavedObjectsSchemaDefinition" needs to be exported by the entry point index.d.ts - constructor(schemaDefinition?: SavedObjectsSchemaDefinition); - // (undocumented) - getConvertToAliasScript(type: string): string | undefined; - // (undocumented) - getIndexForType(config: LegacyConfig, type: string): string | undefined; - // (undocumented) - isHiddenType(type: string): boolean; - // (undocumented) - isMultiNamespace(type: string): boolean; - // (undocumented) - isNamespaceAgnostic(type: string): boolean; - // (undocumented) - isSingleNamespace(type: string): boolean; -} - // @public export class SavedObjectsSerializer { // @internal @@ -2707,6 +2631,12 @@ export interface SavedObjectsUpdateResponse extends Omit string; + static namespaceStringToId: (namespace: string) => string | undefined; +} + // @public export class SavedObjectTypeRegistry { getAllTypes(): SavedObjectsType[]; @@ -2953,11 +2883,7 @@ export const validBodyOutput: readonly ["data", "stream"]; // Warnings were encountered during analysis: // // src/core/server/http/router/response.ts:316:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts -// src/core/server/legacy/types.ts:163:3 - (ae-forgotten-export) The symbol "VarsProvider" needs to be exported by the entry point index.d.ts -// src/core/server/legacy/types.ts:164:3 - (ae-forgotten-export) The symbol "VarsReplacer" needs to be exported by the entry point index.d.ts -// src/core/server/legacy/types.ts:165:3 - (ae-forgotten-export) The symbol "LegacyNavLinkSpec" needs to be exported by the entry point index.d.ts -// src/core/server/legacy/types.ts:166:3 - (ae-forgotten-export) The symbol "LegacyAppSpec" needs to be exported by the entry point index.d.ts -// src/core/server/legacy/types.ts:167:16 - (ae-forgotten-export) The symbol "LegacyPluginSpec" needs to be exported by the entry point index.d.ts +// src/core/server/legacy/types.ts:135:16 - (ae-forgotten-export) The symbol "LegacyPluginSpec" needs to be exported by the entry point index.d.ts // src/core/server/plugins/types.ts:272:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts // src/core/server/plugins/types.ts:272:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts // src/core/server/plugins/types.ts:274:3 - (ae-forgotten-export) The symbol "PathConfigType" needs to be exported by the entry point index.d.ts diff --git a/src/core/server/server.test.ts b/src/core/server/server.test.ts index 1bd364c2f87b7..8bf16d9130ef5 100644 --- a/src/core/server/server.test.ts +++ b/src/core/server/server.test.ts @@ -41,7 +41,6 @@ import { Server } from './server'; import { getEnvOptions } from './config/__mocks__/env'; import { loggingSystemMock } from './logging/logging_system.mock'; import { rawConfigServiceMock } from './config/raw_config_service.mock'; -import { PluginName } from './plugins'; const env = new Env('.', getEnvOptions()); const logger = loggingSystemMock.create(); @@ -114,31 +113,6 @@ test('injects legacy dependency to context#setup()', async () => { }); }); -test('injects legacy dependency to status#setup()', async () => { - const server = new Server(rawConfigService, env, logger); - - const pluginDependencies = new Map([ - ['a', []], - ['b', ['a']], - ]); - mockPluginsService.discover.mockResolvedValue({ - pluginTree: { asOpaqueIds: new Map(), asNames: pluginDependencies }, - uiPlugins: { internal: new Map(), public: new Map(), browserConfigs: new Map() }, - }); - - await server.setup(); - - expect(mockStatusService.setup).toHaveBeenCalledWith({ - elasticsearch: expect.any(Object), - savedObjects: expect.any(Object), - pluginDependencies: new Map([ - ['a', []], - ['b', ['a']], - ['legacy', ['a', 'b']], - ]), - }); -}); - test('runs services on "start"', async () => { const server = new Server(rawConfigService, env, logger); diff --git a/src/core/server/server.ts b/src/core/server/server.ts index e2f77f0551f34..a02b0f51b559f 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -145,7 +145,6 @@ export class Server { const savedObjectsSetup = await this.savedObjects.setup({ http: httpSetup, elasticsearch: elasticsearchServiceSetup, - legacyPlugins, }); const uiSettingsSetup = await this.uiSettings.setup({ @@ -157,12 +156,7 @@ export class Server { const statusSetup = await this.status.setup({ elasticsearch: elasticsearchServiceSetup, - // We inject a fake "legacy plugin" with dependencies on every plugin so that legacy can access plugin status from - // any KP plugin - pluginDependencies: new Map([ - ...pluginTree.asNames, - ['legacy', [...pluginTree.asNames.keys()]], - ]), + pluginDependencies: pluginTree.asNames, savedObjects: savedObjectsSetup, }); diff --git a/src/core/server/status/get_summary_status.ts b/src/core/server/status/get_summary_status.ts index 1dc92839e8261..8d97cdbd9b15b 100644 --- a/src/core/server/status/get_summary_status.ts +++ b/src/core/server/status/get_summary_status.ts @@ -27,17 +27,15 @@ export const getSummaryStatus = ( statuses: Array<[string, ServiceStatus]>, { allAvailableSummary = `All services are available` }: { allAvailableSummary?: string } = {} ): ServiceStatus => { - const grouped = groupByLevel(statuses); - const highestSeverityLevel = getHighestSeverityLevel(grouped.keys()); - const highestSeverityGroup = grouped.get(highestSeverityLevel)!; + const { highestLevel, highestStatuses } = highestLevelSummary(statuses); - if (highestSeverityLevel === ServiceStatusLevels.available) { + if (highestLevel === ServiceStatusLevels.available) { return { level: ServiceStatusLevels.available, summary: allAvailableSummary, }; - } else if (highestSeverityGroup.size === 1) { - const [serviceName, status] = [...highestSeverityGroup.entries()][0]; + } else if (highestStatuses.length === 1) { + const [serviceName, status] = highestStatuses[0]! as [string, ServiceStatus]; return { ...status, summary: `[${serviceName}]: ${status.summary!}`, @@ -49,44 +47,36 @@ export const getSummaryStatus = ( }; } else { return { - level: highestSeverityLevel, - summary: `[${highestSeverityGroup.size}] services are ${highestSeverityLevel.toString()}`, + level: highestLevel, + summary: `[${highestStatuses.length}] services are ${highestLevel.toString()}`, // TODO: include URL to status page detail: `See the status page for more information`, meta: { - affectedServices: Object.fromEntries([...highestSeverityGroup]), + affectedServices: Object.fromEntries(highestStatuses), }, }; } }; -const groupByLevel = ( - statuses: Array<[string, ServiceStatus]> -): Map> => { - const byLevel = new Map>(); +type StatusPair = [string, ServiceStatus]; - for (const [serviceName, status] of statuses) { - let levelMap = byLevel.get(status.level); - if (!levelMap) { - levelMap = new Map(); - byLevel.set(status.level, levelMap); - } +const highestLevelSummary = ( + statuses: StatusPair[] +): { highestLevel: ServiceStatusLevel; highestStatuses: StatusPair[] } => { + let highestLevel: ServiceStatusLevel = ServiceStatusLevels.available; + let highestStatuses: StatusPair[] = []; - levelMap.set(serviceName, status); + for (const pair of statuses) { + if (pair[1].level === highestLevel) { + highestStatuses.push(pair); + } else if (pair[1].level > highestLevel) { + highestLevel = pair[1].level; + highestStatuses = [pair]; + } } - return byLevel; -}; - -const getHighestSeverityLevel = (levels: Iterable): ServiceStatusLevel => { - const sorted = [...levels].sort((a, b) => { - if (a < b) { - return -1; - } else if (a > b) { - return 1; - } else { - return 0; - } - }); - return sorted[sorted.length - 1] ?? ServiceStatusLevels.available; + return { + highestLevel, + highestStatuses, + }; }; diff --git a/src/core/server/status/plugins_status.test.ts b/src/core/server/status/plugins_status.test.ts index b2d2ac8a5ef90..a75dc8c283698 100644 --- a/src/core/server/status/plugins_status.test.ts +++ b/src/core/server/status/plugins_status.test.ts @@ -312,9 +312,9 @@ describe('PluginStatusService', () => { pluginA$.next(available); pluginA$.next(degraded); // Waiting for the debounce timeout should cut a new update - await delay(100); + await delay(500); pluginA$.next(available); - await delay(100); + await delay(500); subscription.unsubscribe(); expect(statusUpdates).toMatchInlineSnapshot(` diff --git a/src/core/server/status/plugins_status.ts b/src/core/server/status/plugins_status.ts index df6f13eeec4e5..113d59b327c11 100644 --- a/src/core/server/status/plugins_status.ts +++ b/src/core/server/status/plugins_status.ts @@ -52,7 +52,7 @@ export class PluginsStatusService { return this.getPluginStatuses$(dependencies).pipe( // Prevent many emissions at once from dependency status resolution from making this too noisy - debounceTime(100) + debounceTime(500) ); } diff --git a/src/core/server/status/status_service.test.ts b/src/core/server/status/status_service.test.ts index 341c40a86bf77..dcb1e0a559f5d 100644 --- a/src/core/server/status/status_service.test.ts +++ b/src/core/server/status/status_service.test.ts @@ -215,20 +215,20 @@ describe('StatusService', () => { // Wait for timers to ensure that duplicate events are still filtered out regardless of debouncing. elasticsearch$.next(available); - await delay(100); + await delay(500); elasticsearch$.next(available); - await delay(100); + await delay(500); elasticsearch$.next({ level: ServiceStatusLevels.available, summary: `Wow another summary`, }); - await delay(100); + await delay(500); savedObjects$.next(degraded); - await delay(100); + await delay(500); savedObjects$.next(available); - await delay(100); + await delay(500); savedObjects$.next(available); - await delay(100); + await delay(500); subscription.unsubscribe(); expect(statusUpdates).toMatchInlineSnapshot(` @@ -278,9 +278,9 @@ describe('StatusService', () => { savedObjects$.next(available); savedObjects$.next(degraded); // Waiting for the debounce timeout should cut a new update - await delay(100); + await delay(500); savedObjects$.next(available); - await delay(100); + await delay(500); subscription.unsubscribe(); expect(statusUpdates).toMatchInlineSnapshot(` diff --git a/src/core/server/status/status_service.ts b/src/core/server/status/status_service.ts index 59e81343597c9..8fe65eddb61d3 100644 --- a/src/core/server/status/status_service.ts +++ b/src/core/server/status/status_service.ts @@ -60,7 +60,7 @@ export class StatusService implements CoreService { this.pluginsStatus.getAll$() ).pipe( // Prevent many emissions at once from dependency status resolution from making this too noisy - debounceTime(100), + debounceTime(500), map(([coreStatus, pluginsStatus]) => { const summary = getSummaryStatus([ ...Object.entries(coreStatus), @@ -69,7 +69,8 @@ export class StatusService implements CoreService { this.logger.debug(`Recalculated overall status`, { status: summary }); return summary; }), - distinctUntilChanged(isDeepStrictEqual) + distinctUntilChanged(isDeepStrictEqual), + shareReplay(1) ); return { diff --git a/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts index d2e31dad58e55..c7d5413ecca56 100644 --- a/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts @@ -24,7 +24,7 @@ import { TestElasticsearchUtils, TestKibanaUtils, TestUtils, -} from '../../../../../test_utils/kbn_server'; +} from '../../../../test_helpers/kbn_server'; import { createOrUpgradeSavedConfig } from '../create_or_upgrade_saved_config'; import { loggingSystemMock } from '../../../logging/logging_system.mock'; import { httpServerMock } from '../../../http/http_server.mocks'; @@ -36,8 +36,6 @@ describe('createOrUpgradeSavedConfig()', () => { let esServer: TestElasticsearchUtils; let kbn: TestKibanaUtils; - let kbnServer: TestKibanaUtils['kbnServer']; - beforeAll(async function () { servers = createTestServers({ adjustTimeout: (t) => { @@ -46,10 +44,8 @@ describe('createOrUpgradeSavedConfig()', () => { }); esServer = await servers.startES(); kbn = await servers.startKibana(); - kbnServer = kbn.kbnServer; - const savedObjects = kbnServer.server.savedObjects; - savedObjectsClient = savedObjects.getScopedSavedObjectsClient( + savedObjectsClient = kbn.coreStart.savedObjects.getScopedClient( httpServerMock.createKibanaRequest() ); diff --git a/src/core/server/ui_settings/integration_tests/lib/servers.ts b/src/core/server/ui_settings/integration_tests/lib/servers.ts index 04979b69b32b9..0bdc821f42581 100644 --- a/src/core/server/ui_settings/integration_tests/lib/servers.ts +++ b/src/core/server/ui_settings/integration_tests/lib/servers.ts @@ -24,7 +24,7 @@ import { TestElasticsearchUtils, TestKibanaUtils, TestUtils, -} from '../../../../../test_utils/kbn_server'; +} from '../../../../test_helpers/kbn_server'; import { LegacyAPICaller } from '../../../elasticsearch/'; import { httpServerMock } from '../../../http/http_server.mocks'; @@ -68,8 +68,7 @@ export function getServices() { const callCluster = esServer.es.getCallCluster(); - const savedObjects = kbnServer.server.savedObjects; - const savedObjectsClient = savedObjects.getScopedSavedObjectsClient( + const savedObjectsClient = kbn.coreStart.savedObjects.getScopedClient( httpServerMock.createKibanaRequest() ); diff --git a/src/core/server/ui_settings/integration_tests/routes.test.ts b/src/core/server/ui_settings/integration_tests/routes.test.ts index b18cc370fac3c..063d68e3866b7 100644 --- a/src/core/server/ui_settings/integration_tests/routes.test.ts +++ b/src/core/server/ui_settings/integration_tests/routes.test.ts @@ -18,7 +18,7 @@ */ import { schema } from '@kbn/config-schema'; -import * as kbnTestServer from '../../../../test_utils/kbn_server'; +import * as kbnTestServer from '../../../test_helpers/kbn_server'; describe('ui settings service', () => { describe('routes', () => { diff --git a/src/core/server/utils/index.ts b/src/core/server/utils/index.ts index b01a4c4e04899..d9c4217c4117f 100644 --- a/src/core/server/utils/index.ts +++ b/src/core/server/utils/index.ts @@ -20,3 +20,4 @@ export * from './crypto'; export * from './from_root'; export * from './package_json'; +export * from './streams'; diff --git a/src/core/server/utils/streams/concat_stream.test.ts b/src/core/server/utils/streams/concat_stream.test.ts new file mode 100644 index 0000000000000..e964ab2a7a97e --- /dev/null +++ b/src/core/server/utils/streams/concat_stream.test.ts @@ -0,0 +1,74 @@ +/* + * 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 { createListStream, createPromiseFromStreams, createConcatStream } from './index'; + +describe('concatStream', () => { + test('accepts an initial value', async () => { + const output = await createPromiseFromStreams([ + createListStream([1, 2, 3]), + createConcatStream([0]), + ]); + + expect(output).toEqual([0, 1, 2, 3]); + }); + + describe(`combines using the previous value's concat method`, () => { + test('works with strings', async () => { + const output = await createPromiseFromStreams([ + createListStream(['a', 'b', 'c']), + createConcatStream(), + ]); + expect(output).toEqual('abc'); + }); + + test('works with arrays', async () => { + const output = await createPromiseFromStreams([ + createListStream([[1], [2, 3, 4], [10]]), + createConcatStream(), + ]); + expect(output).toEqual([1, 2, 3, 4, 10]); + }); + + test('works with a mixture, starting with array', async () => { + const output = await createPromiseFromStreams([ + createListStream([[], 1, 2, 3, 4, [5, 6, 7]]), + createConcatStream(), + ]); + expect(output).toEqual([1, 2, 3, 4, 5, 6, 7]); + }); + + test('fails when the value does not have a concat method', async () => { + let promise; + try { + promise = createPromiseFromStreams([createListStream([1, '1']), createConcatStream()]); + } catch (err) { + throw new Error('createPromiseFromStreams() should not fail synchronously'); + } + + try { + await promise; + throw new Error('Promise should have rejected'); + } catch (err) { + expect(err).toBeInstanceOf(Error); + expect(err.message).toContain('concat'); + } + }); + }); +}); diff --git a/src/core/server/utils/streams/concat_stream.ts b/src/core/server/utils/streams/concat_stream.ts new file mode 100644 index 0000000000000..03450cb51b832 --- /dev/null +++ b/src/core/server/utils/streams/concat_stream.ts @@ -0,0 +1,46 @@ +/* + * 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 { createReduceStream } from './reduce_stream'; + +/** + * Creates a Transform stream that consumes all provided + * values and concatenates them using each values `concat` + * method. + * + * Concatenate strings: + * createListStream(['f', 'o', 'o']) + * .pipe(createConcatStream()) + * .on('data', console.log) + * // logs "foo" + * + * Concatenate values into an array: + * createListStream([1,2,3]) + * .pipe(createConcatStream([])) + * .on('data', console.log) + * // logs "[1,2,3]" + * + * + * @param {any} initial The initial value that subsequent + * items will concat with + * @return {Transform} + */ +export function createConcatStream(initial?: T) { + return createReduceStream((acc, chunk) => acc.concat(chunk), initial); +} diff --git a/src/core/server/utils/streams/concat_stream_providers.test.ts b/src/core/server/utils/streams/concat_stream_providers.test.ts new file mode 100644 index 0000000000000..b742a770b70c8 --- /dev/null +++ b/src/core/server/utils/streams/concat_stream_providers.test.ts @@ -0,0 +1,66 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Readable } from 'stream'; + +import { concatStreamProviders } from './concat_stream_providers'; +import { createListStream } from './list_stream'; +import { createConcatStream } from './concat_stream'; +import { createPromiseFromStreams } from './promise_from_streams'; + +describe('concatStreamProviders() helper', () => { + test('writes the data from an array of stream providers into a destination stream in order', async () => { + const results = await createPromiseFromStreams([ + concatStreamProviders([ + () => createListStream(['foo', 'bar']), + () => createListStream(['baz']), + () => createListStream(['bug']), + ]), + createConcatStream(''), + ]); + + expect(results).toBe('foobarbazbug'); + }); + + test('emits the errors from a sub-stream to the destination', async () => { + const dest = concatStreamProviders([ + () => createListStream(['foo', 'bar']), + () => + new Readable({ + read() { + this.emit('error', new Error('foo')); + }, + }), + ]); + + const errorListener = jest.fn(); + dest.on('error', errorListener); + + await expect(createPromiseFromStreams([dest])).rejects.toThrowErrorMatchingInlineSnapshot( + `"foo"` + ); + expect(errorListener.mock.calls).toMatchInlineSnapshot(` +Array [ + Array [ + [Error: foo], + ], +] +`); + }); +}); diff --git a/src/core/server/utils/streams/concat_stream_providers.ts b/src/core/server/utils/streams/concat_stream_providers.ts new file mode 100644 index 0000000000000..bb836e3d73787 --- /dev/null +++ b/src/core/server/utils/streams/concat_stream_providers.ts @@ -0,0 +1,64 @@ +/* + * 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 { Readable, PassThrough, TransformOptions } from 'stream'; + +/** + * Write the data and errors from a list of stream providers + * to a single stream in order. Stream providers are only + * called right before they will be consumed, and only one + * provider will be active at a time. + * + * @param {Array<() => ReadableStream>} sourceProviders + * @param {PassThroughOptions} options options passed to the PassThrough constructor + * @return {WritableStream} combined stream + */ +export function concatStreamProviders( + sourceProviders: Array<() => Readable>, + options?: TransformOptions +) { + const destination = new PassThrough(options); + const queue = sourceProviders.slice(); + + (function pipeNext() { + const provider = queue.shift(); + + if (!provider) { + return; + } + + const source = provider(); + const isLast = !queue.length; + + // if there are more sources to pipe, hook + // into the source completion + if (!isLast) { + source.once('end', pipeNext); + } + + source + // proxy errors from the source to the destination + .once('error', (error) => destination.emit('error', error)) + // pipe the source to the destination but only proxy the + // end event if this is the last source + .pipe(destination, { end: isLast }); + })(); + + return destination; +} diff --git a/src/core/server/utils/streams/filter_stream.test.ts b/src/core/server/utils/streams/filter_stream.test.ts new file mode 100644 index 0000000000000..41073e54b0a84 --- /dev/null +++ b/src/core/server/utils/streams/filter_stream.test.ts @@ -0,0 +1,77 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + createConcatStream, + createFilterStream, + createListStream, + createPromiseFromStreams, +} from './index'; + +describe('createFilterStream()', () => { + test('calls the function with each item in the source stream', async () => { + const filter = jest.fn().mockReturnValue(true); + + await createPromiseFromStreams([createListStream(['a', 'b', 'c']), createFilterStream(filter)]); + + expect(filter).toMatchInlineSnapshot(` + [MockFunction] { + "calls": Array [ + Array [ + "a", + ], + Array [ + "b", + ], + Array [ + "c", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": true, + }, + Object { + "type": "return", + "value": true, + }, + Object { + "type": "return", + "value": true, + }, + ], + } + `); + }); + + test('send the filtered values on the output stream', async () => { + const result = await createPromiseFromStreams([ + createListStream([1, 2, 3]), + createFilterStream((n) => n % 2 === 0), + createConcatStream([]), + ]); + + expect(result).toMatchInlineSnapshot(` + Array [ + 2, + ] + `); + }); +}); diff --git a/src/legacy/utils/streams/filter_stream.ts b/src/core/server/utils/streams/filter_stream.ts similarity index 100% rename from src/legacy/utils/streams/filter_stream.ts rename to src/core/server/utils/streams/filter_stream.ts diff --git a/src/core/server/utils/streams/index.ts b/src/core/server/utils/streams/index.ts new file mode 100644 index 0000000000000..447d1ed5b1c53 --- /dev/null +++ b/src/core/server/utils/streams/index.ts @@ -0,0 +1,29 @@ +/* + * 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 { concatStreamProviders } from './concat_stream_providers'; +export { createIntersperseStream } from './intersperse_stream'; +export { createSplitStream } from './split_stream'; +export { createListStream } from './list_stream'; +export { createReduceStream } from './reduce_stream'; +export { createPromiseFromStreams } from './promise_from_streams'; +export { createConcatStream } from './concat_stream'; +export { createMapStream } from './map_stream'; +export { createReplaceStream } from './replace_stream'; +export { createFilterStream } from './filter_stream'; diff --git a/src/core/server/utils/streams/intersperse_stream.test.ts b/src/core/server/utils/streams/intersperse_stream.test.ts new file mode 100644 index 0000000000000..9aa15035d2a1c --- /dev/null +++ b/src/core/server/utils/streams/intersperse_stream.test.ts @@ -0,0 +1,54 @@ +/* + * 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 { + createPromiseFromStreams, + createListStream, + createIntersperseStream, + createConcatStream, +} from './index'; + +describe('intersperseStream', () => { + test('places the intersperse value between each provided value', async () => { + expect( + await createPromiseFromStreams([ + createListStream(['to', 'be', 'or', 'not', 'to', 'be']), + createIntersperseStream(' '), + createConcatStream(), + ]) + ).toBe('to be or not to be'); + }); + + test('emits values as soon as possible, does not needlessly buffer', async () => { + const str = createIntersperseStream('y'); + const onData = jest.fn(); + str.on('data', onData); + + str.write('a'); + expect(onData).toHaveBeenCalledTimes(1); + expect(onData.mock.calls[0]).toEqual(['a']); + onData.mockClear(); + + str.write('b'); + expect(onData).toHaveBeenCalledTimes(2); + expect(onData.mock.calls[0]).toEqual(['y']); + expect(onData).toHaveBeenCalledTimes(2); + expect(onData.mock.calls[1]).toEqual(['b']); + }); +}); diff --git a/src/core/server/utils/streams/intersperse_stream.ts b/src/core/server/utils/streams/intersperse_stream.ts new file mode 100644 index 0000000000000..272507221caff --- /dev/null +++ b/src/core/server/utils/streams/intersperse_stream.ts @@ -0,0 +1,64 @@ +/* + * 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 { Transform } from 'stream'; + +/** + * Create a Transform stream that receives values in object mode, + * and intersperses a chunk between each object received. + * + * This is useful for writing lists: + * + * createListStream(['foo', 'bar']) + * .pipe(createIntersperseStream('\n')) + * .pipe(process.stdout) // outputs "foo\nbar" + * + * Combine with a concat stream to get "join" like functionality: + * + * await createPromiseFromStreams([ + * createListStream(['foo', 'bar']), + * createIntersperseStream(' '), + * createConcatStream() + * ]) // produces a single value "foo bar" + * + * @param {String|Buffer} intersperseChunk + * @return {Transform} + */ +export function createIntersperseStream(intersperseChunk: string | Buffer) { + let first = true; + + return new Transform({ + writableObjectMode: true, + readableObjectMode: true, + transform(chunk, enc, callback) { + try { + if (first) { + first = false; + } else { + this.push(intersperseChunk); + } + + this.push(chunk); + callback(); + } catch (err) { + callback(err); + } + }, + }); +} diff --git a/src/core/server/utils/streams/list_stream.test.ts b/src/core/server/utils/streams/list_stream.test.ts new file mode 100644 index 0000000000000..2a20c929db6b9 --- /dev/null +++ b/src/core/server/utils/streams/list_stream.test.ts @@ -0,0 +1,44 @@ +/* + * 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 { createListStream } from './index'; + +describe('listStream', () => { + test('provides the values in the initial list', async () => { + const str = createListStream([1, 2, 3, 4]); + const onData = jest.fn(); + str.on('data', onData); + + await new Promise((resolve) => str.on('end', resolve)); + + expect(onData).toHaveBeenCalledTimes(4); + expect(onData.mock.calls[0]).toEqual([1]); + expect(onData.mock.calls[1]).toEqual([2]); + expect(onData.mock.calls[2]).toEqual([3]); + expect(onData.mock.calls[3]).toEqual([4]); + }); + + test('does not modify the list passed', async () => { + const list = [1, 2, 3, 4]; + const str = createListStream(list); + str.resume(); + await new Promise((resolve) => str.on('end', resolve)); + expect(list).toEqual([1, 2, 3, 4]); + }); +}); diff --git a/src/core/server/utils/streams/list_stream.ts b/src/core/server/utils/streams/list_stream.ts new file mode 100644 index 0000000000000..e62f6d3fa930b --- /dev/null +++ b/src/core/server/utils/streams/list_stream.ts @@ -0,0 +1,44 @@ +/* + * 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 { Readable } from 'stream'; + +/** + * Create a Readable stream that provides the items + * from a list as objects to subscribers + * + * @param {Array} items - the list of items to provide + * @return {Readable} + */ +export function createListStream(items: T | T[] = []) { + const queue = Array.isArray(items) ? [...items] : [items]; + + return new Readable({ + objectMode: true, + read(size) { + queue.splice(0, size).forEach((item) => { + this.push(item); + }); + + if (!queue.length) { + this.push(null); + } + }, + }); +} diff --git a/src/core/server/utils/streams/map_stream.test.ts b/src/core/server/utils/streams/map_stream.test.ts new file mode 100644 index 0000000000000..bf0cab39c21f4 --- /dev/null +++ b/src/core/server/utils/streams/map_stream.test.ts @@ -0,0 +1,61 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { delay } from 'bluebird'; + +import { createPromiseFromStreams } from './promise_from_streams'; +import { createListStream } from './list_stream'; +import { createMapStream } from './map_stream'; +import { createConcatStream } from './concat_stream'; + +describe('createMapStream()', () => { + test('calls the function with each item in the source stream', async () => { + const mapper = jest.fn(); + + await createPromiseFromStreams([createListStream(['a', 'b', 'c']), createMapStream(mapper)]); + + expect(mapper).toHaveBeenCalledTimes(3); + expect(mapper).toHaveBeenCalledWith('a', 0); + expect(mapper).toHaveBeenCalledWith('b', 1); + expect(mapper).toHaveBeenCalledWith('c', 2); + }); + + test('send the return value from the mapper on the output stream', async () => { + const result = await createPromiseFromStreams([ + createListStream([1, 2, 3]), + createMapStream((n: number) => n * 100), + createConcatStream([]), + ]); + + expect(result).toEqual([100, 200, 300]); + }); + + test('supports async mappers', async () => { + const result = await createPromiseFromStreams([ + createListStream([1, 2, 3]), + createMapStream(async (n: number, i: number) => { + await delay(n); + return n * i; + }), + createConcatStream([]), + ]); + + expect(result).toEqual([0, 2, 6]); + }); +}); diff --git a/src/core/server/utils/streams/map_stream.ts b/src/core/server/utils/streams/map_stream.ts new file mode 100644 index 0000000000000..aad53cc526626 --- /dev/null +++ b/src/core/server/utils/streams/map_stream.ts @@ -0,0 +1,36 @@ +/* + * 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 { Transform } from 'stream'; + +export function createMapStream(fn: (value: T, i: number) => void) { + let i = 0; + + return new Transform({ + objectMode: true, + async transform(value, enc, done) { + try { + this.push(await fn(value, i++)); + done(); + } catch (err) { + done(err); + } + }, + }); +} diff --git a/src/core/server/utils/streams/promise_from_streams.test.ts b/src/core/server/utils/streams/promise_from_streams.test.ts new file mode 100644 index 0000000000000..1f2596c16a6fa --- /dev/null +++ b/src/core/server/utils/streams/promise_from_streams.test.ts @@ -0,0 +1,135 @@ +/* + * 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 { Readable, Writable, Duplex, Transform } from 'stream'; + +import { createListStream, createPromiseFromStreams, createReduceStream } from './index'; + +describe('promiseFromStreams', () => { + test('pipes together an array of streams', async () => { + const str1 = createListStream([1, 2, 3]); + const str2 = createReduceStream((acc, n) => acc + n, 0); + const sumPromise = new Promise((resolve) => str2.once('data', resolve)); + createPromiseFromStreams([str1, str2]); + await new Promise((resolve) => str2.once('end', resolve)); + expect(await sumPromise).toBe(6); + }); + + describe('last stream is writable', () => { + test('waits for the last stream to finish writing', async () => { + let written = ''; + + await createPromiseFromStreams([ + createListStream(['a']), + new Writable({ + write(chunk, enc, cb) { + setTimeout(() => { + written += chunk; + cb(); + }, 100); + }, + }), + ]); + + expect(written).toBe('a'); + }); + + test('resolves to undefined', async () => { + const result = await createPromiseFromStreams([ + createListStream(['a']), + new Writable({ + write(chunk, enc, cb) { + cb(); + }, + }), + ]); + + expect(result).toBe(undefined); + }); + }); + + describe('last stream is readable', () => { + test(`resolves to it's final value`, async () => { + const result = await createPromiseFromStreams([createListStream(['a', 'b', 'c'])]); + + expect(result).toBe('c'); + }); + }); + + describe('last stream is duplex', () => { + test('waits for writing and resolves to final value', async () => { + let written = ''; + + const duplexReadQueue: Array> = []; + const duplexItemsToPush = ['foo', 'bar', null]; + const result = await createPromiseFromStreams([ + createListStream(['a', 'b', 'c']), + new Duplex({ + async read() { + this.push(await duplexReadQueue.shift()); + }, + + write(chunk, enc, cb) { + duplexReadQueue.push( + new Promise((resolve) => { + setTimeout(() => { + written += chunk; + cb(); + resolve(duplexItemsToPush.shift()); + }, 50); + }) + ); + }, + }).setEncoding('utf8'), + ]); + + expect(written).toEqual('abc'); + expect(result).toBe('bar'); + }); + }); + + describe('error handling', () => { + test('read stream gets destroyed when transform stream fails', async () => { + let destroyCalled = false; + const readStream = new Readable({ + read() { + this.push('a'); + this.push('b'); + this.push('c'); + this.push(null); + }, + destroy() { + destroyCalled = true; + }, + }); + const transformStream = new Transform({ + transform(chunk, enc, done) { + done(new Error('Test error')); + }, + }); + try { + await createPromiseFromStreams([readStream, transformStream]); + throw new Error('Should fail'); + } catch (e) { + expect(e.message).toBe('Test error'); + expect(destroyCalled).toBe(true); + } + }); + }); +}); diff --git a/src/core/server/utils/streams/promise_from_streams.ts b/src/core/server/utils/streams/promise_from_streams.ts new file mode 100644 index 0000000000000..f5fc4af62bc83 --- /dev/null +++ b/src/core/server/utils/streams/promise_from_streams.ts @@ -0,0 +1,72 @@ +/* + * 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. + */ + +/** + * Take an array of streams, pipe the output + * from each one into the next, listening for + * errors from any of the streams, and then resolve + * the promise once the final stream has finished + * writing/reading. + * + * If the last stream is readable, it's final value + * will be provided as the promise value. + * + * Errors emitted from any stream will cause + * the promise to be rejected with that error. + * + * @param {Array} streams + * @return {Promise} + */ + +import { pipeline, Writable, Readable } from 'stream'; + +function isReadable(stream: Readable | Writable): stream is Readable { + return 'read' in stream && typeof stream.read === 'function'; +} + +export async function createPromiseFromStreams(streams: [Readable, ...Writable[]]): Promise { + let finalChunk: any; + const last = streams[streams.length - 1]; + if (!isReadable(last) && streams.length === 1) { + // For a nicer error than what stream.pipeline throws + throw new Error('A minimum of 2 streams is required when a non-readable stream is given'); + } + if (isReadable(last)) { + // We are pushing a writable stream to capture the last chunk + streams.push( + new Writable({ + // Use object mode even when "last" stream isn't. This allows to + // capture the last chunk as-is. + objectMode: true, + write(chunk, enc, done) { + finalChunk = chunk; + done(); + }, + }) + ); + } + + return new Promise((resolve, reject) => { + // @ts-expect-error 'pipeline' doesn't support variable length of arguments + pipeline(...streams, (err) => { + if (err) return reject(err); + resolve(finalChunk); + }); + }); +} diff --git a/src/core/server/utils/streams/reduce_stream.test.ts b/src/core/server/utils/streams/reduce_stream.test.ts new file mode 100644 index 0000000000000..e4a7dc1cef491 --- /dev/null +++ b/src/core/server/utils/streams/reduce_stream.test.ts @@ -0,0 +1,87 @@ +/* + * 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 { Transform } from 'stream'; +import { createReduceStream, createPromiseFromStreams, createListStream } from './index'; + +const promiseFromEvent = (name: string, emitter: Transform) => + new Promise((resolve) => emitter.on(name, () => resolve(name))); + +describe('reduceStream', () => { + test('calls the reducer for each item provided', async () => { + const stub = jest.fn(); + await createPromiseFromStreams([ + createListStream([1, 2, 3]), + createReduceStream((val, chunk, enc) => { + stub(val, chunk, enc); + return chunk; + }, 0), + ]); + expect(stub).toHaveBeenCalledTimes(3); + expect(stub.mock.calls[0]).toEqual([0, 1, 'utf8']); + expect(stub.mock.calls[1]).toEqual([1, 2, 'utf8']); + expect(stub.mock.calls[2]).toEqual([2, 3, 'utf8']); + }); + + test('provides the return value of the last iteration of the reducer', async () => { + const result = await createPromiseFromStreams([ + createListStream('abcdefg'.split('')), + createReduceStream((acc) => acc + 1, 0), + ]); + expect(result).toBe(7); + }); + + test('emits an error if an iteration fails', async () => { + const reduce = createReduceStream((acc, i) => { + expect(i).toBe(1); + return acc; + }, 0); + const errorEvent = promiseFromEvent('error', reduce); + + reduce.write(1); + reduce.write(2); + reduce.resume(); + await errorEvent; + }); + + test('stops calling the reducer if an iteration fails, emits no data', async () => { + const reducer = jest.fn((acc, i) => { + if (i < 100) return acc + i; + else throw new Error(i); + }); + const reduce$ = createReduceStream(reducer, 0); + + const dataStub = jest.fn(); + const errorStub = jest.fn(); + reduce$.on('data', dataStub); + reduce$.on('error', errorStub); + const endEvent = promiseFromEvent('end', reduce$); + + reduce$.write(1); + reduce$.write(2); + reduce$.write(300); + reduce$.write(400); + reduce$.write(1000); + reduce$.end(); + + await endEvent; + expect(reducer).toHaveBeenCalledTimes(3); + expect(dataStub).toHaveBeenCalledTimes(0); + expect(errorStub).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/core/server/utils/streams/reduce_stream.ts b/src/core/server/utils/streams/reduce_stream.ts new file mode 100644 index 0000000000000..9129df096ad13 --- /dev/null +++ b/src/core/server/utils/streams/reduce_stream.ts @@ -0,0 +1,83 @@ +/* + * 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 { Transform } from 'stream'; + +/** + * Create a transform stream that consumes each chunk it receives + * and passes it to the reducer, which will return the new value + * for the stream. Once all chunks have been received the reduce + * stream provides the result of final call to the reducer to + * subscribers. + * + * @param {Function} + * @param {any} initial Initial value for the stream, if undefined + * then the first chunk provided is used as the + * initial value. + * @return {Transform} + */ +export function createReduceStream( + reducer: (value: any, chunk: T, enc: string) => T, + initial?: T +) { + let i = -1; + let value = initial; + + // if the reducer throws an error then the value is + // considered invalid and the stream will never provide + // it to subscribers. We will also stop calling the + // reducer for any new data that is provided to us + let failed = false; + + if (typeof reducer !== 'function') { + throw new TypeError('reducer must be a function'); + } + + return new Transform({ + readableObjectMode: true, + writableObjectMode: true, + async transform(chunk, enc, callback) { + try { + if (failed) { + return callback(); + } + + i += 1; + if (i === 0 && initial === undefined) { + value = chunk; + } else { + value = await reducer(value, chunk, enc); + } + + callback(); + } catch (err) { + failed = true; + callback(err); + } + }, + + flush(callback) { + if (!failed) { + this.push(value); + } + + callback(); + }, + }); +} diff --git a/src/core/server/utils/streams/replace_stream.test.ts b/src/core/server/utils/streams/replace_stream.test.ts new file mode 100644 index 0000000000000..c9da42395fb85 --- /dev/null +++ b/src/core/server/utils/streams/replace_stream.test.ts @@ -0,0 +1,132 @@ +/* + * 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 { Writable, Readable } from 'stream'; + +import { + createReplaceStream, + createConcatStream, + createPromiseFromStreams, + createListStream, + createMapStream, +} from './index'; + +async function concatToString(streams: [Readable, ...Writable[]]) { + return await createPromiseFromStreams([ + ...streams, + createMapStream((buff: Buffer) => buff.toString('utf8')), + createConcatStream(''), + ]); +} + +describe('replaceStream', () => { + test('produces buffers when it receives buffers', async () => { + const chunks = await createPromiseFromStreams([ + createListStream([Buffer.from('foo'), Buffer.from('bar')]), + createReplaceStream('o', '0'), + createConcatStream([]), + ]); + + chunks.forEach((chunk) => { + expect(chunk).toBeInstanceOf(Buffer); + }); + }); + + test('produces buffers when it receives strings', async () => { + const chunks = await createPromiseFromStreams([ + createListStream(['foo', 'bar']), + createReplaceStream('o', '0'), + createConcatStream([]), + ]); + + chunks.forEach((chunk) => { + expect(chunk).toBeInstanceOf(Buffer); + }); + }); + + test('expects toReplace to be a string', () => { + // @ts-expect-error + expect(() => createReplaceStream(Buffer.from('foo'))).toThrowError(/be a string/); + }); + + test('replaces multiple single-char instances in a single chunk', async () => { + expect( + await concatToString([ + createListStream([Buffer.from('f00 bar')]), + createReplaceStream('0', 'o'), + ]) + ).toBe('foo bar'); + }); + + test('replaces multiple single-char instances in multiple chunks', async () => { + expect( + await concatToString([ + createListStream([Buffer.from('f0'), Buffer.from('0 bar')]), + createReplaceStream('0', 'o'), + ]) + ).toBe('foo bar'); + }); + + test('replaces single multi-char instances in single chunks', async () => { + expect( + await concatToString([ + createListStream([Buffer.from('f0'), Buffer.from('0 bar')]), + createReplaceStream('0', 'o'), + ]) + ).toBe('foo bar'); + }); + + test('replaces multiple multi-char instances in single chunks', async () => { + expect( + await concatToString([ + createListStream([Buffer.from('foo ba'), Buffer.from('r b'), Buffer.from('az bar')]), + createReplaceStream('bar', '*'), + ]) + ).toBe('foo * baz *'); + }); + + test('replaces multi-char instance that stretches multiple chunks', async () => { + expect( + await concatToString([ + createListStream([ + Buffer.from('foo supe'), + Buffer.from('rcalifra'), + Buffer.from('gilistic'), + Buffer.from('expialid'), + Buffer.from('ocious bar'), + ]), + createReplaceStream('supercalifragilisticexpialidocious', '*'), + ]) + ).toBe('foo * bar'); + }); + + test('ignores missing multi-char instance', async () => { + expect( + await concatToString([ + createListStream([ + Buffer.from('foo supe'), + Buffer.from('rcalifra'), + Buffer.from('gili stic'), + Buffer.from('expialid'), + Buffer.from('ocious bar'), + ]), + createReplaceStream('supercalifragilisticexpialidocious', '*'), + ]) + ).toBe('foo supercalifragili sticexpialidocious bar'); + }); +}); diff --git a/src/core/server/utils/streams/replace_stream.ts b/src/core/server/utils/streams/replace_stream.ts new file mode 100644 index 0000000000000..05391bb3341c2 --- /dev/null +++ b/src/core/server/utils/streams/replace_stream.ts @@ -0,0 +1,86 @@ +/* + * 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 { Transform } from 'stream'; + +export function createReplaceStream(toReplace: string, replacement: string | Buffer) { + if (typeof toReplace !== 'string') { + throw new TypeError('toReplace must be a string'); + } + + let buffer = Buffer.alloc(0); + return new Transform({ + objectMode: false, + async transform(value, enc, done) { + try { + buffer = Buffer.concat([buffer, value], buffer.length + value.length); + + while (true) { + // try to find the next instance of `toReplace` in buffer + const index = buffer.indexOf(toReplace); + + // if there is no next instance, break + if (index === -1) { + break; + } + + // flush everything to the left of the next instance + // of `toReplace` + this.push(buffer.slice(0, index)); + + // then flush an instance of `replacement` + this.push(replacement); + + // and finally update the buffer to include everything + // to the right of `toReplace`, dropping to replace from the buffer + buffer = buffer.slice(index + toReplace.length); + } + + // until now we have only flushed data that is to the left + // of a discovered instance of `toReplace`. If `toReplace` is + // never found this would lead to us buffering the entire stream. + // + // Instead, we only keep enough buffer to complete a potentially + // partial instance of `toReplace` + if (buffer.length > toReplace.length) { + // the entire buffer except the last `toReplace.length` bytes + // so that if all but one byte from `toReplace` is in the buffer, + // and the next chunk delivers the necessary byte, the buffer will then + // contain a complete `toReplace` token. + this.push(buffer.slice(0, buffer.length - toReplace.length)); + buffer = buffer.slice(-toReplace.length); + } + + done(); + } catch (err) { + done(err); + } + }, + + flush(callback) { + if (buffer.length) { + this.push(buffer); + } + + // @ts-expect-error + buffer = null; + callback(); + }, + }); +} diff --git a/src/core/server/utils/streams/split_stream.test.ts b/src/core/server/utils/streams/split_stream.test.ts new file mode 100644 index 0000000000000..f131bd0661e54 --- /dev/null +++ b/src/core/server/utils/streams/split_stream.test.ts @@ -0,0 +1,71 @@ +/* + * 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 { Transform } from 'stream'; +import { createSplitStream, createConcatStream, createPromiseFromStreams } from './index'; + +async function split(stream: Transform, input: Array) { + const concat = createConcatStream(); + concat.write([]); + stream.pipe(concat); + const output = createPromiseFromStreams([concat]); + + input.forEach((i: any) => { + stream.write(i); + }); + stream.end(); + + return await output; +} + +describe('splitStream', () => { + test('splits buffers, produces strings', async () => { + const output = await split(createSplitStream('&'), [Buffer.from('foo&bar')]); + expect(output).toEqual(['foo', 'bar']); + }); + + test('supports mixed input', async () => { + const output = await split(createSplitStream('&'), [Buffer.from('foo&b'), 'ar']); + expect(output).toEqual(['foo', 'bar']); + }); + + test('supports buffer split chunks', async () => { + const output = await split(createSplitStream(Buffer.from('&')), ['foo&b', 'ar']); + expect(output).toEqual(['foo', 'bar']); + }); + + test('splits provided values by a delimiter', async () => { + const output = await split(createSplitStream('&'), ['foo&b', 'ar']); + expect(output).toEqual(['foo', 'bar']); + }); + + test('handles multi-character delimiters', async () => { + const output = await split(createSplitStream('oo'), ['foo&b', 'ar']); + expect(output).toEqual(['f', '&bar']); + }); + + test('handles delimiters that span multiple chunks', async () => { + const output = await split(createSplitStream('ba'), ['foo&b', 'ar']); + expect(output).toEqual(['foo&', 'r']); + }); + + test('produces an empty chunk if the split char is at the end of the input', async () => { + const output = await split(createSplitStream('&bar'), ['foo&b', 'ar']); + expect(output).toEqual(['foo', '']); + }); +}); diff --git a/src/core/server/utils/streams/split_stream.ts b/src/core/server/utils/streams/split_stream.ts new file mode 100644 index 0000000000000..ae820f60abbf6 --- /dev/null +++ b/src/core/server/utils/streams/split_stream.ts @@ -0,0 +1,74 @@ +/* + * 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 { Transform } from 'stream'; + +/** + * Creates a Transform stream that consumes a stream of Buffers + * and produces a stream of strings (in object mode) by splitting + * the received bytes using the splitChunk. + * + * Ways this is behaves like String#split: + * - instances of splitChunk are removed from the input + * - splitChunk can be on any size + * - if there are no bytes found after the last splitChunk + * a final empty chunk is emitted + * + * Ways this deviates from String#split: + * - splitChunk cannot be a regexp + * - an empty string or Buffer will not produce a stream of individual + * bytes like `string.split('')` would + * + * @param {String} splitChunk + * @return {Transform} + */ +export function createSplitStream(splitChunk: string | Uint8Array) { + let unsplitBuffer = Buffer.alloc(0); + + return new Transform({ + writableObjectMode: false, + readableObjectMode: true, + transform(chunk, enc, callback) { + try { + let i; + let toSplit = Buffer.concat([unsplitBuffer, chunk]); + while ((i = toSplit.indexOf(splitChunk)) !== -1) { + const slice = toSplit.slice(0, i); + toSplit = toSplit.slice(i + splitChunk.length); + this.push(slice.toString('utf8')); + } + + unsplitBuffer = toSplit; + callback(); + } catch (err) { + callback(err); + } + }, + + flush(callback) { + try { + this.push(unsplitBuffer.toString('utf8')); + + callback(); + } catch (err) { + callback(err); + } + }, + }); +} diff --git a/src/test_utils/public/http_test_setup.ts b/src/core/test_helpers/http_test_setup.ts similarity index 85% rename from src/test_utils/public/http_test_setup.ts rename to src/core/test_helpers/http_test_setup.ts index 7c70f64887af1..50ea43fb22b5e 100644 --- a/src/test_utils/public/http_test_setup.ts +++ b/src/core/test_helpers/http_test_setup.ts @@ -17,9 +17,9 @@ * under the License. */ -import { HttpService } from '../../core/public/http'; -import { fatalErrorsServiceMock } from '../../core/public/fatal_errors/fatal_errors_service.mock'; -import { injectedMetadataServiceMock } from '../../core/public/injected_metadata/injected_metadata_service.mock'; +import { HttpService } from '../public/http'; +import { fatalErrorsServiceMock } from '../public/fatal_errors/fatal_errors_service.mock'; +import { injectedMetadataServiceMock } from '../public/injected_metadata/injected_metadata_service.mock'; export type SetupTap = ( injectedMetadata: ReturnType, diff --git a/src/test_utils/kbn_server.ts b/src/core/test_helpers/kbn_server.ts similarity index 95% rename from src/test_utils/kbn_server.ts rename to src/core/test_helpers/kbn_server.ts index e44ce0de403d9..488c4b919d3e4 100644 --- a/src/test_utils/kbn_server.ts +++ b/src/core/test_helpers/kbn_server.ts @@ -26,16 +26,17 @@ import { kibanaServerTestUser, kibanaTestUser, setupUsers, - // @ts-ignore: implicit any for JS file } from '@kbn/test'; import { defaultsDeep, get } from 'lodash'; import { resolve } from 'path'; import { BehaviorSubject } from 'rxjs'; import supertest from 'supertest'; -import { LegacyAPICaller } from '../core/server'; -import { CliArgs, Env } from '../core/server/config'; -import { Root } from '../core/server/root'; -import KbnServer from '../legacy/server/kbn_server'; + +import { CoreStart } from 'src/core/server'; +import { LegacyAPICaller } from '../server/elasticsearch'; +import { CliArgs, Env } from '../server/config'; +import { Root } from '../server/root'; +import KbnServer from '../../legacy/server/kbn_server'; export type HttpMethod = 'delete' | 'get' | 'head' | 'post' | 'put'; @@ -53,7 +54,7 @@ const DEFAULTS_SETTINGS = { }; const DEFAULT_SETTINGS_WITH_CORE_PLUGINS = { - plugins: { scanDirs: [resolve(__dirname, '../legacy/core_plugins')] }, + plugins: { scanDirs: [resolve(__dirname, '../../legacy/core_plugins')] }, elasticsearch: { hosts: [esTestConfig.getUrl()], username: kibanaServerTestUser.username, @@ -170,6 +171,7 @@ export interface TestElasticsearchUtils { export interface TestKibanaUtils { root: Root; + coreStart: CoreStart; kbnServer: KbnServer; stop: () => Promise; } @@ -289,13 +291,14 @@ export function createTestServers({ const root = createRootWithCorePlugins(kbnSettings); await root.setup(); - await root.start(); + const coreStart = await root.start(); const kbnServer = getKbnServer(root); return { root, kbnServer, + coreStart, stop: async () => await root.shutdown(), }; }, diff --git a/src/dev/build/lib/watch_stdio_for_line.ts b/src/dev/build/lib/watch_stdio_for_line.ts index 2322d017abc61..3d7929ccfc33a 100644 --- a/src/dev/build/lib/watch_stdio_for_line.ts +++ b/src/dev/build/lib/watch_stdio_for_line.ts @@ -24,7 +24,7 @@ import { createPromiseFromStreams, createSplitStream, createMapStream, -} from '../../../legacy/utils/streams'; +} from '../../../core/server/utils'; // creates a stream that skips empty lines unless they are followed by // another line, preventing the empty lines produced by splitStream diff --git a/src/dev/build/tasks/create_archives_task.ts b/src/dev/build/tasks/create_archives_task.ts index 0083881e9f748..a05e383394ecf 100644 --- a/src/dev/build/tasks/create_archives_task.ts +++ b/src/dev/build/tasks/create_archives_task.ts @@ -92,8 +92,8 @@ export const CreateArchives: Task = { }); metrics.push({ - group: `${build.isOss() ? 'oss ' : ''}distributable file count`, - id: 'total', + group: 'distributable file count', + id: build.isOss() ? 'oss' : 'default', value: fileCount, }); } diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker index d7f137e965327..b02b7cc16ec4a 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker @@ -18,6 +18,8 @@ kibana_vars=( console.enabled console.proxyConfig console.proxyFilter + ops.cGroupOverrides.cpuPath + ops.cGroupOverrides.cpuAcctPath cpu.cgroup.path.override cpuacct.cgroup.path.override csp.rules @@ -279,4 +281,4 @@ umask 0002 # Therefore, we set this value here so that cgroup statistics are # available for the container this process will run in. -exec /usr/share/kibana/bin/kibana --cpu.cgroup.path.override=/ --cpuacct.cgroup.path.override=/ ${longopts} "$@" +exec /usr/share/kibana/bin/kibana --ops.cGroupOverrides.cpuPath=/ --ops.cGroupOverrides.cpuAcctPath=/ ${longopts} "$@" diff --git a/src/dev/notice/generate_notice_from_source.ts b/src/dev/notice/generate_notice_from_source.ts index 0bef5bc5f32d4..9f7eb9d9e1aa4 100644 --- a/src/dev/notice/generate_notice_from_source.ts +++ b/src/dev/notice/generate_notice_from_source.ts @@ -41,7 +41,7 @@ interface Options { * into the repository. */ export async function generateNoticeFromSource({ productName, directory, log }: Options) { - const globs = ['**/*.{js,less,css,ts}']; + const globs = ['**/*.{js,less,css,ts,tsx}']; const options = { cwd: directory, diff --git a/src/dev/run_i18n_integrate.ts b/src/dev/run_i18n_integrate.ts index 25c3ea32783aa..c0b2302c91c54 100644 --- a/src/dev/run_i18n_integrate.ts +++ b/src/dev/run_i18n_integrate.ts @@ -111,6 +111,7 @@ run( const reporter = new ErrorReporter(); const messages: Map = new Map(); await list.run({ messages, reporter }); + process.exitCode = 0; } catch (error) { process.exitCode = 1; if (error instanceof ErrorReporter) { @@ -120,6 +121,7 @@ run( log.error(error); } } + process.exit(); }, { flags: { diff --git a/src/dev/typescript/build_refs.ts b/src/dev/typescript/build_refs.ts new file mode 100644 index 0000000000000..cbb596c185f8b --- /dev/null +++ b/src/dev/typescript/build_refs.ts @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import execa from 'execa'; +import { run, ToolingLog } from '@kbn/dev-utils'; + +export async function buildRefs(log: ToolingLog) { + try { + log.info('Building TypeScript projects refs...'); + await execa(require.resolve('typescript/bin/tsc'), ['-b', 'tsconfig.refs.json']); + } catch (e) { + log.error(e); + process.exit(1); + } +} + +export async function runBuildRefs() { + run(async ({ log }) => { + await buildRefs(log); + }); +} diff --git a/src/dev/typescript/projects.ts b/src/dev/typescript/projects.ts index 065321e355256..e18c82b5b9e96 100644 --- a/src/dev/typescript/projects.ts +++ b/src/dev/typescript/projects.ts @@ -24,6 +24,7 @@ import { Project } from './project'; export const PROJECTS = [ new Project(resolve(REPO_ROOT, 'tsconfig.json')), + new Project(resolve(REPO_ROOT, 'src/test_utils/tsconfig.json')), new Project(resolve(REPO_ROOT, 'test/tsconfig.json'), { name: 'kibana/test' }), new Project(resolve(REPO_ROOT, 'x-pack/tsconfig.json')), new Project(resolve(REPO_ROOT, 'x-pack/test/tsconfig.json'), { name: 'x-pack/test' }), diff --git a/src/dev/typescript/run_type_check_cli.ts b/src/dev/typescript/run_type_check_cli.ts index 9eeaeb4da7042..e1fca23274a5a 100644 --- a/src/dev/typescript/run_type_check_cli.ts +++ b/src/dev/typescript/run_type_check_cli.ts @@ -24,8 +24,9 @@ import getopts from 'getopts'; import { execInProjects } from './exec_in_projects'; import { filterProjectsByFlag } from './projects'; +import { buildRefs } from './build_refs'; -export function runTypeCheckCli() { +export async function runTypeCheckCli() { const extraFlags: string[] = []; const opts = getopts(process.argv.slice(2), { boolean: ['skip-lib-check', 'help'], @@ -79,7 +80,16 @@ export function runTypeCheckCli() { process.exit(); } - const tscArgs = ['--noEmit', '--pretty', ...(opts['skip-lib-check'] ? ['--skipLibCheck'] : [])]; + await buildRefs(log); + + const tscArgs = [ + // composite project cannot be used with --noEmit + ...['--composite', 'false'], + ...['--emitDeclarationOnly', 'false'], + '--noEmit', + '--pretty', + ...(opts['skip-lib-check'] ? ['--skipLibCheck'] : []), + ]; const projects = filterProjectsByFlag(opts.project).filter((p) => !p.disableTypeCheck); if (!projects.length) { diff --git a/src/fixtures/stubbed_saved_object_index_pattern.ts b/src/fixtures/stubbed_saved_object_index_pattern.ts index 02e6cb85e341f..44b391f14cf9c 100644 --- a/src/fixtures/stubbed_saved_object_index_pattern.ts +++ b/src/fixtures/stubbed_saved_object_index_pattern.ts @@ -30,6 +30,7 @@ export function stubbedSavedObjectIndexPattern(id: string | null = null) { timeFieldName: 'timestamp', customFormats: '{}', fields: mockLogstashFields, + title: 'title', }, version: 2, }; diff --git a/src/legacy/core_plugins/elasticsearch/index.js b/src/legacy/core_plugins/elasticsearch/index.js index 599886788604b..f90f490d68035 100644 --- a/src/legacy/core_plugins/elasticsearch/index.js +++ b/src/legacy/core_plugins/elasticsearch/index.js @@ -16,18 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -import { first } from 'rxjs/operators'; import { Cluster } from './server/lib/cluster'; import { createProxy } from './server/lib/create_proxy'; export default function (kibana) { - let defaultVars; - return new kibana.Plugin({ require: [], - uiExports: { injectDefaultVars: () => defaultVars }, - async init(server) { // All methods that ES plugin exposes are synchronous so we should get the first // value from all observables here to be able to synchronously return and create @@ -36,16 +31,6 @@ export default function (kibana) { const adminCluster = new Cluster(client); const dataCluster = new Cluster(client); - const esConfig = await server.newPlatform.__internals.elasticsearch.legacy.config$ - .pipe(first()) - .toPromise(); - - defaultVars = { - esRequestTimeout: esConfig.requestTimeout.asMilliseconds(), - esShardTimeout: esConfig.shardTimeout.asMilliseconds(), - esApiVersion: esConfig.apiVersion, - }; - const clusters = new Map(); server.expose('getCluster', (name) => { if (name === 'admin') { diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js deleted file mode 100644 index 722d75d00f78f..0000000000000 --- a/src/legacy/core_plugins/kibana/index.js +++ /dev/null @@ -1,39 +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 { getUiSettingDefaults } from './server/ui_setting_defaults'; - -export default function (kibana) { - return new kibana.Plugin({ - id: 'kibana', - config: function (Joi) { - return Joi.object({ - enabled: Joi.boolean().default(true), - index: Joi.string().default('.kibana'), - autocompleteTerminateAfter: Joi.number().integer().min(1).default(100000), - // TODO Also allow units here like in elasticsearch config once this is moved to the new platform - autocompleteTimeout: Joi.number().integer().min(1).default(1000), - }).default(); - }, - - uiExports: { - uiSettingDefaults: getUiSettingDefaults(), - }, - }); -} diff --git a/src/legacy/core_plugins/kibana/package.json b/src/legacy/core_plugins/kibana/package.json deleted file mode 100644 index 94db646611df0..0000000000000 --- a/src/legacy/core_plugins/kibana/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "kibana", - "version": "kibana", - "config": { - "@elastic/eslint-import-resolver-kibana": { - "projectRoot": false - } - } -} diff --git a/src/legacy/core_plugins/kibana/public/index.scss b/src/legacy/core_plugins/kibana/public/index.scss deleted file mode 100644 index 7de0c8fc15f94..0000000000000 --- a/src/legacy/core_plugins/kibana/public/index.scss +++ /dev/null @@ -1,7 +0,0 @@ -// Elastic charts -@import '@elastic/charts/dist/theme'; -@import '@elastic/eui/src/themes/charts/theme'; - -// Public UI styles -@import 'src/legacy/ui/public/index'; - diff --git a/src/legacy/core_plugins/kibana/server/ui_setting_defaults.js b/src/legacy/core_plugins/kibana/server/ui_setting_defaults.js deleted file mode 100644 index 7de5fb581643a..0000000000000 --- a/src/legacy/core_plugins/kibana/server/ui_setting_defaults.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export function getUiSettingDefaults() { - // wrapped in provider so that a new instance is given to each app/test - return {}; -} diff --git a/src/legacy/deprecation/__tests__/create_transform.js b/src/legacy/deprecation/__tests__/create_transform.js deleted file mode 100644 index d3838da5c3399..0000000000000 --- a/src/legacy/deprecation/__tests__/create_transform.js +++ /dev/null @@ -1,59 +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 { createTransform } from '../create_transform'; -import expect from '@kbn/expect'; -import sinon from 'sinon'; - -describe('deprecation', function () { - describe('createTransform', function () { - it(`doesn't modify settings parameter`, function () { - const settings = { - original: true, - }; - const deprecations = [ - (settings) => { - settings.original = false; - }, - ]; - createTransform(deprecations)(settings); - expect(settings.original).to.be(true); - }); - - it('calls single deprecation in array', function () { - const deprecations = [sinon.spy()]; - createTransform(deprecations)({}); - expect(deprecations[0].calledOnce).to.be(true); - }); - - it('calls multiple deprecations in array', function () { - const deprecations = [sinon.spy(), sinon.spy()]; - createTransform(deprecations)({}); - expect(deprecations[0].calledOnce).to.be(true); - expect(deprecations[1].calledOnce).to.be(true); - }); - - it('passes log function to deprecation', function () { - const deprecation = sinon.spy(); - const log = function () {}; - createTransform([deprecation])({}, log); - expect(deprecation.args[0][1]).to.be(log); - }); - }); -}); diff --git a/src/legacy/deprecation/create_transform.js b/src/legacy/deprecation/create_transform.js deleted file mode 100644 index 72e8e153ed819..0000000000000 --- a/src/legacy/deprecation/create_transform.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { deepCloneWithBuffers as clone } from '../utils'; -import { forEach, noop } from 'lodash'; - -export function createTransform(deprecations) { - return (settings, log = noop) => { - const result = clone(settings); - - forEach(deprecations, (deprecation) => { - deprecation(result, log); - }); - - return result; - }; -} diff --git a/src/legacy/deprecation/deprecations/__tests__/rename.js b/src/legacy/deprecation/deprecations/__tests__/rename.js deleted file mode 100644 index 47c6b3257ff69..0000000000000 --- a/src/legacy/deprecation/deprecations/__tests__/rename.js +++ /dev/null @@ -1,83 +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 { rename } from '../rename'; -import sinon from 'sinon'; - -describe('deprecation/deprecations', function () { - describe('rename', function () { - it('should rename simple property', function () { - const value = 'value'; - const settings = { - before: value, - }; - - rename('before', 'after')(settings); - expect(settings.before).to.be(undefined); - expect(settings.after).to.be(value); - }); - - it('should rename nested property', function () { - const value = 'value'; - const settings = { - someObject: { - before: value, - }, - }; - - rename('someObject.before', 'someObject.after')(settings); - expect(settings.someObject.before).to.be(undefined); - expect(settings.someObject.after).to.be(value); - }); - - it('should rename property, even when the value is null', function () { - const value = null; - const settings = { - before: value, - }; - - rename('before', 'after')(settings); - expect(settings.before).to.be(undefined); - expect(settings.after).to.be(null); - }); - - it(`shouldn't log when a rename doesn't occur`, function () { - const settings = { - exists: true, - }; - - const log = sinon.spy(); - rename('doesntExist', 'alsoDoesntExist')(settings, log); - expect(log.called).to.be(false); - }); - - it('should log when a rename does occur', function () { - const settings = { - exists: true, - }; - - const log = sinon.spy(); - rename('exists', 'alsoExists')(settings, log); - - expect(log.calledOnce).to.be(true); - expect(log.args[0][0]).to.match(/exists.+deprecated/); - }); - }); -}); diff --git a/src/legacy/deprecation/deprecations/__tests__/unused.js b/src/legacy/deprecation/deprecations/__tests__/unused.js deleted file mode 100644 index 4907c2b166989..0000000000000 --- a/src/legacy/deprecation/deprecations/__tests__/unused.js +++ /dev/null @@ -1,76 +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 sinon from 'sinon'; -import { unused } from '../unused'; - -describe('deprecation/deprecations', function () { - describe('unused', function () { - it('should remove unused setting', function () { - const settings = { - old: true, - }; - - unused('old')(settings); - expect(settings.old).to.be(undefined); - }); - - it(`shouldn't remove used setting`, function () { - const value = 'value'; - const settings = { - new: value, - }; - - unused('old')(settings); - expect(settings.new).to.be(value); - }); - - it('should remove unused setting, even when null', function () { - const settings = { - old: null, - }; - - unused('old')(settings); - expect(settings.old).to.be(undefined); - }); - - it('should log when removing unused setting', function () { - const settings = { - old: true, - }; - - const log = sinon.spy(); - unused('old')(settings, log); - - expect(log.calledOnce).to.be(true); - expect(log.args[0][0]).to.match(/old.+deprecated/); - }); - - it(`shouldn't log when no setting is unused`, function () { - const settings = { - new: true, - }; - - const log = sinon.spy(); - unused('old')(settings, log); - expect(log.called).to.be(false); - }); - }); -}); diff --git a/src/legacy/deprecation/deprecations/index.js b/src/legacy/deprecation/deprecations/index.js deleted file mode 100644 index 527c99309ba80..0000000000000 --- a/src/legacy/deprecation/deprecations/index.js +++ /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. - */ - -export { rename } from './rename'; -export { unused } from './unused'; diff --git a/src/legacy/deprecation/deprecations/rename.js b/src/legacy/deprecation/deprecations/rename.js deleted file mode 100644 index c96b9146b4e2c..0000000000000 --- a/src/legacy/deprecation/deprecations/rename.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { set } from '@elastic/safer-lodash-set'; -import { get, isUndefined, noop } from 'lodash'; -import { unset } from '../../utils'; - -export function rename(oldKey, newKey) { - return (settings, log = noop) => { - const value = get(settings, oldKey); - if (isUndefined(value)) { - return; - } - - unset(settings, oldKey); - set(settings, newKey, value); - - log(`Config key "${oldKey}" is deprecated. It has been replaced with "${newKey}"`); - }; -} diff --git a/src/legacy/deprecation/deprecations/unused.js b/src/legacy/deprecation/deprecations/unused.js deleted file mode 100644 index 4291063dc482b..0000000000000 --- a/src/legacy/deprecation/deprecations/unused.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { get, isUndefined, noop } from 'lodash'; -import { unset } from '../../utils'; - -export function unused(oldKey) { - return (settings, log = noop) => { - const value = get(settings, oldKey); - if (isUndefined(value)) { - return; - } - - unset(settings, oldKey); - log(`${oldKey} is deprecated and is no longer used`); - }; -} diff --git a/src/legacy/deprecation/get_transform.js b/src/legacy/deprecation/get_transform.js deleted file mode 100644 index bf286901af62c..0000000000000 --- a/src/legacy/deprecation/get_transform.js +++ /dev/null @@ -1,30 +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 { noop } from 'lodash'; - -import { createTransform } from './create_transform'; -import { rename, unused } from './deprecations'; - -export async function getTransform(spec) { - const deprecationsProvider = spec.getDeprecationsProvider() || noop; - if (!deprecationsProvider) return; - const transforms = (await deprecationsProvider({ rename, unused })) || []; - return createTransform(transforms); -} diff --git a/src/legacy/deprecation/index.js b/src/legacy/deprecation/index.js deleted file mode 100644 index 787563e7353ce..0000000000000 --- a/src/legacy/deprecation/index.js +++ /dev/null @@ -1,24 +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 { rename, unused } from './deprecations'; - -export { createTransform } from './create_transform'; -export { getTransform } from './get_transform'; -export const Deprecations = { rename, unused }; diff --git a/src/legacy/plugin_discovery/plugin_config/__tests__/extend_config_service.js b/src/legacy/plugin_discovery/plugin_config/__tests__/extend_config_service.js index a74bfb872e99c..40f84f6f54b3b 100644 --- a/src/legacy/plugin_discovery/plugin_config/__tests__/extend_config_service.js +++ b/src/legacy/plugin_discovery/plugin_config/__tests__/extend_config_service.js @@ -45,10 +45,6 @@ describe('plugin discovery/extend config service', () => { enabled: Joi.boolean().default(true), test: Joi.string().default('bonk'), }).default(), - - deprecations({ rename }) { - return [rename('oldTest', 'test')]; - }, }), }) .getPluginSpecs() @@ -74,16 +70,14 @@ describe('plugin discovery/extend config service', () => { getConfigPrefix: sandbox.stub().returns(configPrefix), }; - const logDeprecation = sandbox.stub(); - const getSettings = sandbox.stub(SettingsNS, 'getSettings').returns(rootSettings.foo.bar); const getSchema = sandbox.stub(SchemaNS, 'getSchema').returns(schema); - await extendConfigService(pluginSpec, config, rootSettings, logDeprecation); + await extendConfigService(pluginSpec, config, rootSettings); sinon.assert.calledOnce(getSettings); - sinon.assert.calledWithExactly(getSettings, pluginSpec, rootSettings, logDeprecation); + sinon.assert.calledWithExactly(getSettings, pluginSpec, rootSettings); sinon.assert.calledOnce(getSchema); sinon.assert.calledWithExactly(getSchema, pluginSpec); @@ -145,41 +139,6 @@ describe('plugin discovery/extend config service', () => { expect(error.message).to.contain('"test" must be a string'); } }); - - it('calls logDeprecation() with deprecation messages', async () => { - const config = Config.withDefaultSchema(); - const logDeprecation = sinon.stub(); - await extendConfigService( - pluginSpec, - config, - { - foo: { - bar: { - baz: { - oldTest: '123', - }, - }, - }, - }, - logDeprecation - ); - sinon.assert.calledOnce(logDeprecation); - sinon.assert.calledWithExactly(logDeprecation, sinon.match('"oldTest" is deprecated')); - }); - - it('uses settings after transforming deprecations', async () => { - const config = Config.withDefaultSchema(); - await extendConfigService(pluginSpec, config, { - foo: { - bar: { - baz: { - oldTest: '123', - }, - }, - }, - }); - expect(config.get('foo.bar.baz.test')).to.be('123'); - }); }); describe('disableConfigExtension()', () => { diff --git a/src/legacy/plugin_discovery/plugin_config/__tests__/settings.js b/src/legacy/plugin_discovery/plugin_config/__tests__/settings.js index 2a26e29dfd63a..750c5ee6c6f50 100644 --- a/src/legacy/plugin_discovery/plugin_config/__tests__/settings.js +++ b/src/legacy/plugin_discovery/plugin_config/__tests__/settings.js @@ -18,7 +18,6 @@ */ import expect from '@kbn/expect'; -import sinon from 'sinon'; import { PluginPack } from '../../plugin_pack'; import { getSettings } from '../settings'; @@ -33,7 +32,6 @@ describe('plugin_discovery/settings', () => { provider: ({ Plugin }) => new Plugin({ configPrefix: 'a.b.c', - deprecations: ({ rename }) => [rename('foo', 'bar')], }), }) .getPluginSpecs() @@ -59,28 +57,5 @@ describe('plugin_discovery/settings', () => { it('allows rootSettings to be undefined', async () => { expect(await getSettings(pluginSpec)).to.eql(undefined); }); - - it('resolves deprecations', async () => { - const logDeprecation = sinon.stub(); - expect( - await getSettings( - pluginSpec, - { - a: { - b: { - c: { - foo: true, - }, - }, - }, - }, - logDeprecation - ) - ).to.eql({ - bar: true, - }); - - sinon.assert.calledOnce(logDeprecation); - }); }); }); diff --git a/src/legacy/plugin_discovery/plugin_config/extend_config_service.js b/src/legacy/plugin_discovery/plugin_config/extend_config_service.js index 9257227c0d62b..a6d5d4ae5f990 100644 --- a/src/legacy/plugin_discovery/plugin_config/extend_config_service.js +++ b/src/legacy/plugin_discovery/plugin_config/extend_config_service.js @@ -30,8 +30,8 @@ import { getSchema, getStubSchema } from './schema'; * @param {Function} [logDeprecation] * @return {Promise} */ -export async function extendConfigService(spec, config, rootSettings, logDeprecation) { - const settings = await getSettings(spec, rootSettings, logDeprecation); +export async function extendConfigService(spec, config, rootSettings) { + const settings = await getSettings(spec, rootSettings); const schema = await getSchema(spec); config.extendSchema(schema, settings, spec.getConfigPrefix()); } diff --git a/src/legacy/plugin_discovery/plugin_config/settings.js b/src/legacy/plugin_discovery/plugin_config/settings.js index 44ecb5718fe21..e6a4741d76eca 100644 --- a/src/legacy/plugin_discovery/plugin_config/settings.js +++ b/src/legacy/plugin_discovery/plugin_config/settings.js @@ -19,20 +19,16 @@ import { get } from 'lodash'; -import { getTransform } from '../../deprecation'; - /** * Get the settings for a pluginSpec from the raw root settings while * optionally calling logDeprecation() with warnings about deprecated * settings that were used * @param {PluginSpec} spec * @param {Object} rootSettings - * @param {Function} [logDeprecation] * @return {Promise} */ -export async function getSettings(spec, rootSettings, logDeprecation) { +export async function getSettings(spec, rootSettings) { const prefix = spec.getConfigPrefix(); const rawSettings = get(rootSettings, prefix); - const transform = await getTransform(spec); - return transform(rawSettings, logDeprecation); + return rawSettings; } diff --git a/src/legacy/plugin_discovery/plugin_spec/plugin_spec_options.d.ts b/src/legacy/plugin_discovery/plugin_spec/plugin_spec_options.d.ts index e51a355cbc8d2..e1ed2f57375a4 100644 --- a/src/legacy/plugin_discovery/plugin_spec/plugin_spec_options.d.ts +++ b/src/legacy/plugin_discovery/plugin_spec/plugin_spec_options.d.ts @@ -18,14 +18,10 @@ */ import { Server } from '../../server/kbn_server'; import { Capabilities } from '../../../core/server'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { SavedObjectsLegacyManagementDefinition } from '../../../core/server/saved_objects/types'; export type InitPluginFunction = (server: Server) => void; export interface UiExports { injectDefaultVars?: (server: Server) => { [key: string]: any }; - savedObjectsManagement?: SavedObjectsLegacyManagementDefinition; - mappings?: unknown; } export interface PluginSpecOptions { diff --git a/src/legacy/plugin_discovery/types.ts b/src/legacy/plugin_discovery/types.ts index 283806f69599a..700ca6fa68c95 100644 --- a/src/legacy/plugin_discovery/types.ts +++ b/src/legacy/plugin_discovery/types.ts @@ -19,11 +19,6 @@ import { Server } from '../server/kbn_server'; import { Capabilities } from '../../core/server'; -// Disable lint errors for imports from src/core/* until SavedObjects migration is complete -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { SavedObjectsSchemaDefinition } from '../../core/server/saved_objects/schema'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { SavedObjectsLegacyManagementDefinition } from '../../core/server/saved_objects/types'; import { AppCategory } from '../../core/types'; /** @@ -70,8 +65,6 @@ export interface LegacyPluginOptions { home: string[]; mappings: any; migrations: any; - savedObjectSchemas: SavedObjectsSchemaDefinition; - savedObjectsManagement: SavedObjectsLegacyManagementDefinition; visTypes: string[]; embeddableActions?: string[]; embeddableFactories?: string[]; diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index 6cd3f8dc448b0..ce7a500a00dc8 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -49,22 +49,6 @@ export default () => csp: HANDLED_IN_NEW_PLATFORM, - cpu: Joi.object({ - cgroup: Joi.object({ - path: Joi.object({ - override: Joi.string().default(), - }), - }), - }), - - cpuacct: Joi.object({ - cgroup: Joi.object({ - path: Joi.object({ - override: Joi.string().default(), - }), - }), - }), - server: Joi.object({ name: Joi.string().default(os.hostname()), // keep them for BWC, remove when not used in Legacy. @@ -144,6 +128,10 @@ export default () => ops: Joi.object({ interval: Joi.number().default(5000), + cGroupOverrides: Joi.object().keys({ + cpuPath: Joi.string().default(), + cpuAcctPath: Joi.string().default(), + }), }).default(), plugins: Joi.object({ @@ -231,6 +219,15 @@ export default () => locale: Joi.string().default('en'), }).default(), + // temporarily moved here from the (now deleted) kibana legacy plugin + kibana: Joi.object({ + enabled: Joi.boolean().default(true), + index: Joi.string().default('.kibana'), + autocompleteTerminateAfter: Joi.number().integer().min(1).default(100000), + // TODO Also allow units here like in elasticsearch config once this is moved to the new platform + autocompleteTimeout: Joi.number().integer().min(1).default(1000), + }).default(), + savedObjects: Joi.object({ maxImportPayloadBytes: Joi.number().default(10485760), maxImportExportSize: Joi.number().default(10000), diff --git a/src/legacy/server/http/integration_tests/max_payload_size.test.js b/src/legacy/server/http/integration_tests/max_payload_size.test.js index 789a54f681ba6..2d0718dd35606 100644 --- a/src/legacy/server/http/integration_tests/max_payload_size.test.js +++ b/src/legacy/server/http/integration_tests/max_payload_size.test.js @@ -17,7 +17,7 @@ * under the License. */ -import * as kbnTestServer from '../../../../test_utils/kbn_server'; +import * as kbnTestServer from '../../../../core/test_helpers/kbn_server'; let root; beforeAll(async () => { diff --git a/src/legacy/server/i18n/index.ts b/src/legacy/server/i18n/index.ts index 09f7022436049..e895f83fe6901 100644 --- a/src/legacy/server/i18n/index.ts +++ b/src/legacy/server/i18n/index.ts @@ -20,7 +20,6 @@ import { i18n, i18nLoader } from '@kbn/i18n'; import { basename } from 'path'; import { Server } from 'hapi'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths import { fromRoot } from '../../../core/server/utils'; import { getTranslationPaths } from './get_translations_path'; import { I18N_RC } from './constants'; diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts index 1a1f43b93f26e..663542618375a 100644 --- a/src/legacy/server/kbn_server.d.ts +++ b/src/legacy/server/kbn_server.d.ts @@ -17,43 +17,30 @@ * under the License. */ -import { ResponseObject, Server } from 'hapi'; -import { UnwrapPromise } from '@kbn/utility-types'; +import { Server } from 'hapi'; import { TelemetryCollectionManagerPluginSetup } from 'src/plugins/telemetry_collection_manager/server'; import { - ConfigService, CoreSetup, CoreStart, - ElasticsearchServiceSetup, EnvironmentMode, LoggerFactory, - SavedObjectsClientContract, - SavedObjectsLegacyService, - SavedObjectsClientProviderOptions, - IUiSettingsClient, PackageInfo, - LegacyRequest, LegacyServiceSetupDeps, - LegacyServiceStartDeps, LegacyServiceDiscoverPlugins, } from '../../core/server'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { LegacyConfig, ILegacyService, ILegacyInternals } from '../../core/server/legacy'; +import { LegacyConfig, ILegacyInternals } from '../../core/server/legacy'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { UiPlugins } from '../../core/server/plugins'; -import { CallClusterWithRequest, ElasticsearchPlugin } from '../core_plugins/elasticsearch'; +import { ElasticsearchPlugin } from '../core_plugins/elasticsearch'; import { UsageCollectionSetup } from '../../plugins/usage_collection/server'; import { HomeServerPluginSetup } from '../../plugins/home/server'; // lot of legacy code was assuming this type only had these two methods export type KibanaConfig = Pick; -export interface UiApp { - getId(): string; -} - // Extend the defaults with the plugins and server methods we need. declare module 'hapi' { interface PluginProperties { @@ -65,31 +52,9 @@ declare module 'hapi' { interface Server { config: () => KibanaConfig; - savedObjects: SavedObjectsLegacyService; - injectUiAppVars: (pluginName: string, getAppVars: () => { [key: string]: any }) => void; - getHiddenUiAppById(appId: string): UiApp; - addScopedTutorialContextFactory: ( - scopedTutorialContextFactory: (...args: any[]) => any - ) => void; - getInjectedUiAppVars: (pluginName: string) => { [key: string]: any }; - getUiNavLinks(): Array<{ _id: string }>; - addMemoizedFactoryToRequest: ( - name: string, - factoryFn: (request: Request) => Record - ) => void; logWithMetadata: (tags: string[], message: string, meta: Record) => void; newPlatform: KbnServer['newPlatform']; } - - interface Request { - getSavedObjectsClient(options?: SavedObjectsClientProviderOptions): SavedObjectsClientContract; - getBasePath(): string; - getUiSettingsService(): IUiSettingsClient; - } - - interface ResponseToolkit { - renderAppWithDefaultConfig(app: UiApp): ResponseObject; - } } type KbnMixinFunc = (kbnServer: KbnServer, server: Server, config: any) => Promise | void; @@ -105,11 +70,9 @@ export interface KibanaCore { __internals: { elasticsearch: LegacyServiceSetupDeps['core']['elasticsearch']; hapiServer: LegacyServiceSetupDeps['core']['http']['server']; - kibanaMigrator: LegacyServiceStartDeps['core']['savedObjects']['migrator']; legacy: ILegacyInternals; rendering: LegacyServiceSetupDeps['core']['rendering']; uiPlugins: UiPlugins; - savedObjectsClientProvider: LegacyServiceStartDeps['core']['savedObjects']['clientProvider']; }; env: { mode: Readonly; @@ -168,6 +131,3 @@ export default class KbnServer { // Re-export commonly used hapi types. export { Server, Request, ResponseToolkit } from 'hapi'; - -// Re-export commonly accessed api types. -export { SavedObjectsLegacyService, SavedObjectsClient } from 'src/core/server'; diff --git a/src/legacy/server/kbn_server.js b/src/legacy/server/kbn_server.js index 320086b6d6531..a5eefd140c8fa 100644 --- a/src/legacy/server/kbn_server.js +++ b/src/legacy/server/kbn_server.js @@ -33,8 +33,6 @@ import pidMixin from './pid'; import configCompleteMixin from './config/complete'; import { optimizeMixin } from '../../optimize'; import * as Plugins from './plugins'; -import { savedObjectsMixin } from './saved_objects/saved_objects_mixin'; -import { serverExtensionsMixin } from './server_extensions'; import { uiMixin } from '../ui'; import { i18nMixin } from './i18n'; @@ -91,8 +89,6 @@ export default class KbnServer { coreMixin, - // adds methods for extending this.server - serverExtensionsMixin, loggingMixin, warningsMixin, statusMixin, @@ -111,9 +107,6 @@ export default class KbnServer { uiMixin, - // setup saved object routes - savedObjectsMixin, - // setup routes that serve the @kbn/optimizer output optimizeMixin, diff --git a/src/legacy/server/logging/log_format.js b/src/legacy/server/logging/log_format.js index 8a80cbef1a9c5..6edda8c4be907 100644 --- a/src/legacy/server/logging/log_format.js +++ b/src/legacy/server/logging/log_format.js @@ -91,7 +91,7 @@ export default class TransformObjStream extends Stream.Transform { method: event.method || '', headers: event.headers, remoteAddress: source.remoteAddress, - userAgent: source.remoteAddress, + userAgent: source.userAgent, referer: source.referer, }; diff --git a/src/legacy/server/logging/log_format_json.test.js b/src/legacy/server/logging/log_format_json.test.js index 31e622ecae611..ec7296d21672b 100644 --- a/src/legacy/server/logging/log_format_json.test.js +++ b/src/legacy/server/logging/log_format_json.test.js @@ -21,7 +21,7 @@ import moment from 'moment'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { attachMetaData } from '../../../../src/core/server/legacy/logging/legacy_logging_server'; -import { createListStream, createPromiseFromStreams } from '../../utils'; +import { createListStream, createPromiseFromStreams } from '../../../core/server/utils'; import KbnLoggerJsonFormat from './log_format_json'; @@ -65,12 +65,14 @@ describe('KbnLoggerJsonFormat', () => { }, }; const result = await createPromiseFromStreams([createListStream([event]), format]); - const { type, method, statusCode, message } = JSON.parse(result); + const { type, method, statusCode, message, req } = JSON.parse(result); expect(type).toBe('response'); expect(method).toBe('GET'); expect(statusCode).toBe(200); expect(message).toBe('GET /path/to/resource 200 12000ms - 13.0B'); + expect(req.remoteAddress).toBe('127.0.0.1'); + expect(req.userAgent).toBe('Test Thing'); }); it('ops', async () => { diff --git a/src/legacy/server/logging/log_format_string.test.js b/src/legacy/server/logging/log_format_string.test.js index 067ad70380961..842325865cce2 100644 --- a/src/legacy/server/logging/log_format_string.test.js +++ b/src/legacy/server/logging/log_format_string.test.js @@ -21,7 +21,7 @@ import moment from 'moment'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { attachMetaData } from '../../../../src/core/server/legacy/logging/legacy_logging_server'; -import { createListStream, createPromiseFromStreams } from '../../utils'; +import { createListStream, createPromiseFromStreams } from '../../../core/server/utils'; import KbnLoggerStringFormat from './log_format_string'; diff --git a/src/legacy/server/saved_objects/saved_objects_mixin.js b/src/legacy/server/saved_objects/saved_objects_mixin.js deleted file mode 100644 index 185c8807ae8b5..0000000000000 --- a/src/legacy/server/saved_objects/saved_objects_mixin.js +++ /dev/null @@ -1,112 +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. - */ - -// Disable lint errors for imports from src/core/server/saved_objects until SavedObjects migration is complete -/* eslint-disable @kbn/eslint/no-restricted-paths */ -import { SavedObjectsSchema } from '../../../core/server/saved_objects/schema'; -import { - SavedObjectsClient, - SavedObjectsRepository, - exportSavedObjectsToStream, - importSavedObjectsFromStream, - resolveSavedObjectsImportErrors, -} from '../../../core/server/saved_objects'; -import { convertTypesToLegacySchema } from '../../../core/server/saved_objects/utils'; - -export function savedObjectsMixin(kbnServer, server) { - const migrator = kbnServer.newPlatform.__internals.kibanaMigrator; - const typeRegistry = kbnServer.newPlatform.start.core.savedObjects.getTypeRegistry(); - const mappings = migrator.getActiveMappings(); - const allTypes = typeRegistry.getAllTypes().map((t) => t.name); - const visibleTypes = typeRegistry.getVisibleTypes().map((t) => t.name); - const schema = new SavedObjectsSchema(convertTypesToLegacySchema(typeRegistry.getAllTypes())); - - server.decorate('server', 'kibanaMigrator', migrator); - - const warn = (message) => server.log(['warning', 'saved-objects'], message); - // we use kibana.index which is technically defined in the kibana plugin, so if - // we don't have the plugin (mainly tests) we can't initialize the saved objects - if (!kbnServer.pluginSpecs.some((p) => p.getId() === 'kibana')) { - warn('Saved Objects uninitialized because the Kibana plugin is disabled.'); - return; - } - - const serializer = kbnServer.newPlatform.start.core.savedObjects.createSerializer(); - - const createRepository = (callCluster, includedHiddenTypes = []) => { - if (typeof callCluster !== 'function') { - throw new TypeError('Repository requires a "callCluster" function to be provided.'); - } - // throw an exception if an extraType is not defined. - includedHiddenTypes.forEach((type) => { - if (!allTypes.includes(type)) { - throw new Error(`Missing mappings for saved objects type '${type}'`); - } - }); - const combinedTypes = visibleTypes.concat(includedHiddenTypes); - const allowedTypes = [...new Set(combinedTypes)]; - - const config = server.config(); - - return new SavedObjectsRepository({ - index: config.get('kibana.index'), - migrator, - mappings, - typeRegistry, - serializer, - allowedTypes, - callCluster, - }); - }; - - const provider = kbnServer.newPlatform.__internals.savedObjectsClientProvider; - - const service = { - types: visibleTypes, - SavedObjectsClient, - SavedObjectsRepository, - getSavedObjectsRepository: createRepository, - getScopedSavedObjectsClient: (...args) => provider.getClient(...args), - setScopedSavedObjectsClientFactory: (...args) => provider.setClientFactory(...args), - addScopedSavedObjectsClientWrapperFactory: (...args) => - provider.addClientWrapperFactory(...args), - importExport: { - objectLimit: server.config().get('savedObjects.maxImportExportSize'), - importSavedObjects: importSavedObjectsFromStream, - resolveImportErrors: resolveSavedObjectsImportErrors, - getSortedObjectsForExport: exportSavedObjectsToStream, - }, - schema, - }; - server.decorate('server', 'savedObjects', service); - - const savedObjectsClientCache = new WeakMap(); - server.decorate('request', 'getSavedObjectsClient', function (options) { - const request = this; - - if (savedObjectsClientCache.has(request)) { - return savedObjectsClientCache.get(request); - } - - const savedObjectsClient = server.savedObjects.getScopedSavedObjectsClient(request, options); - - savedObjectsClientCache.set(request, savedObjectsClient); - return savedObjectsClient; - }); -} diff --git a/src/legacy/server/saved_objects/saved_objects_mixin.test.js b/src/legacy/server/saved_objects/saved_objects_mixin.test.js deleted file mode 100644 index 63e4a632ab5e0..0000000000000 --- a/src/legacy/server/saved_objects/saved_objects_mixin.test.js +++ /dev/null @@ -1,282 +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 { savedObjectsMixin } from './saved_objects_mixin'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { mockKibanaMigrator } from '../../../core/server/saved_objects/migrations/kibana/kibana_migrator.mock'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { savedObjectsClientProviderMock } from '../../../core/server/saved_objects/service/lib/scoped_client_provider.mock'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { convertLegacyTypes } from '../../../core/server/saved_objects/utils'; -import { SavedObjectTypeRegistry } from '../../../core/server'; -import { coreMock } from '../../../core/server/mocks'; - -const mockConfig = { - get: jest.fn().mockReturnValue('anything'), -}; - -const savedObjectMappings = [ - { - pluginId: 'testtype', - properties: { - testtype: { - properties: { - name: { type: 'keyword' }, - }, - }, - }, - }, - { - pluginId: 'testtype2', - properties: { - doc1: { - properties: { - name: { type: 'keyword' }, - }, - }, - doc2: { - properties: { - name: { type: 'keyword' }, - }, - }, - }, - }, - { - pluginId: 'secretPlugin', - properties: { - hiddentype: { - properties: { - secret: { type: 'keyword' }, - }, - }, - }, - }, -]; - -const savedObjectSchemas = { - hiddentype: { - hidden: true, - }, - doc1: { - indexPattern: 'other-index', - }, -}; - -const savedObjectTypes = convertLegacyTypes( - { - savedObjectMappings, - savedObjectSchemas, - savedObjectMigrations: {}, - }, - mockConfig -); - -const typeRegistry = new SavedObjectTypeRegistry(); -savedObjectTypes.forEach((type) => typeRegistry.registerType(type)); - -const migrator = mockKibanaMigrator.create({ - types: savedObjectTypes, -}); - -describe('Saved Objects Mixin', () => { - let mockKbnServer; - let mockServer; - const mockCallCluster = jest.fn(); - const stubCallCluster = jest.fn(); - const config = { - 'kibana.index': 'kibana.index', - 'savedObjects.maxImportExportSize': 10000, - }; - const stubConfig = jest.fn((key) => { - return config[key]; - }); - - beforeEach(() => { - const clientProvider = savedObjectsClientProviderMock.create(); - mockServer = { - log: jest.fn(), - route: jest.fn(), - decorate: jest.fn(), - config: () => { - return { - get: stubConfig, - }; - }, - plugins: { - elasticsearch: { - getCluster: () => { - return { - callWithRequest: mockCallCluster, - callWithInternalUser: stubCallCluster, - }; - }, - waitUntilReady: jest.fn(), - }, - }, - }; - - const coreStart = coreMock.createStart(); - coreStart.savedObjects.getTypeRegistry.mockReturnValue(typeRegistry); - - mockKbnServer = { - newPlatform: { - __internals: { - kibanaMigrator: migrator, - savedObjectsClientProvider: clientProvider, - }, - setup: { - core: coreMock.createSetup(), - }, - start: { - core: coreStart, - }, - }, - server: mockServer, - ready: () => {}, - pluginSpecs: { - some: () => { - return true; - }, - }, - uiExports: { - savedObjectMappings, - savedObjectSchemas, - }, - }; - }); - - describe('no kibana plugin', () => { - it('should not try to create anything', () => { - mockKbnServer.pluginSpecs.some = () => false; - savedObjectsMixin(mockKbnServer, mockServer); - expect(mockServer.log).toHaveBeenCalledWith(expect.any(Array), expect.any(String)); - expect(mockServer.decorate).toHaveBeenCalledWith( - 'server', - 'kibanaMigrator', - expect.any(Object) - ); - expect(mockServer.decorate).toHaveBeenCalledTimes(1); - expect(mockServer.route).not.toHaveBeenCalled(); - }); - }); - - describe('Saved object service', () => { - let service; - - beforeEach(async () => { - await savedObjectsMixin(mockKbnServer, mockServer); - const call = mockServer.decorate.mock.calls.filter( - ([objName, methodName]) => objName === 'server' && methodName === 'savedObjects' - ); - service = call[0][2]; - }); - - it('should return all but hidden types', async () => { - expect(service).toBeDefined(); - expect(service.types).toEqual(['testtype', 'doc1', 'doc2']); - }); - - const mockCallEs = jest.fn(); - describe('repository creation', () => { - it('should not allow a repository with an undefined type', () => { - expect(() => { - service.getSavedObjectsRepository(mockCallEs, ['extraType']); - }).toThrow(new Error("Missing mappings for saved objects type 'extraType'")); - }); - - it('should create a repository without hidden types', () => { - const repository = service.getSavedObjectsRepository(mockCallEs); - expect(repository).toBeDefined(); - expect(repository._allowedTypes).toEqual(['testtype', 'doc1', 'doc2']); - }); - - it('should create a repository with a unique list of allowed types', () => { - const repository = service.getSavedObjectsRepository(mockCallEs, ['doc1', 'doc1', 'doc1']); - expect(repository._allowedTypes).toEqual(['testtype', 'doc1', 'doc2']); - }); - - it('should create a repository with extraTypes minus duplicate', () => { - const repository = service.getSavedObjectsRepository(mockCallEs, [ - 'hiddentype', - 'hiddentype', - ]); - expect(repository._allowedTypes).toEqual(['testtype', 'doc1', 'doc2', 'hiddentype']); - }); - - it('should not allow a repository without a callCluster function', () => { - expect(() => { - service.getSavedObjectsRepository({}); - }).toThrow(new Error('Repository requires a "callCluster" function to be provided.')); - }); - }); - - describe('get client', () => { - it('should have a method to get the client', () => { - expect(service).toHaveProperty('getScopedSavedObjectsClient'); - }); - - it('should have a method to set the client factory', () => { - expect(service).toHaveProperty('setScopedSavedObjectsClientFactory'); - }); - - it('should have a method to add a client wrapper factory', () => { - expect(service).toHaveProperty('addScopedSavedObjectsClientWrapperFactory'); - }); - - it('should allow you to set a scoped saved objects client factory', () => { - expect(() => { - service.setScopedSavedObjectsClientFactory({}); - }).not.toThrowError(); - }); - - it('should allow you to add a scoped saved objects client wrapper factory', () => { - expect(() => { - service.addScopedSavedObjectsClientWrapperFactory({}); - }).not.toThrowError(); - }); - }); - - describe('#getSavedObjectsClient', () => { - let getSavedObjectsClient; - - beforeEach(() => { - savedObjectsMixin(mockKbnServer, mockServer); - const call = mockServer.decorate.mock.calls.filter( - ([objName, methodName]) => objName === 'request' && methodName === 'getSavedObjectsClient' - ); - getSavedObjectsClient = call[0][2]; - }); - - it('should be callable', () => { - mockServer.savedObjects = service; - getSavedObjectsClient = getSavedObjectsClient.bind({}); - expect(() => { - getSavedObjectsClient(); - }).not.toThrowError(); - }); - - it('should use cached request object', () => { - mockServer.savedObjects = service; - getSavedObjectsClient = getSavedObjectsClient.bind({ _test: 'me' }); - const savedObjectsClient = getSavedObjectsClient(); - expect(getSavedObjectsClient()).toEqual(savedObjectsClient); - }); - }); - }); -}); diff --git a/src/legacy/server/server_extensions/add_memoized_factory_to_request.test.js b/src/legacy/server/server_extensions/add_memoized_factory_to_request.test.js deleted file mode 100644 index 48bd082468061..0000000000000 --- a/src/legacy/server/server_extensions/add_memoized_factory_to_request.test.js +++ /dev/null @@ -1,161 +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 sinon from 'sinon'; - -import { serverExtensionsMixin } from './server_extensions_mixin'; - -describe('server.addMemoizedFactoryToRequest()', () => { - const setup = () => { - class Request {} - - class Server { - constructor() { - sinon.spy(this, 'decorate'); - } - decorate(type, name, value) { - switch (type) { - case 'request': - return (Request.prototype[name] = value); - case 'server': - return (Server.prototype[name] = value); - default: - throw new Error(`Unexpected decorate type ${type}`); - } - } - } - - const server = new Server(); - serverExtensionsMixin({}, server); - return { server, Request }; - }; - - it('throws when propertyName is not a string', () => { - const { server } = setup(); - expect(() => server.addMemoizedFactoryToRequest()).toThrowError('methodName must be a string'); - expect(() => server.addMemoizedFactoryToRequest(null)).toThrowError( - 'methodName must be a string' - ); - expect(() => server.addMemoizedFactoryToRequest(1)).toThrowError('methodName must be a string'); - expect(() => server.addMemoizedFactoryToRequest(true)).toThrowError( - 'methodName must be a string' - ); - expect(() => server.addMemoizedFactoryToRequest(/abc/)).toThrowError( - 'methodName must be a string' - ); - expect(() => server.addMemoizedFactoryToRequest(['foo'])).toThrowError( - 'methodName must be a string' - ); - expect(() => server.addMemoizedFactoryToRequest([1])).toThrowError( - 'methodName must be a string' - ); - expect(() => server.addMemoizedFactoryToRequest({})).toThrowError( - 'methodName must be a string' - ); - }); - - it('throws when factory is not a function', () => { - const { server } = setup(); - expect(() => server.addMemoizedFactoryToRequest('name')).toThrowError( - 'factory must be a function' - ); - expect(() => server.addMemoizedFactoryToRequest('name', null)).toThrowError( - 'factory must be a function' - ); - expect(() => server.addMemoizedFactoryToRequest('name', 1)).toThrowError( - 'factory must be a function' - ); - expect(() => server.addMemoizedFactoryToRequest('name', true)).toThrowError( - 'factory must be a function' - ); - expect(() => server.addMemoizedFactoryToRequest('name', /abc/)).toThrowError( - 'factory must be a function' - ); - expect(() => server.addMemoizedFactoryToRequest('name', ['foo'])).toThrowError( - 'factory must be a function' - ); - expect(() => server.addMemoizedFactoryToRequest('name', [1])).toThrowError( - 'factory must be a function' - ); - expect(() => server.addMemoizedFactoryToRequest('name', {})).toThrowError( - 'factory must be a function' - ); - }); - - it('throws when factory takes more than one arg', () => { - const { server } = setup(); - /* eslint-disable no-unused-vars */ - expect(() => server.addMemoizedFactoryToRequest('name', () => {})).not.toThrowError( - 'more than one argument' - ); - expect(() => server.addMemoizedFactoryToRequest('name', (a) => {})).not.toThrowError( - 'more than one argument' - ); - expect(() => server.addMemoizedFactoryToRequest('name', (a, b) => {})).toThrowError( - 'more than one argument' - ); - expect(() => server.addMemoizedFactoryToRequest('name', (a, b, c) => {})).toThrowError( - 'more than one argument' - ); - expect(() => server.addMemoizedFactoryToRequest('name', (a, b, c, d) => {})).toThrowError( - 'more than one argument' - ); - expect(() => server.addMemoizedFactoryToRequest('name', (a, b, c, d, e) => {})).toThrowError( - 'more than one argument' - ); - /* eslint-enable no-unused-vars */ - }); - - it('decorates request objects with a function at `propertyName`', () => { - const { server, Request } = setup(); - - expect(new Request()).not.toHaveProperty('decorated'); - server.addMemoizedFactoryToRequest('decorated', () => {}); - expect(typeof new Request().decorated).toBe('function'); - }); - - it('caches invocations of the factory to the request instance', () => { - const { server, Request } = setup(); - const factory = sinon.stub().returnsArg(0); - server.addMemoizedFactoryToRequest('foo', factory); - - const request1 = new Request(); - const request2 = new Request(); - - // call `foo()` on both requests a bunch of times, each time - // the return value should be exactly the same - expect(request1.foo()).toBe(request1); - expect(request1.foo()).toBe(request1); - expect(request1.foo()).toBe(request1); - expect(request1.foo()).toBe(request1); - expect(request1.foo()).toBe(request1); - expect(request1.foo()).toBe(request1); - - expect(request2.foo()).toBe(request2); - expect(request2.foo()).toBe(request2); - expect(request2.foo()).toBe(request2); - expect(request2.foo()).toBe(request2); - - // only two different requests, so factory should have only been - // called twice with the two request objects - sinon.assert.calledTwice(factory); - sinon.assert.calledWithExactly(factory, request1); - sinon.assert.calledWithExactly(factory, request2); - }); -}); diff --git a/src/legacy/server/server_extensions/index.js b/src/legacy/server/server_extensions/index.js deleted file mode 100644 index e17bd488897f7..0000000000000 --- a/src/legacy/server/server_extensions/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { serverExtensionsMixin } from './server_extensions_mixin'; diff --git a/src/legacy/server/server_extensions/server_extensions_mixin.js b/src/legacy/server/server_extensions/server_extensions_mixin.js deleted file mode 100644 index 19c0b24ae15a1..0000000000000 --- a/src/legacy/server/server_extensions/server_extensions_mixin.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. - */ - -export function serverExtensionsMixin(kbnServer, server) { - /** - * Decorate all request objects with a new method, `methodName`, - * that will call the `factory` on first invocation and return - * the result of the first call to subsequent invocations. - * - * @method server.addMemoizedFactoryToRequest - * @param {string} methodName location on the request this - * factory should be added - * @param {Function} factory the factory to add to the request, - * which will be called once per request - * with a single argument, the request. - * @return {undefined} - */ - server.decorate('server', 'addMemoizedFactoryToRequest', (methodName, factory) => { - if (typeof methodName !== 'string') { - throw new TypeError('methodName must be a string'); - } - - if (typeof factory !== 'function') { - throw new TypeError('factory must be a function'); - } - - if (factory.length > 1) { - throw new TypeError(` - factory must not take more than one argument, the request object. - Memoization is done based on the request instance and is cached and reused - regardless of other arguments. If you want to have a per-request cache that - also does some sort of secondary memoization then return an object or function - from the memoized decorator and do secondary memoization there. - `); - } - - const requestCache = new WeakMap(); - server.decorate('request', methodName, function () { - const request = this; - - if (!requestCache.has(request)) { - requestCache.set(request, factory(request)); - } - - return requestCache.get(request); - }); - }); -} diff --git a/src/legacy/server/status/lib/metrics.js b/src/legacy/server/status/lib/metrics.js index 2631b245e72ab..478bf0829b1aa 100644 --- a/src/legacy/server/status/lib/metrics.js +++ b/src/legacy/server/status/lib/metrics.js @@ -116,8 +116,8 @@ export class Metrics { async captureCGroups() { try { const cgroup = await cGroupStats({ - cpuPath: this.config.get('cpu.cgroup.path.override'), - cpuAcctPath: this.config.get('cpuacct.cgroup.path.override'), + cpuPath: this.config.get('ops.cGroupOverrides.cpuPath'), + cpuAcctPath: this.config.get('ops.cGroupOverrides.cpuAcctPath'), }); if (isObject(cgroup)) { diff --git a/src/legacy/ui/ui_apps/__snapshots__/ui_apps_mixin.test.js.snap b/src/legacy/ui/ui_apps/__snapshots__/ui_apps_mixin.test.js.snap deleted file mode 100644 index cb2b6cda26a81..0000000000000 --- a/src/legacy/ui/ui_apps/__snapshots__/ui_apps_mixin.test.js.snap +++ /dev/null @@ -1,92 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`UiAppsMixin creates kbnServer.uiApps from uiExports 1`] = ` -Array [ - StubUiApp { - "_hidden": true, - "_id": "foo", - }, - StubUiApp { - "_hidden": false, - "_id": "bar", - }, -] -`; - -exports[`UiAppsMixin decorates server with methods 1`] = ` -Array [ - Array [ - "server", - "getAllUiApps", - [Function], - ], - Array [ - "server", - "getUiAppById", - [Function], - ], - Array [ - "server", - "getHiddenUiAppById", - [Function], - ], - Array [ - "server", - "injectUiAppVars", - [Function], - ], - Array [ - "server", - "getInjectedUiAppVars", - [Function], - ], -] -`; - -exports[`UiAppsMixin server.getAllUiApps() returns hidden and non-hidden apps 1`] = ` -Array [ - StubUiApp { - "_hidden": true, - "_id": "foo", - }, - StubUiApp { - "_hidden": false, - "_id": "bar", - }, -] -`; - -exports[`UiAppsMixin server.getHiddenUiAppById() returns hidden apps when requested, undefined for non-hidden and unknown apps 1`] = ` -StubUiApp { - "_hidden": true, - "_id": "foo", -} -`; - -exports[`UiAppsMixin server.getUiAppById() returns non-hidden apps when requested, undefined for non-hidden and unknown apps 1`] = ` -StubUiApp { - "_hidden": false, - "_id": "bar", -} -`; - -exports[`UiAppsMixin server.injectUiAppVars()/server.getInjectedUiAppVars() merges injected vars provided by multiple providers in the order they are registered: foo 1`] = ` -Object { - "bar": false, - "baz": 1, - "box": true, - "foo": true, -} -`; - -exports[`UiAppsMixin server.injectUiAppVars()/server.getInjectedUiAppVars() stored injectVars provider and returns provider result when requested: bar 1`] = ` -Object { - "thisIsFoo": false, -} -`; - -exports[`UiAppsMixin server.injectUiAppVars()/server.getInjectedUiAppVars() stored injectVars provider and returns provider result when requested: foo 1`] = ` -Object { - "thisIsFoo": true, -} -`; diff --git a/src/legacy/ui/ui_apps/__tests__/ui_app.js b/src/legacy/ui/ui_apps/__tests__/ui_app.js deleted file mode 100644 index bb4bcfe2d7443..0000000000000 --- a/src/legacy/ui/ui_apps/__tests__/ui_app.js +++ /dev/null @@ -1,306 +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 sinon from 'sinon'; -import expect from '@kbn/expect'; - -import { UiApp } from '../ui_app'; -import { UiNavLink } from '../../ui_nav_links'; - -function createStubUiAppSpec(extraParams) { - return { - id: 'uiapp-test', - main: 'main.js', - title: 'UIApp Test', - order: 9000, - icon: 'ui_app_test.svg', - linkToLastSubUrl: true, - hidden: false, - listed: false, - ...extraParams, - }; -} - -function createStubKbnServer(extraParams) { - return { - plugins: [], - config: { - get: sinon.stub().withArgs('server.basePath').returns(''), - }, - server: {}, - ...extraParams, - }; -} - -function createUiApp(spec = createStubUiAppSpec(), kbnServer = createStubKbnServer()) { - return new UiApp(kbnServer, spec); -} - -describe('ui apps / UiApp', () => { - describe('constructor', () => { - it('throws an exception if an ID is not given', () => { - const spec = {}; // should have id property - expect(() => createUiApp(spec)).to.throwException(); - }); - - describe('defaults', () => { - const spec = { id: 'uiapp-test-defaults' }; - const app = createUiApp(spec); - - it('has the ID from the spec', () => { - expect(app.getId()).to.be(spec.id); - }); - - it('has no plugin ID', () => { - expect(app.getPluginId()).to.be(undefined); - }); - - it('is not hidden', () => { - expect(app.isHidden()).to.be(false); - }); - - it('is listed', () => { - expect(app.isListed()).to.be(true); - }); - - it('has a navLink', () => { - expect(app.getNavLink()).to.be.a(UiNavLink); - }); - - it('has no main module', () => { - expect(app.getMainModuleId()).to.be(undefined); - }); - - it('has a mostly empty JSON representation', () => { - expect(JSON.parse(JSON.stringify(app))).to.eql({ - id: spec.id, - navLink: { - id: 'uiapp-test-defaults', - order: 0, - url: '/app/uiapp-test-defaults', - subUrlBase: '/app/uiapp-test-defaults', - linkToLastSubUrl: true, - hidden: false, - disabled: false, - tooltip: '', - }, - }); - }); - }); - - describe('mock spec', () => { - const spec = createStubUiAppSpec(); - const app = createUiApp(spec); - - it('has the ID from the spec', () => { - expect(app.getId()).to.be(spec.id); - }); - - it('has no plugin ID', () => { - expect(app.getPluginId()).to.be(undefined); - }); - - it('is not hidden', () => { - expect(app.isHidden()).to.be(false); - }); - - it('is also not listed', () => { - expect(app.isListed()).to.be(false); - }); - - it('has no navLink', () => { - expect(app.getNavLink()).to.be(undefined); - }); - - it('has a main module', () => { - expect(app.getMainModuleId()).to.be('main.js'); - }); - - it('has spec values in JSON representation', () => { - expect(JSON.parse(JSON.stringify(app))).to.eql({ - id: spec.id, - title: spec.title, - icon: spec.icon, - main: spec.main, - linkToLastSubUrl: spec.linkToLastSubUrl, - navLink: { - id: 'uiapp-test', - title: 'UIApp Test', - order: 9000, - url: '/app/uiapp-test', - subUrlBase: '/app/uiapp-test', - icon: 'ui_app_test.svg', - linkToLastSubUrl: true, - hidden: false, - disabled: false, - tooltip: '', - }, - }); - }); - }); - - /* - * The "hidden" and "listed" flags have an bound relationship. The "hidden" - * flag gets cast to a boolean value, and the "listed" flag is dependent on - * "hidden" - */ - describe('hidden flag', () => { - describe('is cast to boolean value', () => { - it('when undefined', () => { - const kbnServer = createStubKbnServer(); - const spec = { - id: 'uiapp-test', - }; - const newApp = new UiApp(kbnServer, spec); - expect(newApp.isHidden()).to.be(false); - }); - - it('when null', () => { - const kbnServer = createStubKbnServer(); - const spec = { - id: 'uiapp-test', - hidden: null, - }; - const newApp = new UiApp(kbnServer, spec); - expect(newApp.isHidden()).to.be(false); - }); - - it('when 0', () => { - const kbnServer = createStubKbnServer(); - const spec = { - id: 'uiapp-test', - hidden: 0, - }; - const newApp = new UiApp(kbnServer, spec); - expect(newApp.isHidden()).to.be(false); - }); - - it('when true', () => { - const kbnServer = createStubKbnServer(); - const spec = { - id: 'uiapp-test', - hidden: true, - }; - const newApp = new UiApp(kbnServer, spec); - expect(newApp.isHidden()).to.be(true); - }); - - it('when 1', () => { - const kbnServer = createStubKbnServer(); - const spec = { - id: 'uiapp-test', - hidden: 1, - }; - const newApp = new UiApp(kbnServer, spec); - expect(newApp.isHidden()).to.be(true); - }); - }); - }); - - describe('listed flag', () => { - describe('defaults to the opposite value of hidden', () => { - it(`when it's null and hidden is true`, () => { - const kbnServer = createStubKbnServer(); - const spec = { - id: 'uiapp-test', - hidden: true, - listed: null, - }; - const newApp = new UiApp(kbnServer, spec); - expect(newApp.isListed()).to.be(false); - }); - - it(`when it's null and hidden is false`, () => { - const kbnServer = createStubKbnServer(); - const spec = { - id: 'uiapp-test', - hidden: false, - listed: null, - }; - const newApp = new UiApp(kbnServer, spec); - expect(newApp.isListed()).to.be(true); - }); - - it(`when it's undefined and hidden is false`, () => { - const kbnServer = createStubKbnServer(); - const spec = { - id: 'uiapp-test', - hidden: false, - }; - const newApp = new UiApp(kbnServer, spec); - expect(newApp.isListed()).to.be(true); - }); - - it(`when it's undefined and hidden is true`, () => { - const kbnServer = createStubKbnServer(); - const spec = { - id: 'uiapp-test', - hidden: true, - }; - const newApp = new UiApp(kbnServer, spec); - expect(newApp.isListed()).to.be(false); - }); - }); - - it(`is set to true when it's passed as true`, () => { - const kbnServer = createStubKbnServer(); - const spec = { - id: 'uiapp-test', - listed: true, - }; - const newApp = new UiApp(kbnServer, spec); - expect(newApp.isListed()).to.be(true); - }); - - it(`is set to false when it's passed as false`, () => { - const kbnServer = createStubKbnServer(); - const spec = { - id: 'uiapp-test', - listed: false, - }; - const newApp = new UiApp(kbnServer, spec); - expect(newApp.isListed()).to.be(false); - }); - }); - }); - - describe('pluginId', () => { - describe('does not match a kbnServer plugin', () => { - it('throws an error at instantiation', () => { - expect(() => { - createUiApp(createStubUiAppSpec({ pluginId: 'foo' })); - }).to.throwException((error) => { - expect(error.message).to.match(/Unknown plugin id/); - }); - }); - }); - }); - - describe('#getMainModuleId', () => { - it('returns undefined by default', () => { - const app = createUiApp({ id: 'foo' }); - expect(app.getMainModuleId()).to.be(undefined); - }); - - it('returns main module id', () => { - const app = createUiApp({ id: 'foo', main: 'bar' }); - expect(app.getMainModuleId()).to.be('bar'); - }); - }); -}); diff --git a/src/legacy/ui/ui_apps/index.js b/src/legacy/ui/ui_apps/index.js deleted file mode 100644 index d64848b2c1a92..0000000000000 --- a/src/legacy/ui/ui_apps/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { uiAppsMixin } from './ui_apps_mixin'; diff --git a/src/legacy/ui/ui_apps/ui_app.js b/src/legacy/ui/ui_apps/ui_app.js deleted file mode 100644 index 7da9e39394deb..0000000000000 --- a/src/legacy/ui/ui_apps/ui_app.js +++ /dev/null @@ -1,128 +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 { UiNavLink } from '../ui_nav_links'; - -export class UiApp { - constructor(kbnServer, spec) { - const { - pluginId, - id = pluginId, - main, - title, - order = 0, - icon, - euiIconType, - hidden, - linkToLastSubUrl, - disableSubUrlTracking, - listed, - category, - url = `/app/${id}`, - } = spec; - - if (!id) { - throw new Error('Every app must specify an id'); - } - - this._id = id; - this._main = main; - this._title = title; - this._order = order; - this._icon = icon; - this._euiIconType = euiIconType; - this._linkToLastSubUrl = linkToLastSubUrl; - this._disableSubUrlTracking = disableSubUrlTracking; - this._category = category; - this._hidden = hidden; - this._listed = listed; - this._url = url; - this._pluginId = pluginId; - this._kbnServer = kbnServer; - - if (this._pluginId && !this._getPlugin()) { - throw new Error(`Unknown plugin id "${this._pluginId}"`); - } - - if (!this.isHidden()) { - // unless an app is hidden it gets a navlink, but we only respond to `getNavLink()` - // if the app is also listed. This means that all apps in the kibanaPayload will - // have a navLink property since that list includes all normally accessible apps - this._navLink = new UiNavLink({ - id: this._id, - title: this._title, - order: this._order, - icon: this._icon, - euiIconType: this._euiIconType, - url: this._url, - linkToLastSubUrl: this._linkToLastSubUrl, - disableSubUrlTracking: this._disableSubUrlTracking, - category: this._category, - }); - } - } - - getId() { - return this._id; - } - - getPluginId() { - const plugin = this._getPlugin(); - return plugin ? plugin.id : undefined; - } - - isHidden() { - return !!this._hidden; - } - - isListed() { - return !this.isHidden() && (this._listed == null || !!this._listed); - } - - getNavLink() { - if (this.isListed()) { - return this._navLink; - } - } - - getMainModuleId() { - return this._main; - } - - _getPlugin() { - const pluginId = this._pluginId; - const { plugins } = this._kbnServer; - - return pluginId ? plugins.find((plugin) => plugin.id === pluginId) : undefined; - } - - toJSON() { - return { - id: this._id, - title: this._title, - icon: this._icon, - euiIconType: this._euiIconType, - main: this._main, - navLink: this._navLink, - linkToLastSubUrl: this._linkToLastSubUrl, - disableSubUrlTracking: this._disableSubUrlTracking, - category: this._category, - }; - } -} diff --git a/src/legacy/ui/ui_apps/ui_apps_mixin.js b/src/legacy/ui/ui_apps/ui_apps_mixin.js deleted file mode 100644 index c80b12a46bee3..0000000000000 --- a/src/legacy/ui/ui_apps/ui_apps_mixin.js +++ /dev/null @@ -1,67 +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 { UiApp } from './ui_app'; - -/** - * @typedef {import('../../server/kbn_server').default} KbnServer - */ - -/** - * - * @param {KbnServer} kbnServer - * @param {KbnServer['server']} server - */ -export function uiAppsMixin(kbnServer, server) { - const { uiAppSpecs = [] } = kbnServer.uiExports; - const existingIds = new Set(); - const appsById = new Map(); - const hiddenAppsById = new Map(); - - kbnServer.uiApps = uiAppSpecs.map((spec) => { - const app = new UiApp(kbnServer, spec); - const id = app.getId(); - - if (!existingIds.has(id)) { - existingIds.add(id); - } else { - throw new Error(`Unable to create two apps with the id ${id}.`); - } - - if (app.isHidden()) { - hiddenAppsById.set(id, app); - } else { - appsById.set(id, app); - } - - return app; - }); - - server.decorate('server', 'getAllUiApps', () => kbnServer.uiApps.slice(0)); - server.decorate('server', 'getUiAppById', (id) => appsById.get(id)); - server.decorate('server', 'getHiddenUiAppById', (id) => hiddenAppsById.get(id)); - server.decorate('server', 'injectUiAppVars', (appId, provider) => - kbnServer.newPlatform.__internals.legacy.injectUiAppVars(appId, provider) - ); - server.decorate( - 'server', - 'getInjectedUiAppVars', - async (appId) => await kbnServer.newPlatform.__internals.legacy.getInjectedUiAppVars(appId) - ); -} diff --git a/src/legacy/ui/ui_apps/ui_apps_mixin.test.js b/src/legacy/ui/ui_apps/ui_apps_mixin.test.js deleted file mode 100644 index 048358edfc10b..0000000000000 --- a/src/legacy/ui/ui_apps/ui_apps_mixin.test.js +++ /dev/null @@ -1,144 +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 { LegacyInternals } from '../../../core/server'; - -import { uiAppsMixin } from './ui_apps_mixin'; - -jest.mock('./ui_app', () => ({ - UiApp: class StubUiApp { - constructor(kbnServer, spec) { - this._id = spec.id; - this._hidden = !!spec.hidden; - } - getId() { - return this._id; - } - isHidden() { - return this._hidden; - } - }, -})); - -describe('UiAppsMixin', () => { - let kbnServer; - let server; - let uiExports; - - beforeEach(() => { - uiExports = { - uiAppSpecs: [ - { - id: 'foo', - hidden: true, - }, - { - id: 'bar', - hidden: false, - }, - ], - }; - server = { - decorate: jest.fn((type, name, value) => { - if (type !== 'server') { - return; - } - - server[name] = value; - }), - }; - kbnServer = { - uiExports, - newPlatform: { - __internals: { - legacy: new LegacyInternals(uiExports, {}, server), - }, - }, - }; - - uiAppsMixin(kbnServer, server); - }); - - it('creates kbnServer.uiApps from uiExports', () => { - expect(kbnServer.uiApps).toMatchSnapshot(); - }); - - it('decorates server with methods', () => { - expect(server.decorate.mock.calls).toMatchSnapshot(); - }); - - describe('server.getAllUiApps()', () => { - it('returns hidden and non-hidden apps', () => { - expect(server.getAllUiApps()).toMatchSnapshot(); - }); - }); - - describe('server.getUiAppById()', () => { - it('returns non-hidden apps when requested, undefined for non-hidden and unknown apps', () => { - expect(server.getUiAppById('foo')).toBe(undefined); - expect(server.getUiAppById('bar')).toMatchSnapshot(); - expect(server.getUiAppById('baz')).toBe(undefined); - }); - }); - - describe('server.getHiddenUiAppById()', () => { - it('returns hidden apps when requested, undefined for non-hidden and unknown apps', () => { - expect(server.getHiddenUiAppById('foo')).toMatchSnapshot(); - expect(server.getHiddenUiAppById('bar')).toBe(undefined); - expect(server.getHiddenUiAppById('baz')).toBe(undefined); - }); - }); - - describe('server.injectUiAppVars()/server.getInjectedUiAppVars()', () => { - it('stored injectVars provider and returns provider result when requested', async () => { - server.injectUiAppVars('foo', () => ({ - thisIsFoo: true, - })); - - server.injectUiAppVars('bar', async () => ({ - thisIsFoo: false, - })); - - await expect(server.getInjectedUiAppVars('foo')).resolves.toMatchSnapshot('foo'); - await expect(server.getInjectedUiAppVars('bar')).resolves.toMatchSnapshot('bar'); - await expect(server.getInjectedUiAppVars('baz')).resolves.toEqual({}); - }); - - it('merges injected vars provided by multiple providers in the order they are registered', async () => { - server.injectUiAppVars('foo', () => ({ - foo: true, - bar: true, - baz: true, - })); - - server.injectUiAppVars('foo', async () => ({ - bar: false, - box: true, - })); - - server.injectUiAppVars('foo', async () => ({ - baz: 1, - })); - - await expect(server.getInjectedUiAppVars('foo')).resolves.toMatchSnapshot('foo'); - await expect(server.getInjectedUiAppVars('bar')).resolves.toEqual({}); - await expect(server.getInjectedUiAppVars('baz')).resolves.toEqual({}); - }); - }); -}); diff --git a/src/legacy/ui/ui_exports/README.md b/src/legacy/ui/ui_exports/README.md index ab81febe67993..7fb117b1c25b9 100644 --- a/src/legacy/ui/ui_exports/README.md +++ b/src/legacy/ui/ui_exports/README.md @@ -88,7 +88,6 @@ This reducer format was chosen so that it will be easier to look back at these r The [`ui_exports/ui_export_defaults`][UiExportDefaults] module defines the default shape of the uiExports object produced by `collectUiExports()`. The defaults generally describe the `uiExports` from the UI System itself, like default visTypes and such. -[UiApp]: ../ui_apps/ui_app.js "UiApp class definition" [UiExportDefaults]: ./ui_export_defaults.js "uiExport defaults definition" [UiExportTypes]: ./ui_export_types/index.js "Index of default ui_export_types module" [UiAppExportType]: ./ui_export_types/ui_apps.js "UiApp extension type definition" diff --git a/src/legacy/ui/ui_exports/__tests__/collect_ui_exports.js b/src/legacy/ui/ui_exports/__tests__/collect_ui_exports.js deleted file mode 100644 index 5b2af9f82333c..0000000000000 --- a/src/legacy/ui/ui_exports/__tests__/collect_ui_exports.js +++ /dev/null @@ -1,117 +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 { PluginPack } from '../../../plugin_discovery'; - -import { collectUiExports } from '../collect_ui_exports'; - -const specs = new PluginPack({ - path: '/dev/null', - pkg: { - name: 'test', - version: 'kibana', - }, - provider({ Plugin }) { - return [ - new Plugin({ - id: 'test', - uiExports: { - savedObjectSchemas: { - foo: { - isNamespaceAgnostic: true, - }, - }, - }, - }), - new Plugin({ - id: 'test2', - uiExports: { - savedObjectSchemas: { - bar: { - isNamespaceAgnostic: true, - }, - }, - }, - }), - ]; - }, -}).getPluginSpecs(); - -describe('plugin discovery', () => { - describe('collectUiExports()', () => { - it('merges uiExports from all provided plugin specs', () => { - const uiExports = collectUiExports(specs); - - expect(uiExports.savedObjectSchemas).to.eql({ - foo: { - isNamespaceAgnostic: true, - }, - bar: { - isNamespaceAgnostic: true, - }, - }); - }); - - it(`throws an error when migrations and mappings aren't defined in the same plugin`, () => { - const invalidSpecs = new PluginPack({ - path: '/dev/null', - pkg: { - name: 'test', - version: 'kibana', - }, - provider({ Plugin }) { - return [ - new Plugin({ - id: 'test', - uiExports: { - mappings: { - 'test-type': { - properties: {}, - }, - }, - }, - }), - new Plugin({ - id: 'test2', - uiExports: { - migrations: { - 'test-type': { - '1.2.3': (doc) => { - return doc; - }, - }, - }, - }, - }), - ]; - }, - }).getPluginSpecs(); - expect(() => collectUiExports(invalidSpecs)).to.throwError((err) => { - expect(err).to.be.a(Error); - expect(err).to.have.property( - 'message', - 'Migrations and mappings must be defined together in the uiExports of a single plugin. ' + - 'test2 defines migrations for types test-type but does not define their mappings.' - ); - }); - }); - }); -}); diff --git a/src/legacy/ui/ui_mixin.js b/src/legacy/ui/ui_mixin.js index 54da001d20669..caa0ca4890661 100644 --- a/src/legacy/ui/ui_mixin.js +++ b/src/legacy/ui/ui_mixin.js @@ -17,10 +17,8 @@ * under the License. */ -import { uiAppsMixin } from './ui_apps'; import { uiRenderMixin } from './ui_render'; export async function uiMixin(kbnServer) { - await kbnServer.mixin(uiAppsMixin); await kbnServer.mixin(uiRenderMixin); } diff --git a/src/legacy/ui/ui_nav_links/__tests__/ui_nav_link.js b/src/legacy/ui/ui_nav_links/__tests__/ui_nav_link.js deleted file mode 100644 index 42368722f11ff..0000000000000 --- a/src/legacy/ui/ui_nav_links/__tests__/ui_nav_link.js +++ /dev/null @@ -1,129 +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 { UiNavLink } from '../ui_nav_link'; - -describe('UiNavLink', () => { - describe('constructor', () => { - it('initializes the object properties as expected', () => { - const spec = { - id: 'discover', - title: 'Discover', - order: -1003, - url: '/app/discover#/', - euiIconType: 'discoverApp', - hidden: true, - disabled: true, - }; - - const link = new UiNavLink(spec); - expect(link.toJSON()).to.eql({ - id: spec.id, - title: spec.title, - order: spec.order, - url: spec.url, - subUrlBase: spec.url, - icon: spec.icon, - euiIconType: spec.euiIconType, - hidden: spec.hidden, - disabled: spec.disabled, - category: undefined, - - // defaults - linkToLastSubUrl: true, - disableSubUrlTracking: undefined, - tooltip: '', - }); - }); - - it('initializes the order property to 0 when order is not specified in the spec', () => { - const spec = { - id: 'discover', - title: 'Discover', - url: '/app/discover#/', - }; - const link = new UiNavLink(spec); - - expect(link.toJSON()).to.have.property('order', 0); - }); - - it('initializes the linkToLastSubUrl property to false when false is specified in the spec', () => { - const spec = { - id: 'discover', - title: 'Discover', - order: -1003, - url: '/app/discover#/', - linkToLastSubUrl: false, - }; - const link = new UiNavLink(spec); - - expect(link.toJSON()).to.have.property('linkToLastSubUrl', false); - }); - - it('initializes the linkToLastSubUrl property to true by default', () => { - const spec = { - id: 'discover', - title: 'Discover', - order: -1003, - url: '/app/discover#/', - }; - const link = new UiNavLink(spec); - - expect(link.toJSON()).to.have.property('linkToLastSubUrl', true); - }); - - it('initializes the hidden property to false by default', () => { - const spec = { - id: 'discover', - title: 'Discover', - order: -1003, - url: '/app/discover#/', - }; - const link = new UiNavLink(spec); - - expect(link.toJSON()).to.have.property('hidden', false); - }); - - it('initializes the disabled property to false by default', () => { - const spec = { - id: 'discover', - title: 'Discover', - order: -1003, - url: '/app/discover#/', - }; - const link = new UiNavLink(spec); - - expect(link.toJSON()).to.have.property('disabled', false); - }); - - it('initializes the tooltip property to an empty string by default', () => { - const spec = { - id: 'discover', - title: 'Discover', - order: -1003, - url: '/app/discover#/', - }; - const link = new UiNavLink(spec); - - expect(link.toJSON()).to.have.property('tooltip', ''); - }); - }); -}); diff --git a/src/legacy/ui/ui_nav_links/index.js b/src/legacy/ui/ui_nav_links/index.js deleted file mode 100644 index e725a77ee1183..0000000000000 --- a/src/legacy/ui/ui_nav_links/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { UiNavLink } from './ui_nav_link'; diff --git a/src/legacy/ui/ui_nav_links/ui_nav_link.js b/src/legacy/ui/ui_nav_links/ui_nav_link.js deleted file mode 100644 index f56809f7ebb80..0000000000000 --- a/src/legacy/ui/ui_nav_links/ui_nav_link.js +++ /dev/null @@ -1,74 +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 class UiNavLink { - constructor(spec) { - const { - id, - title, - order = 0, - url, - subUrlBase, - disableSubUrlTracking, - icon, - euiIconType, - linkToLastSubUrl = true, - hidden = false, - disabled = false, - tooltip = '', - category, - } = spec; - - this._id = id; - this._title = title; - this._order = order; - this._url = url; - this._subUrlBase = subUrlBase || url; - this._disableSubUrlTracking = disableSubUrlTracking; - this._icon = icon; - this._euiIconType = euiIconType; - this._linkToLastSubUrl = linkToLastSubUrl; - this._hidden = hidden; - this._disabled = disabled; - this._tooltip = tooltip; - this._category = category; - } - - getOrder() { - return this._order; - } - - toJSON() { - return { - id: this._id, - title: this._title, - order: this._order, - url: this._url, - subUrlBase: this._subUrlBase, - icon: this._icon, - euiIconType: this._euiIconType, - linkToLastSubUrl: this._linkToLastSubUrl, - disableSubUrlTracking: this._disableSubUrlTracking, - hidden: this._hidden, - disabled: this._disabled, - tooltip: this._tooltip, - category: this._category, - }; - } -} diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index 8cc2cd1321a62..e3b7c1e0c3ff9 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -182,49 +182,36 @@ export function uiRenderMixin(kbnServer, server, config) { path: '/app/{id}/{any*}', method: 'GET', async handler(req, h) { - const id = req.params.id; - const app = server.getUiAppById(id); try { - return await h.renderApp(app); + return await h.renderApp(); } catch (err) { throw Boom.boomify(err); } }, }); - async function renderApp( - h, - app = { getId: () => 'core' }, - includeUserSettings = true, - overrides = {} - ) { + async function renderApp(h) { + const app = { getId: () => 'core' }; const { http } = kbnServer.newPlatform.setup.core; - const { - rendering, - legacy, - savedObjectsClientProvider: savedObjects, - } = kbnServer.newPlatform.__internals; + const { savedObjects } = kbnServer.newPlatform.start.core; + const { rendering, legacy } = kbnServer.newPlatform.__internals; + const req = KibanaRequest.from(h.request); const uiSettings = kbnServer.newPlatform.start.core.uiSettings.asScopedToClient( - savedObjects.getClient(h.request) + savedObjects.getScopedClient(req) ); const vars = await legacy.getVars(app.getId(), h.request, { apmConfig: getApmConfig(h.request.path), - ...overrides, }); const content = await rendering.render(h.request, uiSettings, { app, - includeUserSettings, + includeUserSettings: true, vars, }); return h.response(content).type('text/html').header('content-security-policy', http.csp.header); } - server.decorate('toolkit', 'renderApp', function (app, overrides) { - return renderApp(this, app, true, overrides); - }); - - server.decorate('toolkit', 'renderAppWithDefaultConfig', function (app) { - return renderApp(this, app, false); + server.decorate('toolkit', 'renderApp', function () { + return renderApp(this); }); } diff --git a/src/legacy/utils/artifact_type.ts b/src/legacy/utils/artifact_type.ts index 69f728e9e2220..ef471ef8e050d 100644 --- a/src/legacy/utils/artifact_type.ts +++ b/src/legacy/utils/artifact_type.ts @@ -17,7 +17,6 @@ * under the License. */ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths import { pkg } from '../../core/server/utils'; export const IS_KIBANA_DISTRIBUTABLE = pkg.build && pkg.build.distributable === true; export const IS_KIBANA_RELEASE = pkg.build && pkg.build.release === true; diff --git a/src/legacy/utils/index.d.ts b/src/legacy/utils/index.d.ts index c294c79542bbe..a57caad1d34bf 100644 --- a/src/legacy/utils/index.d.ts +++ b/src/legacy/utils/index.d.ts @@ -18,16 +18,3 @@ */ export function unset(object: object, rawPath: string): void; - -export { - concatStreamProviders, - createConcatStream, - createFilterStream, - createIntersperseStream, - createListStream, - createMapStream, - createPromiseFromStreams, - createReduceStream, - createReplaceStream, - createSplitStream, -} from './streams'; diff --git a/src/legacy/utils/index.js b/src/legacy/utils/index.js index 4274fb2e4901a..e2e2331b3aea6 100644 --- a/src/legacy/utils/index.js +++ b/src/legacy/utils/index.js @@ -17,21 +17,7 @@ * under the License. */ -export { BinderBase } from './binder'; -export { BinderFor } from './binder_for'; export { deepCloneWithBuffers } from './deep_clone_with_buffers'; export { unset } from './unset'; export { IS_KIBANA_DISTRIBUTABLE } from './artifact_type'; export { IS_KIBANA_RELEASE } from './artifact_type'; - -export { - concatStreamProviders, - createConcatStream, - createIntersperseStream, - createListStream, - createPromiseFromStreams, - createReduceStream, - createSplitStream, - createMapStream, - createReplaceStream, -} from './streams'; diff --git a/src/legacy/utils/path_contains.js b/src/legacy/utils/path_contains.js deleted file mode 100644 index 60d05c1099554..0000000000000 --- a/src/legacy/utils/path_contains.js +++ /dev/null @@ -1,24 +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 { relative } from 'path'; - -export default function pathContains(root, child) { - return relative(child, root).slice(0, 2) !== '..'; -} diff --git a/src/legacy/utils/streams/concat_stream.js b/src/legacy/utils/streams/concat_stream.js deleted file mode 100644 index e3f8f7261d2b7..0000000000000 --- a/src/legacy/utils/streams/concat_stream.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { createReduceStream } from './reduce_stream'; - -/** - * Creates a Transform stream that consumes all provided - * values and concatenates them using each values `concat` - * method. - * - * Concatenate strings: - * createListStream(['f', 'o', 'o']) - * .pipe(createConcatStream()) - * .on('data', console.log) - * // logs "foo" - * - * Concatenate values into an array: - * createListStream([1,2,3]) - * .pipe(createConcatStream([])) - * .on('data', console.log) - * // logs "[1,2,3]" - * - * - * @param {any} initial The initial value that subsequent - * items will concat with - * @return {Transform} - */ -export function createConcatStream(initial) { - return createReduceStream((acc, chunk) => acc.concat(chunk), initial); -} diff --git a/src/legacy/utils/streams/concat_stream_providers.js b/src/legacy/utils/streams/concat_stream_providers.js deleted file mode 100644 index 11dfb84284df3..0000000000000 --- a/src/legacy/utils/streams/concat_stream_providers.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 { PassThrough } from 'stream'; - -/** - * Write the data and errors from a list of stream providers - * to a single stream in order. Stream providers are only - * called right before they will be consumed, and only one - * provider will be active at a time. - * - * @param {Array<() => ReadableStream>} sourceProviders - * @param {PassThroughOptions} options options passed to the PassThrough constructor - * @return {WritableStream} combined stream - */ -export function concatStreamProviders(sourceProviders, options = {}) { - const destination = new PassThrough(options); - const queue = sourceProviders.slice(); - - (function pipeNext() { - const provider = queue.shift(); - - if (!provider) { - return; - } - - const source = provider(); - const isLast = !queue.length; - - // if there are more sources to pipe, hook - // into the source completion - if (!isLast) { - source.once('end', pipeNext); - } - - source - // proxy errors from the source to the destination - .once('error', (error) => destination.emit('error', error)) - // pipe the source to the destination but only proxy the - // end event if this is the last source - .pipe(destination, { end: isLast }); - })(); - - return destination; -} diff --git a/src/legacy/utils/streams/index.d.ts b/src/legacy/utils/streams/index.d.ts deleted file mode 100644 index 470b5d9fa3505..0000000000000 --- a/src/legacy/utils/streams/index.d.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Readable, Writable, Transform, TransformOptions } from 'stream'; - -export function concatStreamProviders( - sourceProviders: Array<() => Readable>, - options: TransformOptions -): Transform; -export function createIntersperseStream(intersperseChunk: string | Buffer): Transform; -export function createSplitStream(splitChunk: T): Transform; -export function createListStream(items: any | any[]): Readable; -export function createReduceStream(reducer: (value: any, chunk: T, enc: string) => T): Transform; -export function createPromiseFromStreams([first, ...rest]: [Readable, ...Writable[]]): Promise< - T ->; -export function createConcatStream(initial?: any): Transform; -export function createMapStream(fn: (value: T, i: number) => void): Transform; -export function createReplaceStream(toReplace: string, replacement: string | Buffer): Transform; -export function createFilterStream(fn: (obj: T) => boolean): Transform; diff --git a/src/legacy/utils/streams/intersperse_stream.js b/src/legacy/utils/streams/intersperse_stream.js deleted file mode 100644 index 5f9f0b03cd7eb..0000000000000 --- a/src/legacy/utils/streams/intersperse_stream.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 { Transform } from 'stream'; - -/** - * Create a Transform stream that receives values in object mode, - * and intersperses a chunk between each object received. - * - * This is useful for writing lists: - * - * createListStream(['foo', 'bar']) - * .pipe(createIntersperseStream('\n')) - * .pipe(process.stdout) // outputs "foo\nbar" - * - * Combine with a concat stream to get "join" like functionality: - * - * await createPromiseFromStreams([ - * createListStream(['foo', 'bar']), - * createIntersperseStream(' '), - * createConcatStream() - * ]) // produces a single value "foo bar" - * - * @param {String|Buffer} intersperseChunk - * @return {Transform} - */ -export function createIntersperseStream(intersperseChunk) { - let first = true; - - return new Transform({ - writableObjectMode: true, - readableObjectMode: true, - transform(chunk, enc, callback) { - try { - if (first) { - first = false; - } else { - this.push(intersperseChunk); - } - - this.push(chunk); - callback(null); - } catch (err) { - callback(err); - } - }, - }); -} diff --git a/src/legacy/utils/streams/list_stream.js b/src/legacy/utils/streams/list_stream.js deleted file mode 100644 index a614620b054b7..0000000000000 --- a/src/legacy/utils/streams/list_stream.js +++ /dev/null @@ -1,44 +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 { Readable } from 'stream'; - -/** - * Create a Readable stream that provides the items - * from a list as objects to subscribers - * - * @param {Array} items - the list of items to provide - * @return {Readable} - */ -export function createListStream(items = []) { - const queue = [].concat(items); - - return new Readable({ - objectMode: true, - read(size) { - queue.splice(0, size).forEach((item) => { - this.push(item); - }); - - if (!queue.length) { - this.push(null); - } - }, - }); -} diff --git a/src/legacy/utils/streams/map_stream.js b/src/legacy/utils/streams/map_stream.js deleted file mode 100644 index 4e906471330f1..0000000000000 --- a/src/legacy/utils/streams/map_stream.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Transform } from 'stream'; - -export function createMapStream(fn) { - let i = 0; - - return new Transform({ - objectMode: true, - async transform(value, enc, done) { - try { - this.push(await fn(value, i++)); - done(); - } catch (err) { - done(err); - } - }, - }); -} diff --git a/src/legacy/utils/streams/promise_from_streams.js b/src/legacy/utils/streams/promise_from_streams.js deleted file mode 100644 index 05f6a08aa1a09..0000000000000 --- a/src/legacy/utils/streams/promise_from_streams.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. - */ - -/** - * Take an array of streams, pipe the output - * from each one into the next, listening for - * errors from any of the streams, and then resolve - * the promise once the final stream has finished - * writing/reading. - * - * If the last stream is readable, it's final value - * will be provided as the promise value. - * - * Errors emitted from any stream will cause - * the promise to be rejected with that error. - * - * @param {Array} streams - * @return {Promise} - */ - -import { pipeline, Writable } from 'stream'; - -export async function createPromiseFromStreams(streams) { - let finalChunk; - const last = streams[streams.length - 1]; - if (typeof last.read !== 'function' && streams.length === 1) { - // For a nicer error than what stream.pipeline throws - throw new Error('A minimum of 2 streams is required when a non-readable stream is given'); - } - if (typeof last.read === 'function') { - // We are pushing a writable stream to capture the last chunk - streams.push( - new Writable({ - // Use object mode even when "last" stream isn't. This allows to - // capture the last chunk as-is. - objectMode: true, - write(chunk, enc, done) { - finalChunk = chunk; - done(); - }, - }) - ); - } - return new Promise((resolve, reject) => { - pipeline(...streams, (err) => { - if (err) return reject(err); - resolve(finalChunk); - }); - }); -} diff --git a/src/legacy/utils/streams/reduce_stream.js b/src/legacy/utils/streams/reduce_stream.js deleted file mode 100644 index d66b0124d1dab..0000000000000 --- a/src/legacy/utils/streams/reduce_stream.js +++ /dev/null @@ -1,80 +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 { Transform } from 'stream'; - -/** - * Create a transform stream that consumes each chunk it receives - * and passes it to the reducer, which will return the new value - * for the stream. Once all chunks have been received the reduce - * stream provides the result of final call to the reducer to - * subscribers. - * - * @param {Function} - * @param {any} initial Initial value for the stream, if undefined - * then the first chunk provided is used as the - * initial value. - * @return {Transform} - */ -export function createReduceStream(reducer, initial) { - let i = -1; - let value = initial; - - // if the reducer throws an error then the value is - // considered invalid and the stream will never provide - // it to subscribers. We will also stop calling the - // reducer for any new data that is provided to us - let failed = false; - - if (typeof reducer !== 'function') { - throw new TypeError('reducer must be a function'); - } - - return new Transform({ - readableObjectMode: true, - writableObjectMode: true, - async transform(chunk, enc, callback) { - try { - if (failed) { - return callback(); - } - - i += 1; - if (i === 0 && initial === undefined) { - value = chunk; - } else { - value = await reducer(value, chunk, enc); - } - - callback(); - } catch (err) { - failed = true; - callback(err); - } - }, - - flush(callback) { - if (!failed) { - this.push(value); - } - - callback(); - }, - }); -} diff --git a/src/legacy/utils/streams/replace_stream.js b/src/legacy/utils/streams/replace_stream.js deleted file mode 100644 index 7309bd241fa52..0000000000000 --- a/src/legacy/utils/streams/replace_stream.js +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Transform } from 'stream'; - -export function createReplaceStream(toReplace, replacement) { - if (typeof toReplace !== 'string') { - throw new TypeError('toReplace must be a string'); - } - - let buffer = Buffer.alloc(0); - return new Transform({ - objectMode: false, - async transform(value, enc, done) { - try { - buffer = Buffer.concat([buffer, value], buffer.length + value.length); - - while (true) { - // try to find the next instance of `toReplace` in buffer - const index = buffer.indexOf(toReplace); - - // if there is no next instance, break - if (index === -1) { - break; - } - - // flush everything to the left of the next instance - // of `toReplace` - this.push(buffer.slice(0, index)); - - // then flush an instance of `replacement` - this.push(replacement); - - // and finally update the buffer to include everything - // to the right of `toReplace`, dropping to replace from the buffer - buffer = buffer.slice(index + toReplace.length); - } - - // until now we have only flushed data that is to the left - // of a discovered instance of `toReplace`. If `toReplace` is - // never found this would lead to us buffering the entire stream. - // - // Instead, we only keep enough buffer to complete a potentially - // partial instance of `toReplace` - if (buffer.length > toReplace.length) { - // the entire buffer except the last `toReplace.length` bytes - // so that if all but one byte from `toReplace` is in the buffer, - // and the next chunk delivers the necessary byte, the buffer will then - // contain a complete `toReplace` token. - this.push(buffer.slice(0, buffer.length - toReplace.length)); - buffer = buffer.slice(-toReplace.length); - } - - done(); - } catch (err) { - done(err); - } - }, - - flush(callback) { - if (buffer.length) { - this.push(buffer); - } - - buffer = null; - callback(); - }, - }); -} diff --git a/src/legacy/utils/streams/split_stream.js b/src/legacy/utils/streams/split_stream.js deleted file mode 100644 index f55cbc7bd290d..0000000000000 --- a/src/legacy/utils/streams/split_stream.js +++ /dev/null @@ -1,74 +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 { Transform } from 'stream'; - -/** - * Creates a Transform stream that consumes a stream of Buffers - * and produces a stream of strings (in object mode) by splitting - * the received bytes using the splitChunk. - * - * Ways this is behaves like String#split: - * - instances of splitChunk are removed from the input - * - splitChunk can be on any size - * - if there are no bytes found after the last splitChunk - * a final empty chunk is emitted - * - * Ways this deviates from String#split: - * - splitChunk cannot be a regexp - * - an empty string or Buffer will not produce a stream of individual - * bytes like `string.split('')` would - * - * @param {String} splitChunk - * @return {Transform} - */ -export function createSplitStream(splitChunk) { - let unsplitBuffer = Buffer.alloc(0); - - return new Transform({ - writableObjectMode: false, - readableObjectMode: true, - transform(chunk, enc, callback) { - try { - let i; - let toSplit = Buffer.concat([unsplitBuffer, chunk]); - while ((i = toSplit.indexOf(splitChunk)) !== -1) { - const slice = toSplit.slice(0, i); - toSplit = toSplit.slice(i + splitChunk.length); - this.push(slice.toString('utf8')); - } - - unsplitBuffer = toSplit; - callback(null); - } catch (err) { - callback(err); - } - }, - - flush(callback) { - try { - this.push(unsplitBuffer.toString('utf8')); - - callback(null); - } catch (err) { - callback(err); - } - }, - }); -} diff --git a/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx b/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx index bbc3f3632bf64..8c9e3847844d9 100644 --- a/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx +++ b/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx @@ -121,7 +121,7 @@ export class AdvancedSettingsComponent extends Component< setTimeout(() => { const id = hash.replace('#', ''); const element = document.getElementById(id); - const globalNavOffset = document.getElementById('headerGlobalNav')?.offsetHeight || 0; + const globalNavOffset = document.getElementById('globalHeaderBars')?.offsetHeight || 0; if (element) { element.scrollIntoView(); diff --git a/src/plugins/advanced_settings/public/management_app/components/_index.scss b/src/plugins/advanced_settings/public/management_app/components/_index.scss deleted file mode 100644 index d2d2e38947f76..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './form/index'; diff --git a/src/plugins/advanced_settings/public/management_app/components/form/_form.scss b/src/plugins/advanced_settings/public/management_app/components/form/_form.scss deleted file mode 100644 index 8d768d200fdd2..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/form/_form.scss +++ /dev/null @@ -1,15 +0,0 @@ -@import '@elastic/eui/src/global_styling/variables/header'; -@import '@elastic/eui/src/components/nav_drawer/variables'; - -// TODO #64541 -// Delete this whole file -.mgtAdvancedSettingsForm__bottomBar { - margin-left: $euiNavDrawerWidthCollapsed; - z-index: 9; // Puts it inuder the nav drawer when expanded - &--pushForNav { - margin-left: $euiNavDrawerWidthExpanded; - } - @include euiBreakpoint('xs', 's') { - margin-left: 0; - } -} diff --git a/src/plugins/advanced_settings/public/management_app/components/form/_index.scss b/src/plugins/advanced_settings/public/management_app/components/form/_index.scss deleted file mode 100644 index 2ef4ef1d20ce9..0000000000000 --- a/src/plugins/advanced_settings/public/management_app/components/form/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './form'; diff --git a/src/plugins/advanced_settings/public/management_app/components/form/form.tsx b/src/plugins/advanced_settings/public/management_app/components/form/form.tsx index 5533f684870d9..0378d816fd2c3 100644 --- a/src/plugins/advanced_settings/public/management_app/components/form/form.tsx +++ b/src/plugins/advanced_settings/public/management_app/components/form/form.tsx @@ -18,7 +18,6 @@ */ import React, { PureComponent, Fragment } from 'react'; -import classNames from 'classnames'; import { EuiFlexGroup, @@ -45,7 +44,6 @@ import { Field, getEditableValue } from '../field'; import { FieldSetting, SettingsChanges, FieldState } from '../../types'; type Category = string; -const NAV_IS_LOCKED_KEY = 'core.chrome.isLocked'; interface FormProps { settings: Record; @@ -326,23 +324,8 @@ export class Form extends PureComponent { renderBottomBar = () => { const areChangesInvalid = this.areChangesInvalid(); - - // TODO #64541 - // Delete these classes - let bottomBarClasses = ''; - const pageNav = this.props.settings.general.find( - (setting) => setting.name === 'pageNavigation' - ); - - if (pageNav?.value === 'legacy') { - bottomBarClasses = classNames('mgtAdvancedSettingsForm__bottomBar', { - // eslint-disable-next-line @typescript-eslint/naming-convention - 'mgtAdvancedSettingsForm__bottomBar--pushForNav': - localStorage.getItem(NAV_IS_LOCKED_KEY) === 'true', - }); - } return ( - + { // dates format: { - __one_of: _.flatten([ - _.map( - [ - 'date', - 'date_time', - 'date_time_no_millis', - 'ordinal_date', - 'ordinal_date_time', - 'ordinal_date_time_no_millis', - 'time', - 'time_no_millis', - 't_time', - 't_time_no_millis', - 'week_date', - 'week_date_time', - 'week_date_time_no_millis', - ], - function (s) { - return ['basic_' + s, 'strict_' + s]; - } - ), - [ + __one_of: [ + ...[ + 'date', + 'date_time', + 'date_time_no_millis', + 'ordinal_date', + 'ordinal_date_time', + 'ordinal_date_time_no_millis', + 'time', + 'time_no_millis', + 't_time', + 't_time_no_millis', + 'week_date', + 'week_date_time', + 'week_date_time_no_millis', + ].map(function (s) { + return ['basic_' + s, 'strict_' + s]; + }), + ...[ 'date', 'date_hour', 'date_hour_minute', @@ -214,7 +209,7 @@ export const mappings = (specService: SpecDefinitionsService) => { 'epoch_millis', 'epoch_second', ], - ]), + ], }, fielddata: { diff --git a/src/plugins/dashboard/kibana.json b/src/plugins/dashboard/kibana.json index 1b38c6d124fe1..531074f9fa60b 100644 --- a/src/plugins/dashboard/kibana.json +++ b/src/plugins/dashboard/kibana.json @@ -6,6 +6,7 @@ "embeddable", "inspector", "kibanaLegacy", + "urlForwarding", "navigation", "uiActions", "savedObjects" diff --git a/src/plugins/dashboard/public/application/_dashboard_app.scss b/src/plugins/dashboard/public/application/_dashboard_app.scss index 719d0a3268b5d..94634d2c408e5 100644 --- a/src/plugins/dashboard/public/application/_dashboard_app.scss +++ b/src/plugins/dashboard/public/application/_dashboard_app.scss @@ -1,7 +1,6 @@ .dshAppContainer { display: flex; flex-direction: column; - height: 100%; // TODO #64541 - can delete this flex: 1; } diff --git a/src/plugins/dashboard/public/application/actions/open_replace_panel_flyout.tsx b/src/plugins/dashboard/public/application/actions/open_replace_panel_flyout.tsx index c676ca052d687..54a294fd2f4ac 100644 --- a/src/plugins/dashboard/public/application/actions/open_replace_panel_flyout.tsx +++ b/src/plugins/dashboard/public/application/actions/open_replace_panel_flyout.tsx @@ -60,7 +60,8 @@ export async function openReplacePanelFlyout(options: { /> ), { - 'data-test-subj': 'replacePanelFlyout', + 'data-test-subj': 'dashboardReplacePanel', + ownFocus: true, } ); } diff --git a/src/plugins/dashboard/public/application/actions/replace_panel_flyout.tsx b/src/plugins/dashboard/public/application/actions/replace_panel_flyout.tsx index 0000f63c48c2d..4e228bc1a7a06 100644 --- a/src/plugins/dashboard/public/application/actions/replace_panel_flyout.tsx +++ b/src/plugins/dashboard/public/application/actions/replace_panel_flyout.tsx @@ -19,16 +19,15 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; -import _ from 'lodash'; -import { EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui'; +import { EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui'; import { NotificationsStart, Toast } from 'src/core/public'; import { DashboardPanelState } from '../embeddable'; import { - IContainer, - IEmbeddable, EmbeddableInput, EmbeddableOutput, EmbeddableStart, + IContainer, + IEmbeddable, SavedObjectEmbeddableInput, } from '../../embeddable_plugin'; @@ -122,7 +121,7 @@ export class ReplacePanelFlyout extends React.Component { const panelToReplace = 'Replace panel ' + this.props.panelToRemove.getTitle() + ' with:'; return ( - + <>

@@ -131,7 +130,7 @@ export class ReplacePanelFlyout extends React.Component { {savedObjectsFinder} - + ); } } diff --git a/src/plugins/dashboard/public/application/application.ts b/src/plugins/dashboard/public/application/application.ts index 21f423d009ee7..6fe8f403f1a31 100644 --- a/src/plugins/dashboard/public/application/application.ts +++ b/src/plugins/dashboard/public/application/application.ts @@ -31,6 +31,7 @@ import { SavedObjectsClientContract, PluginInitializerContext, ScopedHistory, + AppMountParameters, } from 'kibana/public'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; import { Storage } from '../../../kibana_utils/public'; @@ -41,6 +42,7 @@ import { NavigationPublicPluginStart as NavigationStart } from '../../../navigat import { DataPublicPluginStart } from '../../../data/public'; import { SharePluginStart } from '../../../share/public'; import { KibanaLegacyStart, configureAppAngularModule } from '../../../kibana_legacy/public'; +import { UrlForwardingStart } from '../../../url_forwarding/public'; import { SavedObjectLoader, SavedObjectsStart } from '../../../saved_objects/public'; // required for i18nIdDirective @@ -69,9 +71,10 @@ export interface RenderDeps { localStorage: Storage; share?: SharePluginStart; usageCollection?: UsageCollectionSetup; - navigateToDefaultApp: KibanaLegacyStart['navigateToDefaultApp']; - navigateToLegacyKibanaUrl: KibanaLegacyStart['navigateToLegacyKibanaUrl']; + navigateToDefaultApp: UrlForwardingStart['navigateToDefaultApp']; + navigateToLegacyKibanaUrl: UrlForwardingStart['navigateToLegacyKibanaUrl']; scopedHistory: () => ScopedHistory; + setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; savedObjects: SavedObjectsStart; restorePreviousUrl: () => void; } diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index 0d20fdee07df5..dd5eb1ee5ccaa 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -24,9 +24,18 @@ import { EuiCheckboxGroupIdToSelectedMap } from '@elastic/eui/src/components/for import React, { useState, ReactElement } from 'react'; import ReactDOM from 'react-dom'; import angular from 'angular'; +import deepEqual from 'fast-deep-equal'; import { Observable, pipe, Subscription, merge } from 'rxjs'; -import { filter, map, debounceTime, mapTo, startWith, switchMap } from 'rxjs/operators'; +import { + filter, + map, + debounceTime, + mapTo, + startWith, + switchMap, + distinctUntilChanged, +} from 'rxjs/operators'; import { History } from 'history'; import { SavedObjectSaveOpts } from 'src/plugins/saved_objects/public'; import { NavigationPublicPluginStart as NavigationStart } from 'src/plugins/navigation/public'; @@ -79,8 +88,8 @@ import { AngularHttpError, KibanaLegacyStart, subscribeWithScope, - migrateLegacyQuery, } from '../../../kibana_legacy/public'; +import { migrateLegacyQuery } from './lib/migrate_legacy_query'; export interface DashboardAppControllerDependencies extends RenderDeps { $scope: DashboardAppScope; @@ -144,6 +153,7 @@ export class DashboardAppController { i18n: i18nStart, }, history, + setHeaderActionMenu, kbnUrlStateStorage, usageCollection, navigation, @@ -279,6 +289,12 @@ export class DashboardAppController { const updateIndexPatternsOperator = pipe( filter((container: DashboardContainer) => !!container && !isErrorEmbeddable(container)), map(getDashboardIndexPatterns), + distinctUntilChanged((a, b) => + deepEqual( + a.map((ip) => ip.id), + b.map((ip) => ip.id) + ) + ), // using switchMap for previous task cancellation switchMap((panelIndexPatterns: IndexPattern[]) => { return new Observable((observer) => { @@ -405,17 +421,29 @@ export class DashboardAppController { ) : null; }; - outputSubscription = new Subscription(); - outputSubscription.add( - dashboardContainer - .getOutput$() - .pipe( - mapTo(dashboardContainer), - startWith(dashboardContainer), // to trigger initial index pattern update - updateIndexPatternsOperator + outputSubscription = merge( + // output of dashboard container itself + dashboardContainer.getOutput$(), + // plus output of dashboard container children, + // children may change, so make sure we subscribe/unsubscribe with switchMap + dashboardContainer.getOutput$().pipe( + map(() => dashboardContainer!.getChildIds()), + distinctUntilChanged(deepEqual), + switchMap((newChildIds: string[]) => + merge( + ...newChildIds.map((childId) => + dashboardContainer!.getChild(childId).getOutput$() + ) + ) ) - .subscribe() - ); + ) + ) + .pipe( + mapTo(dashboardContainer), + startWith(dashboardContainer), // to trigger initial index pattern update + updateIndexPatternsOperator + ) + .subscribe(); inputSubscription = dashboardContainer.getInput$().subscribe(() => { let dirty = false; @@ -682,7 +710,13 @@ export class DashboardAppController { }; const dashboardNavBar = document.getElementById('dashboardChrome'); const updateNavBar = () => { - ReactDOM.render(, dashboardNavBar); + ReactDOM.render( + , + dashboardNavBar + ); }; const unmountNavBar = () => { diff --git a/src/plugins/dashboard/public/application/dashboard_state_manager.ts b/src/plugins/dashboard/public/application/dashboard_state_manager.ts index 5fed38487dc54..910a2b470b2eb 100644 --- a/src/plugins/dashboard/public/application/dashboard_state_manager.ts +++ b/src/plugins/dashboard/public/application/dashboard_state_manager.ts @@ -25,7 +25,7 @@ import { History } from 'history'; import { Filter, Query, TimefilterContract as Timefilter } from 'src/plugins/data/public'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; -import { migrateLegacyQuery } from '../../../kibana_legacy/public'; +import { migrateLegacyQuery } from './lib/migrate_legacy_query'; import { ViewMode } from '../embeddable_plugin'; import { getAppStateDefaults, migrateAppState, getDashboardIdFromUrl } from './lib'; diff --git a/src/plugins/kibana_legacy/common/migrate_legacy_query.ts b/src/plugins/dashboard/public/application/lib/migrate_legacy_query.ts similarity index 100% rename from src/plugins/kibana_legacy/common/migrate_legacy_query.ts rename to src/plugins/dashboard/public/application/lib/migrate_legacy_query.ts diff --git a/src/plugins/dashboard/public/attribute_service/attribute_service.test.ts b/src/plugins/dashboard/public/attribute_service/attribute_service.test.ts new file mode 100644 index 0000000000000..06f380ca3862b --- /dev/null +++ b/src/plugins/dashboard/public/attribute_service/attribute_service.test.ts @@ -0,0 +1,193 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ATTRIBUTE_SERVICE_KEY } from './attribute_service'; +import { mockAttributeService } from './attribute_service_mock'; +import { coreMock } from '../../../../core/public/mocks'; + +interface TestAttributes { + title: string; + testAttr1?: string; + testAttr2?: { array: unknown[]; testAttr3: string }; +} + +interface TestByValueInput { + id: string; + [ATTRIBUTE_SERVICE_KEY]: TestAttributes; +} + +describe('attributeService', () => { + const defaultTestType = 'defaultTestType'; + let attributes: TestAttributes; + let byValueInput: TestByValueInput; + let byReferenceInput: { id: string; savedObjectId: string }; + + beforeEach(() => { + attributes = { + title: 'ultra title', + testAttr1: 'neat first attribute', + testAttr2: { array: [1, 2, 3], testAttr3: 'super attribute' }, + }; + byValueInput = { + id: '456', + attributes, + }; + byReferenceInput = { + id: '456', + savedObjectId: '123', + }; + }); + + describe('determining input type', () => { + const defaultAttributeService = mockAttributeService(defaultTestType); + const customAttributeService = mockAttributeService( + defaultTestType + ); + + it('can determine input type given default types', () => { + expect( + defaultAttributeService.inputIsRefType({ id: '456', savedObjectId: '123' }) + ).toBeTruthy(); + expect( + defaultAttributeService.inputIsRefType({ + id: '456', + attributes: { title: 'wow I am by value' }, + }) + ).toBeFalsy(); + }); + it('can determine input type given custom types', () => { + expect( + customAttributeService.inputIsRefType({ id: '456', savedObjectId: '123' }) + ).toBeTruthy(); + expect( + customAttributeService.inputIsRefType({ + id: '456', + [ATTRIBUTE_SERVICE_KEY]: { title: 'wow I am by value' }, + }) + ).toBeFalsy(); + }); + }); + + describe('unwrapping attributes', () => { + it('can unwrap all default attributes when given reference type input', async () => { + const core = coreMock.createStart(); + core.savedObjects.client.get = jest.fn().mockResolvedValueOnce({ + attributes, + }); + const attributeService = mockAttributeService( + defaultTestType, + undefined, + core + ); + expect(await attributeService.unwrapAttributes(byReferenceInput)).toEqual(attributes); + }); + + it('returns attributes when when given value type input', async () => { + const attributeService = mockAttributeService(defaultTestType); + expect(await attributeService.unwrapAttributes(byValueInput)).toEqual(attributes); + }); + + it('runs attributes through a custom unwrap method', async () => { + const core = coreMock.createStart(); + core.savedObjects.client.get = jest.fn().mockResolvedValueOnce({ + attributes, + }); + const attributeService = mockAttributeService( + defaultTestType, + { + customUnwrapMethod: (savedObject) => ({ + ...savedObject.attributes, + testAttr2: { array: [1, 2, 3, 4, 5], testAttr3: 'kibanana' }, + }), + }, + core + ); + expect(await attributeService.unwrapAttributes(byReferenceInput)).toEqual({ + ...attributes, + testAttr2: { array: [1, 2, 3, 4, 5], testAttr3: 'kibanana' }, + }); + }); + }); + + describe('wrapping attributes', () => { + it('returns given attributes when use ref type is false', async () => { + const attributeService = mockAttributeService(defaultTestType); + expect(await attributeService.wrapAttributes(attributes, false)).toEqual({ attributes }); + }); + + it('updates existing saved object with new attributes when given id', async () => { + const core = coreMock.createStart(); + const attributeService = mockAttributeService( + defaultTestType, + undefined, + core + ); + expect(await attributeService.wrapAttributes(attributes, true, byReferenceInput)).toEqual( + byReferenceInput + ); + expect(core.savedObjects.client.update).toHaveBeenCalledWith( + defaultTestType, + '123', + attributes + ); + }); + + it('creates new saved object with attributes when given no id', async () => { + const core = coreMock.createStart(); + core.savedObjects.client.create = jest.fn().mockResolvedValueOnce({ + id: '678', + }); + const attributeService = mockAttributeService( + defaultTestType, + undefined, + core + ); + expect(await attributeService.wrapAttributes(attributes, true)).toEqual({ + savedObjectId: '678', + }); + expect(core.savedObjects.client.create).toHaveBeenCalledWith(defaultTestType, attributes); + }); + + it('uses custom save method when given an id', async () => { + const customSaveMethod = jest.fn().mockReturnValue({ id: '123' }); + const attributeService = mockAttributeService(defaultTestType, { + customSaveMethod, + }); + expect(await attributeService.wrapAttributes(attributes, true, byReferenceInput)).toEqual( + byReferenceInput + ); + expect(customSaveMethod).toHaveBeenCalledWith( + defaultTestType, + attributes, + byReferenceInput.savedObjectId + ); + }); + + it('uses custom save method given no id', async () => { + const customSaveMethod = jest.fn().mockReturnValue({ id: '678' }); + const attributeService = mockAttributeService(defaultTestType, { + customSaveMethod, + }); + expect(await attributeService.wrapAttributes(attributes, true)).toEqual({ + savedObjectId: '678', + }); + expect(customSaveMethod).toHaveBeenCalledWith(defaultTestType, attributes, undefined); + }); + }); +}); diff --git a/src/plugins/dashboard/public/attribute_service/attribute_service.tsx b/src/plugins/dashboard/public/attribute_service/attribute_service.tsx index fe5f6a0c8e2bd..a36363d22d87d 100644 --- a/src/plugins/dashboard/public/attribute_service/attribute_service.tsx +++ b/src/plugins/dashboard/public/attribute_service/attribute_service.tsx @@ -19,11 +19,16 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; +import { get } from 'lodash'; import { EmbeddableInput, SavedObjectEmbeddableInput, isSavedObjectEmbeddableInput, IEmbeddable, + Container, + EmbeddableStart, + EmbeddableFactory, + EmbeddableFactoryNotFoundError, } from '../embeddable_plugin'; import { SavedObjectsClientContract, @@ -34,17 +39,10 @@ import { } from '../../../../core/public'; import { SavedObjectSaveModal, - showSaveModal, OnSaveProps, SaveResult, checkForDuplicateTitle, } from '../../../saved_objects/public'; -import { - EmbeddableStart, - EmbeddableFactory, - EmbeddableFactoryNotFoundError, - Container, -} from '../../../embeddable/public'; /** * The attribute service is a shared, generic service that embeddables can use to provide the functionality @@ -52,26 +50,46 @@ import { * can also be used as a higher level wrapper to transform an embeddable input shape that references a saved object * into an embeddable input shape that contains that saved object's attributes by value. */ +export const ATTRIBUTE_SERVICE_KEY = 'attributes'; + +export interface AttributeServiceOptions { + customSaveMethod?: ( + type: string, + attributes: A, + savedObjectId?: string + ) => Promise<{ id: string }>; + customUnwrapMethod?: (savedObject: SimpleSavedObject) => A; +} + export class AttributeService< SavedObjectAttributes extends { title: string }, - ValType extends EmbeddableInput & { attributes: SavedObjectAttributes }, - RefType extends SavedObjectEmbeddableInput + ValType extends EmbeddableInput & { + [ATTRIBUTE_SERVICE_KEY]: SavedObjectAttributes; + } = EmbeddableInput & { [ATTRIBUTE_SERVICE_KEY]: SavedObjectAttributes }, + RefType extends SavedObjectEmbeddableInput = SavedObjectEmbeddableInput > { - private embeddableFactory: EmbeddableFactory; + private embeddableFactory?: EmbeddableFactory; constructor( private type: string, + private showSaveModal: ( + saveModal: React.ReactElement, + I18nContext: I18nStart['Context'] + ) => void, private savedObjectsClient: SavedObjectsClientContract, private overlays: OverlayStart, private i18nContext: I18nStart['Context'], private toasts: NotificationsStart['toasts'], - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'] + getEmbeddableFactory?: EmbeddableStart['getEmbeddableFactory'], + private options?: AttributeServiceOptions ) { - const factory = getEmbeddableFactory(this.type); - if (!factory) { - throw new EmbeddableFactoryNotFoundError(this.type); + if (getEmbeddableFactory) { + const factory = getEmbeddableFactory(this.type); + if (!factory) { + throw new EmbeddableFactoryNotFoundError(this.type); + } + this.embeddableFactory = factory; } - this.embeddableFactory = factory; } public async unwrapAttributes(input: RefType | ValType): Promise { @@ -79,43 +97,54 @@ export class AttributeService< const savedObject: SimpleSavedObject = await this.savedObjectsClient.get< SavedObjectAttributes >(this.type, input.savedObjectId); - return savedObject.attributes; + return this.options?.customUnwrapMethod + ? this.options?.customUnwrapMethod(savedObject) + : { ...savedObject.attributes }; } - return input.attributes; + return input[ATTRIBUTE_SERVICE_KEY]; } public async wrapAttributes( newAttributes: SavedObjectAttributes, useRefType: boolean, - embeddable?: IEmbeddable + input?: ValType | RefType ): Promise> { + const originalInput = input ? input : {}; const savedObjectId = - embeddable && isSavedObjectEmbeddableInput(embeddable.getInput()) - ? (embeddable.getInput() as SavedObjectEmbeddableInput).savedObjectId + input && this.inputIsRefType(input) + ? (input as SavedObjectEmbeddableInput).savedObjectId : undefined; if (!useRefType) { - return { attributes: newAttributes } as ValType; - } else { - try { - if (savedObjectId) { - await this.savedObjectsClient.update(this.type, savedObjectId, newAttributes); - return { savedObjectId } as RefType; - } else { - const savedItem = await this.savedObjectsClient.create(this.type, newAttributes); - return { savedObjectId: savedItem.id } as RefType; - } - } catch (error) { - this.toasts.addDanger({ - title: i18n.translate('dashboard.attributeService.saveToLibraryError', { - defaultMessage: `Panel was not saved to the library. Error: {errorMessage}`, - values: { - errorMessage: error.message, - }, - }), - 'data-test-subj': 'saveDashboardFailure', - }); - return Promise.reject({ error }); + return { [ATTRIBUTE_SERVICE_KEY]: newAttributes } as ValType; + } + try { + if (this.options?.customSaveMethod) { + const savedItem = await this.options.customSaveMethod( + this.type, + newAttributes, + savedObjectId + ); + return { ...originalInput, savedObjectId: savedItem.id } as RefType; + } + + if (savedObjectId) { + await this.savedObjectsClient.update(this.type, savedObjectId, newAttributes); + return { ...originalInput, savedObjectId } as RefType; } + + const savedItem = await this.savedObjectsClient.create(this.type, newAttributes); + return { ...originalInput, savedObjectId: savedItem.id } as RefType; + } catch (error) { + this.toasts.addDanger({ + title: i18n.translate('dashboard.attributeService.saveToLibraryError', { + defaultMessage: `Panel was not saved to the library. Error: {errorMessage}`, + values: { + errorMessage: error.message, + }, + }), + 'data-test-subj': 'saveDashboardFailure', + }); + return Promise.reject({ error }); } } @@ -146,7 +175,7 @@ export class AttributeService< getInputAsRefType = async ( input: ValType | RefType, - saveOptions?: { showSaveModal: boolean } | { title: string } + saveOptions?: { showSaveModal: boolean; saveModalTitle?: string } | { title: string } ): Promise => { if (this.inputIsRefType(input)) { return input; @@ -159,7 +188,7 @@ export class AttributeService< copyOnSave: false, lastSavedTitle: '', getEsType: () => this.type, - getDisplayName: this.embeddableFactory.getDisplayName, + getDisplayName: this.embeddableFactory?.getDisplayName || (() => this.type), }, props.isTitleDuplicateConfirmed, props.onTitleDuplicate, @@ -169,7 +198,7 @@ export class AttributeService< } ); try { - const newAttributes = { ...input.attributes }; + const newAttributes = { ...input[ATTRIBUTE_SERVICE_KEY] }; newAttributes.title = props.newTitle; const wrappedInput = (await this.wrapAttributes(newAttributes, true)) as RefType; resolve(wrappedInput); @@ -181,11 +210,11 @@ export class AttributeService< }; if (saveOptions && (saveOptions as { showSaveModal: boolean }).showSaveModal) { - showSaveModal( + this.showSaveModal( reject()} - title={input.attributes.title} + title={get(saveOptions, 'saveModalTitle', input[ATTRIBUTE_SERVICE_KEY].title)} showCopyOnSave={false} objectType={this.type} showDescription={false} diff --git a/src/plugins/dashboard/public/attribute_service/attribute_service_mock.tsx b/src/plugins/dashboard/public/attribute_service/attribute_service_mock.tsx new file mode 100644 index 0000000000000..321a53361fc7a --- /dev/null +++ b/src/plugins/dashboard/public/attribute_service/attribute_service_mock.tsx @@ -0,0 +1,49 @@ +/* + * 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 { EmbeddableInput, SavedObjectEmbeddableInput } from '../embeddable_plugin'; +import { coreMock } from '../../../../core/public/mocks'; +import { AttributeServiceOptions } from './attribute_service'; +import { CoreStart } from '../../../../core/public'; +import { AttributeService, ATTRIBUTE_SERVICE_KEY } from '..'; + +export const mockAttributeService = < + A extends { title: string }, + V extends EmbeddableInput & { [ATTRIBUTE_SERVICE_KEY]: A } = EmbeddableInput & { + [ATTRIBUTE_SERVICE_KEY]: A; + }, + R extends SavedObjectEmbeddableInput = SavedObjectEmbeddableInput +>( + type: string, + options?: AttributeServiceOptions, + customCore?: jest.Mocked +): AttributeService => { + const core = customCore ? customCore : coreMock.createStart(); + const service = new AttributeService( + type, + jest.fn(), + core.savedObjects.client, + core.overlays, + core.i18n.Context, + core.notifications.toasts, + jest.fn().mockReturnValue(() => ({ getDisplayName: () => type })), + options + ); + return service; +}; diff --git a/src/plugins/dashboard/public/index.ts b/src/plugins/dashboard/public/index.ts index 8a9954cc77a2e..e22d1f038a456 100644 --- a/src/plugins/dashboard/public/index.ts +++ b/src/plugins/dashboard/public/index.ts @@ -40,7 +40,7 @@ export { export { addEmbeddableToDashboardUrl } from './url_utils/url_helper'; export { SavedObjectDashboard } from './saved_dashboards'; export { SavedDashboardPanel } from './types'; -export { AttributeService } from './attribute_service/attribute_service'; +export { AttributeService, ATTRIBUTE_SERVICE_KEY } from './attribute_service/attribute_service'; export function plugin(initializerContext: PluginInitializerContext) { return new DashboardPlugin(initializerContext); diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 3df52f4e7a205..5a45229a58a7d 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -33,6 +33,7 @@ import { SavedObjectsClientContract, ScopedHistory, } from 'src/core/public'; +import { UrlForwardingSetup, UrlForwardingStart } from 'src/plugins/url_forwarding/public'; import { UsageCollectionSetup } from '../../usage_collection/public'; import { CONTEXT_MENU_TRIGGER, @@ -52,6 +53,7 @@ import { getSavedObjectFinder, SavedObjectLoader, SavedObjectsStart, + showSaveModal, } from '../../saved_objects/public'; import { ExitFullScreenButton as ExitFullScreenButtonUi, @@ -102,6 +104,10 @@ import { addEmbeddableToDashboardUrl } from './url_utils/url_helper'; import { PlaceholderEmbeddableFactory } from './application/embeddable/placeholder'; import { UrlGeneratorState } from '../../share/public'; import { AttributeService } from '.'; +import { + AttributeServiceOptions, + ATTRIBUTE_SERVICE_KEY, +} from './attribute_service/attribute_service'; declare module '../../share/public' { export interface UrlGeneratorStateMapping { @@ -120,6 +126,7 @@ interface SetupDependencies { embeddable: EmbeddableSetup; home?: HomePublicPluginSetup; kibanaLegacy: KibanaLegacySetup; + urlForwarding: UrlForwardingSetup; share?: SharePluginSetup; uiActions: UiActionsSetup; usageCollection?: UsageCollectionSetup; @@ -128,6 +135,7 @@ interface SetupDependencies { interface StartDependencies { data: DataPublicPluginStart; kibanaLegacy: KibanaLegacyStart; + urlForwarding: UrlForwardingStart; embeddable: EmbeddableStart; inspector: InspectorStartContract; navigation: NavigationStart; @@ -150,10 +158,13 @@ export interface DashboardStart { DashboardContainerByValueRenderer: ReturnType; getAttributeService: < A extends { title: string }, - V extends EmbeddableInput & { attributes: A }, - R extends SavedObjectEmbeddableInput + V extends EmbeddableInput & { [ATTRIBUTE_SERVICE_KEY]: A } = EmbeddableInput & { + [ATTRIBUTE_SERVICE_KEY]: A; + }, + R extends SavedObjectEmbeddableInput = SavedObjectEmbeddableInput >( - type: string + type: string, + options?: AttributeServiceOptions ) => AttributeService; } @@ -182,7 +193,16 @@ export class DashboardPlugin public setup( core: CoreSetup, - { share, uiActions, embeddable, home, kibanaLegacy, data, usageCollection }: SetupDependencies + { + share, + uiActions, + embeddable, + home, + kibanaLegacy, + urlForwarding, + data, + usageCollection, + }: SetupDependencies ): Setup { this.dashboardFeatureFlagConfig = this.initializerContext.config.get< DashboardFeatureFlagConfig @@ -290,7 +310,7 @@ export class DashboardPlugin id: DashboardConstants.DASHBOARDS_ID, title: 'Dashboard', order: 2500, - euiIconType: 'dashboardApp', + euiIconType: 'logoKibana', defaultPath: `#${DashboardConstants.LANDING_PAGE_PATH}`, updater$: this.appStateUpdater, category: DEFAULT_APP_CATEGORIES.kibana, @@ -303,7 +323,8 @@ export class DashboardPlugin navigation, share: shareStart, data: dataStart, - kibanaLegacy: { dashboardConfig, navigateToDefaultApp, navigateToLegacyKibanaUrl }, + kibanaLegacy: { dashboardConfig }, + urlForwarding: { navigateToDefaultApp, navigateToLegacyKibanaUrl }, savedObjects, } = pluginsStart; @@ -331,6 +352,7 @@ export class DashboardPlugin localStorage: new Storage(localStorage), usageCollection, scopedHistory: () => this.currentHistory!, + setHeaderActionMenu: params.setHeaderActionMenu, savedObjects, restorePreviousUrl, }; @@ -349,7 +371,7 @@ export class DashboardPlugin initAngularBootstrap(); core.application.register(app); - kibanaLegacy.forwardApp( + urlForwarding.forwardApp( DashboardConstants.DASHBOARDS_ID, DashboardConstants.DASHBOARDS_ID, (path) => { @@ -358,7 +380,7 @@ export class DashboardPlugin return `#/list${tail || ''}`; } ); - kibanaLegacy.forwardApp( + urlForwarding.forwardApp( DashboardConstants.DASHBOARD_ID, DashboardConstants.DASHBOARDS_ID, (path) => { @@ -465,14 +487,16 @@ export class DashboardPlugin DashboardContainerByValueRenderer: createDashboardContainerByValueRenderer({ factory: dashboardContainerFactory, }), - getAttributeService: (type: string) => + getAttributeService: (type: string, options) => new AttributeService( type, + showSaveModal, core.savedObjects.client, core.overlays, core.i18n.Context, core.notifications.toasts, - embeddable.getEmbeddableFactory + embeddable.getEmbeddableFactory, + options ), }; } diff --git a/src/plugins/dashboard/public/saved_dashboards/saved_dashboards.ts b/src/plugins/dashboard/public/saved_dashboards/saved_dashboards.ts index 09357072a13a6..3bd4d66a693b1 100644 --- a/src/plugins/dashboard/public/saved_dashboards/saved_dashboards.ts +++ b/src/plugins/dashboard/public/saved_dashboards/saved_dashboards.ts @@ -35,5 +35,5 @@ interface Services { */ export function createSavedDashboardLoader(services: Services) { const SavedDashboard = createSavedDashboardClass(services); - return new SavedObjectLoader(SavedDashboard, services.savedObjectsClient, services.chrome); + return new SavedObjectLoader(SavedDashboard, services.savedObjectsClient); } diff --git a/src/plugins/data/common/constants.ts b/src/plugins/data/common/constants.ts index 22db1552e4303..43120583bd3a4 100644 --- a/src/plugins/data/common/constants.ts +++ b/src/plugins/data/common/constants.ts @@ -32,6 +32,7 @@ export const UI_SETTINGS = { COURIER_MAX_CONCURRENT_SHARD_REQUESTS: 'courier:maxConcurrentShardRequests', COURIER_BATCH_SEARCHES: 'courier:batchSearches', SEARCH_INCLUDE_FROZEN: 'search:includeFrozen', + SEARCH_TIMEOUT: 'search:timeout', HISTOGRAM_BAR_TARGET: 'histogram:barTarget', HISTOGRAM_MAX_BARS: 'histogram:maxBars', HISTORY_LIMIT: 'history:limit', diff --git a/src/plugins/data/common/es_query/filters/get_display_value.ts b/src/plugins/data/common/es_query/filters/get_display_value.ts index 10b4dab3f46ef..28ba0ab629e8f 100644 --- a/src/plugins/data/common/es_query/filters/get_display_value.ts +++ b/src/plugins/data/common/es_query/filters/get_display_value.ts @@ -17,30 +17,26 @@ * under the License. */ -import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { IIndexPattern, IFieldType } from '../..'; +import { IIndexPattern } from '../..'; import { getIndexPatternFromFilter } from './get_index_pattern_from_filter'; import { Filter } from '../filters'; function getValueFormatter(indexPattern?: IIndexPattern, key?: string) { - if (!indexPattern || !key) return; + // checking getFormatterForField exists because there is at least once case where an index pattern + // is an object rather than an IndexPattern class + if (!indexPattern || !indexPattern.getFormatterForField || !key) return; - let format = get(indexPattern, ['fields', 'byName', key, 'format']); - if (!format && (indexPattern.fields as any).getByName) { - // TODO: Why is indexPatterns sometimes a map and sometimes an array? - const field: IFieldType = (indexPattern.fields as any).getByName(key); - if (!field) { - throw new Error( - i18n.translate('data.filter.filterBar.fieldNotFound', { - defaultMessage: 'Field {key} not found in index pattern {indexPattern}', - values: { key, indexPattern: indexPattern.title }, - }) - ); - } - format = field.format; + const field = indexPattern.fields.find((f) => f.name === key); + if (!field) { + throw new Error( + i18n.translate('data.filter.filterBar.fieldNotFound', { + defaultMessage: 'Field {key} not found in index pattern {indexPattern}', + values: { key, indexPattern: indexPattern.title }, + }) + ); } - return format; + return indexPattern.getFormatterForField(field); } export function getDisplayValueFromFilter(filter: Filter, indexPatterns: IIndexPattern[]): string { diff --git a/src/plugins/data/common/es_query/kuery/node_types/types.ts b/src/plugins/data/common/es_query/kuery/node_types/types.ts index 6d3019e75d483..894bd1812f084 100644 --- a/src/plugins/data/common/es_query/kuery/node_types/types.ts +++ b/src/plugins/data/common/es_query/kuery/node_types/types.ts @@ -76,6 +76,7 @@ export interface NamedArgTypeBuildNode { } interface WildcardType { + wildcardSymbol: string; buildNode: (value: string) => WildcardTypeBuildNode | KueryNode; test: (node: any, string: string) => boolean; toElasticsearchQuery: (node: any) => string; diff --git a/src/plugins/data/common/field_formats/converters/duration.test.ts b/src/plugins/data/common/field_formats/converters/duration.test.ts index d6205d54bd702..69163842f3498 100644 --- a/src/plugins/data/common/field_formats/converters/duration.test.ts +++ b/src/plugins/data/common/field_formats/converters/duration.test.ts @@ -24,11 +24,16 @@ describe('Duration Format', () => { inputFormat: 'seconds', outputFormat: 'humanize', outputPrecision: undefined, + showSuffix: undefined, fixtures: [ { input: -60, output: 'minus a minute', }, + { + input: 1, + output: 'a few seconds', + }, { input: 60, output: 'a minute', @@ -44,6 +49,7 @@ describe('Duration Format', () => { inputFormat: 'minutes', outputFormat: 'humanize', outputPrecision: undefined, + showSuffix: undefined, fixtures: [ { input: -60, @@ -64,6 +70,7 @@ describe('Duration Format', () => { inputFormat: 'minutes', outputFormat: 'asHours', outputPrecision: undefined, + showSuffix: undefined, fixtures: [ { input: -60, @@ -84,6 +91,7 @@ describe('Duration Format', () => { inputFormat: 'seconds', outputFormat: 'asSeconds', outputPrecision: 0, + showSuffix: undefined, fixtures: [ { input: -60, @@ -104,6 +112,7 @@ describe('Duration Format', () => { inputFormat: 'seconds', outputFormat: 'asSeconds', outputPrecision: 2, + showSuffix: undefined, fixtures: [ { input: -60, @@ -124,15 +133,34 @@ describe('Duration Format', () => { ], }); + testCase({ + inputFormat: 'seconds', + outputFormat: 'asSeconds', + outputPrecision: 0, + showSuffix: true, + fixtures: [ + { + input: -60, + output: '-60 Seconds', + }, + { + input: -32.333, + output: '-32 Seconds', + }, + ], + }); + function testCase({ inputFormat, outputFormat, outputPrecision, + showSuffix, fixtures, }: { inputFormat: string; outputFormat: string; outputPrecision: number | undefined; + showSuffix: boolean | undefined; fixtures: any[]; }) { fixtures.forEach((fixture: Record) => { @@ -143,7 +171,7 @@ describe('Duration Format', () => { outputPrecision ? `, ${outputPrecision} decimals` : '' }`, () => { const duration = new DurationFormat( - { inputFormat, outputFormat, outputPrecision }, + { inputFormat, outputFormat, outputPrecision, showSuffix }, jest.fn() ); expect(duration.convert(input)).toBe(output); diff --git a/src/plugins/data/common/field_formats/converters/duration.ts b/src/plugins/data/common/field_formats/converters/duration.ts index 53c2aba98120e..a3ce3d4dfd795 100644 --- a/src/plugins/data/common/field_formats/converters/duration.ts +++ b/src/plugins/data/common/field_formats/converters/duration.ts @@ -190,6 +190,7 @@ export class DurationFormat extends FieldFormat { const inputFormat = this.param('inputFormat'); const outputFormat = this.param('outputFormat') as keyof Duration; const outputPrecision = this.param('outputPrecision'); + const showSuffix = Boolean(this.param('showSuffix')); const human = this.isHuman(); const prefix = val < 0 && human @@ -200,6 +201,9 @@ export class DurationFormat extends FieldFormat { const duration = parseInputAsDuration(val, inputFormat) as Record; const formatted = duration[outputFormat](); const precise = human ? formatted : formatted.toFixed(outputPrecision); - return prefix + precise; + const type = outputFormats.find(({ method }) => method === outputFormat); + const suffix = showSuffix && type ? ` ${type.text}` : ''; + + return prefix + precise + suffix; }; } diff --git a/src/plugins/data/common/field_formats/converters/source.ts b/src/plugins/data/common/field_formats/converters/source.ts index 9c81bb011e127..e7dce82c725d2 100644 --- a/src/plugins/data/common/field_formats/converters/source.ts +++ b/src/plugins/data/common/field_formats/converters/source.ts @@ -60,7 +60,7 @@ export class SourceFormat extends FieldFormat { textConvert: TextContextTypeConvert = (value) => JSON.stringify(value); htmlConvert: HtmlContextTypeConvert = (value, options = {}) => { - const { field, hit } = options; + const { field, hit, indexPattern } = options; if (!field) { const converter = this.getConverterFor('text') as Function; @@ -69,7 +69,7 @@ export class SourceFormat extends FieldFormat { } const highlights = (hit && hit.highlight) || {}; - const formatted = field.indexPattern.formatHit(hit); + const formatted = indexPattern.formatHit(hit); const highlightPairs: any[] = []; const sourcePairs: any[] = []; const isShortDots = this.getConfig!(UI_SETTINGS.SHORT_DOTS_ENABLE); diff --git a/src/plugins/data/common/field_formats/field_formats_registry.ts b/src/plugins/data/common/field_formats/field_formats_registry.ts index 4b46adf399363..dbc3693c99779 100644 --- a/src/plugins/data/common/field_formats/field_formats_registry.ts +++ b/src/plugins/data/common/field_formats/field_formats_registry.ts @@ -181,11 +181,11 @@ export class FieldFormatsRegistry { * @param {ES_FIELD_TYPES[]} esTypes * @return {FieldFormat} */ - getDefaultInstancePlain( + getDefaultInstancePlain = ( fieldType: KBN_FIELD_TYPES, esTypes?: ES_FIELD_TYPES[], params: Record = {} - ): FieldFormat { + ): FieldFormat => { const conf = this.getDefaultConfig(fieldType, esTypes); const instanceParams = { ...conf.params, @@ -193,7 +193,7 @@ export class FieldFormatsRegistry { }; return this.getInstance(conf.id, instanceParams); - } + }; /** * Returns a cache key built by the given variables for caching in memoized * Where esType contains fieldType, fieldType is returned diff --git a/src/plugins/data/common/field_formats/types.ts b/src/plugins/data/common/field_formats/types.ts index daa44b2b0f85b..af956a20c0dc5 100644 --- a/src/plugins/data/common/field_formats/types.ts +++ b/src/plugins/data/common/field_formats/types.ts @@ -27,6 +27,7 @@ export type FieldFormatsContentType = 'html' | 'text'; /** @internal **/ export interface HtmlContextTypeOptions { field?: any; + indexPattern?: any; hit?: Record; } diff --git a/src/plugins/data/common/index_patterns/errors.ts b/src/plugins/data/common/index_patterns/errors.ts new file mode 100644 index 0000000000000..3d92bae1968fb --- /dev/null +++ b/src/plugins/data/common/index_patterns/errors.ts @@ -0,0 +1,29 @@ +/* + * 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 { FieldSpec } from './types'; + +export class FieldTypeUnknownError extends Error { + public readonly fieldSpec: FieldSpec; + constructor(message: string, spec: FieldSpec) { + super(message); + this.name = 'FieldTypeUnknownError'; + this.fieldSpec = spec; + } +} diff --git a/src/plugins/data/common/index_patterns/fields/__snapshots__/index_pattern_field.test.ts.snap b/src/plugins/data/common/index_patterns/fields/__snapshots__/index_pattern_field.test.ts.snap index e61593f6bfb27..4279dd320ad62 100644 --- a/src/plugins/data/common/index_patterns/fields/__snapshots__/index_pattern_field.test.ts.snap +++ b/src/plugins/data/common/index_patterns/fields/__snapshots__/index_pattern_field.test.ts.snap @@ -14,7 +14,7 @@ Object { }, "count": 1, "esTypes": Array [ - "type", + "text", ], "lang": "lang", "name": "name", @@ -30,7 +30,7 @@ Object { "path": "path", }, }, - "type": "type", + "type": "string", } `; @@ -48,7 +48,7 @@ Object { }, "count": 1, "esTypes": Array [ - "type", + "text", ], "format": Object { "id": "number", @@ -70,6 +70,6 @@ Object { "path": "path", }, }, - "type": "type", + "type": "string", } `; diff --git a/src/plugins/data/common/index_patterns/fields/field_list.ts b/src/plugins/data/common/index_patterns/fields/field_list.ts index d2489a5d1f7e3..4cf6075869851 100644 --- a/src/plugins/data/common/index_patterns/fields/field_list.ts +++ b/src/plugins/data/common/index_patterns/fields/field_list.ts @@ -35,6 +35,7 @@ export interface IIndexPatternFieldList extends Array { removeAll(): void; replaceAll(specs: FieldSpec[]): void; update(field: FieldSpec): void; + toSpec(options?: { getFormatterForField?: IndexPattern['getFormatterForField'] }): FieldSpec[]; } export type CreateIndexPatternFieldList = ( @@ -44,87 +45,79 @@ export type CreateIndexPatternFieldList = ( onNotification?: OnNotification ) => IIndexPatternFieldList; -export class FieldList extends Array implements IIndexPatternFieldList { - private byName: FieldMap = new Map(); - private groups: Map = new Map(); - private indexPattern: IndexPattern; - private shortDotsEnable: boolean; - private onNotification: OnNotification; - private setByName = (field: IndexPatternField) => this.byName.set(field.name, field); - private setByGroup = (field: IndexPatternField) => { - if (typeof this.groups.get(field.type) === 'undefined') { - this.groups.set(field.type, new Map()); +// extending the array class and using a constructor doesn't work well +// when calling filter and similar so wrapping in a callback. +// to be removed in the future +export const fieldList = ( + specs: FieldSpec[] = [], + shortDotsEnable = false +): IIndexPatternFieldList => { + class FldList extends Array implements IIndexPatternFieldList { + private byName: FieldMap = new Map(); + private groups: Map = new Map(); + private setByName = (field: IndexPatternField) => this.byName.set(field.name, field); + private setByGroup = (field: IndexPatternField) => { + if (typeof this.groups.get(field.type) === 'undefined') { + this.groups.set(field.type, new Map()); + } + this.groups.get(field.type)!.set(field.name, field); + }; + private removeByGroup = (field: IFieldType) => this.groups.get(field.type)!.delete(field.name); + private calcDisplayName = (name: string) => + shortDotsEnable ? shortenDottedString(name) : name; + constructor() { + super(); + specs.map((field) => this.add(field)); } - this.groups.get(field.type)!.set(field.name, field); - }; - private removeByGroup = (field: IFieldType) => this.groups.get(field.type)!.delete(field.name); - private calcDisplayName = (name: string) => - this.shortDotsEnable ? shortenDottedString(name) : name; - constructor( - indexPattern: IndexPattern, - specs: FieldSpec[] = [], - shortDotsEnable = false, - onNotification: OnNotification = () => {} - ) { - super(); - this.indexPattern = indexPattern; - this.shortDotsEnable = shortDotsEnable; - this.onNotification = onNotification; - specs.map((field) => this.add(field)); - } + public readonly getAll = () => [...this.byName.values()]; + public readonly getByName = (name: IndexPatternField['name']) => this.byName.get(name); + public readonly getByType = (type: IndexPatternField['type']) => [ + ...(this.groups.get(type) || new Map()).values(), + ]; + public readonly add = (field: FieldSpec) => { + const newField = new IndexPatternField(field, this.calcDisplayName(field.name)); + this.push(newField); + this.setByName(newField); + this.setByGroup(newField); + }; - public readonly getAll = () => [...this.byName.values()]; - public readonly getByName = (name: IndexPatternField['name']) => this.byName.get(name); - public readonly getByType = (type: IndexPatternField['type']) => [ - ...(this.groups.get(type) || new Map()).values(), - ]; - public readonly add = (field: FieldSpec) => { - const newField = new IndexPatternField( - this.indexPattern, - field, - this.calcDisplayName(field.name), - this.onNotification - ); - this.push(newField); - this.setByName(newField); - this.setByGroup(newField); - }; + public readonly remove = (field: IFieldType) => { + this.removeByGroup(field); + this.byName.delete(field.name); - public readonly remove = (field: IFieldType) => { - this.removeByGroup(field); - this.byName.delete(field.name); + const fieldIndex = findIndex(this, { name: field.name }); + this.splice(fieldIndex, 1); + }; - const fieldIndex = findIndex(this, { name: field.name }); - this.splice(fieldIndex, 1); - }; + public readonly update = (field: FieldSpec) => { + const newField = new IndexPatternField(field, this.calcDisplayName(field.name)); + const index = this.findIndex((f) => f.name === newField.name); + this.splice(index, 1, newField); + this.setByName(newField); + this.removeByGroup(newField); + this.setByGroup(newField); + }; - public readonly update = (field: FieldSpec) => { - const newField = new IndexPatternField( - this.indexPattern, - field, - this.calcDisplayName(field.name), - this.onNotification - ); - const index = this.findIndex((f) => f.name === newField.name); - this.splice(index, 1, newField); - this.setByName(newField); - this.removeByGroup(newField); - this.setByGroup(newField); - }; + public readonly removeAll = () => { + this.length = 0; + this.byName.clear(); + this.groups.clear(); + }; - public readonly removeAll = () => { - this.length = 0; - this.byName.clear(); - this.groups.clear(); - }; + public readonly replaceAll = (spcs: FieldSpec[]) => { + this.removeAll(); + spcs.forEach(this.add); + }; - public readonly replaceAll = (specs: FieldSpec[]) => { - this.removeAll(); - specs.forEach(this.add); - }; + public toSpec({ + getFormatterForField, + }: { + getFormatterForField?: IndexPattern['getFormatterForField']; + } = {}) { + return [...this.map((field) => field.toSpec({ getFormatterForField }))]; + } + } - public readonly toSpec = () => { - return [...this.map((field) => field.toSpec())]; - }; -} + return new FldList(); +}; diff --git a/src/plugins/data/common/index_patterns/fields/index_pattern_field.test.ts b/src/plugins/data/common/index_patterns/fields/index_pattern_field.test.ts index 0cd0fe8324809..3c4fac81c2c7c 100644 --- a/src/plugins/data/common/index_patterns/fields/index_pattern_field.test.ts +++ b/src/plugins/data/common/index_patterns/fields/index_pattern_field.test.ts @@ -19,7 +19,7 @@ import { IndexPatternField } from './index_pattern_field'; import { IndexPattern } from '../index_patterns'; -import { KBN_FIELD_TYPES } from '../../../common'; +import { KBN_FIELD_TYPES, FieldFormat } from '../../../common'; import { FieldSpec } from '../types'; describe('Field', function () { @@ -28,21 +28,16 @@ describe('Field', function () { } function getField(values = {}) { - return new IndexPatternField( - fieldValues.indexPattern as IndexPattern, - { ...fieldValues, ...values }, - 'displayName', - () => {} - ); + return new IndexPatternField({ ...fieldValues, ...values }, 'displayName'); } const fieldValues = { name: 'name', - type: 'type', + type: 'string', script: 'script', lang: 'lang', count: 1, - esTypes: ['type'], + esTypes: ['text'], aggregatable: true, filterable: true, searchable: true, @@ -125,7 +120,7 @@ describe('Field', function () { const fieldB = getField({ indexed: true, type: KBN_FIELD_TYPES.STRING }); expect(fieldB.sortable).toEqual(true); - const fieldC = getField({ indexed: false }); + const fieldC = getField({ indexed: false, aggregatable: false, scripted: false }); expect(fieldC.sortable).toEqual(false); }); @@ -139,31 +134,26 @@ describe('Field', function () { const fieldC = getField({ indexed: true, type: KBN_FIELD_TYPES.STRING }); expect(fieldC.filterable).toEqual(true); - const fieldD = getField({ scripted: false, indexed: false }); + const fieldD = getField({ scripted: false, indexed: false, searchable: false }); expect(fieldD.filterable).toEqual(false); }); it('exports the property to JSON', () => { - const field = new IndexPatternField( - { fieldFormatMap: { name: {} } } as IndexPattern, - fieldValues, - 'displayName', - () => {} - ); + const field = new IndexPatternField(fieldValues, 'displayName'); expect(flatten(field)).toMatchSnapshot(); }); it('spec snapshot', () => { - const field = new IndexPatternField( - { - fieldFormatMap: { - name: { toJSON: () => ({ id: 'number', params: { pattern: '$0,0.[00]' } }) }, - }, - } as IndexPattern, - fieldValues, - 'displayName', - () => {} - ); - expect(field.toSpec()).toMatchSnapshot(); + const field = new IndexPatternField(fieldValues, 'displayName'); + const getFormatterForField = () => + ({ + toJSON: () => ({ + id: 'number', + params: { + pattern: '$0,0.[00]', + }, + }), + } as FieldFormat); + expect(field.toSpec({ getFormatterForField })).toMatchSnapshot(); }); }); diff --git a/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts b/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts index 965f1a7f63065..7f72bfe55c7cd 100644 --- a/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts +++ b/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts @@ -20,40 +20,27 @@ import { i18n } from '@kbn/i18n'; import { KbnFieldType, getKbnFieldType } from '../../kbn_field_types'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; -import { FieldFormat } from '../../field_formats'; import { IFieldType } from './types'; -import { OnNotification, FieldSpec } from '../types'; - -import { IndexPattern } from '../index_patterns'; +import { FieldSpec, IndexPattern } from '../..'; +import { FieldTypeUnknownError } from '../errors'; export class IndexPatternField implements IFieldType { readonly spec: FieldSpec; // not writable or serialized - readonly indexPattern: IndexPattern; readonly displayName: string; private readonly kbnFieldType: KbnFieldType; - constructor( - indexPattern: IndexPattern, - spec: FieldSpec, - displayName: string, - onNotification: OnNotification - ) { - this.indexPattern = indexPattern; + constructor(spec: FieldSpec, displayName: string) { this.spec = { ...spec, type: spec.name === '_source' ? '_source' : spec.type }; this.displayName = displayName; this.kbnFieldType = getKbnFieldType(spec.type); if (spec.type && this.kbnFieldType?.name === KBN_FIELD_TYPES.UNKNOWN) { - const title = i18n.translate('data.indexPatterns.unknownFieldHeader', { - values: { type: spec.type }, - defaultMessage: 'Unknown field type {type}', - }); - const text = i18n.translate('data.indexPatterns.unknownFieldErrorMessage', { - values: { name: spec.name, title: indexPattern.title }, - defaultMessage: 'Field {name} in indexPattern {title} is using an unknown field type.', + const msg = i18n.translate('data.indexPatterns.unknownFieldTypeErrorMsg', { + values: { type: spec.type, name: spec.name }, + defaultMessage: `Field '{name}' Unknown field type '{type}'`, }); - onNotification({ title, text, color: 'danger', iconType: 'alert' }); + throw new FieldTypeUnknownError(msg, spec); } } @@ -143,10 +130,6 @@ export class IndexPatternField implements IFieldType { return this.aggregatable; } - public get format(): FieldFormat { - return this.indexPattern.getFormatterForField(this); - } - public toJSON() { return { count: this.count, @@ -165,7 +148,11 @@ export class IndexPatternField implements IFieldType { }; } - public toSpec() { + public toSpec({ + getFormatterForField, + }: { + getFormatterForField?: IndexPattern['getFormatterForField']; + } = {}) { return { count: this.count, script: this.script, @@ -179,7 +166,7 @@ export class IndexPatternField implements IFieldType { aggregatable: this.aggregatable, readFromDocValues: this.readFromDocValues, subType: this.subType, - format: this.indexPattern?.fieldFormatMap[this.name]?.toJSON() || undefined, + format: getFormatterForField ? getFormatterForField(this).toJSON() : undefined, }; } } diff --git a/src/plugins/data/common/index_patterns/fields/types.ts b/src/plugins/data/common/index_patterns/fields/types.ts index 558b5b57dce40..5814760601a67 100644 --- a/src/plugins/data/common/index_patterns/fields/types.ts +++ b/src/plugins/data/common/index_patterns/fields/types.ts @@ -17,7 +17,7 @@ * under the License. */ -import { FieldSpec, IFieldSubType } from '../types'; +import { FieldSpec, IFieldSubType, IndexPattern } from '../..'; export interface IFieldType { name: string; @@ -38,5 +38,5 @@ export interface IFieldType { subType?: IFieldSubType; displayName?: string; format?: any; - toSpec?: () => FieldSpec; + toSpec?: (options?: { getFormatterForField?: IndexPattern['getFormatterForField'] }) => FieldSpec; } diff --git a/src/plugins/data/common/index_patterns/index.ts b/src/plugins/data/common/index_patterns/index.ts index 51a642b775c29..08f478404be2c 100644 --- a/src/plugins/data/common/index_patterns/index.ts +++ b/src/plugins/data/common/index_patterns/index.ts @@ -20,3 +20,5 @@ export * from './fields'; export * from './types'; export { IndexPatternsService } from './index_patterns'; +export type { IndexPattern } from './index_patterns'; +export * from './errors'; diff --git a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap index 047ac836a87d1..1871627da76de 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap +++ b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap @@ -32,7 +32,12 @@ Object { "esTypes": Array [ "boolean", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "ssl", "readFromDocValues": true, @@ -49,7 +54,12 @@ Object { "esTypes": Array [ "date", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "@timestamp", "readFromDocValues": true, @@ -66,7 +76,12 @@ Object { "esTypes": Array [ "date", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "time", "readFromDocValues": true, @@ -83,7 +98,12 @@ Object { "esTypes": Array [ "keyword", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "@tags", "readFromDocValues": true, @@ -100,7 +120,12 @@ Object { "esTypes": Array [ "date", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "utc_time", "readFromDocValues": true, @@ -117,7 +142,12 @@ Object { "esTypes": Array [ "integer", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "phpmemory", "readFromDocValues": true, @@ -134,7 +164,12 @@ Object { "esTypes": Array [ "ip", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "ip", "readFromDocValues": true, @@ -151,7 +186,12 @@ Object { "esTypes": Array [ "attachment", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "request_body", "readFromDocValues": true, @@ -168,7 +208,12 @@ Object { "esTypes": Array [ "geo_point", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "point", "readFromDocValues": true, @@ -185,7 +230,12 @@ Object { "esTypes": Array [ "geo_shape", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "area", "readFromDocValues": false, @@ -202,7 +252,12 @@ Object { "esTypes": Array [ "murmur3", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "hashed", "readFromDocValues": false, @@ -219,7 +274,12 @@ Object { "esTypes": Array [ "geo_point", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "geo.coordinates", "readFromDocValues": true, @@ -236,7 +296,12 @@ Object { "esTypes": Array [ "text", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "extension", "readFromDocValues": false, @@ -253,7 +318,12 @@ Object { "esTypes": Array [ "keyword", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "extension.keyword", "readFromDocValues": true, @@ -274,7 +344,12 @@ Object { "esTypes": Array [ "text", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "machine.os", "readFromDocValues": false, @@ -291,7 +366,12 @@ Object { "esTypes": Array [ "keyword", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "machine.os.raw", "readFromDocValues": true, @@ -312,7 +392,12 @@ Object { "esTypes": Array [ "keyword", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "geo.src", "readFromDocValues": true, @@ -329,7 +414,12 @@ Object { "esTypes": Array [ "_id", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "_id", "readFromDocValues": false, @@ -346,7 +436,12 @@ Object { "esTypes": Array [ "_type", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "_type", "readFromDocValues": false, @@ -363,7 +458,12 @@ Object { "esTypes": Array [ "_source", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "_source", "readFromDocValues": false, @@ -380,7 +480,12 @@ Object { "esTypes": Array [ "text", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "non-filterable", "readFromDocValues": false, @@ -397,7 +502,12 @@ Object { "esTypes": Array [ "text", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "non-sortable", "readFromDocValues": false, @@ -414,7 +524,12 @@ Object { "esTypes": Array [ "conflict", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "custom_user_field", "readFromDocValues": true, @@ -431,7 +546,12 @@ Object { "esTypes": Array [ "text", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": "expression", "name": "script string", "readFromDocValues": false, @@ -448,7 +568,12 @@ Object { "esTypes": Array [ "long", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": "expression", "name": "script number", "readFromDocValues": false, @@ -465,7 +590,12 @@ Object { "esTypes": Array [ "date", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": "painless", "name": "script date", "readFromDocValues": false, @@ -482,7 +612,12 @@ Object { "esTypes": Array [ "murmur3", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": "expression", "name": "script murmur3", "readFromDocValues": false, @@ -496,7 +631,7 @@ Object { "id": "test-pattern", "sourceFilters": undefined, "timeFieldName": "timestamp", - "title": "test-pattern", + "title": "title", "typeMeta": undefined, "version": 2, } diff --git a/src/plugins/data/common/index_patterns/index_patterns/format_hit.ts b/src/plugins/data/common/index_patterns/index_patterns/format_hit.ts index a0597ed4b9026..b47fef107258a 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/format_hit.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/format_hit.ts @@ -34,9 +34,9 @@ export function formatHitProvider(indexPattern: IndexPattern, defaultFormat: any type: FieldFormatsContentType = 'html' ) { const field = indexPattern.fields.getByName(fieldName); - const format = field ? field.format : defaultFormat; + const format = field ? indexPattern.getFormatterForField(field) : defaultFormat; - return format.convert(val, type, { field, hit }); + return format.convert(val, type, { field, hit, indexPattern }); } function formatHit(hit: Record, type: string = 'html') { diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts index f7e1156170f03..f49897c47d562 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { defaults, map, last, get } from 'lodash'; +import { defaults, map, last } from 'lodash'; import { IndexPattern } from './index_pattern'; @@ -29,6 +29,7 @@ import { stubbedSavedObjectIndexPattern } from '../../../../../fixtures/stubbed_ import { IndexPatternField } from '../fields'; import { fieldFormatsMock } from '../../field_formats/mocks'; +import { FieldFormat, IndexPatternsService } from '../..'; class MockFieldFormatter {} @@ -115,6 +116,7 @@ function create(id: string, payload?: any): Promise { apiClient, patternCache, fieldFormats: fieldFormatsMock, + indexPatternsService: {} as IndexPatternsService, onNotification: () => {}, onError: () => {}, shortDotsEnable: false, @@ -150,9 +152,6 @@ describe('IndexPattern', () => { expect(indexPattern).toHaveProperty('getNonScriptedFields'); expect(indexPattern).toHaveProperty('addScriptedField'); expect(indexPattern).toHaveProperty('removeScriptedField'); - expect(indexPattern).toHaveProperty('toString'); - expect(indexPattern).toHaveProperty('toJSON'); - expect(indexPattern).toHaveProperty('save'); // properties expect(indexPattern).toHaveProperty('fields'); @@ -170,7 +169,6 @@ describe('IndexPattern', () => { test('should have expected properties on fields', function () { expect(indexPattern.fields[0]).toHaveProperty('displayName'); expect(indexPattern.fields[0]).toHaveProperty('filterable'); - expect(indexPattern.fields[0]).toHaveProperty('format'); expect(indexPattern.fields[0]).toHaveProperty('sortable'); expect(indexPattern.fields[0]).toHaveProperty('scripted'); }); @@ -319,16 +317,18 @@ describe('IndexPattern', () => { describe('toSpec', () => { test('should match snapshot', () => { - indexPattern.fieldFormatMap.bytes = { + const formatter = { toJSON: () => ({ id: 'number', params: { pattern: '$0,0.[00]' } }), - }; + } as FieldFormat; + indexPattern.getFormatterForField = () => formatter; expect(indexPattern.toSpec()).toMatchSnapshot(); }); test('can restore from spec', async () => { - indexPattern.fieldFormatMap.bytes = { + const formatter = { toJSON: () => ({ id: 'number', params: { pattern: '$0,0.[00]' } }), - }; + } as FieldFormat; + indexPattern.getFormatterForField = () => formatter; const spec = indexPattern.toSpec(); const restoredPattern = await create(spec.id as string); restoredPattern.initFromSpec(spec); @@ -389,60 +389,4 @@ describe('IndexPattern', () => { }); }); }); - - test('should handle version conflicts', async () => { - setDocsourcePayload(null, { - id: 'foo', - version: 'foo', - attributes: { - title: 'something', - }, - }); - // Create a normal index pattern - const pattern = new IndexPattern('foo', { - savedObjectsClient: savedObjectsClient as any, - apiClient, - patternCache, - fieldFormats: fieldFormatsMock, - onNotification: () => {}, - onError: () => {}, - shortDotsEnable: false, - metaFields: [], - }); - await pattern.init(); - - expect(get(pattern, 'version')).toBe('fooa'); - - // Create the same one - we're going to handle concurrency - const samePattern = new IndexPattern('foo', { - savedObjectsClient: savedObjectsClient as any, - apiClient, - patternCache, - fieldFormats: fieldFormatsMock, - onNotification: () => {}, - onError: () => {}, - shortDotsEnable: false, - metaFields: [], - }); - await samePattern.init(); - - expect(get(samePattern, 'version')).toBe('fooaa'); - - // This will conflict because samePattern did a save (from refreshFields) - // but the resave should work fine - pattern.title = 'foo2'; - await pattern.save(); - - // This should not be able to recover - samePattern.title = 'foo3'; - - let result; - try { - await samePattern.save(); - } catch (err) { - result = err; - } - - expect(result.res.status).toBe(409); - }); }); diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts index ea91a9bb14e1f..76f1a5e59d0ee 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts @@ -26,11 +26,12 @@ import { ES_FIELD_TYPES, KBN_FIELD_TYPES, IIndexPattern, + FieldTypeUnknownError, FieldFormatNotFoundError, } from '../../../common'; import { findByTitle } from '../utils'; import { IndexPatternMissingIndices } from '../lib'; -import { IndexPatternField, IIndexPatternFieldList, FieldList } from '../fields'; +import { IndexPatternField, IIndexPatternFieldList, fieldList } from '../fields'; import { createFieldsFetcher } from './_fields_fetcher'; import { formatHitProvider } from './format_hit'; import { flattenHitWrapper } from './flatten_hit'; @@ -40,8 +41,8 @@ import { PatternCache } from './_pattern_cache'; import { expandShorthand, FieldMappingSpec, MappingObject } from '../../field_mapping'; import { IndexPatternSpec, TypeMeta, FieldSpec, SourceFilter } from '../types'; import { SerializedFieldFormat } from '../../../../expressions/common'; +import { IndexPatternsService } from '..'; -const MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS = 3; const savedObjectType = 'index-pattern'; interface IndexPatternDeps { @@ -49,6 +50,7 @@ interface IndexPatternDeps { apiClient: IIndexPatternsApiClient; patternCache: PatternCache; fieldFormats: FieldFormatsStartCommon; + indexPatternsService: IndexPatternsService; onNotification: OnNotification; onError: OnError; shortDotsEnable: boolean; @@ -69,17 +71,18 @@ export class IndexPattern implements IIndexPattern { public flattenHit: any; public metaFields: string[]; - private version: string | undefined; + public version: string | undefined; private savedObjectsClient: SavedObjectsClientCommon; private patternCache: PatternCache; public sourceFilters?: SourceFilter[]; - private originalBody: { [key: string]: any } = {}; + // todo make read only, update via method or factor out + public originalBody: { [key: string]: any } = {}; public fieldsFetcher: any; // probably want to factor out any direct usage and change to private + private indexPatternsService: IndexPatternsService; private shortDotsEnable: boolean = false; private fieldFormats: FieldFormatsStartCommon; private onNotification: OnNotification; private onError: OnError; - private apiClient: IIndexPatternsApiClient; private mapping: MappingObject = expandShorthand({ title: ES_FIELD_TYPES.TEXT, @@ -110,6 +113,7 @@ export class IndexPattern implements IIndexPattern { apiClient, patternCache, fieldFormats, + indexPatternsService, onNotification, onError, shortDotsEnable = false, @@ -120,15 +124,14 @@ export class IndexPattern implements IIndexPattern { this.savedObjectsClient = savedObjectsClient; this.patternCache = patternCache; this.fieldFormats = fieldFormats; + this.indexPatternsService = indexPatternsService; this.onNotification = onNotification; this.onError = onError; this.shortDotsEnable = shortDotsEnable; this.metaFields = metaFields; + this.fields = fieldList([], this.shortDotsEnable); - this.fields = new FieldList(this, [], this.shortDotsEnable, this.onNotification); - - this.apiClient = apiClient; this.fieldsFetcher = createFieldsFetcher(this, apiClient, metaFields); this.flattenHit = flattenHitWrapper(this, metaFields); this.formatHit = formatHitProvider( @@ -138,6 +141,22 @@ export class IndexPattern implements IIndexPattern { this.formatField = this.formatHit.formatField; } + private unknownFieldErrorNotification( + fieldType: string, + fieldName: string, + indexPatternTitle: string + ) { + const title = i18n.translate('data.indexPatterns.unknownFieldHeader', { + values: { type: fieldType }, + defaultMessage: 'Unknown field type {type}', + }); + const text = i18n.translate('data.indexPatterns.unknownFieldErrorMessage', { + values: { name: fieldName, title: indexPatternTitle }, + defaultMessage: 'Field {name} in indexPattern {title} is using an unknown field type.', + }); + this.onNotification({ title, text, color: 'danger', iconType: 'alert' }); + } + private serializeFieldFormatMap(flat: any, format: string, field: string | undefined) { if (format && field) { flat[field] = format; @@ -181,7 +200,15 @@ export class IndexPattern implements IIndexPattern { await this.refreshFields(); } else { if (specs) { - this.fields.replaceAll(specs); + try { + this.fields.replaceAll(specs); + } catch (err) { + if (err instanceof FieldTypeUnknownError) { + this.unknownFieldErrorNotification(err.fieldSpec.name, err.fieldSpec.type, this.title); + } else { + throw err; + } + } } } } @@ -203,7 +230,15 @@ export class IndexPattern implements IIndexPattern { this.timeFieldName = spec.timeFieldName; this.sourceFilters = spec.sourceFilters; - this.fields.replaceAll(spec.fields || []); + try { + this.fields.replaceAll(spec.fields || []); + } catch (err) { + if (err instanceof FieldTypeUnknownError) { + this.unknownFieldErrorNotification(err.fieldSpec.name, err.fieldSpec.type, this.title); + } else { + throw err; + } + } this.typeMeta = spec.typeMeta; this.fieldFormatMap = _.mapValues(fieldFormatMap, (mapping) => { @@ -322,7 +357,7 @@ export class IndexPattern implements IIndexPattern { title: this.title, timeFieldName: this.timeFieldName, sourceFilters: this.sourceFilters, - fields: this.fields.toSpec(), + fields: this.fields.toSpec({ getFormatterForField: this.getFormatterForField.bind(this) }), typeMeta: this.typeMeta, }; } @@ -342,19 +377,25 @@ export class IndexPattern implements IIndexPattern { throw new DuplicateField(name); } - this.fields.add({ - name, - script, - type: fieldType, - scripted: true, - lang, - aggregatable: true, - searchable: true, - count: 0, - readFromDocValues: false, - }); - - await this.save(); + try { + this.fields.add({ + name, + script, + type: fieldType, + scripted: true, + lang, + aggregatable: true, + searchable: true, + count: 0, + readFromDocValues: false, + }); + } catch (err) { + if (err instanceof FieldTypeUnknownError) { + this.unknownFieldErrorNotification(err.fieldSpec.name, err.fieldSpec.type, this.title); + } else { + throw err; + } + } } removeScriptedField(fieldName: string) { @@ -362,7 +403,6 @@ export class IndexPattern implements IIndexPattern { if (field) { this.fields.remove(field); } - return this.save(); } async popularizeField(fieldName: string, unit = 1) { @@ -441,7 +481,7 @@ export class IndexPattern implements IIndexPattern { fields: this.mapping.fields._serialize!(this.fields), fieldFormatMap: this.mapping.fieldFormatMap._serialize!(this.fieldFormatMap), type: this.type, - typeMeta: this.mapping.typeMeta._serialize!(this.mapping), + typeMeta: this.mapping.typeMeta._serialize!(this.typeMeta), }; } @@ -483,131 +523,52 @@ export class IndexPattern implements IIndexPattern { return await _create(potentialDuplicateByTitle.id); } - async save(saveAttempts: number = 0): Promise { - if (!this.id) return; - const body = this.prepBody(); - - const originalChangedKeys: string[] = []; - Object.entries(body).forEach(([key, value]) => { - if (value !== this.originalBody[key]) { - originalChangedKeys.push(key); - } - }); - - return this.savedObjectsClient - .update(savedObjectType, this.id, body, { version: this.version }) - .then((resp) => { - this.id = resp.id; - this.version = resp.version; - }) - .catch((err) => { - if ( - _.get(err, 'res.status') === 409 && - saveAttempts++ < MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS - ) { - const samePattern = new IndexPattern(this.id, { - savedObjectsClient: this.savedObjectsClient, - apiClient: this.apiClient, - patternCache: this.patternCache, - fieldFormats: this.fieldFormats, - onNotification: this.onNotification, - onError: this.onError, - shortDotsEnable: this.shortDotsEnable, - metaFields: this.metaFields, - }); - - return samePattern.init().then(() => { - // What keys changed from now and what the server returned - const updatedBody = samePattern.prepBody(); - - // Build a list of changed keys from the server response - // and ensure we ignore the key if the server response - // is the same as the original response (since that is expected - // if we made a change in that key) - - const serverChangedKeys: string[] = []; - Object.entries(updatedBody).forEach(([key, value]) => { - if (value !== (body as any)[key] && value !== this.originalBody[key]) { - serverChangedKeys.push(key); - } - }); - - let unresolvedCollision = false; - for (const originalKey of originalChangedKeys) { - for (const serverKey of serverChangedKeys) { - if (originalKey === serverKey) { - unresolvedCollision = true; - break; - } - } - } - - if (unresolvedCollision) { - const title = i18n.translate('data.indexPatterns.unableWriteLabel', { - defaultMessage: - 'Unable to write index pattern! Refresh the page to get the most up to date changes for this index pattern.', - }); - - this.onNotification({ title, color: 'danger' }); - throw err; - } - - // Set the updated response on this object - serverChangedKeys.forEach((key) => { - (this as any)[key] = (samePattern as any)[key]; - }); - this.version = samePattern.version; - - // Clear cache - this.patternCache.clear(this.id!); - - // Try the save again - return this.save(saveAttempts); - }); - } - throw err; - }); - } - async _fetchFields() { const fields = await this.fieldsFetcher.fetch(this); const scripted = this.getScriptedFields().map((field) => field.spec); - this.fields.replaceAll([...fields, ...scripted]); + try { + this.fields.replaceAll([...fields, ...scripted]); + } catch (err) { + if (err instanceof FieldTypeUnknownError) { + this.unknownFieldErrorNotification(err.fieldSpec.name, err.fieldSpec.type, this.title); + } else { + throw err; + } + } } refreshFields() { - return this._fetchFields() - .then(() => this.save()) - .catch((err) => { - // https://github.com/elastic/kibana/issues/9224 - // This call will attempt to remap fields from the matching - // ES index which may not actually exist. In that scenario, - // we still want to notify the user that there is a problem - // but we do not want to potentially make any pages unusable - // so do not rethrow the error here - - if (err instanceof IndexPatternMissingIndices) { - this.onNotification({ title: (err as any).message, color: 'danger', iconType: 'alert' }); - return []; - } - - this.onError(err, { - title: i18n.translate('data.indexPatterns.fetchFieldErrorTitle', { - defaultMessage: 'Error fetching fields for index pattern {title} (ID: {id})', - values: { - id: this.id, - title: this.title, - }, - }), - }); - }); - } - - toJSON() { - return this.id; - } - - toString() { - return '' + this.toJSON(); + return ( + this._fetchFields() + // todo + .then(() => this.indexPatternsService.save(this)) + .catch((err) => { + // https://github.com/elastic/kibana/issues/9224 + // This call will attempt to remap fields from the matching + // ES index which may not actually exist. In that scenario, + // we still want to notify the user that there is a problem + // but we do not want to potentially make any pages unusable + // so do not rethrow the error here + + if (err instanceof IndexPatternMissingIndices) { + this.onNotification({ + title: (err as any).message, + color: 'danger', + iconType: 'alert', + }); + return []; + } + + this.onError(err, { + title: i18n.translate('data.indexPatterns.fetchFieldErrorTitle', { + defaultMessage: 'Error fetching fields for index pattern {title} (ID: {id})', + values: { + id: this.id, + title: this.title, + }, + }), + }); + }) + ); } } diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts index 8223b31042124..c79c7900148ea 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts @@ -17,28 +17,26 @@ * under the License. */ -import { IndexPatternsService } from './index_patterns'; +import { defaults } from 'lodash'; +import { IndexPatternsService } from '.'; import { fieldFormatsMock } from '../../field_formats/mocks'; -import { - UiSettingsCommon, - IIndexPatternsApiClient, - SavedObjectsClientCommon, - SavedObject, -} from '../types'; +import { stubbedSavedObjectIndexPattern } from '../../../../../fixtures/stubbed_saved_object_index_pattern'; +import { UiSettingsCommon, SavedObjectsClientCommon, SavedObject } from '../types'; + +const createFieldsFetcher = jest.fn().mockImplementation(() => ({ + getFieldsForWildcard: jest.fn().mockImplementation(() => { + return new Promise((resolve) => resolve([])); + }), + every: jest.fn(), +})); const fieldFormats = fieldFormatsMock; -jest.mock('./index_pattern', () => { - class IndexPattern { - init = async () => { - return this; - }; - } +let object: any = {}; - return { - IndexPattern, - }; -}); +function setDocsourcePayload(id: string | null, providedPayload: any) { + object = defaults(providedPayload || {}, stubbedSavedObjectIndexPattern(id)); +} describe('IndexPatterns', () => { let indexPatterns: IndexPatternsService; @@ -53,6 +51,25 @@ describe('IndexPatterns', () => { > ); savedObjectsClient.delete = jest.fn(() => Promise.resolve({}) as Promise); + savedObjectsClient.get = jest.fn().mockImplementation(() => object); + savedObjectsClient.create = jest.fn(); + savedObjectsClient.update = jest + .fn() + .mockImplementation(async (type, id, body, { version }) => { + if (object.version !== version) { + throw new Object({ + res: { + status: 409, + }, + }); + } + object.attributes.title = body.title; + object.version += 'a'; + return { + id: object.id, + version: object.version, + }; + }); indexPatterns = new IndexPatternsService({ uiSettings: ({ @@ -60,7 +77,7 @@ describe('IndexPatterns', () => { getAll: () => {}, } as any) as UiSettingsCommon, savedObjectsClient: (savedObjectsClient as unknown) as SavedObjectsClientCommon, - apiClient: {} as IIndexPatternsApiClient, + apiClient: createFieldsFetcher(), fieldFormats, onNotification: () => {}, onError: () => {}, @@ -70,6 +87,14 @@ describe('IndexPatterns', () => { test('does cache gets for the same id', async () => { const id = '1'; + setDocsourcePayload(id, { + id: 'foo', + version: 'foo', + attributes: { + title: 'something', + }, + }); + const indexPattern = await indexPatterns.get(id); expect(indexPattern).toBeDefined(); @@ -107,4 +132,41 @@ describe('IndexPatterns', () => { await indexPatterns.delete(id); expect(indexPattern).not.toBe(await indexPatterns.get(id)); }); + + test('should handle version conflicts', async () => { + setDocsourcePayload(null, { + id: 'foo', + version: 'foo', + attributes: { + title: 'something', + }, + }); + + // Create a normal index patterns + const pattern = await indexPatterns.make('foo'); + + expect(pattern.version).toBe('fooa'); + + // Create the same one - we're going to handle concurrency + const samePattern = await indexPatterns.make('foo'); + + expect(samePattern.version).toBe('fooaa'); + + // This will conflict because samePattern did a save (from refreshFields) + // but the resave should work fine + pattern.title = 'foo2'; + await indexPatterns.save(pattern); + + // This should not be able to recover + samePattern.title = 'foo3'; + + let result; + try { + await indexPatterns.save(samePattern); + } catch (err) { + result = err; + } + + expect(result.res.status).toBe(409); + }); }); diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts index 0ad9ae8f2014f..88a7e9f6cef4c 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts @@ -17,6 +17,7 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import { SavedObjectsClientCommon } from '../..'; import { createIndexPatternCache } from '.'; @@ -25,7 +26,6 @@ import { createEnsureDefaultIndexPattern, EnsureDefaultIndexPattern, } from './ensure_default_index_pattern'; -import { IndexPatternField } from '../fields'; import { OnNotification, OnError, @@ -38,6 +38,8 @@ import { FieldFormatsStartCommon } from '../../field_formats'; import { UI_SETTINGS, SavedObject } from '../../../common'; const indexPatternCache = createIndexPatternCache(); +const MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS = 3; +const savedObjectType = 'index-pattern'; type IndexPatternCachedFieldType = 'id' | 'title'; @@ -86,15 +88,6 @@ export class IndexPatternsService { ); } - public createField( - indexPattern: IndexPattern, - spec: IndexPatternField['spec'], - displayName: string, - onNotification: OnNotification - ) { - return new IndexPatternField(indexPattern, spec, displayName, onNotification); - } - private async refreshSavedObjectsCache() { this.savedObjectsCache = await this.savedObjectsClient.find({ type: 'index-pattern', @@ -191,6 +184,7 @@ export class IndexPatternsService { apiClient: this.apiClient, patternCache: indexPatternCache, fieldFormats: this.fieldFormats, + indexPatternsService: this, onNotification: this.onNotification, onError: this.onError, shortDotsEnable, @@ -201,6 +195,93 @@ export class IndexPatternsService { return indexPattern; } + async save(indexPattern: IndexPattern, saveAttempts: number = 0): Promise { + if (!indexPattern.id) return; + const shortDotsEnable = await this.config.get(UI_SETTINGS.SHORT_DOTS_ENABLE); + const metaFields = await this.config.get(UI_SETTINGS.META_FIELDS); + + const body = indexPattern.prepBody(); + + const originalChangedKeys: string[] = []; + Object.entries(body).forEach(([key, value]) => { + if (value !== indexPattern.originalBody[key]) { + originalChangedKeys.push(key); + } + }); + + return this.savedObjectsClient + .update(savedObjectType, indexPattern.id, body, { version: indexPattern.version }) + .then((resp) => { + indexPattern.id = resp.id; + indexPattern.version = resp.version; + }) + .catch((err) => { + if (err?.res?.status === 409 && saveAttempts++ < MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS) { + const samePattern = new IndexPattern(indexPattern.id, { + savedObjectsClient: this.savedObjectsClient, + apiClient: this.apiClient, + patternCache: indexPatternCache, + fieldFormats: this.fieldFormats, + indexPatternsService: this, + onNotification: this.onNotification, + onError: this.onError, + shortDotsEnable, + metaFields, + }); + + return samePattern.init().then(() => { + // What keys changed from now and what the server returned + const updatedBody = samePattern.prepBody(); + + // Build a list of changed keys from the server response + // and ensure we ignore the key if the server response + // is the same as the original response (since that is expected + // if we made a change in that key) + + const serverChangedKeys: string[] = []; + Object.entries(updatedBody).forEach(([key, value]) => { + if (value !== (body as any)[key] && value !== indexPattern.originalBody[key]) { + serverChangedKeys.push(key); + } + }); + + let unresolvedCollision = false; + for (const originalKey of originalChangedKeys) { + for (const serverKey of serverChangedKeys) { + if (originalKey === serverKey) { + unresolvedCollision = true; + break; + } + } + } + + if (unresolvedCollision) { + const title = i18n.translate('data.indexPatterns.unableWriteLabel', { + defaultMessage: + 'Unable to write index pattern! Refresh the page to get the most up to date changes for this index pattern.', + }); + + this.onNotification({ title, color: 'danger' }); + throw err; + } + + // Set the updated response on this object + serverChangedKeys.forEach((key) => { + (indexPattern as any)[key] = (samePattern as any)[key]; + }); + indexPattern.version = samePattern.version; + + // Clear cache + indexPatternCache.clear(indexPattern.id!); + + // Try the save again + return this.save(indexPattern, saveAttempts); + }); + } + throw err; + }); + } + async make(id?: string): Promise { const shortDotsEnable = await this.config.get(UI_SETTINGS.SHORT_DOTS_ENABLE); const metaFields = await this.config.get(UI_SETTINGS.META_FIELDS); @@ -210,6 +291,7 @@ export class IndexPatternsService { apiClient: this.apiClient, patternCache: indexPatternCache, fieldFormats: this.fieldFormats, + indexPatternsService: this, onNotification: this.onNotification, onError: this.onError, shortDotsEnable, diff --git a/src/plugins/data/common/search/aggs/agg_config.test.ts b/src/plugins/data/common/search/aggs/agg_config.test.ts index a443eacee731c..f6fcc29805dc4 100644 --- a/src/plugins/data/common/search/aggs/agg_config.test.ts +++ b/src/plugins/data/common/search/aggs/agg_config.test.ts @@ -25,8 +25,7 @@ import { AggType } from './agg_type'; import { AggTypesRegistryStart } from './agg_types_registry'; import { mockAggTypesRegistry } from './test_helpers'; import { MetricAggType } from './metrics/metric_agg_type'; -import { IndexPattern } from '../../index_patterns/index_patterns/index_pattern'; -import { IIndexPatternFieldList } from '../../index_patterns/fields'; +import { IndexPattern, IndexPatternField, IIndexPatternFieldList } from '../../index_patterns'; describe('AggConfig', () => { let indexPattern: IndexPattern; @@ -67,6 +66,9 @@ describe('AggConfig', () => { getByName: (name: string) => fields.find((f) => f.name === name), filter: () => fields, } as unknown) as IndexPattern['fields'], + getFormatterForField: (field: IndexPatternField) => ({ + toJSON: () => ({}), + }), } as IndexPattern; typesRegistry = mockAggTypesRegistry(); }); diff --git a/src/plugins/data/common/search/aggs/agg_config.ts b/src/plugins/data/common/search/aggs/agg_config.ts index b5747ce7bb9bd..201e9f1ec402c 100644 --- a/src/plugins/data/common/search/aggs/agg_config.ts +++ b/src/plugins/data/common/search/aggs/agg_config.ts @@ -21,12 +21,13 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import { Assign, Ensure } from '@kbn/utility-types'; -import { FetchOptions, ISearchSource } from 'src/plugins/data/public'; +import { ISearchSource } from 'src/plugins/data/public'; import { ExpressionAstFunction, ExpressionAstArgument, SerializedFieldFormat, } from 'src/plugins/expressions/common'; +import { ISearchOptions } from '../es_search'; import { IAggType } from './agg_type'; import { writeParams } from './agg_params'; @@ -213,11 +214,11 @@ export class AggConfig { /** * Hook for pre-flight logic, see AggType#onSearchRequestStart - * @param {Courier.SearchSource} searchSource - * @param {Courier.FetchOptions} options + * @param {SearchSource} searchSource + * @param {ISearchOptions} options * @return {Promise} */ - onSearchRequestStart(searchSource: ISearchSource, options?: FetchOptions) { + onSearchRequestStart(searchSource: ISearchSource, options?: ISearchOptions) { if (!this.type) { return Promise.resolve(); } diff --git a/src/plugins/data/common/search/aggs/agg_configs.ts b/src/plugins/data/common/search/aggs/agg_configs.ts index 203eda3a907ee..282e6f3b538a4 100644 --- a/src/plugins/data/common/search/aggs/agg_configs.ts +++ b/src/plugins/data/common/search/aggs/agg_configs.ts @@ -20,7 +20,7 @@ import _ from 'lodash'; import { Assign } from '@kbn/utility-types'; -import { FetchOptions, ISearchSource } from 'src/plugins/data/public'; +import { ISearchOptions, ISearchSource } from 'src/plugins/data/public'; import { AggConfig, AggConfigSerialized, IAggConfig } from './agg_config'; import { IAggType } from './agg_type'; import { AggTypesRegistryStart } from './agg_types_registry'; @@ -300,7 +300,7 @@ export class AggConfigs { return _.find(reqAgg.getResponseAggs(), { id }); } - onSearchRequestStart(searchSource: ISearchSource, options?: FetchOptions) { + onSearchRequestStart(searchSource: ISearchSource, options?: ISearchOptions) { return Promise.all( // @ts-ignore this.getRequestAggs().map((agg: AggConfig) => agg.onSearchRequestStart(searchSource, options)) diff --git a/src/plugins/data/common/search/aggs/agg_type.test.ts b/src/plugins/data/common/search/aggs/agg_type.test.ts index bf1136159dfe8..16a5586858ab9 100644 --- a/src/plugins/data/common/search/aggs/agg_type.test.ts +++ b/src/plugins/data/common/search/aggs/agg_type.test.ts @@ -147,6 +147,9 @@ describe('AggType Class', () => { }, }, }, + aggConfigs: { + indexPattern: { getFormatterForField: () => ({ toJSON: () => ({ id: 'format' }) }) }, + }, } as unknown) as IAggConfig; const aggType = new AggType({ name: 'name', diff --git a/src/plugins/data/common/search/aggs/agg_type.ts b/src/plugins/data/common/search/aggs/agg_type.ts index 2ee604c1bf25d..1e3839038b0f7 100644 --- a/src/plugins/data/common/search/aggs/agg_type.ts +++ b/src/plugins/data/common/search/aggs/agg_type.ts @@ -271,7 +271,9 @@ export class AggType< this.getSerializedFormat = config.getSerializedFormat || ((agg: TAggConfig) => { - return agg.params.field ? agg.params.field.format.toJSON() : {}; + return agg.params.field + ? agg.aggConfigs.indexPattern.getFormatterForField(agg.params.field).toJSON() + : {}; }); this.getValue = config.getValue || ((agg: TAggConfig, bucket: any) => {}); diff --git a/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts index 1a7deafb548ae..7c09d2e64e8b7 100644 --- a/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts +++ b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts @@ -140,7 +140,7 @@ export const buildOtherBucketAgg = ( const bucketAggs = aggConfigs.aggs.filter((agg) => agg.type.type === AggGroupNames.Buckets); const index = bucketAggs.findIndex((agg) => agg.id === aggWithOtherBucket.id); const aggs = aggConfigs.toDsl(); - const indexPattern = aggWithOtherBucket.params.field.indexPattern; + const indexPattern = aggWithOtherBucket.aggConfigs.indexPattern; // create filters aggregation const filterAgg = aggConfigs.createAggConfig( @@ -211,7 +211,7 @@ export const buildOtherBucketAgg = ( filters.push( buildExistsFilter( aggWithOtherBucket.params.field, - aggWithOtherBucket.params.field.indexPattern + aggWithOtherBucket.aggConfigs.indexPattern ) ); } @@ -264,7 +264,7 @@ export const mergeOtherBucketAggResponse = ( const phraseFilter = buildPhrasesFilter( otherAgg.params.field, requestFilterTerms, - otherAgg.params.field.indexPattern + otherAgg.aggConfigs.indexPattern ); phraseFilter.meta.negate = true; bucket.filters = [phraseFilter]; @@ -276,7 +276,7 @@ export const mergeOtherBucketAggResponse = ( ) ) { bucket.filters.push( - buildExistsFilter(otherAgg.params.field, otherAgg.params.field.indexPattern) + buildExistsFilter(otherAgg.params.field, otherAgg.aggConfigs.indexPattern) ); } aggResultBuckets.push(bucket); diff --git a/src/plugins/data/common/search/aggs/buckets/create_filter/histogram.test.ts b/src/plugins/data/common/search/aggs/buckets/create_filter/histogram.test.ts index b57d530ef40e8..dc1d0ec0a152f 100644 --- a/src/plugins/data/common/search/aggs/buckets/create_filter/histogram.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/create_filter/histogram.test.ts @@ -40,6 +40,7 @@ describe('AggConfig Filters', () => { getByName: () => field, filter: () => [field], }, + getFormatterForField: () => new BytesFormat({}, getConfig), } as any; return new AggConfigs( diff --git a/src/plugins/data/common/search/aggs/buckets/create_filter/range.test.ts b/src/plugins/data/common/search/aggs/buckets/create_filter/range.test.ts index 30af970f55aa9..b53ae44c05075 100644 --- a/src/plugins/data/common/search/aggs/buckets/create_filter/range.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/create_filter/range.test.ts @@ -30,7 +30,6 @@ describe('AggConfig Filters', () => { const getAggConfigs = () => { const field = { name: 'bytes', - format: new BytesFormat({}, getConfig), }; const indexPattern = { @@ -40,6 +39,7 @@ describe('AggConfig Filters', () => { getByName: () => field, filter: () => [field], }, + getFormatterForField: () => new BytesFormat({}, getConfig), } as any; return new AggConfigs( diff --git a/src/plugins/data/common/search/aggs/buckets/create_filter/terms.ts b/src/plugins/data/common/search/aggs/buckets/create_filter/terms.ts index 95de19b96abd4..ccd1cf6e358b4 100644 --- a/src/plugins/data/common/search/aggs/buckets/create_filter/terms.ts +++ b/src/plugins/data/common/search/aggs/buckets/create_filter/terms.ts @@ -27,7 +27,7 @@ import { export const createFilterTerms = (aggConfig: IBucketAggConfig, key: string, params: any) => { const field = aggConfig.params.field; - const indexPattern = field.indexPattern; + const indexPattern = aggConfig.aggConfigs.indexPattern; if (key === '__other__') { const terms = params.terms; diff --git a/src/plugins/data/common/search/aggs/buckets/date_range.ts b/src/plugins/data/common/search/aggs/buckets/date_range.ts index eda35a77afa5f..f9a3acb990fbf 100644 --- a/src/plugins/data/common/search/aggs/buckets/date_range.ts +++ b/src/plugins/data/common/search/aggs/buckets/date_range.ts @@ -58,7 +58,9 @@ export const getDateRangeBucketAgg = ({ getSerializedFormat(agg) { return { id: 'date_range', - params: agg.params.field ? agg.params.field.format.toJSON() : {}, + params: agg.params.field + ? agg.aggConfigs.indexPattern.getFormatterForField(agg.params.field).toJSON() + : {}, }; }, makeLabel(aggConfig) { diff --git a/src/plugins/data/common/search/aggs/buckets/ip_range.ts b/src/plugins/data/common/search/aggs/buckets/ip_range.ts index 46e0b62d0f8d7..d0a6174b011fc 100644 --- a/src/plugins/data/common/search/aggs/buckets/ip_range.ts +++ b/src/plugins/data/common/search/aggs/buckets/ip_range.ts @@ -59,7 +59,9 @@ export const getIpRangeBucketAgg = () => getSerializedFormat(agg) { return { id: 'ip_range', - params: agg.params.field ? agg.params.field.format.toJSON() : {}, + params: agg.params.field + ? agg.aggConfigs.indexPattern.getFormatterForField(agg.params.field).toJSON() + : {}, }; }, makeLabel(aggConfig) { diff --git a/src/plugins/data/common/search/aggs/buckets/range.test.ts b/src/plugins/data/common/search/aggs/buckets/range.test.ts index b23b03db6a9ec..b8241e04ea1ee 100644 --- a/src/plugins/data/common/search/aggs/buckets/range.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/range.test.ts @@ -27,12 +27,6 @@ describe('Range Agg', () => { const getAggConfigs = () => { const field = { name: 'bytes', - format: new NumberFormat( - { - pattern: '0,0.[000] b', - }, - getConfig - ), }; const indexPattern = { @@ -42,6 +36,13 @@ describe('Range Agg', () => { getByName: () => field, filter: () => [field], }, + getFormatterForField: () => + new NumberFormat( + { + pattern: '0,0.[000] b', + }, + getConfig + ), } as any; return new AggConfigs( diff --git a/src/plugins/data/common/search/aggs/buckets/range.ts b/src/plugins/data/common/search/aggs/buckets/range.ts index 91a357b635950..169b234845274 100644 --- a/src/plugins/data/common/search/aggs/buckets/range.ts +++ b/src/plugins/data/common/search/aggs/buckets/range.ts @@ -78,7 +78,9 @@ export const getRangeBucketAgg = ({ getFieldFormatsStart }: RangeBucketAggDepend return key; }, getSerializedFormat(agg) { - const format = agg.params.field ? agg.params.field.format.toJSON() : {}; + const format = agg.params.field + ? agg.aggConfigs.indexPattern.getFormatterForField(agg.params.field).toJSON() + : { id: undefined, params: undefined }; return { id: 'range', params: { diff --git a/src/plugins/data/common/search/aggs/buckets/terms.ts b/src/plugins/data/common/search/aggs/buckets/terms.ts index 5c8483cf21369..1363d38748c8b 100644 --- a/src/plugins/data/common/search/aggs/buckets/terms.ts +++ b/src/plugins/data/common/search/aggs/buckets/terms.ts @@ -82,7 +82,9 @@ export const getTermsBucketAgg = () => return agg.getFieldDisplayName() + ': ' + params.order.text; }, getSerializedFormat(agg) { - const format = agg.params.field ? agg.params.field.format.toJSON() : {}; + const format = agg.params.field + ? agg.aggConfigs.indexPattern.getFormatterForField(agg.params.field).toJSON() + : { id: undefined, params: undefined }; return { id: 'terms', params: { diff --git a/src/plugins/data/common/search/aggs/metrics/parent_pipeline.test.ts b/src/plugins/data/common/search/aggs/metrics/parent_pipeline.test.ts index c6bba56f73ec7..4815ab0ac56dc 100644 --- a/src/plugins/data/common/search/aggs/metrics/parent_pipeline.test.ts +++ b/src/plugins/data/common/search/aggs/metrics/parent_pipeline.test.ts @@ -66,9 +66,6 @@ describe('parent pipeline aggs', function () { ) => { const field = { name: 'field', - format: { - toJSON: () => ({ id: 'bytes' }), - }, }; const indexPattern = { id: '1234', @@ -77,6 +74,9 @@ describe('parent pipeline aggs', function () { getByName: () => field, filter: () => [field], }, + getFormatterForField: () => ({ + toJSON: () => ({ id: 'bytes' }), + }), } as any; const aggConfigs = new AggConfigs( diff --git a/src/plugins/data/common/search/aggs/metrics/sibling_pipeline.test.ts b/src/plugins/data/common/search/aggs/metrics/sibling_pipeline.test.ts index a157d225c839c..32737f7b7237d 100644 --- a/src/plugins/data/common/search/aggs/metrics/sibling_pipeline.test.ts +++ b/src/plugins/data/common/search/aggs/metrics/sibling_pipeline.test.ts @@ -72,6 +72,9 @@ describe('sibling pipeline aggs', () => { getByName: () => field, filter: () => [field], }, + getFormatterForField: () => ({ + toJSON: () => ({ id: 'bytes' }), + }), } as any; const aggConfigs = new AggConfigs( diff --git a/src/plugins/data/common/search/aggs/param_types/base.ts b/src/plugins/data/common/search/aggs/param_types/base.ts index 3a12a9a54500f..c0316c974e26f 100644 --- a/src/plugins/data/common/search/aggs/param_types/base.ts +++ b/src/plugins/data/common/search/aggs/param_types/base.ts @@ -17,7 +17,7 @@ * under the License. */ -import { FetchOptions, ISearchSource } from 'src/plugins/data/public'; +import { ISearchOptions, ISearchSource } from 'src/plugins/data/public'; import { ExpressionAstFunction } from 'src/plugins/expressions/common'; import { IAggConfigs } from '../agg_configs'; import { IAggConfig } from '../agg_config'; @@ -56,7 +56,7 @@ export class BaseParamType { modifyAggConfigOnSearchRequestStart: ( aggConfig: TAggConfig, searchSource?: ISearchSource, - options?: FetchOptions + options?: ISearchOptions ) => void; constructor(config: Record) { diff --git a/src/plugins/data/common/search/aggs/types.ts b/src/plugins/data/common/search/aggs/types.ts index dabd653463d4f..aec3dcc9d068c 100644 --- a/src/plugins/data/common/search/aggs/types.ts +++ b/src/plugins/data/common/search/aggs/types.ts @@ -93,7 +93,7 @@ export interface AggsCommonStart { * is only used internally. The difference is that AggsStart includes the * typings for the registry with initialized agg types. * - * @internal + * @public */ export type AggsStart = Assign; diff --git a/src/plugins/data/common/search/es_search/index.ts b/src/plugins/data/common/search/es_search/index.ts index 7bc9cada8f0ee..d8f7b5091eb8f 100644 --- a/src/plugins/data/common/search/es_search/index.ts +++ b/src/plugins/data/common/search/es_search/index.ts @@ -17,9 +17,4 @@ * under the License. */ -export { - ISearchRequestParams, - IEsSearchRequest, - IEsSearchResponse, - ES_SEARCH_STRATEGY, -} from './types'; +export * from './types'; diff --git a/src/plugins/data/common/search/es_search/types.ts b/src/plugins/data/common/search/es_search/types.ts index 3184fbe341705..81124c1e095f7 100644 --- a/src/plugins/data/common/search/es_search/types.ts +++ b/src/plugins/data/common/search/es_search/types.ts @@ -22,6 +22,17 @@ import { IKibanaSearchRequest, IKibanaSearchResponse } from '../types'; export const ES_SEARCH_STRATEGY = 'es'; +export interface ISearchOptions { + /** + * An `AbortSignal` that allows the caller of `search` to abort a search request. + */ + abortSignal?: AbortSignal; + /** + * Use this option to force using a specific server side search strategy. Leave empty to use the default strategy. + */ + strategy?: string; +} + export type ISearchRequestParams> = { trackTotalHits?: boolean; } & Search; @@ -42,3 +53,6 @@ export interface IEsSearchResponse extends IKibanaSearchResponse { isPartial?: boolean; rawResponse: SearchResponse; } + +export const isEsResponse = (response: any): response is IEsSearchResponse => + response && response.rawResponse; diff --git a/src/plugins/data/common/search/index.ts b/src/plugins/data/common/search/index.ts index d8184551b7f3d..061974d860246 100644 --- a/src/plugins/data/common/search/index.ts +++ b/src/plugins/data/common/search/index.ts @@ -22,10 +22,4 @@ export * from './es_search'; export * from './expressions'; export * from './tabify'; export * from './types'; - -export { - IEsSearchRequest, - IEsSearchResponse, - ES_SEARCH_STRATEGY, - ISearchRequestParams, -} from './es_search'; +export * from './es_search'; diff --git a/src/plugins/data/kibana.json b/src/plugins/data/kibana.json index b4f20ec6225e2..9cb9b1745373a 100644 --- a/src/plugins/data/kibana.json +++ b/src/plugins/data/kibana.json @@ -13,7 +13,6 @@ "usageCollection", "kibanaUtils", "kibanaReact", - "kibanaLegacy", "inspector" ] } diff --git a/src/plugins/data/public/actions/apply_filter_action.ts b/src/plugins/data/public/actions/apply_filter_action.ts index a2621e6ce8802..944da72bd11d1 100644 --- a/src/plugins/data/public/actions/apply_filter_action.ts +++ b/src/plugins/data/public/actions/apply_filter_action.ts @@ -44,6 +44,7 @@ export function createFilterAction( return createAction({ type: ACTION_GLOBAL_APPLY_FILTER, id: ACTION_GLOBAL_APPLY_FILTER, + order: 100, getIconType: () => 'filter', getDisplayName: () => { return i18n.translate('data.filter.applyFilterActionTitle', { diff --git a/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts b/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts index a3b9b0b344823..2ad20c3807819 100644 --- a/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts @@ -30,11 +30,7 @@ import { ValueClickContext } from '../../../../embeddable/public'; const mockField = { name: 'bytes', - indexPattern: { - id: 'logstash-*', - }, filterable: true, - format: new fieldFormats.BytesFormat({}, (() => {}) as FieldFormatsGetConfigFn), }; describe('createFiltersFromValueClick', () => { @@ -81,6 +77,8 @@ describe('createFiltersFromValueClick', () => { getByName: () => mockField, filter: () => [mockField], }, + getFormatterForField: () => + new fieldFormats.BytesFormat({}, (() => {}) as FieldFormatsGetConfigFn), }), } as unknown) as IndexPatternsContract); }); diff --git a/src/plugins/data/public/field_formats/field_formats_registry.stub.ts b/src/plugins/data/public/field_formats/field_formats_registry.stub.ts new file mode 100644 index 0000000000000..e8741ca41036b --- /dev/null +++ b/src/plugins/data/public/field_formats/field_formats_registry.stub.ts @@ -0,0 +1,36 @@ +/* + * 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 { CoreSetup } from 'src/core/public'; +import { deserializeFieldFormat } from './utils/deserialize'; +import { baseFormattersPublic } from './constants'; +import { DataPublicPluginStart, fieldFormats } from '..'; + +export const getFieldFormatsRegistry = (core: CoreSetup) => { + const fieldFormatsRegistry = new fieldFormats.FieldFormatsRegistry(); + const getConfig = core.uiSettings.get.bind(core.uiSettings); + + fieldFormatsRegistry.init(getConfig, {}, baseFormattersPublic); + + fieldFormatsRegistry.deserialize = deserializeFieldFormat.bind( + fieldFormatsRegistry as DataPublicPluginStart['fieldFormats'] + ); + + return fieldFormatsRegistry; +}; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 27b16c57ffecf..5038af9409316 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -172,7 +172,7 @@ import { } from '../common/field_formats'; import { DateNanosFormat, DateFormat } from './field_formats'; -export { baseFormattersPublic } from './field_formats'; +export { baseFormattersPublic, FieldFormatsStart } from './field_formats'; // Field formats helpers namespace: export const fieldFormats = { @@ -262,7 +262,7 @@ export { UI_SETTINGS, TypeMeta as IndexPatternTypeMeta, AggregationRestrictions as IndexPatternAggRestrictions, - FieldList, + fieldList, } from '../common'; /* @@ -276,6 +276,7 @@ export { QuerySuggestionGetFnArgs, QuerySuggestionBasic, QuerySuggestionField, + AutocompleteStart, } from './autocomplete'; /* @@ -313,6 +314,7 @@ import { export { // aggs + AggConfigSerialized, AggGroupLabels, AggGroupName, AggGroupNames, @@ -337,12 +339,13 @@ export { TabbedTable, } from '../common'; +export type { AggConfigs, AggConfig } from '../common'; + export { // search ES_SEARCH_STRATEGY, EsQuerySortValue, extractSearchSourceReferences, - FetchOptions, getEsPreference, getSearchParamsFromRequest, IEsSearchRequest, @@ -351,8 +354,10 @@ export { IKibanaSearchResponse, injectSearchSourceReferences, ISearch, + ISearchSetup, + ISearchStart, + ISearchStartSearchSource, ISearchGeneric, - ISearchOptions, ISearchSource, parseSearchSourceJSON, RequestTimeoutError, @@ -367,6 +372,10 @@ export { EsRawResponseExpressionTypeDefinition, } from './search'; +export type { SearchSource } from './search'; + +export { ISearchOptions } from '../common'; + // Search namespace export const search = { aggs: { @@ -429,8 +438,12 @@ export { TimeHistory, TimefilterContract, TimeHistoryContract, + QueryStateChange, + QueryStart, } from './query'; +export { AggsStart } from './search/aggs'; + export { getTime, // kbn field types @@ -454,7 +467,13 @@ export function plugin(initializerContext: PluginInitializerContext { injectedMetadata.getBasePath.mockReturnValue('/hola/daro/'); diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index 3bc19a578a417..3b18e0fbed537 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -19,13 +19,7 @@ import './index.scss'; -import { - PluginInitializerContext, - CoreSetup, - CoreStart, - Plugin, - PackageInfo, -} from 'src/core/public'; +import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; import { ConfigSchema } from '../config'; import { Storage, IStorageWrapper, createStartServicesGetter } from '../../kibana_utils/public'; import { @@ -100,7 +94,6 @@ export class DataPublicPlugin private readonly fieldFormatsService: FieldFormatsService; private readonly queryService: QueryService; private readonly storage: IStorageWrapper; - private readonly packageInfo: PackageInfo; constructor(initializerContext: PluginInitializerContext) { this.searchService = new SearchService(); @@ -108,7 +101,6 @@ export class DataPublicPlugin this.fieldFormatsService = new FieldFormatsService(); this.autocomplete = new AutocompleteService(initializerContext); this.storage = new Storage(window.localStorage); - this.packageInfo = initializerContext.env.packageInfo; } public setup( @@ -145,7 +137,6 @@ export class DataPublicPlugin const searchService = this.searchService.setup(core, { usageCollection, - packageInfo: this.packageInfo, expressions, }); diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 0c4465ae7f4b9..fa5d3cd85f430 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -26,11 +26,12 @@ import { EuiGlobalToastListToast } from '@elastic/eui'; import { ExclusiveUnion } from '@elastic/eui'; import { ExpressionAstFunction } from 'src/plugins/expressions/common'; import { ExpressionsSetup } from 'src/plugins/expressions/public'; -import { FetchOptions as FetchOptions_2 } from 'src/plugins/data/public'; import { History } from 'history'; import { Href } from 'history'; +import { HttpStart } from 'src/core/public'; import { IconType } from '@elastic/eui'; import { InjectedIntl } from '@kbn/i18n/react'; +import { ISearchOptions as ISearchOptions_2 } from 'src/plugins/data/public'; import { ISearchSource as ISearchSource_2 } from 'src/plugins/data/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { IUiSettingsClient } from 'src/core/public'; @@ -64,7 +65,6 @@ import { Search } from '@elastic/elasticsearch/api/requestParams'; import { SearchResponse } from 'elasticsearch'; import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/expressions/common'; import { Subscription } from 'rxjs'; -import { Toast } from 'kibana/public'; import { ToastInputFields } from 'src/core/public/notifications'; import { ToastsSetup } from 'kibana/public'; import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport'; @@ -83,7 +83,90 @@ import { UserProvidedValues } from 'src/core/server/types'; // @public (undocumented) export const ACTION_GLOBAL_APPLY_FILTER = "ACTION_GLOBAL_APPLY_FILTER"; -// Warning: (ae-forgotten-export) The symbol "AggConfigSerialized" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "AggConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class AggConfig { + // Warning: (ae-incompatible-release-tags) The symbol "__constructor" is marked as @public, but its signature references "IAggConfigs" which is marked as @internal + constructor(aggConfigs: IAggConfigs, opts: AggConfigOptions); + // Warning: (ae-incompatible-release-tags) The symbol "aggConfigs" is marked as @public, but its signature references "IAggConfigs" which is marked as @internal + // + // (undocumented) + aggConfigs: IAggConfigs; + // (undocumented) + brandNew?: boolean; + // (undocumented) + createFilter(key: string, params?: {}): any; + // (undocumented) + enabled: boolean; + static ensureIds(list: any[]): any[]; + // (undocumented) + fieldIsTimeField(): boolean | "" | undefined; + // (undocumented) + fieldName(): any; + // (undocumented) + getAggParams(): import("./param_types/agg").AggParamType[]; + // (undocumented) + getField(): any; + // (undocumented) + getFieldDisplayName(): any; + // (undocumented) + getIndexPattern(): import("../../../public").IndexPattern; + // (undocumented) + getKey(bucket: any, key?: string): any; + // (undocumented) + getParam(key: string): any; + // (undocumented) + getRequestAggs(): AggConfig[]; + // (undocumented) + getResponseAggs(): AggConfig[]; + // (undocumented) + getTimeRange(): import("../../../public").TimeRange | undefined; + // (undocumented) + getValue(bucket: any): any; + // (undocumented) + id: string; + // (undocumented) + isFilterable(): boolean; + // (undocumented) + makeLabel(percentageMode?: boolean): any; + static nextId(list: IAggConfig[]): number; + onSearchRequestStart(searchSource: ISearchSource_2, options?: ISearchOptions): Promise | Promise; + // (undocumented) + params: any; + // Warning: (ae-incompatible-release-tags) The symbol "parent" is marked as @public, but its signature references "IAggConfigs" which is marked as @internal + // + // (undocumented) + parent?: IAggConfigs; + // (undocumented) + schema?: string; + // Warning: (ae-incompatible-release-tags) The symbol "serialize" is marked as @public, but its signature references "AggConfigSerialized" which is marked as @internal + // + // (undocumented) + serialize(): AggConfigSerialized; + setParams(from: any): void; + // (undocumented) + setType(type: IAggType): void; + // Warning: (ae-incompatible-release-tags) The symbol "toDsl" is marked as @public, but its signature references "IAggConfigs" which is marked as @internal + toDsl(aggConfigs?: IAggConfigs): any; + // (undocumented) + toExpressionAst(): ExpressionAstFunction | undefined; + // Warning: (ae-incompatible-release-tags) The symbol "toJSON" is marked as @public, but its signature references "AggConfigSerialized" which is marked as @internal + // + // @deprecated (undocumented) + toJSON(): AggConfigSerialized; + // Warning: (ae-forgotten-export) The symbol "SerializableState" needs to be exported by the entry point index.d.ts + toSerializedFieldFormat(): {} | Ensure, SerializableState>; + // (undocumented) + get type(): IAggType; + set type(type: IAggType); + // Warning: (ae-incompatible-release-tags) The symbol "write" is marked as @public, but its signature references "IAggConfigs" which is marked as @internal + // + // (undocumented) + write(aggs?: IAggConfigs): Record; +} + +// Warning: (ae-incompatible-release-tags) The symbol "AggConfigOptions" is marked as @public, but its signature references "AggConfigSerialized" which is marked as @internal // Warning: (ae-missing-release-tag) "AggConfigOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -91,6 +174,76 @@ export type AggConfigOptions = Assign; +// Warning: (ae-missing-release-tag) "AggConfigs" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class AggConfigs { + // Warning: (ae-forgotten-export) The symbol "AggConfigsOptions" needs to be exported by the entry point index.d.ts + constructor(indexPattern: IndexPattern, configStates: Pick & Pick<{ + type: string | IAggType; + }, "type"> & Pick<{ + type: string | IAggType; + }, never>, "enabled" | "type" | "schema" | "id" | "params">[] | undefined, opts: AggConfigsOptions); + // (undocumented) + aggs: IAggConfig[]; + // (undocumented) + byId(id: string): AggConfig | undefined; + // (undocumented) + byIndex(index: number): AggConfig; + // (undocumented) + byName(name: string): AggConfig[]; + // (undocumented) + bySchemaName(schema: string): AggConfig[]; + // (undocumented) + byType(type: string): AggConfig[]; + // (undocumented) + byTypeName(type: string): AggConfig[]; + // (undocumented) + clone({ enabledOnly }?: { + enabledOnly?: boolean | undefined; + }): AggConfigs; + // Warning: (ae-forgotten-export) The symbol "CreateAggConfigParams" needs to be exported by the entry point index.d.ts + // + // (undocumented) + createAggConfig: (params: CreateAggConfigParams, { addToAggConfigs }?: { + addToAggConfigs?: boolean | undefined; + }) => T; + // (undocumented) + getAll(): AggConfig[]; + // (undocumented) + getRequestAggById(id: string): AggConfig | undefined; + // (undocumented) + getRequestAggs(): AggConfig[]; + getResponseAggById(id: string): AggConfig | undefined; + getResponseAggs(): AggConfig[]; + // (undocumented) + indexPattern: IndexPattern; + jsonDataEquals(aggConfigs: AggConfig[]): boolean; + // (undocumented) + onSearchRequestStart(searchSource: ISearchSource_2, options?: ISearchOptions_2): Promise<[unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown]>; + // (undocumented) + setTimeRange(timeRange: TimeRange): void; + // (undocumented) + timeRange?: TimeRange; + // (undocumented) + toDsl(hierarchical?: boolean): Record; + } + +// @internal (undocumented) +export type AggConfigSerialized = Ensure<{ + type: string; + enabled?: boolean; + id?: string; + params?: {} | SerializableState; + schema?: string; +}, SerializableState>; + // Warning: (ae-missing-release-tag) "AggGroupLabels" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -126,8 +279,6 @@ export type AggParam = BaseParamType; export interface AggParamOption { // (undocumented) display: string; - // Warning: (ae-forgotten-export) The symbol "AggConfig" needs to be exported by the entry point index.d.ts - // // (undocumented) enabled?(agg: AggConfig): boolean; // (undocumented) @@ -141,10 +292,19 @@ export class AggParamType extends Ba constructor(config: Record); // (undocumented) allowedAggs: string[]; + // Warning: (ae-incompatible-release-tags) The symbol "makeAgg" is marked as @public, but its signature references "AggConfigSerialized" which is marked as @internal + // // (undocumented) makeAgg: (agg: TAggConfig, state?: AggConfigSerialized) => TAggConfig; } +// Warning: (ae-forgotten-export) The symbol "AggsCommonStart" needs to be exported by the entry point index.d.ts +// +// @public +export type AggsStart = Assign; + // Warning: (ae-missing-release-tag) "ApplyGlobalFilterActionContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -159,6 +319,11 @@ export interface ApplyGlobalFilterActionContext { timeFieldName?: string; } +// Warning: (ae-forgotten-export) The symbol "AutocompleteService" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export type AutocompleteStart = ReturnType; + // Warning: (ae-forgotten-export) The symbol "DateFormat" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "DateNanosFormat" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "baseFormattersPublic" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -199,7 +364,6 @@ export enum BUCKET_TYPES { // @public export const castEsToKbnFieldTypeName: (esType: ES_FIELD_TYPES | string) => KBN_FIELD_TYPES; -// Warning: (ae-forgotten-export) The symbol "QueryStart" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "QuerySetup" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "BaseStateContainer" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "connectToQueryState" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -226,7 +390,7 @@ export type CustomFilter = Filter & { // Warning: (ae-missing-release-tag) "DataPublicPluginSetup" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) +// @public export interface DataPublicPluginSetup { // Warning: (ae-forgotten-export) The symbol "DataPublicPluginEnhancements" needs to be exported by the entry point index.d.ts // @@ -242,42 +406,47 @@ export interface DataPublicPluginSetup { fieldFormats: FieldFormatsSetup; // (undocumented) query: QuerySetup; - // Warning: (ae-forgotten-export) The symbol "ISearchSetup" needs to be exported by the entry point index.d.ts - // // (undocumented) search: ISearchSetup; } // Warning: (ae-missing-release-tag) "DataPublicPluginStart" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) +// @public export interface DataPublicPluginStart { - // (undocumented) - actions: { - createFiltersFromValueClickAction: typeof createFiltersFromValueClickAction; - createFiltersFromRangeSelectAction: typeof createFiltersFromRangeSelectAction; - }; - // Warning: (ae-forgotten-export) The symbol "AutocompleteStart" needs to be exported by the entry point index.d.ts - // - // (undocumented) + actions: DataPublicPluginStartActions; autocomplete: AutocompleteStart; - // Warning: (ae-forgotten-export) The symbol "FieldFormatsStart" needs to be exported by the entry point index.d.ts - // - // (undocumented) fieldFormats: FieldFormatsStart; - // (undocumented) indexPatterns: IndexPatternsContract; - // (undocumented) query: QueryStart; - // Warning: (ae-forgotten-export) The symbol "ISearchStart" needs to be exported by the entry point index.d.ts + search: ISearchStart; + ui: DataPublicPluginStartUi; +} + +// Warning: (ae-missing-release-tag) "DataPublicPluginStartActions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface DataPublicPluginStartActions { + // Warning: (ae-forgotten-export) The symbol "createFiltersFromRangeSelectAction" needs to be exported by the entry point index.d.ts // // (undocumented) - search: ISearchStart; + createFiltersFromRangeSelectAction: typeof createFiltersFromRangeSelectAction; + // Warning: (ae-forgotten-export) The symbol "createFiltersFromValueClickAction" needs to be exported by the entry point index.d.ts + // // (undocumented) - ui: { - IndexPatternSelect: React.ComponentType; - SearchBar: React.ComponentType; - }; + createFiltersFromValueClickAction: typeof createFiltersFromValueClickAction; +} + +// Warning: (ae-missing-release-tag) "DataPublicPluginStartUi" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface DataPublicPluginStartUi { + // Warning: (ae-forgotten-export) The symbol "IndexPatternSelectProps" needs to be exported by the entry point index.d.ts + // + // (undocumented) + IndexPatternSelect: React.ComponentType; + // (undocumented) + SearchBar: React.ComponentType; } // @public (undocumented) @@ -486,16 +655,6 @@ export const extractSearchSourceReferences: (state: SearchSourceFields) => [Sear indexRefName?: string; }, SavedObjectReference[]]; -// Warning: (ae-missing-release-tag) "FetchOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export interface FetchOptions { - // (undocumented) - abortSignal?: AbortSignal; - // (undocumented) - searchStrategyId?: string; -} - // Warning: (ae-missing-release-tag) "FieldFormat" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -604,46 +763,16 @@ export type FieldFormatsContentType = 'html' | 'text'; // @public (undocumented) export type FieldFormatsGetConfigFn = GetConfigFn; -// Warning: (ae-missing-release-tag) "FieldList" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// @public (undocumented) +export type FieldFormatsStart = Omit & { + deserialize: FormatFactory; +}; + +// Warning: (ae-forgotten-export) The symbol "FieldSpec" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "fieldList" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export class FieldList extends Array implements IIndexPatternFieldList { - // Warning: (ae-forgotten-export) The symbol "FieldSpec" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "OnNotification" needs to be exported by the entry point index.d.ts - constructor(indexPattern: IndexPattern, specs?: FieldSpec[], shortDotsEnable?: boolean, onNotification?: OnNotification); - // (undocumented) - readonly add: (field: FieldSpec) => void; - // (undocumented) - readonly getAll: () => IndexPatternField[]; - // (undocumented) - readonly getByName: (name: IndexPatternField['name']) => IndexPatternField | undefined; - // (undocumented) - readonly getByType: (type: IndexPatternField['type']) => any[]; - // (undocumented) - readonly remove: (field: IFieldType) => void; - // (undocumented) - readonly removeAll: () => void; - // (undocumented) - readonly replaceAll: (specs: FieldSpec[]) => void; - // (undocumented) - readonly toSpec: () => { - count: number; - script: string | undefined; - lang: string | undefined; - conflictDescriptions: Record | undefined; - name: string; - type: string; - esTypes: string[] | undefined; - scripted: boolean; - searchable: boolean; - aggregatable: boolean; - readFromDocValues: boolean; - subType: import("../types").IFieldSubType | undefined; - format: any; - }[]; - // (undocumented) - readonly update: (field: FieldSpec) => void; -} +export const fieldList: (specs?: FieldSpec[], shortDotsEnable?: boolean) => IIndexPatternFieldList; // @public (undocumented) export interface FieldMappingSpec { @@ -737,7 +866,6 @@ export const getKbnTypeNames: () => string[]; // // @public (undocumented) export function getSearchParamsFromRequest(searchRequest: SearchRequest, dependencies: { - esShardTimeout: number; getConfig: GetConfigFn; }): ISearchRequestParams; @@ -754,8 +882,6 @@ export function getTime(indexPattern: IIndexPattern | undefined, timeRange: Time // @public export type IAggConfig = AggConfig; -// Warning: (ae-forgotten-export) The symbol "AggConfigs" needs to be exported by the entry point index.d.ts -// // @internal export type IAggConfigs = AggConfigs; @@ -868,7 +994,9 @@ export interface IFieldType { // (undocumented) subType?: IFieldSubType; // (undocumented) - toSpec?: () => FieldSpec; + toSpec?: (options?: { + getFormatterForField?: IndexPattern['getFormatterForField']; + }) => FieldSpec; // (undocumented) type: string; // (undocumented) @@ -919,6 +1047,10 @@ export interface IIndexPatternFieldList extends Array { // (undocumented) replaceAll(specs: FieldSpec[]): void; // (undocumented) + toSpec(options?: { + getFormatterForField?: IndexPattern['getFormatterForField']; + }): FieldSpec[]; + // (undocumented) update(field: FieldSpec): void; } @@ -950,7 +1082,7 @@ export type IMetricAggType = MetricAggType; // @public (undocumented) export class IndexPattern implements IIndexPattern { // Warning: (ae-forgotten-export) The symbol "IndexPatternDeps" needs to be exported by the entry point index.d.ts - constructor(id: string | undefined, { savedObjectsClient, apiClient, patternCache, fieldFormats, onNotification, onError, shortDotsEnable, metaFields, }: IndexPatternDeps); + constructor(id: string | undefined, { savedObjectsClient, apiClient, patternCache, fieldFormats, indexPatternsService, onNotification, onError, shortDotsEnable, metaFields, }: IndexPatternDeps); // (undocumented) addScriptedField(name: string, script: string, fieldType: string | undefined, lang: string): Promise; // (undocumented) @@ -1024,6 +1156,10 @@ export class IndexPattern implements IIndexPattern { // (undocumented) metaFields: string[]; // (undocumented) + originalBody: { + [key: string]: any; + }; + // (undocumented) popularizeField(fieldName: string, unit?: number): Promise; // (undocumented) prepBody(): { @@ -1039,9 +1175,7 @@ export class IndexPattern implements IIndexPattern { // (undocumented) refreshFields(): Promise; // (undocumented) - removeScriptedField(fieldName: string): Promise; - // (undocumented) - save(saveAttempts?: number): Promise; + removeScriptedField(fieldName: string): void; // Warning: (ae-forgotten-export) The symbol "SourceFilter" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -1051,16 +1185,14 @@ export class IndexPattern implements IIndexPattern { // (undocumented) title: string; // (undocumented) - toJSON(): string | undefined; - // (undocumented) toSpec(): IndexPatternSpec; // (undocumented) - toString(): string; - // (undocumented) type: string | undefined; // (undocumented) typeMeta?: IndexPatternTypeMeta; - } + // (undocumented) + version: string | undefined; +} // Warning: (ae-missing-release-tag) "AggregationRestrictions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1100,7 +1232,7 @@ export interface IndexPatternAttributes { // // @public (undocumented) export class IndexPatternField implements IFieldType { - constructor(indexPattern: IndexPattern, spec: FieldSpec, displayName: string, onNotification: OnNotification); + constructor(spec: FieldSpec, displayName: string); // (undocumented) get aggregatable(): boolean; // (undocumented) @@ -1116,10 +1248,6 @@ export class IndexPatternField implements IFieldType { // (undocumented) get filterable(): boolean; // (undocumented) - get format(): FieldFormat; - // (undocumented) - readonly indexPattern: IndexPattern; - // (undocumented) get lang(): string | undefined; set lang(lang: string | undefined); // (undocumented) @@ -1155,7 +1283,9 @@ export class IndexPatternField implements IFieldType { subType: import("../types").IFieldSubType | undefined; }; // (undocumented) - toSpec(): { + toSpec({ getFormatterForField, }?: { + getFormatterForField?: IndexPattern['getFormatterForField']; + }): { count: number; script: string | undefined; lang: string | undefined; @@ -1168,7 +1298,10 @@ export class IndexPatternField implements IFieldType { aggregatable: boolean; readFromDocValues: boolean; subType: import("../types").IFieldSubType | undefined; - format: any; + format: { + id: any; + params: any; + } | undefined; }; // (undocumented) get type(): string; @@ -1265,17 +1398,44 @@ export type ISearchGeneric = void; + // Warning: (ae-forgotten-export) The symbol "AggsSetup" needs to be exported by the entry point index.d.ts + // + // (undocumented) + aggs: AggsSetup; + // Warning: (ae-forgotten-export) The symbol "SearchUsageCollector" needs to be exported by the entry point index.d.ts + // + // (undocumented) + usageCollector?: SearchUsageCollector; +} + +// @public export type ISearchSource = Pick; +// @public +export interface ISearchStart { + aggs: AggsStart; + search: ISearchGeneric; + searchSource: ISearchStartSearchSource; +} + +// @public +export interface ISearchStartSearchSource { + create: (fields?: SearchSourceFields) => Promise; + createEmpty: () => ISearchSource; +} + // Warning: (ae-missing-release-tag) "isFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1490,6 +1650,12 @@ export interface Query { }; } +// Warning: (ae-forgotten-export) The symbol "QueryService" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "QueryStart" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type QueryStart = ReturnType; + // Warning: (ae-missing-release-tag) "QueryState" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @@ -1504,11 +1670,22 @@ export interface QueryState { time?: TimeRange; } +// Warning: (ae-forgotten-export) The symbol "QueryStateChangePartial" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "QueryStateChange" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface QueryStateChange extends QueryStateChangePartial { + // (undocumented) + appFilters?: boolean; + // (undocumented) + globalFilters?: boolean; +} + // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "QueryStringInput" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const QueryStringInput: React.FC>; +export const QueryStringInput: React.FC>; // @public (undocumented) export type QuerySuggestion = QuerySuggestionBasic | QuerySuggestionField; @@ -1721,8 +1898,8 @@ export const search: { // Warning: (ae-missing-release-tag) "SearchBar" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "dataTestSubj" | "customSubmitButton" | "screenTitle" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "indicateNoData" | "timeHistory" | "onFiltersUpdated">, any> & { - WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; +export const SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "dataTestSubj" | "timeHistory" | "customSubmitButton" | "screenTitle" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "indicateNoData" | "onFiltersUpdated">, any> & { + WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; }; // Warning: (ae-forgotten-export) The symbol "SearchBarOwnProps" needs to be exported by the entry point index.d.ts @@ -1754,32 +1931,28 @@ export interface SearchError { // // @public (undocumented) export class SearchInterceptor { - constructor(deps: SearchInterceptorDeps, requestTimeout?: number | undefined); + constructor(deps: SearchInterceptorDeps); // @internal protected abortController: AbortController; // @internal (undocumented) protected application: CoreStart['application']; // (undocumented) protected readonly deps: SearchInterceptorDeps; - getPendingCount$(): Observable; - // @internal (undocumented) - protected hideToast: () => void; - // @internal - protected longRunningToast?: Toast; // @internal protected pendingCount$: BehaviorSubject; - // (undocumented) - protected readonly requestTimeout?: number | undefined; - // (undocumented) + // @internal (undocumented) protected runSearch(request: IEsSearchRequest, signal: AbortSignal, strategy?: string): Observable; search(request: IEsSearchRequest, options?: ISearchOptions): Observable; - // (undocumented) - protected setupTimers(options?: ISearchOptions): { + // @internal (undocumented) + protected setupAbortSignal({ abortSignal, timeout, }: { + abortSignal?: AbortSignal; + timeout?: number; + }): { combinedSignal: AbortSignal; cleanup: () => void; }; - // @internal (undocumented) - protected showToast: () => void; + // (undocumented) + protected showTimeoutError: ((e: Error) => void) & import("lodash").Cancelable; // @internal protected timeoutSubscriptions: Subscription; } @@ -1796,8 +1969,6 @@ export interface SearchInterceptorDeps { toasts: ToastsSetup; // (undocumented) uiSettings: CoreSetup_2['uiSettings']; - // Warning: (ae-forgotten-export) The symbol "SearchUsageCollector" needs to be exported by the entry point index.d.ts - // // (undocumented) usageCollector?: SearchUsageCollector; } @@ -1805,9 +1976,59 @@ export interface SearchInterceptorDeps { // @internal export type SearchRequest = Record; +// @public (undocumented) +export class SearchSource { + // Warning: (ae-forgotten-export) The symbol "SearchSourceDependencies" needs to be exported by the entry point index.d.ts + constructor(fields: SearchSourceFields | undefined, dependencies: SearchSourceDependencies); + // @deprecated (undocumented) + create(): SearchSource; + createChild(options?: {}): SearchSource; + createCopy(): SearchSource; + destroy(): void; + fetch(options?: ISearchOptions): Promise>; + getField(field: K, recurse?: boolean): SearchSourceFields[K]; + getFields(): { + type?: string | undefined; + query?: import("../..").Query | undefined; + filter?: Filter | Filter[] | (() => Filter | Filter[] | undefined) | undefined; + sort?: Record | Record[] | undefined; + highlight?: any; + highlightAll?: boolean | undefined; + aggs?: any; + from?: number | undefined; + size?: number | undefined; + source?: string | boolean | string[] | undefined; + version?: boolean | undefined; + fields?: string | boolean | string[] | undefined; + index?: import("../..").IndexPattern | undefined; + searchAfter?: import("./types").EsQuerySearchAfter | undefined; + timeout?: string | undefined; + terminate_after?: number | undefined; + }; + getId(): string; + getOwnField(field: K): SearchSourceFields[K]; + getParent(): SearchSource | undefined; + getSearchRequestBody(): Promise; + getSerializedFields(): SearchSourceFields; + // Warning: (ae-incompatible-release-tags) The symbol "history" is marked as @public, but its signature references "SearchRequest" which is marked as @internal + // + // (undocumented) + history: SearchRequest[]; + onRequestStart(handler: (searchSource: SearchSource, options?: ISearchOptions) => Promise): void; + serialize(): { + searchSourceJSON: string; + references: import("../../../../../core/public").SavedObjectReference[]; + }; + setField(field: K, value: SearchSourceFields[K]): this; + setFields(newFields: SearchSourceFields): this; + // Warning: (ae-forgotten-export) The symbol "SearchSourceOptions" needs to be exported by the entry point index.d.ts + setParent(parent?: ISearchSource, options?: SearchSourceOptions): this; + setPreferredSearchStrategyId(searchStrategyId: string): void; +} + // Warning: (ae-missing-release-tag) "SearchSourceFields" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) +// @public export interface SearchSourceFields { // (undocumented) aggs?: any; @@ -1821,6 +2042,8 @@ export interface SearchSourceFields { highlight?: any; // (undocumented) highlightAll?: boolean; + // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "IndexPatternService" + // // (undocumented) index?: IndexPattern; // (undocumented) @@ -1945,6 +2168,7 @@ export const UI_SETTINGS: { readonly COURIER_MAX_CONCURRENT_SHARD_REQUESTS: "courier:maxConcurrentShardRequests"; readonly COURIER_BATCH_SEARCHES: "courier:batchSearches"; readonly SEARCH_INCLUDE_FROZEN: "search:includeFrozen"; + readonly SEARCH_TIMEOUT: "search:timeout"; readonly HISTOGRAM_BAR_TARGET: "histogram:barTarget"; readonly HISTOGRAM_MAX_BARS: "histogram:maxBars"; readonly HISTORY_LIMIT: "history:limit"; @@ -1971,6 +2195,8 @@ export const UI_SETTINGS: { // src/plugins/data/common/es_query/filters/match_all_filter.ts:28:3 - (ae-forgotten-export) The symbol "MatchAllFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrase_filter.ts:33:3 - (ae-forgotten-export) The symbol "PhraseFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrases_filter.ts:31:3 - (ae-forgotten-export) The symbol "PhrasesFilterMeta" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/search/aggs/types.ts:98:51 - (ae-forgotten-export) The symbol "AggTypesRegistryStart" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/field_formats/field_formats_service.ts:67:3 - (ae-forgotten-export) The symbol "FormatFactory" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "FilterLabel" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "getDisplayValueFromFilter" needs to be exported by the entry point index.d.ts @@ -2003,25 +2229,22 @@ export const UI_SETTINGS: { // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:371:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:371:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:371:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:371:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:373:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:374:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:383:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:384:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:385:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:386:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:394:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:395:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:398:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:382:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:383:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:392:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:393:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:394:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:395:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:403:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:404:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:407:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:45:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/types.ts:62:5 - (ae-forgotten-export) The symbol "createFiltersFromValueClickAction" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/types.ts:63:5 - (ae-forgotten-export) The symbol "createFiltersFromRangeSelectAction" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/types.ts:71:5 - (ae-forgotten-export) The symbol "IndexPatternSelectProps" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/data/public/search/README.md b/src/plugins/data/public/search/README.md index 33e6d9ab0bd1a..0a123ffa3f1e9 100644 --- a/src/plugins/data/public/search/README.md +++ b/src/plugins/data/public/search/README.md @@ -1,13 +1,23 @@ # search -The `search` plugin provides the ability to register search strategies that take in a request -object, and return a response object, of a given shape. +The `search` service provides you with APIs to query Elasticsearch. -Both client side search strategies can be registered, as well as server side search strategies. +The services are split into two parts: (1) low-level API; and (2) high-level API. -The `search` plugin includes two one concrete client side implementations - - `SYNC_SEARCH_STRATEGY` and `ES_SEARCH_STRATEGY` which uses `SYNC_SEARCH_STRATEGY`. There is also one - default server side search strategy, `ES_SEARCH_STRATEGY`. +## Low-level API - Includes the `esSearch` plugin in order to search for data from Elasticsearch using Elasticsearch -DSL. +With low level API you work directly with elasticsearch DSL + +```typescript +const results = await data.search.search(request, params); +``` + +## High-level API + +Using high-level API you work with Kibana abstractions around Elasticsearch DSL: filters, queries, and aggregations. Provided by the *Search Source* service. + +```typescript +const search = data.search.searchSource.createEmpty(); +search.setField('query', data.query.queryString); +const results = await search.fetch(); +``` diff --git a/src/plugins/data/public/search/collectors/create_usage_collector.test.ts b/src/plugins/data/public/search/collectors/create_usage_collector.test.ts index 315d4678cabf1..9cadb1e796ad6 100644 --- a/src/plugins/data/public/search/collectors/create_usage_collector.test.ts +++ b/src/plugins/data/public/search/collectors/create_usage_collector.test.ts @@ -63,31 +63,4 @@ describe('Search Usage Collector', () => { SEARCH_EVENT_TYPE.QUERIES_CANCELLED ); }); - - test('tracks long popups', async () => { - await usageCollector.trackLongQueryPopupShown(); - expect(mockUsageCollectionSetup.reportUiStats).toHaveBeenCalled(); - expect(mockUsageCollectionSetup.reportUiStats.mock.calls[0][1]).toBe(METRIC_TYPE.LOADED); - expect(mockUsageCollectionSetup.reportUiStats.mock.calls[0][2]).toBe( - SEARCH_EVENT_TYPE.LONG_QUERY_POPUP_SHOWN - ); - }); - - test('tracks long popups dismissed', async () => { - await usageCollector.trackLongQueryDialogDismissed(); - expect(mockUsageCollectionSetup.reportUiStats).toHaveBeenCalled(); - expect(mockUsageCollectionSetup.reportUiStats.mock.calls[0][1]).toBe(METRIC_TYPE.CLICK); - expect(mockUsageCollectionSetup.reportUiStats.mock.calls[0][2]).toBe( - SEARCH_EVENT_TYPE.LONG_QUERY_DIALOG_DISMISSED - ); - }); - - test('tracks run query beyond timeout', async () => { - await usageCollector.trackLongQueryRunBeyondTimeout(); - expect(mockUsageCollectionSetup.reportUiStats).toHaveBeenCalled(); - expect(mockUsageCollectionSetup.reportUiStats.mock.calls[0][1]).toBe(METRIC_TYPE.CLICK); - expect(mockUsageCollectionSetup.reportUiStats.mock.calls[0][2]).toBe( - SEARCH_EVENT_TYPE.LONG_QUERY_RUN_BEYOND_TIMEOUT - ); - }); }); diff --git a/src/plugins/data/public/search/collectors/create_usage_collector.ts b/src/plugins/data/public/search/collectors/create_usage_collector.ts index 321b2c5b99049..187ed90652bb2 100644 --- a/src/plugins/data/public/search/collectors/create_usage_collector.ts +++ b/src/plugins/data/public/search/collectors/create_usage_collector.ts @@ -48,29 +48,5 @@ export const createUsageCollector = ( SEARCH_EVENT_TYPE.QUERIES_CANCELLED ); }, - trackLongQueryPopupShown: async () => { - const currentApp = await getCurrentApp(); - return usageCollection?.reportUiStats( - currentApp!, - METRIC_TYPE.LOADED, - SEARCH_EVENT_TYPE.LONG_QUERY_POPUP_SHOWN - ); - }, - trackLongQueryDialogDismissed: async () => { - const currentApp = await getCurrentApp(); - return usageCollection?.reportUiStats( - currentApp!, - METRIC_TYPE.CLICK, - SEARCH_EVENT_TYPE.LONG_QUERY_DIALOG_DISMISSED - ); - }, - trackLongQueryRunBeyondTimeout: async () => { - const currentApp = await getCurrentApp(); - return usageCollection?.reportUiStats( - currentApp!, - METRIC_TYPE.CLICK, - SEARCH_EVENT_TYPE.LONG_QUERY_RUN_BEYOND_TIMEOUT - ); - }, }; }; diff --git a/src/plugins/data/public/search/collectors/types.ts b/src/plugins/data/public/search/collectors/types.ts index 3e98f901eb0c3..bb7fa1e6ae4a2 100644 --- a/src/plugins/data/public/search/collectors/types.ts +++ b/src/plugins/data/public/search/collectors/types.ts @@ -20,15 +20,9 @@ export enum SEARCH_EVENT_TYPE { QUERY_TIMED_OUT = 'queryTimedOut', QUERIES_CANCELLED = 'queriesCancelled', - LONG_QUERY_POPUP_SHOWN = 'longQueryPopupShown', - LONG_QUERY_DIALOG_DISMISSED = 'longQueryDialogDismissed', - LONG_QUERY_RUN_BEYOND_TIMEOUT = 'longQueryRunBeyondTimeout', } export interface SearchUsageCollector { trackQueryTimedOut: () => Promise; trackQueriesCancelled: () => Promise; - trackLongQueryPopupShown: () => Promise; - trackLongQueryDialogDismissed: () => Promise; - trackLongQueryRunBeyondTimeout: () => Promise; } diff --git a/src/plugins/data/public/search/expressions/create_filter.test.ts b/src/plugins/data/public/search/expressions/create_filter.test.ts index 7968c80628531..7cc336a1c20e9 100644 --- a/src/plugins/data/public/search/expressions/create_filter.test.ts +++ b/src/plugins/data/public/search/expressions/create_filter.test.ts @@ -52,6 +52,7 @@ describe('createFilter', () => { getByName: () => field, filter: () => [field], }, + getFormatterForField: () => new BytesFormat({}, (() => {}) as FieldFormatsGetConfigFn), } as any; return new AggConfigs( diff --git a/src/plugins/data/public/search/expressions/esdsl.ts b/src/plugins/data/public/search/expressions/esdsl.ts index d7b897ace29b4..2efb21671b5b7 100644 --- a/src/plugins/data/public/search/expressions/esdsl.ts +++ b/src/plugins/data/public/search/expressions/esdsl.ts @@ -140,7 +140,7 @@ export const esdsl = (): EsdslExpressionFunctionDefinition => ({ body: dsl, }, }, - { signal: abortSignal } + { abortSignal } ) .toPromise(); diff --git a/src/plugins/data/public/search/fetch/get_search_params.test.ts b/src/plugins/data/public/search/fetch/get_search_params.test.ts index 1ecb879b1602d..5e83e1f57bb6d 100644 --- a/src/plugins/data/public/search/fetch/get_search_params.test.ts +++ b/src/plugins/data/public/search/fetch/get_search_params.test.ts @@ -25,44 +25,12 @@ function getConfigStub(config: any = {}): GetConfigFn { } describe('getSearchParams', () => { - test('includes rest_total_hits_as_int', () => { - const config = getConfigStub(); + test('includes custom preference', () => { + const config = getConfigStub({ + [UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE]: 'custom', + [UI_SETTINGS.COURIER_CUSTOM_REQUEST_PREFERENCE]: 'aaa', + }); const searchParams = getSearchParams(config); - expect(searchParams.rest_total_hits_as_int).toBe(true); - }); - - test('includes ignore_unavailable', () => { - const config = getConfigStub(); - const searchParams = getSearchParams(config); - expect(searchParams.ignore_unavailable).toBe(true); - }); - - test('includes ignore_throttled according to search:includeFrozen', () => { - let config = getConfigStub({ [UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: true }); - let searchParams = getSearchParams(config); - expect(searchParams.ignore_throttled).toBe(false); - - config = getConfigStub({ [UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: false }); - searchParams = getSearchParams(config); - expect(searchParams.ignore_throttled).toBe(true); - }); - - test('includes max_concurrent_shard_requests according to courier:maxConcurrentShardRequests', () => { - let config = getConfigStub({ [UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS]: 0 }); - let searchParams = getSearchParams(config); - expect(searchParams.max_concurrent_shard_requests).toBe(undefined); - - config = getConfigStub({ [UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS]: 5 }); - searchParams = getSearchParams(config); - expect(searchParams.max_concurrent_shard_requests).toBe(5); - }); - - test('includes timeout according to esShardTimeout if greater than 0', () => { - const config = getConfigStub(); - let searchParams = getSearchParams(config, 0); - expect(searchParams.timeout).toBe(undefined); - - searchParams = getSearchParams(config, 100); - expect(searchParams.timeout).toBe('100ms'); + expect(searchParams.preference).toBe('aaa'); }); }); diff --git a/src/plugins/data/public/search/fetch/get_search_params.ts b/src/plugins/data/public/search/fetch/get_search_params.ts index 5e0395189f647..ed87c4813951c 100644 --- a/src/plugins/data/public/search/fetch/get_search_params.ts +++ b/src/plugins/data/public/search/fetch/get_search_params.ts @@ -22,26 +22,12 @@ import { SearchRequest } from './types'; const sessionId = Date.now(); -export function getSearchParams(getConfig: GetConfigFn, esShardTimeout: number = 0) { +export function getSearchParams(getConfig: GetConfigFn) { return { - rest_total_hits_as_int: true, - ignore_unavailable: true, - ignore_throttled: getIgnoreThrottled(getConfig), - max_concurrent_shard_requests: getMaxConcurrentShardRequests(getConfig), preference: getPreference(getConfig), - timeout: getTimeout(esShardTimeout), }; } -export function getIgnoreThrottled(getConfig: GetConfigFn) { - return !getConfig(UI_SETTINGS.SEARCH_INCLUDE_FROZEN); -} - -export function getMaxConcurrentShardRequests(getConfig: GetConfigFn) { - const maxConcurrentShardRequests = getConfig(UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS); - return maxConcurrentShardRequests > 0 ? maxConcurrentShardRequests : undefined; -} - export function getPreference(getConfig: GetConfigFn) { const setRequestPreference = getConfig(UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE); if (setRequestPreference === 'sessionId') return sessionId; @@ -50,19 +36,15 @@ export function getPreference(getConfig: GetConfigFn) { : undefined; } -export function getTimeout(esShardTimeout: number) { - return esShardTimeout > 0 ? `${esShardTimeout}ms` : undefined; -} - /** @public */ // TODO: Could provide this on runtime contract with dependencies // already wired up. export function getSearchParamsFromRequest( searchRequest: SearchRequest, - dependencies: { esShardTimeout: number; getConfig: GetConfigFn } + dependencies: { getConfig: GetConfigFn } ): ISearchRequestParams { - const { esShardTimeout, getConfig } = dependencies; - const searchParams = getSearchParams(getConfig, esShardTimeout); + const { getConfig } = dependencies; + const searchParams = getSearchParams(getConfig); return { index: searchRequest.index.title || searchRequest.index, diff --git a/src/plugins/data/public/search/fetch/index.ts b/src/plugins/data/public/search/fetch/index.ts index 79cdad1897f9c..4b8511edfc26f 100644 --- a/src/plugins/data/public/search/fetch/index.ts +++ b/src/plugins/data/public/search/fetch/index.ts @@ -18,14 +18,7 @@ */ export * from './types'; -export { - getSearchParams, - getSearchParamsFromRequest, - getPreference, - getTimeout, - getIgnoreThrottled, - getMaxConcurrentShardRequests, -} from './get_search_params'; +export { getSearchParams, getSearchParamsFromRequest, getPreference } from './get_search_params'; export { RequestFailure } from './request_error'; export { handleResponse } from './handle_response'; diff --git a/src/plugins/data/public/search/fetch/types.ts b/src/plugins/data/public/search/fetch/types.ts index 670c4f731971a..224a597766909 100644 --- a/src/plugins/data/public/search/fetch/types.ts +++ b/src/plugins/data/public/search/fetch/types.ts @@ -17,8 +17,9 @@ * under the License. */ +import { HttpStart } from 'src/core/public'; +import { BehaviorSubject } from 'rxjs'; import { GetConfigFn } from '../../../common'; -import { ISearchStartLegacy } from '../types'; /** * @internal @@ -29,15 +30,10 @@ import { ISearchStartLegacy } from '../types'; */ export type SearchRequest = Record; -export interface FetchOptions { - abortSignal?: AbortSignal; - searchStrategyId?: string; -} - export interface FetchHandlers { - legacySearchService: ISearchStartLegacy; config: { get: GetConfigFn }; - esShardTimeout: number; + http: HttpStart; + loadingCount$: BehaviorSubject; } export interface SearchError { diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts index 14eff13b378ee..c1af9699acbb2 100644 --- a/src/plugins/data/public/search/index.ts +++ b/src/plugins/data/public/search/index.ts @@ -21,10 +21,10 @@ export * from './expressions'; export { ISearch, - ISearchOptions, ISearchGeneric, ISearchSetup, ISearchStart, + ISearchStartSearchSource, SearchEnhancements, } from './types'; @@ -34,7 +34,7 @@ export { getEsPreference } from './es_search'; export { IKibanaSearchResponse, IKibanaSearchRequest } from '../../common/search'; -export { SearchError, FetchOptions, getSearchParamsFromRequest, SearchRequest } from './fetch'; +export { SearchError, getSearchParamsFromRequest, SearchRequest } from './fetch'; export { ISearchSource, diff --git a/src/plugins/data/public/search/legacy/call_client.test.ts b/src/plugins/data/public/search/legacy/call_client.test.ts index a3c4e720b4cab..943a02d22088d 100644 --- a/src/plugins/data/public/search/legacy/call_client.test.ts +++ b/src/plugins/data/public/search/legacy/call_client.test.ts @@ -17,11 +17,13 @@ * under the License. */ +import { coreMock } from '../../../../../core/public/mocks'; import { callClient } from './call_client'; import { SearchStrategySearchParams } from './types'; import { defaultSearchStrategy } from './default_search_strategy'; import { FetchHandlers } from '../fetch'; import { handleResponse } from '../fetch/handle_response'; +import { BehaviorSubject } from 'rxjs'; const mockAbortFn = jest.fn(); jest.mock('../fetch/handle_response', () => ({ @@ -54,7 +56,12 @@ describe('callClient', () => { test('Passes the additional arguments it is given to the search strategy', () => { const searchRequests = [{ _searchStrategyId: 0 }]; - const args = { legacySearchService: {}, config: {}, esShardTimeout: 0 } as FetchHandlers; + const args = { + http: coreMock.createStart().http, + legacySearchService: {}, + config: { get: jest.fn() }, + loadingCount$: new BehaviorSubject(0), + } as FetchHandlers; callClient(searchRequests, [], args); diff --git a/src/plugins/data/public/search/legacy/call_client.ts b/src/plugins/data/public/search/legacy/call_client.ts index 3dcf11f72a742..d66796b9427a1 100644 --- a/src/plugins/data/public/search/legacy/call_client.ts +++ b/src/plugins/data/public/search/legacy/call_client.ts @@ -18,21 +18,22 @@ */ import { SearchResponse } from 'elasticsearch'; -import { FetchOptions, FetchHandlers, handleResponse } from '../fetch'; +import { ISearchOptions } from 'src/plugins/data/common'; +import { FetchHandlers, handleResponse } from '../fetch'; import { defaultSearchStrategy } from './default_search_strategy'; import { SearchRequest } from '../index'; export function callClient( searchRequests: SearchRequest[], - requestsOptions: FetchOptions[] = [], + requestsOptions: ISearchOptions[] = [], fetchHandlers: FetchHandlers ) { // Correlate the options with the request that they're associated with const requestOptionEntries: Array<[ SearchRequest, - FetchOptions + ISearchOptions ]> = searchRequests.map((request, i) => [request, requestsOptions[i]]); - const requestOptionsMap = new Map(requestOptionEntries); + const requestOptionsMap = new Map(requestOptionEntries); const requestResponseMap = new Map>>(); const { searching, abort } = defaultSearchStrategy.search({ diff --git a/src/plugins/data/public/search/legacy/default_search_strategy.test.ts b/src/plugins/data/public/search/legacy/default_search_strategy.test.ts index 4148055c1eb58..e74ab49131430 100644 --- a/src/plugins/data/public/search/legacy/default_search_strategy.test.ts +++ b/src/plugins/data/public/search/legacy/default_search_strategy.test.ts @@ -17,44 +17,26 @@ * under the License. */ -import { IUiSettingsClient } from 'kibana/public'; +import { HttpStart } from 'src/core/public'; +import { coreMock } from '../../../../../core/public/mocks'; import { defaultSearchStrategy } from './default_search_strategy'; -import { searchServiceMock } from '../mocks'; import { SearchStrategySearchParams } from './types'; -import { UI_SETTINGS } from '../../../common'; +import { BehaviorSubject } from 'rxjs'; const { search } = defaultSearchStrategy; -function getConfigStub(config: any = {}) { - return { - get: (key) => config[key], - } as IUiSettingsClient; -} - -const msearchMockResponse: any = Promise.resolve([]); -msearchMockResponse.abort = jest.fn(); -const msearchMock = jest.fn().mockReturnValue(msearchMockResponse); - -const searchMockResponse: any = Promise.resolve([]); -searchMockResponse.abort = jest.fn(); -const searchMock = jest.fn().mockReturnValue(searchMockResponse); +const msearchMock = jest.fn().mockResolvedValue({ body: { responses: [] } }); describe('defaultSearchStrategy', function () { describe('search', function () { - let searchArgs: MockedKeys>; - let es: any; + let searchArgs: MockedKeys; + let http: jest.Mocked; beforeEach(() => { - msearchMockResponse.abort.mockClear(); msearchMock.mockClear(); - searchMockResponse.abort.mockClear(); - searchMock.mockClear(); - - const searchService = searchServiceMock.createStartContract(); - searchService.aggs.calculateAutoTimeExpression = jest.fn().mockReturnValue('1d'); - searchService.__LEGACY.esClient.search = searchMock; - searchService.__LEGACY.esClient.msearch = msearchMock; + http = coreMock.createStart().http; + http.post.mockResolvedValue(msearchMock); searchArgs = { searchRequests: [ @@ -62,49 +44,27 @@ describe('defaultSearchStrategy', function () { index: { title: 'foo' }, }, ], - esShardTimeout: 0, - legacySearchService: searchService.__LEGACY, + http, + config: { + get: jest.fn(), + }, + loadingCount$: new BehaviorSubject(0) as any, }; - - es = searchArgs.legacySearchService.esClient; - }); - - test('does not send max_concurrent_shard_requests by default', async () => { - const config = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }); - await search({ ...searchArgs, config }); - expect(es.msearch.mock.calls[0][0].max_concurrent_shard_requests).toBe(undefined); - }); - - test('allows configuration of max_concurrent_shard_requests', async () => { - const config = getConfigStub({ - [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true, - [UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS]: 42, - }); - await search({ ...searchArgs, config }); - expect(es.msearch.mock.calls[0][0].max_concurrent_shard_requests).toBe(42); - }); - - test('should set rest_total_hits_as_int to true on a request', async () => { - const config = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }); - await search({ ...searchArgs, config }); - expect(es.msearch.mock.calls[0][0]).toHaveProperty('rest_total_hits_as_int', true); - }); - - test('should set ignore_throttled=false when including frozen indices', async () => { - const config = getConfigStub({ - [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true, - [UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: true, - }); - await search({ ...searchArgs, config }); - expect(es.msearch.mock.calls[0][0]).toHaveProperty('ignore_throttled', false); }); - test('should properly call abort with msearch', () => { - const config = getConfigStub({ - [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true, - }); - search({ ...searchArgs, config }).abort(); - expect(msearchMockResponse.abort).toHaveBeenCalled(); + test('calls http.post with the correct arguments', async () => { + await search({ ...searchArgs }); + expect(http.post.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + "/internal/_msearch", + Object { + "body": "{\\"searches\\":[{\\"header\\":{\\"index\\":\\"foo\\"}}]}", + "signal": AbortSignal {}, + }, + ], + ] + `); }); }); }); diff --git a/src/plugins/data/public/search/legacy/default_search_strategy.ts b/src/plugins/data/public/search/legacy/default_search_strategy.ts index 6ccb0a01cf898..cbcd0da20207f 100644 --- a/src/plugins/data/public/search/legacy/default_search_strategy.ts +++ b/src/plugins/data/public/search/legacy/default_search_strategy.ts @@ -17,8 +17,7 @@ * under the License. */ -import { getPreference, getTimeout } from '../fetch'; -import { getMSearchParams } from './get_msearch_params'; +import { getPreference } from '../fetch'; import { SearchStrategyProvider, SearchStrategySearchParams } from './types'; // @deprecated @@ -30,34 +29,45 @@ export const defaultSearchStrategy: SearchStrategyProvider = { }, }; -function msearch({ - searchRequests, - legacySearchService, - config, - esShardTimeout, -}: SearchStrategySearchParams) { - const es = legacySearchService.esClient; - const inlineRequests = searchRequests.map(({ index, body, search_type: searchType }) => { - const inlineHeader = { - index: index.title || index, - search_type: searchType, - ignore_unavailable: true, - preference: getPreference(config.get), +function msearch({ searchRequests, config, http, loadingCount$ }: SearchStrategySearchParams) { + const requests = searchRequests.map(({ index, body }) => { + return { + header: { + index: index.title || index, + preference: getPreference(config.get), + }, + body, }; - const inlineBody = { - ...body, - timeout: getTimeout(esShardTimeout), - }; - return `${JSON.stringify(inlineHeader)}\n${JSON.stringify(inlineBody)}`; }); - const searching = es.msearch({ - ...getMSearchParams(config.get), - body: `${inlineRequests.join('\n')}\n`, - }); + const abortController = new AbortController(); + let resolved = false; + + // Start LoadingIndicator + loadingCount$.next(loadingCount$.getValue() + 1); + + const cleanup = () => { + if (!resolved) { + resolved = true; + // Decrement loading counter & cleanup BehaviorSubject + loadingCount$.next(loadingCount$.getValue() - 1); + loadingCount$.complete(); + } + }; + + const searching = http + .post('/internal/_msearch', { + body: JSON.stringify({ searches: requests }), + signal: abortController.signal, + }) + .then(({ body }) => body?.responses) + .finally(() => cleanup()); return { - searching: searching.then(({ responses }: any) => responses), - abort: searching.abort, + abort: () => { + abortController.abort(); + cleanup(); + }, + searching, }; } diff --git a/src/plugins/data/public/search/legacy/es_client/get_es_client.ts b/src/plugins/data/public/search/legacy/es_client/get_es_client.ts deleted file mode 100644 index 4367544ad9ff4..0000000000000 --- a/src/plugins/data/public/search/legacy/es_client/get_es_client.ts +++ /dev/null @@ -1,98 +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. - */ - -// @ts-ignore -import { default as es } from 'elasticsearch-browser/elasticsearch'; -import { CoreStart, PackageInfo } from 'kibana/public'; -import { BehaviorSubject } from 'rxjs'; - -export function getEsClient({ - esRequestTimeout, - esApiVersion, - http, - packageVersion, -}: { - esRequestTimeout: number; - esApiVersion: string; - http: CoreStart['http']; - packageVersion: PackageInfo['version']; -}) { - // Use legacy es client for msearch. - const client = es.Client({ - host: getEsUrl(http, packageVersion), - log: 'info', - requestTimeout: esRequestTimeout, - apiVersion: esApiVersion, - }); - - const loadingCount$ = new BehaviorSubject(0); - http.addLoadingCountSource(loadingCount$); - - return { - search: wrapEsClientMethod(client, 'search', loadingCount$), - msearch: wrapEsClientMethod(client, 'msearch', loadingCount$), - create: wrapEsClientMethod(client, 'create', loadingCount$), - }; -} - -function wrapEsClientMethod(esClient: any, method: string, loadingCount$: BehaviorSubject) { - return (args: any) => { - // esClient returns a promise, with an additional abort handler - // To tap into the abort handling, we have to override that abort handler. - const customPromiseThingy = esClient[method](args); - const { abort } = customPromiseThingy; - let resolved = false; - - // Start LoadingIndicator - loadingCount$.next(loadingCount$.getValue() + 1); - - // Stop LoadingIndicator when user aborts - customPromiseThingy.abort = () => { - abort(); - if (!resolved) { - resolved = true; - loadingCount$.next(loadingCount$.getValue() - 1); - } - }; - - // Stop LoadingIndicator when promise finishes - customPromiseThingy.finally(() => { - resolved = true; - loadingCount$.next(loadingCount$.getValue() - 1); - }); - - return customPromiseThingy; - }; -} - -function getEsUrl(http: CoreStart['http'], packageVersion: PackageInfo['version']) { - const a = document.createElement('a'); - a.href = http.basePath.prepend('/elasticsearch'); - const protocolPort = /https/.test(a.protocol) ? 443 : 80; - const port = a.port || protocolPort; - return { - host: a.hostname, - port, - protocol: a.protocol, - pathname: a.pathname, - headers: { - 'kbn-version': packageVersion, - }, - }; -} diff --git a/src/plugins/data/public/search/legacy/es_client/index.ts b/src/plugins/data/public/search/legacy/es_client/index.ts deleted file mode 100644 index 78ac83af642d8..0000000000000 --- a/src/plugins/data/public/search/legacy/es_client/index.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. - */ - -export { getEsClient } from './get_es_client'; -export { LegacyApiCaller } from './types'; diff --git a/src/plugins/data/public/search/legacy/es_client/types.ts b/src/plugins/data/public/search/legacy/es_client/types.ts deleted file mode 100644 index 2d35188322a4e..0000000000000 --- a/src/plugins/data/public/search/legacy/es_client/types.ts +++ /dev/null @@ -1,30 +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 { SearchResponse } from 'elasticsearch'; -import { SearchRequest } from '../../fetch'; - -export interface LegacyApiCaller { - search: (searchRequest: SearchRequest) => LegacyApiCallerResponse; - msearch: (searchRequest: SearchRequest) => LegacyApiCallerResponse; -} - -interface LegacyApiCallerResponse extends Promise> { - abort: () => void; -} diff --git a/src/plugins/data/public/search/legacy/fetch_soon.test.ts b/src/plugins/data/public/search/legacy/fetch_soon.test.ts index d7a85e65b475d..d38a41cf5ffbc 100644 --- a/src/plugins/data/public/search/legacy/fetch_soon.test.ts +++ b/src/plugins/data/public/search/legacy/fetch_soon.test.ts @@ -19,10 +19,10 @@ import { fetchSoon } from './fetch_soon'; import { callClient } from './call_client'; -import { FetchHandlers, FetchOptions } from '../fetch/types'; +import { FetchHandlers } from '../fetch/types'; import { SearchRequest } from '../index'; import { SearchResponse } from 'elasticsearch'; -import { GetConfigFn, UI_SETTINGS } from '../../../common'; +import { GetConfigFn, UI_SETTINGS, ISearchOptions } from '../../../common'; function getConfigStub(config: any = {}): GetConfigFn { return (key) => config[key]; @@ -102,7 +102,7 @@ describe('fetchSoon', () => { const options = [{ bar: 1 }, { bar: 2 }]; requests.forEach((request, i) => { - fetchSoon(request, options[i] as FetchOptions, { config } as FetchHandlers); + fetchSoon(request, options[i] as ISearchOptions, { config } as FetchHandlers); }); jest.advanceTimersByTime(50); diff --git a/src/plugins/data/public/search/legacy/fetch_soon.ts b/src/plugins/data/public/search/legacy/fetch_soon.ts index 16920a8a4dd97..37c3827bb7bba 100644 --- a/src/plugins/data/public/search/legacy/fetch_soon.ts +++ b/src/plugins/data/public/search/legacy/fetch_soon.ts @@ -19,9 +19,9 @@ import { SearchResponse } from 'elasticsearch'; import { callClient } from './call_client'; -import { FetchHandlers, FetchOptions } from '../fetch/types'; +import { FetchHandlers } from '../fetch/types'; import { SearchRequest } from '../index'; -import { UI_SETTINGS } from '../../../common'; +import { UI_SETTINGS, ISearchOptions } from '../../../common'; /** * This function introduces a slight delay in the request process to allow multiple requests to queue @@ -29,7 +29,7 @@ import { UI_SETTINGS } from '../../../common'; */ export async function fetchSoon( request: SearchRequest, - options: FetchOptions, + options: ISearchOptions, fetchHandlers: FetchHandlers ) { const msToDelay = fetchHandlers.config.get(UI_SETTINGS.COURIER_BATCH_SEARCHES) ? 50 : 0; @@ -51,7 +51,7 @@ function delay(fn: (...args: any) => T, ms: number): Promise { // The current batch/queue of requests to fetch let requestsToFetch: SearchRequest[] = []; -let requestOptions: FetchOptions[] = []; +let requestOptions: ISearchOptions[] = []; // The in-progress fetch (if there is one) let fetchInProgress: any = null; @@ -65,7 +65,7 @@ let fetchInProgress: any = null; */ async function delayedFetch( request: SearchRequest, - options: FetchOptions, + options: ISearchOptions, fetchHandlers: FetchHandlers, ms: number ): Promise> { diff --git a/src/plugins/data/public/search/legacy/get_msearch_params.test.ts b/src/plugins/data/public/search/legacy/get_msearch_params.test.ts deleted file mode 100644 index d3206950174c8..0000000000000 --- a/src/plugins/data/public/search/legacy/get_msearch_params.test.ts +++ /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 { getMSearchParams } from './get_msearch_params'; -import { GetConfigFn, UI_SETTINGS } from '../../../common'; - -function getConfigStub(config: any = {}): GetConfigFn { - return (key) => config[key]; -} - -describe('getMSearchParams', () => { - test('includes rest_total_hits_as_int', () => { - const config = getConfigStub(); - const msearchParams = getMSearchParams(config); - expect(msearchParams.rest_total_hits_as_int).toBe(true); - }); - - test('includes ignore_throttled according to search:includeFrozen', () => { - let config = getConfigStub({ [UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: true }); - let msearchParams = getMSearchParams(config); - expect(msearchParams.ignore_throttled).toBe(false); - - config = getConfigStub({ [UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: false }); - msearchParams = getMSearchParams(config); - expect(msearchParams.ignore_throttled).toBe(true); - }); - - test('includes max_concurrent_shard_requests according to courier:maxConcurrentShardRequests if greater than 0', () => { - let config = getConfigStub({ [UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS]: 0 }); - let msearchParams = getMSearchParams(config); - expect(msearchParams.max_concurrent_shard_requests).toBe(undefined); - - config = getConfigStub({ [UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS]: 5 }); - msearchParams = getMSearchParams(config); - expect(msearchParams.max_concurrent_shard_requests).toBe(5); - }); - - test('does not include other search params that are included in the msearch header or body', () => { - const config = getConfigStub({ - [UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: false, - [UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS]: 5, - }); - const msearchParams = getMSearchParams(config); - expect(msearchParams.hasOwnProperty('ignore_unavailable')).toBe(false); - expect(msearchParams.hasOwnProperty('preference')).toBe(false); - expect(msearchParams.hasOwnProperty('timeout')).toBe(false); - }); -}); diff --git a/src/plugins/data/public/search/legacy/get_msearch_params.ts b/src/plugins/data/public/search/legacy/get_msearch_params.ts deleted file mode 100644 index c4f77b25078cd..0000000000000 --- a/src/plugins/data/public/search/legacy/get_msearch_params.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 { GetConfigFn } from '../../../common'; -import { getIgnoreThrottled, getMaxConcurrentShardRequests } from '../fetch'; - -export function getMSearchParams(getConfig: GetConfigFn) { - return { - rest_total_hits_as_int: true, - ignore_throttled: getIgnoreThrottled(getConfig), - max_concurrent_shard_requests: getMaxConcurrentShardRequests(getConfig), - }; -} diff --git a/src/plugins/data/public/search/legacy/index.ts b/src/plugins/data/public/search/legacy/index.ts index e2ae72824f3f4..74e516f407e8c 100644 --- a/src/plugins/data/public/search/legacy/index.ts +++ b/src/plugins/data/public/search/legacy/index.ts @@ -18,4 +18,3 @@ */ export { fetchSoon } from './fetch_soon'; -export { getEsClient, LegacyApiCaller } from './es_client'; diff --git a/src/plugins/data/public/search/long_query_notification.tsx b/src/plugins/data/public/search/long_query_notification.tsx deleted file mode 100644 index 1db298618fae8..0000000000000 --- a/src/plugins/data/public/search/long_query_notification.tsx +++ /dev/null @@ -1,59 +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 { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; -import { ApplicationStart } from 'kibana/public'; -import { toMountPoint } from '../../../kibana_react/public'; - -interface Props { - application: ApplicationStart; -} - -export function getLongQueryNotification(props: Props) { - return toMountPoint(); -} - -export function LongQueryNotification(props: Props) { - return ( -
- - - - - { - await props.application.navigateToApp('management/stack/license_management'); - }} - > - - - - -
- ); -} diff --git a/src/plugins/data/public/search/mocks.ts b/src/plugins/data/public/search/mocks.ts index 8ccf46fe7c97d..f4ed7d8b122b9 100644 --- a/src/plugins/data/public/search/mocks.ts +++ b/src/plugins/data/public/search/mocks.ts @@ -35,12 +35,6 @@ function createStartContract(): jest.Mocked { aggs: searchAggsStartMock(), search: jest.fn(), searchSource: searchSourceMock, - __LEGACY: { - esClient: { - search: jest.fn(), - msearch: jest.fn(), - }, - }, }; } diff --git a/src/plugins/data/public/search/search_interceptor.test.ts b/src/plugins/data/public/search/search_interceptor.test.ts index 2eded17bda88c..7bfa6f0ab1bc5 100644 --- a/src/plugins/data/public/search/search_interceptor.test.ts +++ b/src/plugins/data/public/search/search_interceptor.test.ts @@ -32,15 +32,12 @@ jest.useFakeTimers(); describe('SearchInterceptor', () => { beforeEach(() => { mockCoreSetup = coreMock.createSetup(); - searchInterceptor = new SearchInterceptor( - { - toasts: mockCoreSetup.notifications.toasts, - startServices: mockCoreSetup.getStartServices(), - uiSettings: mockCoreSetup.uiSettings, - http: mockCoreSetup.http, - }, - 1000 - ); + searchInterceptor = new SearchInterceptor({ + toasts: mockCoreSetup.notifications.toasts, + startServices: mockCoreSetup.getStartServices(), + uiSettings: mockCoreSetup.uiSettings, + http: mockCoreSetup.http, + }); }); describe('search', () => { @@ -98,6 +95,39 @@ describe('SearchInterceptor', () => { await flushPromises(); }); + test('Should not timeout if requestTimeout is undefined', async () => { + searchInterceptor = new SearchInterceptor({ + startServices: mockCoreSetup.getStartServices(), + uiSettings: mockCoreSetup.uiSettings, + http: mockCoreSetup.http, + toasts: mockCoreSetup.notifications.toasts, + }); + mockCoreSetup.http.fetch.mockImplementationOnce((options: any) => { + return new Promise((resolve, reject) => { + options.signal.addEventListener('abort', () => { + reject(new AbortError()); + }); + + setTimeout(resolve, 5000); + }); + }); + const mockRequest: IEsSearchRequest = { + params: {}, + }; + const response = searchInterceptor.search(mockRequest); + + expect.assertions(1); + const next = jest.fn(); + const complete = () => { + expect(next).toBeCalled(); + }; + response.subscribe({ next, complete }); + + jest.advanceTimersByTime(5000); + + await flushPromises(); + }); + test('Observable should fail if user aborts (test merged signal)', async () => { const abortController = new AbortController(); mockCoreSetup.http.fetch.mockImplementationOnce((options: any) => { @@ -112,7 +142,9 @@ describe('SearchInterceptor', () => { const mockRequest: IEsSearchRequest = { params: {}, }; - const response = searchInterceptor.search(mockRequest, { signal: abortController.signal }); + const response = searchInterceptor.search(mockRequest, { + abortSignal: abortController.signal, + }); const next = jest.fn(); const error = (e: any) => { @@ -126,12 +158,12 @@ describe('SearchInterceptor', () => { await flushPromises(); }); - test('Immediatelly aborts if passed an aborted abort signal', async (done) => { + test('Immediately aborts if passed an aborted abort signal', async (done) => { const abort = new AbortController(); const mockRequest: IEsSearchRequest = { params: {}, }; - const response = searchInterceptor.search(mockRequest, { signal: abort.signal }); + const response = searchInterceptor.search(mockRequest, { abortSignal: abort.signal }); abort.abort(); const error = (e: any) => { @@ -142,44 +174,4 @@ describe('SearchInterceptor', () => { response.subscribe({ error }); }); }); - - describe('getPendingCount$', () => { - test('should observe the number of pending requests', () => { - const pendingCount$ = searchInterceptor.getPendingCount$(); - const pendingNext = jest.fn(); - pendingCount$.subscribe(pendingNext); - - const mockResponse: any = { result: 200 }; - mockCoreSetup.http.fetch.mockResolvedValue(mockResponse); - const mockRequest: IEsSearchRequest = { - params: {}, - }; - const response = searchInterceptor.search(mockRequest); - - response.subscribe({ - complete: () => { - expect(pendingNext.mock.calls).toEqual([[0], [1], [0]]); - }, - }); - }); - - test('should observe the number of pending requests on error', () => { - const pendingCount$ = searchInterceptor.getPendingCount$(); - const pendingNext = jest.fn(); - pendingCount$.subscribe(pendingNext); - - const mockResponse: any = { result: 500 }; - mockCoreSetup.http.fetch.mockRejectedValue(mockResponse); - const mockRequest: IEsSearchRequest = { - params: {}, - }; - const response = searchInterceptor.search(mockRequest); - - response.subscribe({ - complete: () => { - expect(pendingNext.mock.calls).toEqual([[0], [1], [0]]); - }, - }); - }); - }); }); diff --git a/src/plugins/data/public/search/search_interceptor.ts b/src/plugins/data/public/search/search_interceptor.ts index 30e509edd4987..888e12a4285b1 100644 --- a/src/plugins/data/public/search/search_interceptor.ts +++ b/src/plugins/data/public/search/search_interceptor.ts @@ -17,23 +17,35 @@ * under the License. */ -import { trimEnd } from 'lodash'; -import { BehaviorSubject, throwError, timer, Subscription, defer, from, Observable } from 'rxjs'; -import { finalize, filter } from 'rxjs/operators'; -import { Toast, CoreStart, ToastsSetup, CoreSetup } from 'kibana/public'; -import { getCombinedSignal, AbortError } from '../../common/utils'; -import { IEsSearchRequest, IEsSearchResponse, ES_SEARCH_STRATEGY } from '../../common/search'; -import { ISearchOptions } from './types'; -import { getLongQueryNotification } from './long_query_notification'; +import { trimEnd, debounce } from 'lodash'; +import { + BehaviorSubject, + throwError, + timer, + Subscription, + defer, + from, + Observable, + NEVER, +} from 'rxjs'; +import { catchError, finalize } from 'rxjs/operators'; +import { CoreStart, CoreSetup, ToastsSetup } from 'kibana/public'; +import { i18n } from '@kbn/i18n'; +import { + getCombinedSignal, + AbortError, + IEsSearchRequest, + IEsSearchResponse, + ISearchOptions, + ES_SEARCH_STRATEGY, +} from '../../common'; import { SearchUsageCollector } from './collectors'; -const LONG_QUERY_NOTIFICATION_DELAY = 10000; - export interface SearchInterceptorDeps { - toasts: ToastsSetup; http: CoreSetup['http']; uiSettings: CoreSetup['uiSettings']; startServices: Promise<[CoreStart, any, unknown]>; + toasts: ToastsSetup; usageCollector?: SearchUsageCollector; } @@ -56,49 +68,25 @@ export class SearchInterceptor { */ protected timeoutSubscriptions: Subscription = new Subscription(); - /** - * The current long-running toast (if there is one). - * @internal - */ - protected longRunningToast?: Toast; - /** * @internal */ protected application!: CoreStart['application']; - /** - * This class should be instantiated with a `requestTimeout` corresponding with how many ms after - * requests are initiated that they should automatically cancel. - * @param toasts The `core.notifications.toasts` service - * @param application The `core.application` service - * @param requestTimeout Usually config value `elasticsearch.requestTimeout` + /* + * @internal */ - constructor( - protected readonly deps: SearchInterceptorDeps, - protected readonly requestTimeout?: number - ) { + constructor(protected readonly deps: SearchInterceptorDeps) { this.deps.http.addLoadingCountSource(this.pendingCount$); this.deps.startServices.then(([coreStart]) => { this.application = coreStart.application; }); - - // When search requests go out, a notification is scheduled allowing users to continue the - // request past the timeout. When all search requests complete, we remove the notification. - this.getPendingCount$() - .pipe(filter((count) => count === 0)) - .subscribe(this.hideToast); } /** - * Returns an `Observable` over the current number of pending searches. This could mean that one - * of the search requests is still in flight, or that it has only received partial responses. + * @internal */ - public getPendingCount$() { - return this.pendingCount$.asObservable(); - } - protected runSearch( request: IEsSearchRequest, signal: AbortSignal, @@ -128,14 +116,22 @@ export class SearchInterceptor { ): Observable { // Defer the following logic until `subscribe` is actually called return defer(() => { - if (options?.signal?.aborted) { + if (options?.abortSignal?.aborted) { return throwError(new AbortError()); } - const { combinedSignal, cleanup } = this.setupTimers(options); + const { combinedSignal, cleanup } = this.setupAbortSignal({ + abortSignal: options?.abortSignal, + }); this.pendingCount$.next(this.pendingCount$.getValue() + 1); return this.runSearch(request, combinedSignal, options?.strategy).pipe( + catchError((e: any) => { + if (e.body?.attributes?.error === 'Request timed out') { + this.showTimeoutError(e); + } + return throwError(e); + }), finalize(() => { this.pendingCount$.next(this.pendingCount$.getValue() - 1); cleanup(); @@ -144,19 +140,26 @@ export class SearchInterceptor { }); } - protected setupTimers(options?: ISearchOptions) { + /** + * @internal + */ + protected setupAbortSignal({ + abortSignal, + timeout, + }: { + abortSignal?: AbortSignal; + timeout?: number; + }) { // Schedule this request to automatically timeout after some interval const timeoutController = new AbortController(); const { signal: timeoutSignal } = timeoutController; - const timeout$ = timer(this.requestTimeout); + const timeout$ = timeout ? timer(timeout) : NEVER; const subscription = timeout$.subscribe(() => { timeoutController.abort(); + this.showTimeoutError(new AbortError()); }); this.timeoutSubscriptions.add(subscription); - // Schedule the notification to allow users to cancel or wait beyond the timeout - const notificationSubscription = timer(LONG_QUERY_NOTIFICATION_DELAY).subscribe(this.showToast); - // Get a combined `AbortSignal` that will be aborted whenever the first of the following occurs: // 1. The user manually aborts (via `cancelPending`) // 2. The request times out @@ -164,13 +167,12 @@ export class SearchInterceptor { const signals = [ this.abortController.signal, timeoutSignal, - ...(options?.signal ? [options.signal] : []), + ...(abortSignal ? [abortSignal] : []), ]; const combinedSignal = getCombinedSignal(signals); const cleanup = () => { this.timeoutSubscriptions.remove(subscription); - notificationSubscription.unsubscribe(); }; combinedSignal.addEventListener('abort', cleanup); @@ -181,36 +183,23 @@ export class SearchInterceptor { }; } - /** - * @internal - */ - protected showToast = () => { - if (this.longRunningToast) return; - this.longRunningToast = this.deps.toasts.addInfo( - { - title: 'Your query is taking a while', - text: getLongQueryNotification({ - application: this.application, + // Right now we are debouncing but we will hook this up with background sessions to show only one + // error notification per session. + protected showTimeoutError = debounce( + (e: Error) => { + this.deps.toasts.addError(e, { + title: 'Timed out', + toastMessage: i18n.translate('data.search.upgradeLicense', { + defaultMessage: + 'One or more queries timed out. With our free Basic tier, your queries never time out.', }), - }, - { - toastLifeTimeMs: 1000000, - } - ); - }; - - /** - * @internal - */ - protected hideToast = () => { - if (this.longRunningToast) { - this.deps.toasts.remove(this.longRunningToast); - delete this.longRunningToast; - if (this.deps.usageCollector) { - this.deps.usageCollector.trackLongQueryDialogDismissed(); - } + }); + }, + 60000, + { + leading: true, } - }; + ); } export type ISearchInterceptor = PublicMethodsOf; diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index 9a30a15936fe5..6b73761c5a437 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -17,11 +17,11 @@ * under the License. */ -import { Plugin, CoreSetup, CoreStart, PackageInfo } from 'src/core/public'; +import { Plugin, CoreSetup, CoreStart } from 'src/core/public'; +import { BehaviorSubject } from 'rxjs'; import { ISearchSetup, ISearchStart, SearchEnhancements } from './types'; import { createSearchSource, SearchSource, SearchSourceDependencies } from './search_source'; -import { getEsClient, LegacyApiCaller } from './legacy'; import { AggsService, AggsStartDependencies } from './aggs'; import { IndexPatternsContract } from '../index_patterns/index_patterns'; import { ISearchInterceptor, SearchInterceptor } from './search_interceptor'; @@ -33,9 +33,8 @@ import { ExpressionsSetup } from '../../../expressions/public'; /** @internal */ export interface SearchServiceSetupDependencies { - packageInfo: PackageInfo; - usageCollection?: UsageCollectionSetup; expressions: ExpressionsSetup; + usageCollection?: UsageCollectionSetup; } /** @internal */ @@ -45,44 +44,27 @@ export interface SearchServiceStartDependencies { } export class SearchService implements Plugin { - private esClient?: LegacyApiCaller; private readonly aggsService = new AggsService(); private searchInterceptor!: ISearchInterceptor; private usageCollector?: SearchUsageCollector; public setup( { http, getStartServices, injectedMetadata, notifications, uiSettings }: CoreSetup, - { expressions, packageInfo, usageCollection }: SearchServiceSetupDependencies + { expressions, usageCollection }: SearchServiceSetupDependencies ): ISearchSetup { - const esApiVersion = injectedMetadata.getInjectedVar('esApiVersion') as string; - const esRequestTimeout = injectedMetadata.getInjectedVar('esRequestTimeout') as number; - const packageVersion = packageInfo.version; - this.usageCollector = createUsageCollector(getStartServices, usageCollection); - this.esClient = getEsClient({ - esRequestTimeout, - esApiVersion, - http, - packageVersion, - }); - /** * A global object that intercepts all searches and provides convenience methods for cancelling * all pending search requests, as well as getting the number of pending search requests. - * TODO: Make this modular so that apps can opt in/out of search collection, or even provide - * their own search collector instances */ - this.searchInterceptor = new SearchInterceptor( - { - toasts: notifications.toasts, - http, - uiSettings, - startServices: getStartServices(), - usageCollector: this.usageCollector!, - }, - esRequestTimeout - ); + this.searchInterceptor = new SearchInterceptor({ + toasts: notifications.toasts, + http, + uiSettings, + startServices: getStartServices(), + usageCollector: this.usageCollector!, + }); expressions.registerFunction(esdsl); expressions.registerType(esRawResponse); @@ -107,27 +89,31 @@ export class SearchService implements Plugin { return this.searchInterceptor.search(request, options); }) as ISearchGeneric; - const legacySearch = { - esClient: this.esClient!, - }; + const loadingCount$ = new BehaviorSubject(0); + http.addLoadingCountSource(loadingCount$); const searchSourceDependencies: SearchSourceDependencies = { getConfig: uiSettings.get.bind(uiSettings), - esShardTimeout: injectedMetadata.getInjectedVar('esShardTimeout') as number, search, - legacySearch, + http, + loadingCount$, }; return { aggs: this.aggsService.start({ fieldFormats, uiSettings }), search, searchSource: { + /** + * creates searchsource based on serialized search source fields + */ create: createSearchSource(indexPatterns, searchSourceDependencies), + /** + * creates an enpty search source + */ createEmpty: () => { return new SearchSource({}, searchSourceDependencies); }, }, - __LEGACY: legacySearch, }; } diff --git a/src/plugins/data/public/search/search_source/create_search_source.test.ts b/src/plugins/data/public/search/search_source/create_search_source.test.ts index 56f6ca6c56270..bc1c7c06c8806 100644 --- a/src/plugins/data/public/search/search_source/create_search_source.test.ts +++ b/src/plugins/data/public/search/search_source/create_search_source.test.ts @@ -22,7 +22,8 @@ import { SearchSourceDependencies } from './search_source'; import { IIndexPattern } from '../../../common/index_patterns'; import { IndexPatternsContract } from '../../index_patterns/index_patterns'; import { Filter } from '../../../common/es_query/filters'; -import { dataPluginMock } from '../../mocks'; +import { coreMock } from '../../../../../core/public/mocks'; +import { BehaviorSubject } from 'rxjs'; describe('createSearchSource', () => { const indexPatternMock: IIndexPattern = {} as IIndexPattern; @@ -31,13 +32,11 @@ describe('createSearchSource', () => { let createSearchSource: ReturnType; beforeEach(() => { - const data = dataPluginMock.createStartContract(); - dependencies = { getConfig: jest.fn(), search: jest.fn(), - legacySearch: data.search.__LEGACY, - esShardTimeout: 30000, + http: coreMock.createStart().http, + loadingCount$: new BehaviorSubject(0), }; indexPatternContractMock = ({ diff --git a/src/plugins/data/public/search/search_source/create_search_source.ts b/src/plugins/data/public/search/search_source/create_search_source.ts index 4c44f4d62d469..242fbd73fe42b 100644 --- a/src/plugins/data/public/search/search_source/create_search_source.ts +++ b/src/plugins/data/public/search/search_source/create_search_source.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { migrateLegacyQuery } from '../../../../kibana_legacy/common'; +import { migrateLegacyQuery } from './migrate_legacy_query'; import { SearchSource, SearchSourceDependencies } from './search_source'; import { IndexPatternsContract } from '../../index_patterns/index_patterns'; import { SearchSourceFields } from './types'; diff --git a/src/plugins/data/public/search/search_source/migrate_legacy_query.ts b/src/plugins/data/public/search/search_source/migrate_legacy_query.ts new file mode 100644 index 0000000000000..8d9b50d5a66b2 --- /dev/null +++ b/src/plugins/data/public/search/search_source/migrate_legacy_query.ts @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { has } from 'lodash'; +import { Query } from 'src/plugins/data/public'; + +/** + * Creates a standardized query object from old queries that were either strings or pure ES query DSL + * + * @param query - a legacy query, what used to be stored in SearchSource's query property + * @return Object + */ + +export function migrateLegacyQuery(query: Query | { [key: string]: any } | string): Query { + // Lucene was the only option before, so language-less queries are all lucene + if (!has(query, 'language')) { + return { query, language: 'lucene' }; + } + + return query as Query; +} diff --git a/src/plugins/data/public/search/search_source/mocks.ts b/src/plugins/data/public/search/search_source/mocks.ts index 4e1c35557ffa6..adf53bee33fe1 100644 --- a/src/plugins/data/public/search/search_source/mocks.ts +++ b/src/plugins/data/public/search/search_source/mocks.ts @@ -17,7 +17,8 @@ * under the License. */ -import { uiSettingsServiceMock } from '../../../../../core/public/mocks'; +import { BehaviorSubject } from 'rxjs'; +import { httpServiceMock, uiSettingsServiceMock } from '../../../../../core/public/mocks'; import { ISearchSource, SearchSource } from './search_source'; import { SearchSourceFields } from './types'; @@ -52,12 +53,7 @@ export const searchSourceMock = { export const createSearchSourceMock = (fields?: SearchSourceFields) => new SearchSource(fields, { getConfig: uiSettingsServiceMock.createStartContract().get, - esShardTimeout: 30000, search: jest.fn(), - legacySearch: { - esClient: { - search: jest.fn(), - msearch: jest.fn(), - }, - }, + http: httpServiceMock.createStartContract(), + loadingCount$: new BehaviorSubject(0), }); diff --git a/src/plugins/data/public/search/search_source/search_source.test.ts b/src/plugins/data/public/search/search_source/search_source.test.ts index 2f0fa0765e25a..282a33e6d01f7 100644 --- a/src/plugins/data/public/search/search_source/search_source.test.ts +++ b/src/plugins/data/public/search/search_source/search_source.test.ts @@ -17,12 +17,12 @@ * under the License. */ -import { Observable } from 'rxjs'; +import { Observable, BehaviorSubject } from 'rxjs'; import { GetConfigFn } from 'src/plugins/data/common'; import { SearchSource, SearchSourceDependencies } from './search_source'; import { IndexPattern, SortDirection } from '../..'; import { fetchSoon } from '../legacy'; -import { dataPluginMock } from '../../../../data/public/mocks'; +import { coreMock } from '../../../../../core/public/mocks'; jest.mock('../legacy', () => ({ fetchSoon: jest.fn().mockResolvedValue({}), @@ -54,8 +54,6 @@ describe('SearchSource', () => { let searchSourceDependencies: SearchSourceDependencies; beforeEach(() => { - const data = dataPluginMock.createStartContract(); - mockSearchMethod = jest.fn(() => { return new Observable((subscriber) => { setTimeout(() => { @@ -70,8 +68,8 @@ describe('SearchSource', () => { searchSourceDependencies = { getConfig: jest.fn(), search: mockSearchMethod, - legacySearch: data.search.__LEGACY, - esShardTimeout: 30000, + http: coreMock.createStart().http, + loadingCount$: new BehaviorSubject(0), }; }); diff --git a/src/plugins/data/public/search/search_source/search_source.ts b/src/plugins/data/public/search/search_source/search_source.ts index d2e3370762059..a39898e6a9f52 100644 --- a/src/plugins/data/public/search/search_source/search_source.ts +++ b/src/plugins/data/public/search/search_source/search_source.ts @@ -72,25 +72,31 @@ import { setWith } from '@elastic/safer-lodash-set'; import { uniqueId, uniq, extend, pick, difference, omit, isObject, keys, isFunction } from 'lodash'; import { map } from 'rxjs/operators'; +import { HttpStart } from 'src/core/public'; +import { BehaviorSubject } from 'rxjs'; import { normalizeSortRequest } from './normalize_sort_request'; import { filterDocvalueFields } from './filter_docvalue_fields'; import { fieldWildcardFilter } from '../../../../kibana_utils/common'; import { IIndexPattern, ISearchGeneric } from '../..'; import { SearchSourceOptions, SearchSourceFields } from './types'; import { - FetchOptions, RequestFailure, handleResponse, getSearchParamsFromRequest, SearchRequest, } from '../fetch'; -import { getEsQueryConfig, buildEsQuery, Filter, UI_SETTINGS } from '../../../common'; +import { + getEsQueryConfig, + buildEsQuery, + Filter, + UI_SETTINGS, + ISearchOptions, +} from '../../../common'; import { getHighlightRequest } from '../../../common/field_formats'; import { GetConfigFn } from '../../../common/types'; import { fetchSoon } from '../legacy'; import { extractReferences } from './extract_references'; -import { ISearchStartLegacy } from '../types'; /** @internal */ export const searchSourceRequiredUiSettings = [ @@ -111,8 +117,8 @@ export const searchSourceRequiredUiSettings = [ export interface SearchSourceDependencies { getConfig: GetConfigFn; search: ISearchGeneric; - legacySearch: ISearchStartLegacy; - esShardTimeout: number; + http: HttpStart; + loadingCount$: BehaviorSubject; } /** @public **/ @@ -121,7 +127,7 @@ export class SearchSource { private searchStrategyId?: string; private parent?: SearchSource; private requestStartHandlers: Array< - (searchSource: SearchSource, options?: FetchOptions) => Promise + (searchSource: SearchSource, options?: ISearchOptions) => Promise > = []; private inheritOptions: SearchSourceOptions = {}; public history: SearchRequest[] = []; @@ -137,15 +143,19 @@ export class SearchSource { * PUBLIC API *****/ + /** + * internal, dont use + * @param searchStrategyId + */ setPreferredSearchStrategyId(searchStrategyId: string) { this.searchStrategyId = searchStrategyId; } - setFields(newFields: SearchSourceFields) { - this.fields = newFields; - return this; - } - + /** + * sets value to a single search source feild + * @param field: field name + * @param value: value for the field + */ setField(field: K, value: SearchSourceFields[K]) { if (value == null) { delete this.fields[field]; @@ -155,16 +165,33 @@ export class SearchSource { return this; } + /** + * Internal, do not use. Overrides all search source fields with the new field array. + * + * @private + * @param newFields New field array. + */ + setFields(newFields: SearchSourceFields) { + this.fields = newFields; + return this; + } + + /** + * returns search source id + */ getId() { return this.id; } + /** + * returns all search source fields + */ getFields() { return { ...this.fields }; } /** - * Get fields from the fields + * Gets a single field from the fields */ getField(field: K, recurse = true): SearchSourceFields[K] { if (!recurse || this.fields[field] !== void 0) { @@ -181,10 +208,16 @@ export class SearchSource { return this.getField(field, false); } + /** + * @deprecated Don't use. + */ create() { return new SearchSource({}, this.dependencies); } + /** + * creates a copy of this search source (without its children) + */ createCopy() { const newSearchSource = new SearchSource({}, this.dependencies); newSearchSource.setFields({ ...this.fields }); @@ -195,6 +228,10 @@ export class SearchSource { return newSearchSource; } + /** + * creates a new child search source + * @param options + */ createChild(options = {}) { const childSearchSource = new SearchSource({}, this.dependencies); childSearchSource.setParent(this, options); @@ -221,49 +258,12 @@ export class SearchSource { return this.parent; } - /** - * Run a search using the search service - * @return {Observable>} - */ - private fetch$(searchRequest: SearchRequest, signal?: AbortSignal) { - const { search, esShardTimeout, getConfig } = this.dependencies; - - const params = getSearchParamsFromRequest(searchRequest, { - esShardTimeout, - getConfig, - }); - - return search({ params, indexType: searchRequest.indexType }, { signal }).pipe( - map(({ rawResponse }) => handleResponse(searchRequest, rawResponse)) - ); - } - - /** - * Run a search using the search service - * @return {Promise>} - */ - private async legacyFetch(searchRequest: SearchRequest, options: FetchOptions) { - const { esShardTimeout, legacySearch, getConfig } = this.dependencies; - - return await fetchSoon( - searchRequest, - { - ...(this.searchStrategyId && { searchStrategyId: this.searchStrategyId }), - ...options, - }, - { - legacySearchService: legacySearch, - config: { get: getConfig }, - esShardTimeout, - } - ); - } /** * Fetch this source and reject the returned Promise on error * * @async */ - async fetch(options: FetchOptions = {}) { + async fetch(options: ISearchOptions = {}) { const { getConfig } = this.dependencies; await this.requestIsStarting(options); @@ -274,7 +274,7 @@ export class SearchSource { if (getConfig(UI_SETTINGS.COURIER_BATCH_SEARCHES)) { response = await this.legacyFetch(searchRequest, options); } else { - response = await this.fetch$(searchRequest, options.abortSignal).toPromise(); + response = await this.fetch$(searchRequest, options).toPromise(); } // TODO: Remove casting when https://github.com/elastic/elasticsearch-js/issues/1287 is resolved @@ -291,11 +291,14 @@ export class SearchSource { * @return {undefined} */ onRequestStart( - handler: (searchSource: SearchSource, options?: FetchOptions) => Promise + handler: (searchSource: SearchSource, options?: ISearchOptions) => Promise ) { this.requestStartHandlers.push(handler); } + /** + * Returns body contents of the search request, often referred as query DSL. + */ async getSearchRequestBody() { const searchRequest = await this.flatten(); return searchRequest.body; @@ -313,12 +316,49 @@ export class SearchSource { * PRIVATE APIS ******/ + /** + * Run a search using the search service + * @return {Observable>} + */ + private fetch$(searchRequest: SearchRequest, options: ISearchOptions) { + const { search, getConfig } = this.dependencies; + + const params = getSearchParamsFromRequest(searchRequest, { + getConfig, + }); + + return search({ params, indexType: searchRequest.indexType }, options).pipe( + map(({ rawResponse }) => handleResponse(searchRequest, rawResponse)) + ); + } + + /** + * Run a search using the search service + * @return {Promise>} + */ + private async legacyFetch(searchRequest: SearchRequest, options: ISearchOptions) { + const { http, getConfig, loadingCount$ } = this.dependencies; + + return await fetchSoon( + searchRequest, + { + ...(this.searchStrategyId && { searchStrategyId: this.searchStrategyId }), + ...options, + }, + { + http, + config: { get: getConfig }, + loadingCount$, + } + ); + } + /** * Called by requests of this search source when they are started * @param options * @return {Promise} */ - private requestIsStarting(options: FetchOptions = {}) { + private requestIsStarting(options: ISearchOptions = {}) { const handlers = [...this.requestStartHandlers]; // If callParentStartHandlers has been set to true, we also call all // handlers of parent search sources. @@ -475,6 +515,9 @@ export class SearchSource { return searchRequest; } + /** + * serializes search source fields (which can later be passed to {@link ISearchStartSearchSource}) + */ public getSerializedFields() { const { filter: originalFilters, ...searchSourceFields } = omit(this.getFields(), [ 'sort', @@ -526,5 +569,8 @@ export class SearchSource { } } -/** @public **/ +/** + * search source interface + * @public + */ export type ISearchSource = Pick; diff --git a/src/plugins/data/public/search/search_source/types.ts b/src/plugins/data/public/search/search_source/types.ts index c2f8701a64fa3..0882aa9a2ceec 100644 --- a/src/plugins/data/public/search/search_source/types.ts +++ b/src/plugins/data/public/search/search_source/types.ts @@ -34,19 +34,37 @@ export interface SortDirectionNumeric { export type EsQuerySortValue = Record; +/** + * search source fields + */ export interface SearchSourceFields { type?: string; + /** + * {@link Query} + */ query?: Query; + /** + * {@link Filter} + */ filter?: Filter[] | Filter | (() => Filter[] | Filter | undefined); + /** + * {@link EsQuerySortValue} + */ sort?: EsQuerySortValue | EsQuerySortValue[]; highlight?: any; highlightAll?: boolean; + /** + * {@link AggConfigs} + */ aggs?: any; from?: number; size?: number; source?: NameList; version?: boolean; fields?: NameList; + /** + * {@link IndexPatternService} + */ index?: IndexPattern; searchAfter?: EsQuerySearchAfter; timeout?: string; diff --git a/src/plugins/data/public/search/types.ts b/src/plugins/data/public/search/types.ts index 55726e40f5a77..83a542269046f 100644 --- a/src/plugins/data/public/search/types.ts +++ b/src/plugins/data/public/search/types.ts @@ -19,7 +19,6 @@ import { Observable } from 'rxjs'; import { PackageInfo } from 'kibana/server'; -import { LegacyApiCaller } from './legacy/es_client'; import { ISearchInterceptor } from './search_interceptor'; import { ISearchSource, SearchSourceFields } from './search_source'; import { SearchUsageCollector } from './collectors'; @@ -29,15 +28,11 @@ import { IKibanaSearchResponse, IEsSearchRequest, IEsSearchResponse, + ISearchOptions, } from '../../common/search'; import { IndexPatternsContract } from '../../common/index_patterns/index_patterns'; import { UsageCollectionSetup } from '../../../usage_collection/public'; -export interface ISearchOptions { - signal?: AbortSignal; - strategy?: string; -} - export type ISearch = ( request: IKibanaSearchRequest, options?: ISearchOptions @@ -51,10 +46,6 @@ export type ISearchGeneric = < options?: ISearchOptions ) => Observable; -export interface ISearchStartLegacy { - esClient: LegacyApiCaller; -} - export interface SearchEnhancements { searchInterceptor: ISearchInterceptor; } @@ -71,18 +62,42 @@ export interface ISearchSetup { __enhance: (enhancements: SearchEnhancements) => void; } +/** + * high level search service + * @public + */ +export interface ISearchStartSearchSource { + /** + * creates {@link SearchSource} based on provided serialized {@link SearchSourceFields} + * @param fields + */ + create: (fields?: SearchSourceFields) => Promise; + /** + * creates empty {@link SearchSource} + */ + createEmpty: () => ISearchSource; +} +/** + * search service + * @public + */ export interface ISearchStart { + /** + * agg config sub service + * {@link AggsStart} + * + */ aggs: AggsStart; + /** + * low level search + * {@link ISearchGeneric} + */ search: ISearchGeneric; - searchSource: { - create: (fields?: SearchSourceFields) => Promise; - createEmpty: () => ISearchSource; - }; /** - * @deprecated - * @internal + * high level search + * {@link ISearchStartSearchSource} */ - __LEGACY: ISearchStartLegacy; + searchSource: ISearchStartSearchSource; } export { SEARCH_EVENT_TYPE } from './collectors'; diff --git a/src/plugins/data/public/test_utils.ts b/src/plugins/data/public/test_utils.ts new file mode 100644 index 0000000000000..f04b20e96fa1d --- /dev/null +++ b/src/plugins/data/public/test_utils.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 { getFieldFormatsRegistry } from './field_formats/field_formats_registry.stub'; diff --git a/src/plugins/data/public/types.ts b/src/plugins/data/public/types.ts index bffc10642eb47..7b5d79aff24ef 100644 --- a/src/plugins/data/public/types.ts +++ b/src/plugins/data/public/types.ts @@ -46,6 +46,9 @@ export interface DataStartDependencies { uiActions: UiActionsStart; } +/** + * Data plugin public Setup contract + */ export interface DataPublicPluginSetup { autocomplete: AutocompleteSetup; search: ISearchSetup; @@ -57,20 +60,61 @@ export interface DataPublicPluginSetup { __enhance: (enhancements: DataPublicPluginEnhancements) => void; } +/** + * Data plugin prewired UI components + */ +export interface DataPublicPluginStartUi { + IndexPatternSelect: React.ComponentType; + SearchBar: React.ComponentType; +} + +/** + * utilities to generate filters from action context + */ +export interface DataPublicPluginStartActions { + createFiltersFromValueClickAction: typeof createFiltersFromValueClickAction; + createFiltersFromRangeSelectAction: typeof createFiltersFromRangeSelectAction; +} + +/** + * Data plugin public Start contract + */ export interface DataPublicPluginStart { - actions: { - createFiltersFromValueClickAction: typeof createFiltersFromValueClickAction; - createFiltersFromRangeSelectAction: typeof createFiltersFromRangeSelectAction; - }; + /** + * filter creation utilities + * {@link DataPublicPluginStartActions} + */ + actions: DataPublicPluginStartActions; + /** + * autocomplete service + * {@link AutocompleteStart} + */ autocomplete: AutocompleteStart; + /** + * index patterns service + * {@link IndexPatternsContract} + */ indexPatterns: IndexPatternsContract; + /** + * search service + * {@link ISearchStart} + */ search: ISearchStart; + /** + * field formats service + * {@link FieldFormatsStart} + */ fieldFormats: FieldFormatsStart; + /** + * query service + * {@link QueryStart} + */ query: QueryStart; - ui: { - IndexPatternSelect: React.ComponentType; - SearchBar: React.ComponentType; - }; + /** + * prewired UI components + * {@link DataPublicPluginStartUi} + */ + ui: DataPublicPluginStartUi; } export interface IDataPluginServices extends Partial { diff --git a/src/plugins/data/public/ui/query_string_input/_query_bar.scss b/src/plugins/data/public/ui/query_string_input/_query_bar.scss index 00895ec49003b..1ff24c61954e7 100644 --- a/src/plugins/data/public/ui/query_string_input/_query_bar.scss +++ b/src/plugins/data/public/ui/query_string_input/_query_bar.scss @@ -8,30 +8,37 @@ border-right: none !important; } +.kbnQueryBar__textareaWrap { + overflow: visible !important; // Override EUI form control + display: flex; + flex: 1 1 100%; + position: relative; +} + .kbnQueryBar__textarea { z-index: $euiZContentMenu; resize: none !important; // When in the group, it will autosize - height: $euiSizeXXL; + height: $euiFormControlHeight; // Unlike most inputs within layout control groups, the text area still needs a border. // These adjusts help it sit above the control groups shadow to line up correctly. - padding-top: $euiSizeS + 3px !important; - transform: translateY(-2px); - padding: $euiSizeS - 1px; + padding: $euiSizeS; + padding-top: $euiSizeS + 3px; + transform: translateY(-1px) translateX(-1px); - &:not(:focus) { + &:not(:focus):not(:invalid) { @include euiYScrollWithShadows; + } + + &:not(:focus) { white-space: nowrap; overflow-y: hidden; overflow-x: hidden; - border: none; - box-shadow: none; } // When focused, let it scroll &:focus { overflow-x: auto; overflow-y: auto; - width: calc(100% + 1px); // To overtake the group's fake border white-space: normal; } } diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx index 2d311fd88eb39..f159cac664a9e 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx @@ -20,6 +20,7 @@ import React, { Component, RefObject, createRef } from 'react'; import { i18n } from '@kbn/i18n'; +import classNames from 'classnames'; import { EuiTextArea, EuiOutsideClickDetector, @@ -62,6 +63,8 @@ interface Props { onSubmit?: (query: Query) => void; dataTestSubj?: string; size?: SuggestionsListSize; + className?: string; + isInvalid?: boolean; } interface State { @@ -586,9 +589,13 @@ export class QueryStringInputUI extends Component { 'aria-owns': 'kbnTypeahead__items', }; const ariaCombobox = { ...isSuggestionsVisible, role: 'combobox' }; + const className = classNames( + 'euiFormControlLayout euiFormControlLayout--group kbnQueryBar__wrap', + this.props.className + ); return ( -
+
{this.props.prepend}
{ >
{ } role="textbox" data-test-subj={this.props.dataTestSubj || 'queryInput'} + isInvalid={this.props.isInvalid} > {this.getQueryString()} diff --git a/src/plugins/data/public/ui/typeahead/constants.ts b/src/plugins/data/public/ui/typeahead/constants.ts index 08f9bd23e16f3..0e28891a14535 100644 --- a/src/plugins/data/public/ui/typeahead/constants.ts +++ b/src/plugins/data/public/ui/typeahead/constants.ts @@ -33,4 +33,4 @@ export const SUGGESTIONS_LIST_REQUIRED_BOTTOM_SPACE = 250; * A distance in px to display suggestions list right under the query input without a gap * @public */ -export const SUGGESTIONS_LIST_REQUIRED_TOP_OFFSET = 2; +export const SUGGESTIONS_LIST_REQUIRED_TOP_OFFSET = 1; diff --git a/src/plugins/data/public/ui/typeahead/suggestions_component.tsx b/src/plugins/data/public/ui/typeahead/suggestions_component.tsx index dc7c55374f1d5..50ed9e9542d36 100644 --- a/src/plugins/data/public/ui/typeahead/suggestions_component.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestions_component.tsx @@ -154,6 +154,7 @@ export class SuggestionsComponent extends Component { const StyledSuggestionsListDiv = styled.div` ${(props: { queryBarRect: DOMRect; verticalListPosition: string }) => ` position: absolute; + z-index: 4001; left: ${props.queryBarRect.left}px; width: ${props.queryBarRect.width}px; ${props.verticalListPosition}`} diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index c3b06992dba0e..03baff4910309 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -198,8 +198,10 @@ export { OptionedValueProp, ParsedInterval, // search + ISearchOptions, IEsSearchRequest, IEsSearchResponse, + ES_SEARCH_STRATEGY, // tabify TabbedAggColumn, TabbedAggRow, @@ -208,11 +210,13 @@ export { export { ISearchStrategy, - ISearchOptions, ISearchSetup, ISearchStart, + toSnakeCase, getDefaultSearchParams, + getShardTimeout, getTotalLoaded, + shimHitsTotal, usageProvider, SearchUsage, } from './search'; diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts index c34c3a310814c..504ce728481f0 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts @@ -36,7 +36,14 @@ describe('ES search strategy', () => { }, }); const mockContext = { - core: { elasticsearch: { client: { asCurrentUser: { search: mockApiCaller } } } }, + core: { + uiSettings: { + client: { + get: () => {}, + }, + }, + elasticsearch: { client: { asCurrentUser: { search: mockApiCaller } } }, + }, }; const mockConfig$ = pluginInitializerContextConfigMock({}).legacy.globalConfig$; @@ -59,14 +66,13 @@ describe('ES search strategy', () => { expect(mockApiCaller).toBeCalled(); expect(mockApiCaller.mock.calls[0][0]).toEqual({ ...params, - timeout: '0ms', - ignoreUnavailable: true, - restTotalHitsAsInt: true, + ignore_unavailable: true, + track_total_hits: true, }); }); it('calls the API caller with overridden defaults', async () => { - const params = { index: 'logstash-*', ignoreUnavailable: false, timeout: '1000ms' }; + const params = { index: 'logstash-*', ignore_unavailable: false, timeout: '1000ms' }; const esSearch = await esSearchStrategyProvider(mockConfig$, mockLogger); await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params }); @@ -74,7 +80,7 @@ describe('ES search strategy', () => { expect(mockApiCaller).toBeCalled(); expect(mockApiCaller.mock.calls[0][0]).toEqual({ ...params, - restTotalHitsAsInt: true, + track_total_hits: true, }); }); diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.ts b/src/plugins/data/server/search/es_search/es_search_strategy.ts index eabbf3e3e2600..e2ed500689cfa 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.ts @@ -22,7 +22,8 @@ import { SearchResponse } from 'elasticsearch'; import { Observable } from 'rxjs'; import { ApiResponse } from '@elastic/elasticsearch'; import { SearchUsage } from '../collectors/usage'; -import { ISearchStrategy, getDefaultSearchParams, getTotalLoaded } from '..'; +import { toSnakeCase } from './to_snake_case'; +import { ISearchStrategy, getDefaultSearchParams, getTotalLoaded, getShardTimeout } from '..'; export const esSearchStrategyProvider = ( config$: Observable, @@ -33,7 +34,7 @@ export const esSearchStrategyProvider = ( search: async (context, request, options) => { logger.debug(`search ${request.params?.index}`); const config = await config$.pipe(first()).toPromise(); - const defaultParams = getDefaultSearchParams(config); + const uiSettingsClient = await context.core.uiSettings.client; // Only default index pattern type is supported here. // See data_enhanced for other type support. @@ -41,16 +42,21 @@ export const esSearchStrategyProvider = ( throw new Error(`Unsupported index pattern type ${request.indexType}`); } - const params = { + // ignoreThrottled is not supported in OSS + const { ignoreThrottled, ...defaultParams } = await getDefaultSearchParams(uiSettingsClient); + + const params = toSnakeCase({ ...defaultParams, + ...getShardTimeout(config), ...request.params, - }; + }); try { - const esResponse = (await context.core.elasticsearch.client.asCurrentUser.search( - params - )) as ApiResponse>; - const rawResponse = esResponse.body; + // Temporary workaround until https://github.com/elastic/elasticsearch-js/issues/1297 + const promise = context.core.elasticsearch.client.asCurrentUser.search(params); + if (options?.abortSignal) + options.abortSignal.addEventListener('abort', () => promise.abort()); + const { body: rawResponse } = (await promise) as ApiResponse>; if (usage) usage.trackSuccess(rawResponse.took); diff --git a/src/plugins/data/server/search/es_search/get_default_search_params.ts b/src/plugins/data/server/search/es_search/get_default_search_params.ts index b2341ccc0f3c8..13607fce51670 100644 --- a/src/plugins/data/server/search/es_search/get_default_search_params.ts +++ b/src/plugins/data/server/search/es_search/get_default_search_params.ts @@ -17,12 +17,28 @@ * under the License. */ -import { SharedGlobalConfig } from '../../../../../core/server'; +import { SharedGlobalConfig, IUiSettingsClient } from '../../../../../core/server'; +import { UI_SETTINGS } from '../../../common/constants'; -export function getDefaultSearchParams(config: SharedGlobalConfig) { +export function getShardTimeout(config: SharedGlobalConfig) { + const timeout = config.elasticsearch.shardTimeout.asMilliseconds(); + return timeout + ? { + timeout: `${timeout}ms`, + } + : {}; +} + +export async function getDefaultSearchParams(uiSettingsClient: IUiSettingsClient) { + const ignoreThrottled = !(await uiSettingsClient.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN)); + const maxConcurrentShardRequests = await uiSettingsClient.get( + UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS + ); return { - timeout: `${config.elasticsearch.shardTimeout.asMilliseconds()}ms`, + maxConcurrentShardRequests: + maxConcurrentShardRequests > 0 ? maxConcurrentShardRequests : undefined, + ignoreThrottled, ignoreUnavailable: true, // Don't fail if the index/indices don't exist - restTotalHitsAsInt: true, // Get the number of hits as an int rather than a range + trackTotalHits: true, }; } diff --git a/src/plugins/data/server/search/es_search/index.ts b/src/plugins/data/server/search/es_search/index.ts index 20006b70730d8..1bd17fc986168 100644 --- a/src/plugins/data/server/search/es_search/index.ts +++ b/src/plugins/data/server/search/es_search/index.ts @@ -17,7 +17,9 @@ * under the License. */ -export { ES_SEARCH_STRATEGY, IEsSearchRequest, IEsSearchResponse } from '../../../common/search'; export { esSearchStrategyProvider } from './es_search_strategy'; -export { getDefaultSearchParams } from './get_default_search_params'; +export * from './get_default_search_params'; export { getTotalLoaded } from './get_total_loaded'; +export * from './to_snake_case'; + +export { ES_SEARCH_STRATEGY, IEsSearchRequest, IEsSearchResponse } from '../../../common'; diff --git a/src/plugins/data/server/search/es_search/to_snake_case.ts b/src/plugins/data/server/search/es_search/to_snake_case.ts new file mode 100644 index 0000000000000..74f156274cbc6 --- /dev/null +++ b/src/plugins/data/server/search/es_search/to_snake_case.ts @@ -0,0 +1,24 @@ +/* + * 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 { mapKeys, snakeCase } from 'lodash'; + +export function toSnakeCase(obj: Record) { + return mapKeys(obj, (value, key) => snakeCase(key)); +} diff --git a/src/plugins/data/server/search/index.ts b/src/plugins/data/server/search/index.ts index 02c21c3254645..b671ed806510b 100644 --- a/src/plugins/data/server/search/index.ts +++ b/src/plugins/data/server/search/index.ts @@ -17,16 +17,12 @@ * under the License. */ -export { - ISearchStrategy, - ISearchOptions, - ISearchSetup, - ISearchStart, - SearchEnhancements, -} from './types'; +export { ISearchStrategy, ISearchSetup, ISearchStart, SearchEnhancements } from './types'; -export { getDefaultSearchParams, getTotalLoaded } from './es_search'; +export * from './es_search'; export { usageProvider, SearchUsage } from './collectors'; export * from './aggs'; + +export { shimHitsTotal } from './routes'; diff --git a/src/plugins/data/server/search/routes.test.ts b/src/plugins/data/server/search/routes.test.ts deleted file mode 100644 index d91aeee1fe818..0000000000000 --- a/src/plugins/data/server/search/routes.test.ts +++ /dev/null @@ -1,91 +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 { CoreSetup, RequestHandlerContext } from '../../../../../src/core/server'; -import { coreMock, httpServerMock } from '../../../../../src/core/server/mocks'; -import { registerSearchRoute } from './routes'; -import { DataPluginStart } from '../plugin'; -import { dataPluginMock } from '../mocks'; - -describe('Search service', () => { - let mockDataStart: MockedKeys; - let mockCoreSetup: MockedKeys>; - - beforeEach(() => { - mockDataStart = dataPluginMock.createStartContract(); - mockCoreSetup = coreMock.createSetup({ pluginStartContract: mockDataStart }); - }); - - it('handler calls context.search.search with the given request and strategy', async () => { - const response = { id: 'yay' }; - mockDataStart.search.search.mockResolvedValue(response); - const mockContext = {}; - const mockBody = { id: undefined, params: {} }; - const mockParams = { strategy: 'foo' }; - const mockRequest = httpServerMock.createKibanaRequest({ - body: mockBody, - params: mockParams, - }); - const mockResponse = httpServerMock.createResponseFactory(); - - registerSearchRoute(mockCoreSetup); - - const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; - const handler = mockRouter.post.mock.calls[0][1]; - await handler((mockContext as unknown) as RequestHandlerContext, mockRequest, mockResponse); - - expect(mockDataStart.search.search).toBeCalled(); - expect(mockDataStart.search.search.mock.calls[0][1]).toStrictEqual(mockBody); - expect(mockResponse.ok).toBeCalled(); - expect(mockResponse.ok.mock.calls[0][0]).toEqual({ - body: response, - }); - }); - - it('handler throws an error if the search throws an error', async () => { - mockDataStart.search.search.mockRejectedValue({ - message: 'oh no', - body: { - error: 'oops', - }, - }); - - const mockContext = {}; - const mockBody = { id: undefined, params: {} }; - const mockParams = { strategy: 'foo' }; - const mockRequest = httpServerMock.createKibanaRequest({ - body: mockBody, - params: mockParams, - }); - const mockResponse = httpServerMock.createResponseFactory(); - - registerSearchRoute(mockCoreSetup); - - const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; - const handler = mockRouter.post.mock.calls[0][1]; - await handler((mockContext as unknown) as RequestHandlerContext, mockRequest, mockResponse); - - expect(mockDataStart.search.search).toBeCalled(); - expect(mockDataStart.search.search.mock.calls[0][1]).toStrictEqual(mockBody); - expect(mockResponse.customError).toBeCalled(); - const error: any = mockResponse.customError.mock.calls[0][0]; - expect(error.body.message).toBe('oh no'); - expect(error.body.attributes.error).toBe('oops'); - }); -}); diff --git a/src/plugins/data/server/search/routes.ts b/src/plugins/data/server/search/routes.ts deleted file mode 100644 index 3d813f745305f..0000000000000 --- a/src/plugins/data/server/search/routes.ts +++ /dev/null @@ -1,108 +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 { schema } from '@kbn/config-schema'; -import { CoreSetup } from '../../../../core/server'; -import { getRequestAbortedSignal } from '../lib'; -import { DataPluginStart } from '../plugin'; - -export function registerSearchRoute(core: CoreSetup): void { - const router = core.http.createRouter(); - - router.post( - { - path: '/internal/search/{strategy}/{id?}', - validate: { - params: schema.object({ - strategy: schema.string(), - id: schema.maybe(schema.string()), - }), - - query: schema.object({}, { unknowns: 'allow' }), - - body: schema.object({}, { unknowns: 'allow' }), - }, - }, - async (context, request, res) => { - const searchRequest = request.body; - const { strategy, id } = request.params; - const signal = getRequestAbortedSignal(request.events.aborted$); - - const [, , selfStart] = await core.getStartServices(); - - try { - const response = await selfStart.search.search( - context, - { ...searchRequest, id }, - { - signal, - strategy, - } - ); - return res.ok({ body: response }); - } catch (err) { - return res.customError({ - statusCode: err.statusCode || 500, - body: { - message: err.message, - attributes: { - error: err.body?.error || err.message, - }, - }, - }); - } - } - ); - - router.delete( - { - path: '/internal/search/{strategy}/{id}', - validate: { - params: schema.object({ - strategy: schema.string(), - id: schema.string(), - }), - - query: schema.object({}, { unknowns: 'allow' }), - }, - }, - async (context, request, res) => { - const { strategy, id } = request.params; - - const [, , selfStart] = await core.getStartServices(); - const searchStrategy = selfStart.search.getSearchStrategy(strategy); - if (!searchStrategy.cancel) return res.ok(); - - try { - await searchStrategy.cancel(context, id); - return res.ok(); - } catch (err) { - return res.customError({ - statusCode: err.statusCode, - body: { - message: err.message, - attributes: { - error: err.body.error, - }, - }, - }); - } - } - ); -} diff --git a/src/plugins/data/server/search/routes/index.ts b/src/plugins/data/server/search/routes/index.ts new file mode 100644 index 0000000000000..a290f08f9b843 --- /dev/null +++ b/src/plugins/data/server/search/routes/index.ts @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './msearch'; +export * from './search'; +export * from './shim_hits_total'; diff --git a/src/plugins/data/server/search/routes/msearch.test.ts b/src/plugins/data/server/search/routes/msearch.test.ts new file mode 100644 index 0000000000000..3a7d67c31b8be --- /dev/null +++ b/src/plugins/data/server/search/routes/msearch.test.ts @@ -0,0 +1,150 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Observable } from 'rxjs'; + +import { + CoreSetup, + RequestHandlerContext, + SharedGlobalConfig, + StartServicesAccessor, +} from 'src/core/server'; +import { + coreMock, + httpServerMock, + pluginInitializerContextConfigMock, +} from '../../../../../../src/core/server/mocks'; +import { registerMsearchRoute, convertRequestBody } from './msearch'; +import { DataPluginStart } from '../../plugin'; +import { dataPluginMock } from '../../mocks'; + +describe('msearch route', () => { + let mockDataStart: MockedKeys; + let mockCoreSetup: MockedKeys>; + let getStartServices: jest.Mocked>; + let globalConfig$: Observable; + + beforeEach(() => { + mockDataStart = dataPluginMock.createStartContract(); + mockCoreSetup = coreMock.createSetup({ pluginStartContract: mockDataStart }); + getStartServices = mockCoreSetup.getStartServices; + globalConfig$ = pluginInitializerContextConfigMock({}).legacy.globalConfig$; + }); + + it('handler calls /_msearch with the given request', async () => { + const response = { id: 'yay', body: { responses: [{ hits: { total: 5 } }] } }; + const mockClient = { transport: { request: jest.fn().mockResolvedValue(response) } }; + const mockContext = { + core: { + elasticsearch: { client: { asCurrentUser: mockClient } }, + uiSettings: { client: { get: jest.fn() } }, + }, + }; + const mockBody = { searches: [{ header: {}, body: {} }] }; + const mockQuery = {}; + const mockRequest = httpServerMock.createKibanaRequest({ + body: mockBody, + query: mockQuery, + }); + const mockResponse = httpServerMock.createResponseFactory(); + + registerMsearchRoute(mockCoreSetup.http.createRouter(), { getStartServices, globalConfig$ }); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const handler = mockRouter.post.mock.calls[0][1]; + await handler((mockContext as unknown) as RequestHandlerContext, mockRequest, mockResponse); + + expect(mockClient.transport.request.mock.calls[0][0].method).toBe('GET'); + expect(mockClient.transport.request.mock.calls[0][0].path).toBe('/_msearch'); + expect(mockClient.transport.request.mock.calls[0][0].body).toEqual( + convertRequestBody(mockBody as any, {}) + ); + expect(mockResponse.ok).toBeCalled(); + expect(mockResponse.ok.mock.calls[0][0]).toEqual({ + body: response, + }); + }); + + it('handler throws an error if the search throws an error', async () => { + const response = { + message: 'oh no', + body: { + error: 'oops', + }, + }; + const mockClient = { + transport: { request: jest.fn().mockReturnValue(Promise.reject(response)) }, + }; + const mockContext = { + core: { + elasticsearch: { client: { asCurrentUser: mockClient } }, + uiSettings: { client: { get: jest.fn() } }, + }, + }; + const mockBody = { searches: [{ header: {}, body: {} }] }; + const mockQuery = {}; + const mockRequest = httpServerMock.createKibanaRequest({ + body: mockBody, + query: mockQuery, + }); + const mockResponse = httpServerMock.createResponseFactory(); + + registerMsearchRoute(mockCoreSetup.http.createRouter(), { getStartServices, globalConfig$ }); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const handler = mockRouter.post.mock.calls[0][1]; + await handler((mockContext as unknown) as RequestHandlerContext, mockRequest, mockResponse); + + expect(mockClient.transport.request).toBeCalled(); + expect(mockResponse.customError).toBeCalled(); + + const error: any = mockResponse.customError.mock.calls[0][0]; + expect(error.body.message).toBe('oh no'); + expect(error.body.attributes.error).toBe('oops'); + }); + + describe('convertRequestBody', () => { + it('combines header & body into proper msearch request', () => { + const request = { + searches: [{ header: { index: 'foo', preference: 0 }, body: { test: true } }], + }; + expect(convertRequestBody(request, { timeout: '30000ms' })).toMatchInlineSnapshot(` + "{\\"ignore_unavailable\\":true,\\"index\\":\\"foo\\",\\"preference\\":0} + {\\"timeout\\":\\"30000ms\\",\\"test\\":true} + " + `); + }); + + it('handles multiple searches', () => { + const request = { + searches: [ + { header: { index: 'foo', preference: 0 }, body: { test: true } }, + { header: { index: 'bar', preference: 1 }, body: { hello: 'world' } }, + ], + }; + expect(convertRequestBody(request, { timeout: '30000ms' })).toMatchInlineSnapshot(` + "{\\"ignore_unavailable\\":true,\\"index\\":\\"foo\\",\\"preference\\":0} + {\\"timeout\\":\\"30000ms\\",\\"test\\":true} + {\\"ignore_unavailable\\":true,\\"index\\":\\"bar\\",\\"preference\\":1} + {\\"timeout\\":\\"30000ms\\",\\"hello\\":\\"world\\"} + " + `); + }); + }); +}); diff --git a/src/plugins/data/server/search/routes/msearch.ts b/src/plugins/data/server/search/routes/msearch.ts new file mode 100644 index 0000000000000..e1ddb06e4fb6f --- /dev/null +++ b/src/plugins/data/server/search/routes/msearch.ts @@ -0,0 +1,138 @@ +/* + * 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 { first } from 'rxjs/operators'; +import { schema } from '@kbn/config-schema'; + +import { SearchResponse } from 'elasticsearch'; +import { IRouter } from 'src/core/server'; +import { SearchRouteDependencies } from '../search_service'; +import { shimHitsTotal } from './shim_hits_total'; +import { getShardTimeout, getDefaultSearchParams, toSnakeCase } from '..'; + +interface MsearchHeaders { + index: string; + preference?: number | string; +} + +interface MsearchRequest { + header: MsearchHeaders; + body: any; +} + +interface RequestBody { + searches: MsearchRequest[]; +} + +/** @internal */ +export function convertRequestBody( + requestBody: RequestBody, + { timeout }: { timeout?: string } +): string { + return requestBody.searches.reduce((req, curr) => { + const header = JSON.stringify({ + ignore_unavailable: true, + ...curr.header, + }); + const body = JSON.stringify({ + timeout, + ...curr.body, + }); + return `${req}${header}\n${body}\n`; + }, ''); +} + +/** + * The msearch route takes in an array of searches, each consisting of header + * and body json, and reformts them into a single request for the _msearch API. + * + * The reason for taking requests in a different format is so that we can + * inject values into each request without needing to manually parse each one. + * + * This route is internal and _should not be used_ in any new areas of code. + * It only exists as a means of removing remaining dependencies on the + * legacy ES client. + * + * @deprecated + */ +export function registerMsearchRoute(router: IRouter, deps: SearchRouteDependencies): void { + router.post( + { + path: '/internal/_msearch', + validate: { + body: schema.object({ + searches: schema.arrayOf( + schema.object({ + header: schema.object( + { + index: schema.string(), + preference: schema.maybe(schema.oneOf([schema.number(), schema.string()])), + }, + { unknowns: 'allow' } + ), + body: schema.object({}, { unknowns: 'allow' }), + }) + ), + }), + }, + }, + async (context, request, res) => { + const client = context.core.elasticsearch.client.asCurrentUser; + + // get shardTimeout + const config = await deps.globalConfig$.pipe(first()).toPromise(); + const timeout = getShardTimeout(config); + + const body = convertRequestBody(request.body, timeout); + + // trackTotalHits is not supported by msearch + const { trackTotalHits, ...defaultParams } = await getDefaultSearchParams( + context.core.uiSettings.client + ); + + try { + const response = await client.transport.request({ + method: 'GET', + path: '/_msearch', + body, + querystring: toSnakeCase(defaultParams), + }); + + return res.ok({ + body: { + ...response, + body: { + responses: response.body.responses?.map((r: SearchResponse) => shimHitsTotal(r)), + }, + }, + }); + } catch (err) { + return res.customError({ + statusCode: err.statusCode || 500, + body: { + message: err.message, + attributes: { + error: err.body?.error || err.message, + }, + }, + }); + } + } + ); +} diff --git a/src/plugins/data/server/search/routes/search.test.ts b/src/plugins/data/server/search/routes/search.test.ts new file mode 100644 index 0000000000000..d4404c318ab47 --- /dev/null +++ b/src/plugins/data/server/search/routes/search.test.ts @@ -0,0 +1,123 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Observable } from 'rxjs'; + +import { + CoreSetup, + RequestHandlerContext, + SharedGlobalConfig, + StartServicesAccessor, +} from 'src/core/server'; +import { + coreMock, + httpServerMock, + pluginInitializerContextConfigMock, +} from '../../../../../../src/core/server/mocks'; +import { registerSearchRoute } from './search'; +import { DataPluginStart } from '../../plugin'; +import { dataPluginMock } from '../../mocks'; + +describe('Search service', () => { + let mockDataStart: MockedKeys; + let mockCoreSetup: MockedKeys>; + let getStartServices: jest.Mocked>; + let globalConfig$: Observable; + + beforeEach(() => { + mockDataStart = dataPluginMock.createStartContract(); + mockCoreSetup = coreMock.createSetup({ pluginStartContract: mockDataStart }); + getStartServices = mockCoreSetup.getStartServices; + globalConfig$ = pluginInitializerContextConfigMock({}).legacy.globalConfig$; + }); + + it('handler calls context.search.search with the given request and strategy', async () => { + const response = { + id: 'yay', + rawResponse: { + took: 100, + timed_out: true, + _shards: { + total: 0, + successful: 0, + failed: 0, + skipped: 0, + }, + hits: { + total: 0, + max_score: 0, + hits: [], + }, + }, + }; + mockDataStart.search.search.mockResolvedValue(response); + const mockContext = {}; + const mockBody = { id: undefined, params: {} }; + const mockParams = { strategy: 'foo' }; + const mockRequest = httpServerMock.createKibanaRequest({ + body: mockBody, + params: mockParams, + }); + const mockResponse = httpServerMock.createResponseFactory(); + + registerSearchRoute(mockCoreSetup.http.createRouter(), { getStartServices, globalConfig$ }); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const handler = mockRouter.post.mock.calls[0][1]; + await handler((mockContext as unknown) as RequestHandlerContext, mockRequest, mockResponse); + + expect(mockDataStart.search.search).toBeCalled(); + expect(mockDataStart.search.search.mock.calls[0][1]).toStrictEqual(mockBody); + expect(mockResponse.ok).toBeCalled(); + expect(mockResponse.ok.mock.calls[0][0]).toEqual({ + body: response, + }); + }); + + it('handler throws an error if the search throws an error', async () => { + mockDataStart.search.search.mockRejectedValue({ + message: 'oh no', + body: { + error: 'oops', + }, + }); + + const mockContext = {}; + const mockBody = { id: undefined, params: {} }; + const mockParams = { strategy: 'foo' }; + const mockRequest = httpServerMock.createKibanaRequest({ + body: mockBody, + params: mockParams, + }); + const mockResponse = httpServerMock.createResponseFactory(); + + registerSearchRoute(mockCoreSetup.http.createRouter(), { getStartServices, globalConfig$ }); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const handler = mockRouter.post.mock.calls[0][1]; + await handler((mockContext as unknown) as RequestHandlerContext, mockRequest, mockResponse); + + expect(mockDataStart.search.search).toBeCalled(); + expect(mockDataStart.search.search.mock.calls[0][1]).toStrictEqual(mockBody); + expect(mockResponse.customError).toBeCalled(); + const error: any = mockResponse.customError.mock.calls[0][0]; + expect(error.body.message).toBe('oh no'); + expect(error.body.attributes.error).toBe('oops'); + }); +}); diff --git a/src/plugins/data/server/search/routes/search.ts b/src/plugins/data/server/search/routes/search.ts new file mode 100644 index 0000000000000..b5d5ec283767d --- /dev/null +++ b/src/plugins/data/server/search/routes/search.ts @@ -0,0 +1,121 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { IRouter } from 'src/core/server'; +import { getRequestAbortedSignal } from '../../lib'; +import { SearchRouteDependencies } from '../search_service'; +import { shimHitsTotal } from './shim_hits_total'; +import { isEsResponse } from '../../../common'; + +export function registerSearchRoute( + router: IRouter, + { getStartServices }: SearchRouteDependencies +): void { + router.post( + { + path: '/internal/search/{strategy}/{id?}', + validate: { + params: schema.object({ + strategy: schema.string(), + id: schema.maybe(schema.string()), + }), + + query: schema.object({}, { unknowns: 'allow' }), + + body: schema.object({}, { unknowns: 'allow' }), + }, + }, + async (context, request, res) => { + const searchRequest = request.body; + const { strategy, id } = request.params; + const abortSignal = getRequestAbortedSignal(request.events.aborted$); + + const [, , selfStart] = await getStartServices(); + + try { + const response = await selfStart.search.search( + context, + { ...searchRequest, id }, + { + abortSignal, + strategy, + } + ); + + return res.ok({ + body: { + ...response, + ...(isEsResponse(response) + ? { + rawResponse: shimHitsTotal(response.rawResponse), + } + : {}), + }, + }); + } catch (err) { + return res.customError({ + statusCode: err.statusCode || 500, + body: { + message: err.message, + attributes: { + error: err.body?.error || err.message, + }, + }, + }); + } + } + ); + + router.delete( + { + path: '/internal/search/{strategy}/{id}', + validate: { + params: schema.object({ + strategy: schema.string(), + id: schema.string(), + }), + + query: schema.object({}, { unknowns: 'allow' }), + }, + }, + async (context, request, res) => { + const { strategy, id } = request.params; + + const [, , selfStart] = await getStartServices(); + const searchStrategy = selfStart.search.getSearchStrategy(strategy); + if (!searchStrategy.cancel) return res.ok(); + + try { + await searchStrategy.cancel(context, id); + return res.ok(); + } catch (err) { + return res.customError({ + statusCode: err.statusCode, + body: { + message: err.message, + attributes: { + error: err.body.error, + }, + }, + }); + } + } + ); +} diff --git a/src/plugins/data/server/search/routes/shim_hits_total.test.ts b/src/plugins/data/server/search/routes/shim_hits_total.test.ts new file mode 100644 index 0000000000000..0f24735386121 --- /dev/null +++ b/src/plugins/data/server/search/routes/shim_hits_total.test.ts @@ -0,0 +1,69 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { shimHitsTotal } from './shim_hits_total'; + +describe('shimHitsTotal', () => { + test('returns the total if it is already numeric', () => { + const result = shimHitsTotal({ + hits: { + total: 5, + }, + } as any); + expect(result).toEqual({ + hits: { + total: 5, + }, + }); + }); + + test('returns the total if it is inside `value`', () => { + const result = shimHitsTotal({ + hits: { + total: { + value: 5, + }, + }, + } as any); + expect(result).toEqual({ + hits: { + total: 5, + }, + }); + }); + + test('returns other properties from the response', () => { + const result = shimHitsTotal({ + _shards: {}, + hits: { + hits: [], + total: { + value: 5, + }, + }, + } as any); + expect(result).toEqual({ + _shards: {}, + hits: { + hits: [], + total: 5, + }, + }); + }); +}); diff --git a/src/plugins/data/server/search/routes/shim_hits_total.ts b/src/plugins/data/server/search/routes/shim_hits_total.ts new file mode 100644 index 0000000000000..5f95b21358978 --- /dev/null +++ b/src/plugins/data/server/search/routes/shim_hits_total.ts @@ -0,0 +1,33 @@ +/* + * 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 { SearchResponse } from 'elasticsearch'; + +/** + * Temporary workaround until https://github.com/elastic/kibana/issues/26356 is addressed. + * Since we are setting `track_total_hits` in the request, `hits.total` will be an object + * containing the `value`. + * + * @internal + */ +export function shimHitsTotal(response: SearchResponse) { + const total = (response.hits?.total as any)?.value ?? response.hits?.total; + const hits = { ...response.hits, total }; + return { ...response, hits }; +} diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index edc94961c79d8..e19d3dd8a5451 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -17,6 +17,7 @@ * under the License. */ +import { Observable } from 'rxjs'; import { CoreSetup, CoreStart, @@ -24,20 +25,22 @@ import { Plugin, PluginInitializerContext, RequestHandlerContext, -} from '../../../../core/server'; + SharedGlobalConfig, + StartServicesAccessor, +} from 'src/core/server'; import { ISearchSetup, ISearchStart, ISearchStrategy, SearchEnhancements } from './types'; import { AggsService, AggsSetupDependencies } from './aggs'; import { FieldFormatsStart } from '../field_formats'; -import { registerSearchRoute } from './routes'; +import { registerMsearchRoute, registerSearchRoute } from './routes'; import { ES_SEARCH_STRATEGY, esSearchStrategyProvider } from './es_search'; import { DataPluginStart } from '../plugin'; import { UsageCollectionSetup } from '../../../usage_collection/server'; import { registerUsageCollector } from './collectors/register'; import { usageProvider } from './collectors/usage'; import { searchTelemetry } from '../saved_objects'; -import { IEsSearchRequest, IEsSearchResponse } from '../../common'; +import { IEsSearchRequest, IEsSearchResponse, ISearchOptions } from '../../common'; type StrategyMap< SearchStrategyRequest extends IEsSearchRequest = IEsSearchRequest, @@ -55,6 +58,12 @@ export interface SearchServiceStartDependencies { fieldFormats: FieldFormatsStart; } +/** @internal */ +export interface SearchRouteDependencies { + getStartServices: StartServicesAccessor<{}, DataPluginStart>; + globalConfig$: Observable; +} + export class SearchService implements Plugin { private readonly aggsService = new AggsService(); private defaultSearchStrategyName: string = ES_SEARCH_STRATEGY; @@ -66,11 +75,19 @@ export class SearchService implements Plugin { ) {} public setup( - core: CoreSetup, + core: CoreSetup<{}, DataPluginStart>, { registerFunction, usageCollection }: SearchServiceSetupDependencies ): ISearchSetup { const usage = usageCollection ? usageProvider(core) : undefined; + const router = core.http.createRouter(); + const routeDependencies = { + getStartServices: core.getStartServices, + globalConfig$: this.initializerContext.config.legacy.globalConfig$, + }; + registerSearchRoute(router, routeDependencies); + registerMsearchRoute(router, routeDependencies); + this.registerSearchStrategy( ES_SEARCH_STRATEGY, esSearchStrategyProvider( @@ -85,8 +102,6 @@ export class SearchService implements Plugin { registerUsageCollector(usageCollection, this.initializerContext); } - registerSearchRoute(core); - return { __enhance: (enhancements: SearchEnhancements) => { if (this.searchStrategies.hasOwnProperty(enhancements.defaultStrategy)) { @@ -102,11 +117,13 @@ export class SearchService implements Plugin { private search( context: RequestHandlerContext, searchRequest: IEsSearchRequest, - options: Record + options: ISearchOptions ) { - return this.getSearchStrategy( - options.strategy || this.defaultSearchStrategyName - ).search(context, searchRequest, { signal: options.signal }); + return this.getSearchStrategy(options.strategy || this.defaultSearchStrategyName).search( + context, + searchRequest, + options + ); } public start( diff --git a/src/plugins/data/server/search/types.ts b/src/plugins/data/server/search/types.ts index 5ce1bb3e6b9f8..aefdac2ab639f 100644 --- a/src/plugins/data/server/search/types.ts +++ b/src/plugins/data/server/search/types.ts @@ -18,23 +18,15 @@ */ import { RequestHandlerContext } from '../../../../core/server'; -import { IKibanaSearchResponse, IKibanaSearchRequest } from '../../common/search'; +import { ISearchOptions } from '../../common/search'; import { AggsSetup, AggsStart } from './aggs'; -import { SearchUsage } from './collectors/usage'; +import { SearchUsage } from './collectors'; import { IEsSearchRequest, IEsSearchResponse } from './es_search'; export interface SearchEnhancements { defaultStrategy: string; } -export interface ISearchOptions { - /** - * An `AbortSignal` that allows the caller of `search` to abort a search request. - */ - signal?: AbortSignal; - strategy?: string; -} - export interface ISearchSetup { aggs: AggsSetup; /** @@ -74,9 +66,9 @@ export interface ISearchStart< ) => ISearchStrategy; search: ( context: RequestHandlerContext, - request: IKibanaSearchRequest, + request: IEsSearchRequest, options: ISearchOptions - ) => Promise; + ) => Promise; } /** diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 9f114f2132009..cd0369a5c4551 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -48,7 +48,6 @@ import { ExistsParams } from 'elasticsearch'; import { ExplainParams } from 'elasticsearch'; import { ExpressionAstFunction } from 'src/plugins/expressions/common'; import { ExpressionsServerSetup } from 'src/plugins/expressions/server'; -import { FetchOptions } from 'src/plugins/data/public'; import { FieldStatsParams } from 'elasticsearch'; import { GenericParams } from 'elasticsearch'; import { GetParams } from 'elasticsearch'; @@ -99,6 +98,7 @@ import { IngestDeletePipelineParams } from 'elasticsearch'; import { IngestGetPipelineParams } from 'elasticsearch'; import { IngestPutPipelineParams } from 'elasticsearch'; import { IngestSimulateParams } from 'elasticsearch'; +import { ISearchOptions as ISearchOptions_2 } from 'src/plugins/data/public'; import { ISearchSource } from 'src/plugins/data/public'; import { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { KibanaConfigType as KibanaConfigType_2 } from 'src/core/server/kibana_config'; @@ -323,6 +323,11 @@ export enum ES_FIELD_TYPES { _TYPE = "_type" } +// Warning: (ae-missing-release-tag) "ES_SEARCH_STRATEGY" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const ES_SEARCH_STRATEGY = "es"; + // Warning: (ae-forgotten-export) The symbol "ExpressionFunctionDefinition" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "Input" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "Arguments" needs to be exported by the entry point index.d.ts @@ -441,14 +446,25 @@ export interface Filter { query?: any; } -// Warning: (ae-forgotten-export) The symbol "SharedGlobalConfig" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "IUiSettingsClient" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "getDefaultSearchParams" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export function getDefaultSearchParams(config: SharedGlobalConfig): { - timeout: string; +export function getDefaultSearchParams(uiSettingsClient: IUiSettingsClient): Promise<{ + maxConcurrentShardRequests: number | undefined; + ignoreThrottled: boolean; ignoreUnavailable: boolean; - restTotalHitsAsInt: boolean; + trackTotalHits: boolean; +}>; + +// Warning: (ae-forgotten-export) The symbol "SharedGlobalConfig" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "getShardTimeout" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function getShardTimeout(config: SharedGlobalConfig): { + timeout: string; +} | { + timeout?: undefined; }; // Warning: (ae-missing-release-tag) "getTime" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -565,7 +581,9 @@ export interface IFieldType { // Warning: (ae-forgotten-export) The symbol "FieldSpec" needs to be exported by the entry point index.d.ts // // (undocumented) - toSpec?: () => FieldSpec; + toSpec?: (options?: { + getFormatterForField?: IndexPattern['getFormatterForField']; + }) => FieldSpec; // (undocumented) type: string; // (undocumented) @@ -676,8 +694,7 @@ export class IndexPatternsFetcher { // // @public (undocumented) export interface ISearchOptions { - signal?: AbortSignal; - // (undocumented) + abortSignal?: AbortSignal; strategy?: string; } @@ -709,7 +726,7 @@ export interface ISearchStart Promise; + search: (context: RequestHandlerContext, request: IEsSearchRequest, options: ISearchOptions) => Promise; } // Warning: (ae-missing-release-tag) "ISearchStrategy" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -867,7 +884,7 @@ export class Plugin implements Plugin_2>; + search: ISearchStart>; fieldFormats: { fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; @@ -983,6 +1000,33 @@ export interface SearchUsage { trackSuccess(duration: number): Promise; } +// @internal +export function shimHitsTotal(response: SearchResponse): { + hits: { + total: any; + max_score: number; + hits: { + _index: string; + _type: string; + _id: string; + _score: number; + _source: any; + _version?: number | undefined; + _explanation?: import("elasticsearch").Explanation | undefined; + fields?: any; + highlight?: any; + inner_hits?: any; + matched_queries?: string[] | undefined; + sort?: string[] | undefined; + }[]; + }; + took: number; + timed_out: boolean; + _scroll_id?: string | undefined; + _shards: import("elasticsearch").ShardsResponse; + aggregations?: any; +}; + // Warning: (ae-missing-release-tag) "shouldReadFieldFromDocValues" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1021,6 +1065,11 @@ export interface TimeRange { to: string; } +// Warning: (ae-missing-release-tag) "toSnakeCase" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function toSnakeCase(obj: Record): import("lodash").Dictionary; + // Warning: (ae-missing-release-tag) "UI_SETTINGS" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1037,6 +1086,7 @@ export const UI_SETTINGS: { readonly COURIER_MAX_CONCURRENT_SHARD_REQUESTS: "courier:maxConcurrentShardRequests"; readonly COURIER_BATCH_SEARCHES: "courier:batchSearches"; readonly SEARCH_INCLUDE_FROZEN: "search:includeFrozen"; + readonly SEARCH_TIMEOUT: "search:timeout"; readonly HISTOGRAM_BAR_TARGET: "histogram:barTarget"; readonly HISTOGRAM_MAX_BARS: "histogram:maxBars"; readonly HISTORY_LIMIT: "history:limit"; @@ -1063,6 +1113,7 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // Warnings were encountered during analysis: // +// src/plugins/data/common/index_patterns/fields/types.ts:41:25 - (ae-forgotten-export) The symbol "IndexPattern" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:40:23 - (ae-forgotten-export) The symbol "buildCustomFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:40:23 - (ae-forgotten-export) The symbol "buildFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:71:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts @@ -1084,19 +1135,19 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:127:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:127:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:221:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:221:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:221:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:221:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:223:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:224:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:233:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:234:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:235:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:239:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:240:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:244:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:247:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:225:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:225:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:225:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:225:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:227:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:228:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:237:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:238:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:239:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:243:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:244:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:248:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:251:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/server/plugin.ts:88:66 - (ae-forgotten-export) The symbol "DataEnhancements" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/dev_tools/kibana.json b/src/plugins/dev_tools/kibana.json index d83cabd0f0817..f1c6c9ecf87e6 100644 --- a/src/plugins/dev_tools/kibana.json +++ b/src/plugins/dev_tools/kibana.json @@ -3,5 +3,5 @@ "version": "kibana", "server": false, "ui": true, - "requiredPlugins": ["kibanaLegacy"] + "requiredPlugins": ["urlForwarding"] } diff --git a/src/plugins/dev_tools/public/index.scss b/src/plugins/dev_tools/public/index.scss index c9d8dc7470656..4bec602ea42db 100644 --- a/src/plugins/dev_tools/public/index.scss +++ b/src/plugins/dev_tools/public/index.scss @@ -16,10 +16,6 @@ } } -.devApp { - height: 100%; -} - .devAppWrapper { display: flex; flex-direction: column; diff --git a/src/plugins/dev_tools/public/plugin.ts b/src/plugins/dev_tools/public/plugin.ts index 45fa3634bc87e..8c4743c93fab3 100644 --- a/src/plugins/dev_tools/public/plugin.ts +++ b/src/plugins/dev_tools/public/plugin.ts @@ -24,7 +24,7 @@ import { i18n } from '@kbn/i18n'; import { sortBy } from 'lodash'; import { AppNavLinkStatus, DEFAULT_APP_CATEGORIES } from '../../../core/public'; -import { KibanaLegacySetup } from '../../kibana_legacy/public'; +import { UrlForwardingSetup } from '../../url_forwarding/public'; import { CreateDevToolArgs, DevToolApp, createDevToolApp } from './dev_tool'; import './index.scss'; @@ -51,7 +51,7 @@ export class DevToolsPlugin implements Plugin { return sortBy([...this.devTools.values()], 'order'); } - public setup(coreSetup: CoreSetup, { kibanaLegacy }: { kibanaLegacy: KibanaLegacySetup }) { + public setup(coreSetup: CoreSetup, { urlForwarding }: { urlForwarding: UrlForwardingSetup }) { const { application: applicationSetup, getStartServices } = coreSetup; applicationSetup.register({ @@ -60,7 +60,7 @@ export class DevToolsPlugin implements Plugin { defaultMessage: 'Dev Tools', }), updater$: this.appStateUpdater, - euiIconType: 'devToolsApp', + euiIconType: 'logoElastic', order: 9010, category: DEFAULT_APP_CATEGORIES.management, mount: async (params: AppMountParameters) => { @@ -75,7 +75,7 @@ export class DevToolsPlugin implements Plugin { }, }); - kibanaLegacy.forwardApp('dev_tools', 'dev_tools'); + urlForwarding.forwardApp('dev_tools', 'dev_tools'); return { register: (devToolArgs: CreateDevToolArgs) => { diff --git a/src/plugins/discover/kibana.json b/src/plugins/discover/kibana.json index 041f362bf0623..1a23f6deb5fa5 100644 --- a/src/plugins/discover/kibana.json +++ b/src/plugins/discover/kibana.json @@ -9,6 +9,7 @@ "embeddable", "inspector", "kibanaLegacy", + "urlForwarding", "navigation", "uiActions", "visualizations" diff --git a/src/plugins/discover/public/application/angular/discover.html b/src/plugins/discover/public/application/angular/discover.html index d3d4f524873d8..e0e452aaa41c5 100644 --- a/src/plugins/discover/public/application/angular/discover.html +++ b/src/plugins/discover/public/application/angular/discover.html @@ -5,6 +5,7 @@

{{screenTitle}}

{{screenTitle}}

on-remove-field="removeColumn" selected-index-pattern="searchSource.getField('index')" set-index-pattern="setIndexPattern" - state="state" > @@ -78,11 +78,11 @@

{{screenTitle}}

class="dscTimechart" ng-if="opts.timefield" > - an array of sort objects */ -export function getSort(sort: SortPair[], indexPattern: IndexPattern): SortPairObj[] { +export function getSort(sort: SortPair[] | SortPair, indexPattern: IndexPattern): SortPairObj[] { if (Array.isArray(sort)) { + if (isLegacySort(sort)) { + // To stay compatible with legacy sort, which just supported a single sort field + return [{ [sort[0]]: sort[1] }]; + } return sort .map((sortPair: SortPair) => createSortObject(sortPair, indexPattern)) .filter((sortPairObj) => typeof sortPairObj === 'object') as SortPairObj[]; diff --git a/src/plugins/discover/public/application/angular/redirect.ts b/src/plugins/discover/public/application/angular/redirect.ts index bfa2f07f852e9..d3fb47f329d4b 100644 --- a/src/plugins/discover/public/application/angular/redirect.ts +++ b/src/plugins/discover/public/application/angular/redirect.ts @@ -24,10 +24,10 @@ getAngularModule().config(($routeProvider: any) => { const path = window.location.hash.substr(1); getUrlTracker().restorePreviousUrl(); $rootScope.$applyAsync(() => { - const { kibanaLegacy } = getServices(); - const { navigated } = kibanaLegacy.navigateToLegacyKibanaUrl(path); + const { urlForwarding } = getServices(); + const { navigated } = urlForwarding.navigateToLegacyKibanaUrl(path); if (!navigated) { - kibanaLegacy.navigateToDefaultApp(); + urlForwarding.navigateToDefaultApp(); } }); // prevent angular from completing the navigation diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx index 6d1238e02c7fb..b03b37da40908 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx @@ -62,7 +62,6 @@ function getComponent(selected = false, showDetails = false, useShortDots = fals ); const field = new IndexPatternField( - indexPattern, { name: 'bytes', type: 'number', @@ -73,8 +72,7 @@ function getComponent(selected = false, showDetails = false, useShortDots = fals aggregatable: true, readFromDocValues: true, }, - 'bytes', - () => {} + 'bytes' ); const props = { diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field.tsx index 639dbfe09277c..bb330cba68e2e 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field.tsx @@ -133,6 +133,9 @@ export function DiscoverField({ iconType="plusInCircleFilled" className="dscSidebarItem__action" onClick={(ev: React.MouseEvent) => { + if (ev.type === 'click') { + ev.currentTarget.focus(); + } ev.preventDefault(); ev.stopPropagation(); toggleDisplay(field); @@ -155,6 +158,9 @@ export function DiscoverField({ iconType="cross" className="dscSidebarItem__action" onClick={(ev: React.MouseEvent) => { + if (ev.type === 'click') { + ev.currentTarget.focus(); + } ev.preventDefault(); ev.stopPropagation(); toggleDisplay(field); diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx index 1f27766a1756d..850624888b24a 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx @@ -30,7 +30,6 @@ import { SavedObject } from '../../../../../../core/types'; import { FIELDS_LIMIT_SETTING } from '../../../../common'; import { groupFields } from './lib/group_fields'; import { IndexPatternField, IndexPattern, UI_SETTINGS } from '../../../../../data/public'; -import { AppState } from '../../angular/discover_state'; import { getDetails } from './lib/get_details'; import { getDefaultFieldFilter, setFieldFilterProp } from './lib/field_filter'; import { getIndexPatternFieldList } from './lib/get_index_pattern_field_list'; @@ -74,10 +73,6 @@ export interface DiscoverSidebarProps { * Callback function to select another index pattern */ setIndexPattern: (id: string) => void; - /** - * Current app state, used for generating a link to visualize - */ - state: AppState; } export function DiscoverSidebar({ @@ -90,7 +85,6 @@ export function DiscoverSidebar({ onRemoveField, selectedIndexPattern, setIndexPattern, - state, }: DiscoverSidebarProps) { const [showFields, setShowFields] = useState(false); const [fields, setFields] = useState(null); @@ -111,8 +105,8 @@ export function DiscoverSidebar({ ); const getDetailsByField = useCallback( - (ipField: IndexPatternField) => getDetails(ipField, hits, columns), - [hits, columns] + (ipField: IndexPatternField) => getDetails(ipField, hits, columns, selectedIndexPattern), + [hits, columns, selectedIndexPattern] ); const popularLimit = services.uiSettings.get(FIELDS_LIMIT_SETTING); @@ -185,10 +179,10 @@ export function DiscoverSidebar({ aria-labelledby="selected_fields" data-test-subj={`fieldList-selected`} > - {selectedFields.map((field: IndexPatternField, idx: number) => { + {selectedFields.map((field: IndexPatternField) => { return (
  • @@ -260,10 +254,10 @@ export function DiscoverSidebar({ aria-labelledby="available_fields available_fields_popular" data-test-subj={`fieldList-popular`} > - {popularFields.map((field: IndexPatternField, idx: number) => { + {popularFields.map((field: IndexPatternField) => { return (
  • @@ -290,9 +284,13 @@ export function DiscoverSidebar({ aria-labelledby="available_fields" data-test-subj={`fieldList-unpopular`} > - {unpopularFields.map((field: IndexPatternField, idx: number) => { + {unpopularFields.map((field: IndexPatternField) => { return ( -
  • +
  • >, - columns: string[] + columns: string[], + indexPattern: IndexPattern ) { const details = { ...fieldCalculator.getFieldValueCounts({ hits, field, + indexPattern, count: 5, grouped: false, }), @@ -37,7 +39,7 @@ export function getDetails( }; if (details.buckets) { for (const bucket of details.buckets) { - bucket.display = field.format.convert(bucket.value); + bucket.display = indexPattern.getFormatterForField(field).convert(bucket.value); } } return details; diff --git a/src/plugins/discover/public/application/components/sidebar/lib/get_index_pattern_field_list.ts b/src/plugins/discover/public/application/components/sidebar/lib/get_index_pattern_field_list.ts index 00e00aa8e2991..c96a8f5ce17b9 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/get_index_pattern_field_list.ts +++ b/src/plugins/discover/public/application/components/sidebar/lib/get_index_pattern_field_list.ts @@ -31,6 +31,7 @@ export function getIndexPatternFieldList( difference(fieldNamesInDocs, fieldNamesInIndexPattern).forEach((unknownFieldName) => { unknownTypes.push({ + displayName: String(unknownFieldName), name: String(unknownFieldName), type: 'unknown', } as IndexPatternField); diff --git a/src/plugins/discover/public/application/helpers/migrate_legacy_query.ts b/src/plugins/discover/public/application/helpers/migrate_legacy_query.ts new file mode 100644 index 0000000000000..8d9b50d5a66b2 --- /dev/null +++ b/src/plugins/discover/public/application/helpers/migrate_legacy_query.ts @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { has } from 'lodash'; +import { Query } from 'src/plugins/data/public'; + +/** + * Creates a standardized query object from old queries that were either strings or pure ES query DSL + * + * @param query - a legacy query, what used to be stored in SearchSource's query property + * @return Object + */ + +export function migrateLegacyQuery(query: Query | { [key: string]: any } | string): Query { + // Lucene was the only option before, so language-less queries are all lucene + if (!has(query, 'language')) { + return { query, language: 'lucene' }; + } + + return query as Query; +} diff --git a/src/plugins/discover/public/build_services.ts b/src/plugins/discover/public/build_services.ts index 75c83e30d80ad..12562d8571a25 100644 --- a/src/plugins/discover/public/build_services.ts +++ b/src/plugins/discover/public/build_services.ts @@ -43,6 +43,7 @@ import { DiscoverStartPlugins } from './plugin'; import { createSavedSearchesLoader, SavedSearch } from './saved_searches'; import { getHistory } from './kibana_services'; import { KibanaLegacyStart } from '../../kibana_legacy/public'; +import { UrlForwardingStart } from '../../url_forwarding/public'; export interface DiscoverServices { addBasePath: (path: string) => string; @@ -59,6 +60,7 @@ export interface DiscoverServices { metadata: { branch: string }; share?: SharePluginStart; kibanaLegacy: KibanaLegacyStart; + urlForwarding: UrlForwardingStart; timefilter: TimefilterContract; toastNotifications: ToastsStart; getSavedSearchById: (id: string) => Promise; @@ -100,6 +102,7 @@ export async function buildServices( }, share: plugins.share, kibanaLegacy: plugins.kibanaLegacy, + urlForwarding: plugins.urlForwarding, timefilter: plugins.data.query.timefilter.timefilter, toastNotifications: core.notifications.toasts, uiSettings: core.uiSettings, diff --git a/src/plugins/discover/public/kibana_services.ts b/src/plugins/discover/public/kibana_services.ts index bc25fa71dcf41..064030b78d10e 100644 --- a/src/plugins/discover/public/kibana_services.ts +++ b/src/plugins/discover/public/kibana_services.ts @@ -19,7 +19,7 @@ import _ from 'lodash'; import { createHashHistory } from 'history'; -import { ScopedHistory } from 'kibana/public'; +import { ScopedHistory, AppMountParameters } from 'kibana/public'; import { UiActionsStart } from 'src/plugins/ui_actions/public'; import { DiscoverServices } from './build_services'; import { createGetterSetter } from '../../kibana_utils/public'; @@ -58,6 +58,10 @@ export function setServices(newServices: any) { export const setUiActions = (pluginUiActions: UiActionsStart) => (uiActions = pluginUiActions); export const getUiActions = () => uiActions; +export const [getHeaderActionMenuMounter, setHeaderActionMenuMounter] = createGetterSetter< + AppMountParameters['setHeaderActionMenu'] +>('headerActionMenuMounter'); + export const [getUrlTracker, setUrlTracker] = createGetterSetter<{ setTrackedUrl: (url: string) => void; restorePreviousUrl: () => void; diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts index 015f4267646c1..dd9b57b568e42 100644 --- a/src/plugins/discover/public/plugin.ts +++ b/src/plugins/discover/public/plugin.ts @@ -37,6 +37,7 @@ import { NavigationPublicPluginStart as NavigationStart } from 'src/plugins/navi import { SharePluginStart, SharePluginSetup, UrlGeneratorContract } from 'src/plugins/share/public'; import { VisualizationsStart, VisualizationsSetup } from 'src/plugins/visualizations/public'; import { KibanaLegacySetup, KibanaLegacyStart } from 'src/plugins/kibana_legacy/public'; +import { UrlForwardingSetup, UrlForwardingStart } from 'src/plugins/url_forwarding/public'; import { HomePublicPluginSetup } from 'src/plugins/home/public'; import { Start as InspectorPublicPluginStart } from 'src/plugins/inspector/public'; import { DataPublicPluginStart, DataPublicPluginSetup, esFilters } from '../../data/public'; @@ -53,6 +54,7 @@ import { setUrlTracker, setAngularModule, setServices, + setHeaderActionMenuMounter, setUiActions, setScopedHistory, getScopedHistory, @@ -119,6 +121,7 @@ export interface DiscoverSetupPlugins { uiActions: UiActionsSetup; embeddable: EmbeddableSetup; kibanaLegacy: KibanaLegacySetup; + urlForwarding: UrlForwardingSetup; home?: HomePublicPluginSetup; visualizations: VisualizationsSetup; data: DataPublicPluginSetup; @@ -135,6 +138,7 @@ export interface DiscoverStartPlugins { data: DataPublicPluginStart; share?: SharePluginStart; kibanaLegacy: KibanaLegacyStart; + urlForwarding: UrlForwardingStart; inspector: InspectorPublicPluginStart; visualizations: VisualizationsStart; } @@ -237,7 +241,7 @@ export class DiscoverPlugin title: 'Discover', updater$: this.appStateUpdater.asObservable(), order: 1000, - euiIconType: 'discoverApp', + euiIconType: 'logoKibana', defaultPath: '#/', category: DEFAULT_APP_CATEGORIES.kibana, mount: async (params: AppMountParameters) => { @@ -248,6 +252,7 @@ export class DiscoverPlugin throw Error('Discover plugin method initializeInnerAngular is undefined'); } setScopedHistory(params.history); + setHeaderActionMenuMounter(params.setHeaderActionMenu); syncHistoryLocations(); appMounted(); const { @@ -261,19 +266,20 @@ export class DiscoverPlugin params.element.classList.add('dscAppWrapper'); const unmount = await renderApp(innerAngularName, params.element); return () => { + params.element.classList.remove('dscAppWrapper'); unmount(); appUnMounted(); }; }, }); - plugins.kibanaLegacy.forwardApp('doc', 'discover', (path) => { + plugins.urlForwarding.forwardApp('doc', 'discover', (path) => { return `#${path}`; }); - plugins.kibanaLegacy.forwardApp('context', 'discover', (path) => { + plugins.urlForwarding.forwardApp('context', 'discover', (path) => { return `#${path}`; }); - plugins.kibanaLegacy.forwardApp('discover', 'discover', (path) => { + plugins.urlForwarding.forwardApp('discover', 'discover', (path) => { const [, id, tail] = /discover\/([^\?]+)(.*)/.exec(path) || []; if (!id) { return `#${path.replace('/discover', '') || '/'}`; diff --git a/src/plugins/discover/public/saved_searches/saved_searches.ts b/src/plugins/discover/public/saved_searches/saved_searches.ts index 09be10b137494..0bc332ed8ec74 100644 --- a/src/plugins/discover/public/saved_searches/saved_searches.ts +++ b/src/plugins/discover/public/saved_searches/saved_searches.ts @@ -22,11 +22,7 @@ import { createSavedSearchClass } from './_saved_search'; export function createSavedSearchesLoader(services: SavedObjectKibanaServices) { const SavedSearchClass = createSavedSearchClass(services); - const savedSearchLoader = new SavedObjectLoader( - SavedSearchClass, - services.savedObjectsClient, - services.chrome - ); + const savedSearchLoader = new SavedObjectLoader(SavedSearchClass, services.savedObjectsClient); // Customize loader properties since adding an 's' on type doesn't work for type 'search' . savedSearchLoader.loaderProperties = { name: 'searches', diff --git a/src/plugins/embeddable/public/lib/actions/apply_filter_action.ts b/src/plugins/embeddable/public/lib/actions/apply_filter_action.ts index 1cdb5af00e748..3460203aac29c 100644 --- a/src/plugins/embeddable/public/lib/actions/apply_filter_action.ts +++ b/src/plugins/embeddable/public/lib/actions/apply_filter_action.ts @@ -42,6 +42,7 @@ export function createFilterAction(): ActionByType { return createAction({ type: ACTION_APPLY_FILTER, id: ACTION_APPLY_FILTER, + order: 100, getIconType: () => 'filter', getDisplayName: () => { return i18n.translate('embeddableApi.actions.applyFilterActionTitle', { diff --git a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts index 780cff9f4be7e..184178ba80e84 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts @@ -18,12 +18,7 @@ */ import { cloneDeep } from 'lodash'; -import { - ScopedHistory, - ApplicationStart, - PublicLegacyAppInfo, - PublicAppInfo, -} from '../../../../../core/public'; +import { ScopedHistory, ApplicationStart, PublicAppInfo } from '../../../../../core/public'; import { EmbeddableEditorState, isEmbeddableEditorState, @@ -41,7 +36,7 @@ export class EmbeddableStateTransfer { constructor( private navigateToApp: ApplicationStart['navigateToApp'], private scopedHistory?: ScopedHistory, - private appList?: ReadonlyMap | undefined + private appList?: ReadonlyMap | undefined ) {} /** diff --git a/src/plugins/embeddable/public/plugin.tsx b/src/plugins/embeddable/public/plugin.tsx index fb09729ab71c3..2ca31994b722d 100644 --- a/src/plugins/embeddable/public/plugin.tsx +++ b/src/plugins/embeddable/public/plugin.tsx @@ -29,7 +29,6 @@ import { Plugin, ScopedHistory, PublicAppInfo, - PublicLegacyAppInfo, } from '../../../core/public'; import { EmbeddableFactoryRegistry, EmbeddableFactoryProvider } from './types'; import { bootstrap } from './bootstrap'; @@ -92,7 +91,7 @@ export class EmbeddablePublicPlugin implements Plugin; + private appList?: ReadonlyMap; private appListSubscription?: Subscription; constructor(initializerContext: PluginInitializerContext) {} diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/global_flyout/global_flyout.tsx b/src/plugins/es_ui_shared/__packages_do_not_import__/global_flyout/global_flyout.tsx index 548e477c7c411..4dd9cfcaff16b 100644 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/global_flyout/global_flyout.tsx +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/global_flyout/global_flyout.tsx @@ -160,9 +160,7 @@ export const useGlobalFlyout = () => { Array.from(getContents()).forEach(removeContent); } }; - // https://github.com/elastic/kibana/issues/73970 - /* eslint-disable-next-line react-hooks/exhaustive-deps */ - }, [removeContent]); + }, [removeContent, getContents]); return { ...ctx, addContent }; }; diff --git a/src/plugins/es_ui_shared/public/components/json_editor/index.ts b/src/plugins/es_ui_shared/public/components/json_editor/index.ts index 81476a65f4215..63319baa38f5c 100644 --- a/src/plugins/es_ui_shared/public/components/json_editor/index.ts +++ b/src/plugins/es_ui_shared/public/components/json_editor/index.ts @@ -19,4 +19,4 @@ export * from './json_editor'; -export { OnJsonEditorUpdateHandler } from './use_json'; +export { OnJsonEditorUpdateHandler, JsonEditorState } from './use_json'; diff --git a/src/plugins/es_ui_shared/public/components/json_editor/json_editor.tsx b/src/plugins/es_ui_shared/public/components/json_editor/json_editor.tsx index 7d21722781d60..206db5a285620 100644 --- a/src/plugins/es_ui_shared/public/components/json_editor/json_editor.tsx +++ b/src/plugins/es_ui_shared/public/components/json_editor/json_editor.tsx @@ -17,98 +17,97 @@ * under the License. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { EuiFormRow, EuiCodeEditor } from '@elastic/eui'; import { debounce } from 'lodash'; -import { isJSON } from '../../../static/validators/string'; import { useJson, OnJsonEditorUpdateHandler } from './use_json'; -interface Props { - onUpdate: OnJsonEditorUpdateHandler; +interface Props { + onUpdate: OnJsonEditorUpdateHandler; label?: string; helpText?: React.ReactNode; value?: string; - defaultValue?: { [key: string]: any }; + defaultValue?: T; euiCodeEditorProps?: { [key: string]: any }; error?: string | null; } -export const JsonEditor = React.memo( - ({ - label, - helpText, +function JsonEditorComp({ + label, + helpText, + onUpdate, + value, + defaultValue, + euiCodeEditorProps, + error: propsError, +}: Props) { + const { content, setContent, error: internalError, isControlled } = useJson({ + defaultValue, onUpdate, value, - defaultValue, - euiCodeEditorProps, - error: propsError, - }: Props) => { - const isControlled = value !== undefined; + }); - const { content, setContent, error: internalError } = useJson({ - defaultValue, - onUpdate, - isControlled, - }); + const debouncedSetContent = useMemo(() => { + return debounce(setContent, 300); + }, [setContent]); - // https://github.com/elastic/kibana/issues/73971 - /* eslint-disable-next-line react-hooks/exhaustive-deps */ - const debouncedSetContent = useCallback(debounce(setContent, 300), [setContent]); + // We let the consumer control the validation and the error message. + const error = isControlled ? propsError : internalError; - // We let the consumer control the validation and the error message. - const error = isControlled ? propsError : internalError; + const onEuiCodeEditorChange = useCallback( + (updated: string) => { + if (isControlled) { + onUpdate({ + data: { + raw: updated, + format: () => JSON.parse(updated), + }, + validate: () => { + try { + JSON.parse(updated); + return true; + } catch (e) { + return false; + } + }, + isValid: undefined, + }); + } else { + debouncedSetContent(updated); + } + }, + [isControlled, debouncedSetContent, onUpdate] + ); - const onEuiCodeEditorChange = useCallback( - (updated: string) => { - if (isControlled) { - onUpdate({ - data: { - raw: updated, - format() { - return JSON.parse(updated); - }, - }, - validate() { - return isJSON(updated); - }, - isValid: undefined, - }); - } else { - debouncedSetContent(updated); - } - }, - /* eslint-disable-next-line react-hooks/exhaustive-deps */ - [isControlled] - ); + return ( + + + + ); +} - return ( - - - - ); - } -); +export const JsonEditor = React.memo(JsonEditorComp) as typeof JsonEditorComp; diff --git a/src/plugins/es_ui_shared/public/components/json_editor/use_json.ts b/src/plugins/es_ui_shared/public/components/json_editor/use_json.ts index 0ba39f5f05fe6..47d518e6814a4 100644 --- a/src/plugins/es_ui_shared/public/components/json_editor/use_json.ts +++ b/src/plugins/es_ui_shared/public/components/json_editor/use_json.ts @@ -17,24 +17,28 @@ * under the License. */ -import { useEffect, useState, useRef } from 'react'; +import { useEffect, useState, useRef, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { isJSON } from '../../../static/validators/string'; -export type OnJsonEditorUpdateHandler = (arg: { +export interface JsonEditorState { data: { raw: string; format(): T; }; validate(): boolean; isValid: boolean | undefined; -}) => void; +} + +export type OnJsonEditorUpdateHandler = ( + arg: JsonEditorState +) => void; interface Parameters { onUpdate: OnJsonEditorUpdateHandler; defaultValue?: T; - isControlled?: boolean; + value?: string; } const stringifyJson = (json: { [key: string]: any }) => @@ -43,13 +47,16 @@ const stringifyJson = (json: { [key: string]: any }) => export const useJson = ({ defaultValue = {} as T, onUpdate, - isControlled = false, + value, }: Parameters) => { - const didMount = useRef(false); - const [content, setContent] = useState(stringifyJson(defaultValue)); + const isControlled = value !== undefined; + const isMounted = useRef(false); + const [content, setContent] = useState( + isControlled ? value! : stringifyJson(defaultValue) + ); const [error, setError] = useState(null); - const validate = () => { + const validate = useCallback(() => { // We allow empty string as it will be converted to "{}"" const isValid = content.trim() === '' ? true : isJSON(content); if (!isValid) { @@ -62,35 +69,43 @@ export const useJson = ({ setError(null); } return isValid; - }; + }, [content]); - const formatContent = () => { + const formatContent = useCallback(() => { const isValid = validate(); const data = isValid && content.trim() !== '' ? JSON.parse(content) : {}; return data as T; - }; + }, [validate, content]); useEffect(() => { - if (didMount.current) { - const isValid = isControlled ? undefined : validate(); - onUpdate({ - data: { - raw: content, - format: formatContent, - }, - validate, - isValid, - }); - } else { - didMount.current = true; + if (!isMounted.current || isControlled) { + return; } - // https://github.com/elastic/kibana/issues/73971 - /* eslint-disable-next-line react-hooks/exhaustive-deps */ - }, [content]); + + const isValid = validate(); + + onUpdate({ + data: { + raw: content, + format: formatContent, + }, + validate, + isValid, + }); + }, [onUpdate, content, formatContent, validate, isControlled]); + + useEffect(() => { + isMounted.current = true; + + return () => { + isMounted.current = false; + }; + }, []); return { content, setContent, error, + isControlled, }; }; diff --git a/src/plugins/es_ui_shared/public/index.ts b/src/plugins/es_ui_shared/public/index.ts index 995ae0ba42837..5a1c13658604a 100644 --- a/src/plugins/es_ui_shared/public/index.ts +++ b/src/plugins/es_ui_shared/public/index.ts @@ -26,7 +26,7 @@ import * as Monaco from './monaco'; import * as ace from './ace'; import * as GlobalFlyout from './global_flyout'; -export { JsonEditor, OnJsonEditorUpdateHandler } from './components/json_editor'; +export { JsonEditor, OnJsonEditorUpdateHandler, JsonEditorState } from './components/json_editor'; export { SectionLoading } from './components/section_loading'; diff --git a/src/plugins/es_ui_shared/public/request/use_request.test.helpers.tsx b/src/plugins/es_ui_shared/public/request/use_request.test.helpers.tsx index 0d6fd122ad22c..7a42ed7fad427 100644 --- a/src/plugins/es_ui_shared/public/request/use_request.test.helpers.tsx +++ b/src/plugins/es_ui_shared/public/request/use_request.test.helpers.tsx @@ -106,7 +106,7 @@ export const createUseRequestHelpers = (): UseRequestHelpers => { }; const TestComponent = ({ requestConfig }: { requestConfig: UseRequestConfig }) => { - const { isInitialRequest, isLoading, error, data, sendRequest } = useRequest( + const { isInitialRequest, isLoading, error, data, resendRequest } = useRequest( httpClient as HttpSetup, requestConfig ); @@ -115,7 +115,7 @@ export const createUseRequestHelpers = (): UseRequestHelpers => { hookResult.isLoading = isLoading; hookResult.error = error; hookResult.data = data; - hookResult.sendRequest = sendRequest; + hookResult.resendRequest = resendRequest; return null; }; diff --git a/src/plugins/es_ui_shared/public/request/use_request.test.ts b/src/plugins/es_ui_shared/public/request/use_request.test.ts index f7902218d9314..2a639f93b47b4 100644 --- a/src/plugins/es_ui_shared/public/request/use_request.test.ts +++ b/src/plugins/es_ui_shared/public/request/use_request.test.ts @@ -102,7 +102,7 @@ describe('useRequest hook', () => { setupSuccessRequest(); expect(hookResult.isInitialRequest).toBe(true); - hookResult.sendRequest(); + hookResult.resendRequest(); await completeRequest(); expect(hookResult.isInitialRequest).toBe(false); }); @@ -148,7 +148,7 @@ describe('useRequest hook', () => { expect(hookResult.error).toBe(getErrorResponse().error); act(() => { - hookResult.sendRequest(); + hookResult.resendRequest(); }); expect(hookResult.isLoading).toBe(true); expect(hookResult.error).toBe(getErrorResponse().error); @@ -183,7 +183,7 @@ describe('useRequest hook', () => { expect(hookResult.data).toBe(getSuccessResponse().data); act(() => { - hookResult.sendRequest(); + hookResult.resendRequest(); }); expect(hookResult.isLoading).toBe(true); expect(hookResult.data).toBe(getSuccessResponse().data); @@ -215,7 +215,7 @@ describe('useRequest hook', () => { }); describe('callbacks', () => { - describe('sendRequest', () => { + describe('resendRequest', () => { it('sends the request', async () => { const { setupSuccessRequest, completeRequest, hookResult, getSendRequestSpy } = helpers; setupSuccessRequest(); @@ -224,7 +224,7 @@ describe('useRequest hook', () => { expect(getSendRequestSpy().callCount).toBe(1); await act(async () => { - hookResult.sendRequest(); + hookResult.resendRequest(); await completeRequest(); }); expect(getSendRequestSpy().callCount).toBe(2); @@ -239,17 +239,17 @@ describe('useRequest hook', () => { await advanceTime(REQUEST_TIME); expect(getSendRequestSpy().callCount).toBe(1); act(() => { - hookResult.sendRequest(); + hookResult.resendRequest(); }); // The manual request resolves, and we'll send yet another one... await advanceTime(REQUEST_TIME); expect(getSendRequestSpy().callCount).toBe(2); act(() => { - hookResult.sendRequest(); + hookResult.resendRequest(); }); - // At this point, we've moved forward 3s. The poll is set at 2s. If sendRequest didn't + // At this point, we've moved forward 3s. The poll is set at 2s. If resendRequest didn't // reset the poll, the request call count would be 4, not 3. await advanceTime(REQUEST_TIME); expect(getSendRequestSpy().callCount).toBe(3); @@ -291,14 +291,14 @@ describe('useRequest hook', () => { const HALF_REQUEST_TIME = REQUEST_TIME * 0.5; setupSuccessRequest({ pollIntervalMs: REQUEST_TIME }); - // Before the original request resolves, we make a manual sendRequest call. + // Before the original request resolves, we make a manual resendRequest call. await advanceTime(HALF_REQUEST_TIME); expect(getSendRequestSpy().callCount).toBe(0); act(() => { - hookResult.sendRequest(); + hookResult.resendRequest(); }); - // The original quest resolves but it's been marked as outdated by the the manual sendRequest + // The original quest resolves but it's been marked as outdated by the the manual resendRequest // call "interrupts", so data is left undefined. await advanceTime(HALF_REQUEST_TIME); expect(getSendRequestSpy().callCount).toBe(1); diff --git a/src/plugins/es_ui_shared/public/request/use_request.ts b/src/plugins/es_ui_shared/public/request/use_request.ts index 481843bf40e88..e04f84a67b8a3 100644 --- a/src/plugins/es_ui_shared/public/request/use_request.ts +++ b/src/plugins/es_ui_shared/public/request/use_request.ts @@ -20,11 +20,7 @@ import { useEffect, useCallback, useState, useRef, useMemo } from 'react'; import { HttpSetup } from '../../../../../src/core/public'; -import { - sendRequest as sendStatelessRequest, - SendRequestConfig, - SendRequestResponse, -} from './send_request'; +import { sendRequest, SendRequestConfig } from './send_request'; export interface UseRequestConfig extends SendRequestConfig { pollIntervalMs?: number; @@ -37,7 +33,7 @@ export interface UseRequestResponse { isLoading: boolean; error: E | null; data?: D | null; - sendRequest: () => Promise>; + resendRequest: () => void; } export const useRequest = ( @@ -80,7 +76,7 @@ export const useRequest = ( /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [path, method, queryStringified, bodyStringified]); - const sendRequest = useCallback(async () => { + const resendRequest = useCallback(async () => { // If we're on an interval, this allows us to reset it if the user has manually requested the // data, to avoid doubled-up requests. clearPollInterval(); @@ -91,7 +87,7 @@ export const useRequest = ( // "old" error/data or loading state when a new request is in-flight. setIsLoading(true); - const response = await sendStatelessRequest(httpClient, requestBody); + const response = await sendRequest(httpClient, requestBody); const { data: serializedResponseData, error: responseError } = response; const isOutdatedRequest = requestId !== requestCountRef.current; @@ -99,7 +95,7 @@ export const useRequest = ( // Ignore outdated or irrelevant data. if (isOutdatedRequest || isUnmounted) { - return { data: null, error: null }; + return; } setError(responseError); @@ -112,8 +108,6 @@ export const useRequest = ( } // Setting isLoading to false also acts as a signal for scheduling the next poll request. setIsLoading(false); - - return { data: serializedResponseData, error: responseError }; }, [requestBody, httpClient, deserializer, clearPollInterval]); const scheduleRequest = useCallback(() => { @@ -121,19 +115,19 @@ export const useRequest = ( clearPollInterval(); if (pollIntervalMs) { - pollIntervalIdRef.current = setTimeout(sendRequest, pollIntervalMs); + pollIntervalIdRef.current = setTimeout(resendRequest, pollIntervalMs); } - }, [pollIntervalMs, sendRequest, clearPollInterval]); + }, [pollIntervalMs, resendRequest, clearPollInterval]); - // Send the request on component mount and whenever the dependencies of sendRequest() change. + // Send the request on component mount and whenever the dependencies of resendRequest() change. useEffect(() => { - sendRequest(); - }, [sendRequest]); + resendRequest(); + }, [resendRequest]); // Schedule the next poll request when the previous one completes. useEffect(() => { // When a request completes, attempt to schedule the next one. Note that we aren't re-scheduling - // a request whenever sendRequest's dependencies change. isLoading isn't set to false until the + // a request whenever resendRequest's dependencies change. isLoading isn't set to false until the // initial request has completed, so we won't schedule a request on mount. if (!isLoading) { scheduleRequest(); @@ -156,6 +150,6 @@ export const useRequest = ( isLoading, error, data, - sendRequest, // Gives the user the ability to manually request data + resendRequest, // Gives the user the ability to manually request data }; }; diff --git a/src/plugins/es_ui_shared/static/forms/components/fields/range_field.tsx b/src/plugins/es_ui_shared/static/forms/components/fields/range_field.tsx index b83b0af5f97c6..9ffa7adace781 100644 --- a/src/plugins/es_ui_shared/static/forms/components/fields/range_field.tsx +++ b/src/plugins/es_ui_shared/static/forms/components/fields/range_field.tsx @@ -31,17 +31,16 @@ interface Props { export const RangeField = ({ field, euiFieldProps = {}, ...rest }: Props) => { const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + const { onChange: onFieldChange } = field; const onChange = useCallback( (e: React.ChangeEvent | React.MouseEvent) => { const event = ({ ...e, value: `${e.currentTarget.value}` } as unknown) as React.ChangeEvent<{ value: string; }>; - field.onChange(event); + onFieldChange(event); }, - // https://github.com/elastic/kibana/issues/73972 - /* eslint-disable-next-line react-hooks/exhaustive-deps */ - [field.onChange] + [onFieldChange] ); return ( diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.ts index 3688421964d2e..d0ac0d4ab28c3 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.ts @@ -17,18 +17,25 @@ * under the License. */ -import { useState, useEffect, useRef, useCallback } from 'react'; +import { useEffect, useRef, useCallback, useMemo } from 'react'; +import { FormHook, FieldConfig } from '../types'; +import { getFieldValidityAndErrorMessage } from '../helpers'; import { useFormContext } from '../form_context'; +import { useField, InternalFieldConfig } from '../hooks'; interface Props { path: string; initialNumberOfItems?: number; readDefaultValueOnForm?: boolean; + validations?: FieldConfig['validations']; children: (args: { items: ArrayItem[]; + error: string | null; addItem: () => void; removeItem: (id: number) => void; + moveItem: (sourceIdx: number, destinationIdx: number) => void; + form: FormHook; }) => JSX.Element; } @@ -56,32 +63,62 @@ export interface ArrayItem { export const UseArray = ({ path, initialNumberOfItems, + validations, readDefaultValueOnForm = true, children, }: Props) => { - const didMountRef = useRef(false); - const form = useFormContext(); - const defaultValues = readDefaultValueOnForm && (form.getFieldDefaultValue(path) as any[]); + const isMounted = useRef(false); const uniqueId = useRef(0); - const getInitialItemsFromValues = (values: any[]): ArrayItem[] => - values.map((_, index) => ({ + const form = useFormContext(); + const { getFieldDefaultValue } = form; + + const getNewItemAtIndex = useCallback( + (index: number): ArrayItem => ({ id: uniqueId.current++, path: `${path}[${index}]`, - isNew: false, - })); + isNew: true, + }), + [path] + ); + + const fieldDefaultValue = useMemo(() => { + const defaultValues = readDefaultValueOnForm + ? (getFieldDefaultValue(path) as any[]) + : undefined; - const getNewItemAtIndex = (index: number): ArrayItem => ({ - id: uniqueId.current++, - path: `${path}[${index}]`, - isNew: true, - }); + const getInitialItemsFromValues = (values: any[]): ArrayItem[] => + values.map((_, index) => ({ + id: uniqueId.current++, + path: `${path}[${index}]`, + isNew: false, + })); - const initialState = defaultValues - ? getInitialItemsFromValues(defaultValues) - : new Array(initialNumberOfItems).fill('').map((_, i) => getNewItemAtIndex(i)); + return defaultValues + ? getInitialItemsFromValues(defaultValues) + : new Array(initialNumberOfItems).fill('').map((_, i) => getNewItemAtIndex(i)); + }, [path, initialNumberOfItems, readDefaultValueOnForm, getFieldDefaultValue, getNewItemAtIndex]); - const [items, setItems] = useState(initialState); + // Create a new hook field with the "hasValue" set to false so we don't use its value to build the final form data. + // Apart from that the field behaves like a normal field and is hooked into the form validation lifecycle. + const fieldConfigBase: FieldConfig & InternalFieldConfig = { + defaultValue: fieldDefaultValue, + errorDisplayDelay: 0, + isIncludedInOutput: false, + }; + + const fieldConfig: FieldConfig & InternalFieldConfig = validations + ? { validations, ...fieldConfigBase } + : fieldConfigBase; + + const field = useField(form, path, fieldConfig); + const { setValue, value, isChangingValue, errors } = field; + + // Derived state from the field + const error = useMemo(() => { + const { errorMessage } = getFieldValidityAndErrorMessage({ isChangingValue, errors }); + return errorMessage; + }, [isChangingValue, errors]); const updatePaths = useCallback( (_rows: ArrayItem[]) => { @@ -96,29 +133,51 @@ export const UseArray = ({ [path] ); - const addItem = () => { - setItems((previousItems) => { + const addItem = useCallback(() => { + setValue((previousItems) => { const itemIndex = previousItems.length; return [...previousItems, getNewItemAtIndex(itemIndex)]; }); - }; + }, [setValue, getNewItemAtIndex]); - const removeItem = (id: number) => { - setItems((previousItems) => { - const updatedItems = previousItems.filter((item) => item.id !== id); - return updatePaths(updatedItems); - }); - }; + const removeItem = useCallback( + (id: number) => { + setValue((previousItems) => { + const updatedItems = previousItems.filter((item) => item.id !== id); + return updatePaths(updatedItems); + }); + }, + [setValue, updatePaths] + ); - useEffect(() => { - if (didMountRef.current) { - setItems((prev) => { - return updatePaths(prev); + const moveItem = useCallback( + (sourceIdx: number, destinationIdx: number) => { + setValue((previousItems) => { + const nextItems = [...previousItems]; + const removed = nextItems.splice(sourceIdx, 1)[0]; + nextItems.splice(destinationIdx, 0, removed); + return updatePaths(nextItems); }); - } else { - didMountRef.current = true; + }, + [setValue, updatePaths] + ); + + useEffect(() => { + if (!isMounted.current) { + return; } - }, [path, updatePaths]); - return children({ items, addItem, removeItem }); + setValue((prev) => { + return updatePaths(prev); + }); + }, [path, updatePaths, setValue]); + + useEffect(() => { + isMounted.current = true; + return () => { + isMounted.current = false; + }; + }, []); + + return children({ items: value, error, form, addItem, removeItem, moveItem }); }; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx index a55b2f0a8fa29..c14471991ccd3 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx @@ -64,6 +64,93 @@ describe('', () => { }); }); + describe('validation', () => { + let formHook: FormHook | null = null; + + beforeEach(() => { + formHook = null; + }); + + const onFormHook = (form: FormHook) => { + formHook = form; + }; + + const getTestComp = (fieldConfig: FieldConfig) => { + const TestComp = ({ onForm }: { onForm: (form: FormHook) => void }) => { + const { form } = useForm(); + + useEffect(() => { + onForm(form); + }, [onForm, form]); + + return ( +
    + + + ); + }; + return TestComp; + }; + + const setup = (fieldConfig: FieldConfig) => { + return registerTestBed(getTestComp(fieldConfig), { + memoryRouter: { wrapComponent: false }, + defaultProps: { onForm: onFormHook }, + })() as TestBed; + }; + + test('should update the form validity whenever the field value changes', async () => { + const fieldConfig: FieldConfig = { + defaultValue: '', // empty string, which is not valid + validations: [ + { + validator: ({ value }) => { + // Validate that string is not empty + if ((value as string).trim() === '') { + return { message: 'Error: field is empty.' }; + } + }, + }, + ], + }; + + // Mount our TestComponent + const { + form: { setInputValue }, + } = setup(fieldConfig); + + if (formHook === null) { + throw new Error('FormHook object has not been set.'); + } + + let { isValid } = formHook; + expect(isValid).toBeUndefined(); // Initially the form validity is undefined... + + await act(async () => { + await formHook!.validate(); // ...until we validate the form + }); + + ({ isValid } = formHook); + expect(isValid).toBe(false); + + // Change to a non empty string to pass validation + await act(async () => { + setInputValue('myField', 'changedValue'); + }); + + ({ isValid } = formHook); + expect(isValid).toBe(true); + + // Change back to an empty string to fail validation + await act(async () => { + setInputValue('myField', ''); + }); + + ({ isValid } = formHook); + expect(isValid).toBe(false); + }); + }); + describe('serializer(), deserializer(), formatter()', () => { interface MyForm { name: string; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/helpers.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/helpers.ts index 7ea42f81b43cb..a148dc543542b 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/helpers.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/helpers.ts @@ -19,9 +19,10 @@ import { FieldHook } from './types'; -export const getFieldValidityAndErrorMessage = ( - field: FieldHook -): { isInvalid: boolean; errorMessage: string | null } => { +export const getFieldValidityAndErrorMessage = (field: { + isChangingValue: FieldHook['isChangingValue']; + errors: FieldHook['errors']; +}): { isInvalid: boolean; errorMessage: string | null } => { const isInvalid = !field.isChangingValue && field.errors.length > 0; const errorMessage = !field.isChangingValue && field.errors.length ? field.errors[0].message : null; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/index.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/index.ts index 45c11dd6272e4..aa9610dd85ae3 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/index.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/index.ts @@ -17,6 +17,6 @@ * under the License. */ -export { useField } from './use_field'; +export { useField, InternalFieldConfig } from './use_field'; export { useForm } from './use_form'; export { useFormData } from './use_form_data'; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts index fa29f900af2ef..bb4aae6eccae8 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts @@ -22,16 +22,22 @@ import { useMemo, useState, useEffect, useRef, useCallback } from 'react'; import { FormHook, FieldHook, FieldConfig, FieldValidateResponse, ValidationError } from '../types'; import { FIELD_TYPES, VALIDATION_TYPES } from '../constants'; +export interface InternalFieldConfig { + initialValue?: T; + isIncludedInOutput?: boolean; +} + export const useField = ( form: FormHook, path: string, - config: FieldConfig & { initialValue?: T } = {}, + config: FieldConfig & InternalFieldConfig = {}, valueChangeListener?: (value: T) => void ) => { const { type = FIELD_TYPES.TEXT, defaultValue = '', // The value to use a fallback mecanism when no initial value is passed initialValue = config.defaultValue ?? '', // The value explicitly passed + isIncludedInOutput = true, label = '', labelAppend = '', helpText = '', @@ -69,11 +75,12 @@ export const useField = ( const [isChangingValue, setIsChangingValue] = useState(false); const [isValidated, setIsValidated] = useState(false); + const isMounted = useRef(false); const validateCounter = useRef(0); const changeCounter = useRef(0); + const hasBeenReset = useRef(false); const inflightValidation = useRef | null>(null); const debounceTimeout = useRef(null); - const isMounted = useRef(false); // -- HELPERS // ---------------------------------- @@ -142,11 +149,7 @@ export const useField = ( __updateFormDataAt(path, value); // Validate field(s) (that will update form.isValid state) - // We only validate if the value is different than the initial or default value - // to avoid validating after a form.reset() call. - if (value !== initialValue && value !== defaultValue) { - await __validateFields(fieldsToValidateOnChange ?? [path]); - } + await __validateFields(fieldsToValidateOnChange ?? [path]); if (isMounted.current === false) { return; @@ -172,8 +175,6 @@ export const useField = ( }, [ path, value, - defaultValue, - initialValue, valueChangeListener, errorDisplayDelay, fieldsToValidateOnChange, @@ -206,7 +207,7 @@ export const useField = ( validationTypeToValidate, }: { formData: any; - value: unknown; + value: T; validationTypeToValidate?: string; }): ValidationError[] | Promise => { if (!validations) { @@ -239,7 +240,7 @@ export const useField = ( } inflightValidation.current = validator({ - value: (valueToValidate as unknown) as string, + value: valueToValidate, errors: validationErrors, form: { getFormData, getFields }, formData, @@ -285,7 +286,7 @@ export const useField = ( } const validationResult = validator({ - value: (valueToValidate as unknown) as string, + value: valueToValidate, errors: validationErrors, form: { getFormData, getFields }, formData, @@ -393,9 +394,15 @@ export const useField = ( */ const setValue: FieldHook['setValue'] = useCallback( (newValue) => { - const formattedValue = formatInputValue(newValue); - setStateValue(formattedValue); - return formattedValue; + setStateValue((prev) => { + let formattedValue: T; + if (typeof newValue === 'function') { + formattedValue = formatInputValue((newValue as Function)(prev)); + } else { + formattedValue = formatInputValue(newValue); + } + return formattedValue; + }); }, [formatInputValue] ); @@ -468,6 +475,7 @@ export const useField = ( setErrors([]); if (resetValue) { + hasBeenReset.current = true; const newValue = deserializeValue(updatedDefaultValue ?? defaultValue); setValue(newValue); return newValue; @@ -500,6 +508,7 @@ export const useField = ( clearErrors, validate, reset, + __isIncludedInOutput: isIncludedInOutput, __serializeValue: serializeValue, }; }, [ @@ -515,6 +524,7 @@ export const useField = ( isValidating, isValidated, isChangingValue, + isIncludedInOutput, onChange, getErrorsMessages, setValue, @@ -539,6 +549,13 @@ export const useField = ( }, [path, __removeField]); useEffect(() => { + // If the field value has been reset, we don't want to call the "onValueChange()" + // as it will set the "isPristine" state to true or validate the field, which initially we don't want. + if (hasBeenReset.current) { + hasBeenReset.current = false; + return; + } + if (!isMounted.current) { return; } diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx index 4a880415b6d22..edcd84daf5d2f 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx @@ -22,7 +22,13 @@ import { act } from 'react-dom/test-utils'; import { registerTestBed, getRandomString, TestBed } from '../shared_imports'; import { Form, UseField } from '../components'; -import { FormSubmitHandler, OnUpdateHandler, FormHook, ValidationFunc } from '../types'; +import { + FormSubmitHandler, + OnUpdateHandler, + FormHook, + ValidationFunc, + FieldConfig, +} from '../types'; import { useForm } from './use_form'; interface MyForm { @@ -441,5 +447,57 @@ describe('useForm() hook', () => { deeply: { nested: { value: '' } }, // Fallback to empty string as no config was provided }); }); + + test('should not validate the fields after resetting its value (form validity should be undefined)', async () => { + const fieldConfig: FieldConfig = { + defaultValue: '', + validations: [ + { + validator: ({ value }) => { + if ((value as string).trim() === '') { + return { message: 'Error: empty string' }; + } + }, + }, + ], + }; + + const TestResetComp = () => { + const { form } = useForm(); + + useEffect(() => { + formHook = form; + }, [form]); + + return ( +
    + + + ); + }; + + const { + form: { setInputValue }, + } = registerTestBed(TestResetComp, { + memoryRouter: { wrapComponent: false }, + })() as TestBed; + + let { isValid } = formHook!; + expect(isValid).toBeUndefined(); + + await act(async () => { + setInputValue('myField', 'changedValue'); + }); + ({ isValid } = formHook!); + expect(isValid).toBe(true); + + await act(async () => { + // When we reset the form, value is back to "", which is invalid for the field + formHook!.reset(); + }); + + ({ isValid } = formHook!); + expect(isValid).toBeUndefined(); // Make sure it is "undefined" and not "false". + }); }); }); diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts index 7b72a9eeacf7b..b390c17d3c2ff 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts @@ -95,19 +95,25 @@ export function useForm( const fieldsToArray = useCallback(() => Object.values(fieldsRefs.current), []); - const stripEmptyFields = useCallback( - (fields: FieldsMap): FieldsMap => { - if (formOptions.stripEmptyFields) { - return Object.entries(fields).reduce((acc, [key, field]) => { - if (typeof field.value !== 'string' || field.value.trim() !== '') { - acc[key] = field; - } + const getFieldsForOutput = useCallback( + (fields: FieldsMap, opts: { stripEmptyFields: boolean }): FieldsMap => { + return Object.entries(fields).reduce((acc, [key, field]) => { + if (!field.__isIncludedInOutput) { return acc; - }, {} as FieldsMap); - } - return fields; + } + + if (opts.stripEmptyFields) { + const isFieldEmpty = typeof field.value === 'string' && field.value.trim() === ''; + if (isFieldEmpty) { + return acc; + } + } + + acc[key] = field; + return acc; + }, {} as FieldsMap); }, - [formOptions] + [] ); const updateFormDataAt: FormHook['__updateFormDataAt'] = useCallback( @@ -133,8 +139,10 @@ export function useForm( const getFormData: FormHook['getFormData'] = useCallback( (getDataOptions: Parameters['getFormData']>[0] = { unflatten: true }) => { if (getDataOptions.unflatten) { - const nonEmptyFields = stripEmptyFields(fieldsRefs.current); - const fieldsValue = mapFormFields(nonEmptyFields, (field) => field.__serializeValue()); + const fieldsToOutput = getFieldsForOutput(fieldsRefs.current, { + stripEmptyFields: formOptions.stripEmptyFields, + }); + const fieldsValue = mapFormFields(fieldsToOutput, (field) => field.__serializeValue()); return serializer ? (serializer(unflattenObject(fieldsValue)) as T) : (unflattenObject(fieldsValue) as T); @@ -148,7 +156,7 @@ export function useForm( {} as T ); }, - [stripEmptyFields, serializer] + [getFieldsForOutput, formOptions.stripEmptyFields, serializer] ); const getErrors: FormHook['getErrors'] = useCallback(() => { diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/types.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/types.ts index 4b343ec5e9f2e..18b8f478f7c0e 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/types.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/types.ts @@ -108,15 +108,18 @@ export interface FieldHook { errorCode?: string; }) => string | null; onChange: (event: ChangeEvent<{ name?: string; value: string; checked?: boolean }>) => void; - setValue: (value: T) => T; + setValue: (value: T | ((prevValue: T) => T)) => void; setErrors: (errors: ValidationError[]) => void; clearErrors: (type?: string | string[]) => void; validate: (validateData?: { formData?: any; - value?: unknown; + value?: T; validationType?: string; }) => FieldValidateResponse | Promise; reset: (options?: { resetValue?: boolean; defaultValue?: T }) => unknown | undefined; + // Flag to indicate if the field value will be included in the form data outputted + // when calling form.getFormData(); + __isIncludedInOutput: boolean; __serializeValue: (rawValue?: unknown) => unknown; } @@ -127,7 +130,7 @@ export interface FieldConfig { readonly helpText?: string | ReactNode; readonly type?: HTMLInputElement['type']; readonly defaultValue?: ValueType; - readonly validations?: Array>; + readonly validations?: Array>; readonly formatters?: FormatterFunc[]; readonly deserializer?: SerializerFunc; readonly serializer?: SerializerFunc; @@ -163,8 +166,8 @@ export interface ValidationFuncArg { errors: readonly ValidationError[]; } -export type ValidationFunc = ( - data: ValidationFuncArg +export type ValidationFunc = ( + data: ValidationFuncArg ) => ValidationError | void | undefined | Promise | void | undefined>; export interface FieldValidateResponse { @@ -184,8 +187,8 @@ type FormatterFunc = (value: any, formData: FormData) => unknown; // string | number | boolean | string[] ... type FieldValue = unknown; -export interface ValidationConfig { - validator: ValidationFunc; +export interface ValidationConfig { + validator: ValidationFunc; type?: string; /** * By default all validation are blockers, which means that if they fail, the field is invalid. diff --git a/src/plugins/expressions/common/expression_types/specs/boolean.ts b/src/plugins/expressions/common/expression_types/specs/boolean.ts index adbdeafc34fd2..73b0b98eaaf06 100644 --- a/src/plugins/expressions/common/expression_types/specs/boolean.ts +++ b/src/plugins/expressions/common/expression_types/specs/boolean.ts @@ -41,7 +41,6 @@ export const boolean: ExpressionTypeDefinition<'boolean', boolean> = { }, datatable: (value): Datatable => ({ type: 'datatable', - meta: {}, columns: [{ id: 'value', name: 'value', meta: { type: name } }], rows: [{ value }], }), diff --git a/src/plugins/expressions/common/expression_types/specs/datatable.ts b/src/plugins/expressions/common/expression_types/specs/datatable.ts index dd3c653878de7..c201e99faeb03 100644 --- a/src/plugins/expressions/common/expression_types/specs/datatable.ts +++ b/src/plugins/expressions/common/expression_types/specs/datatable.ts @@ -52,7 +52,10 @@ export type DatatableRow = Record; export interface DatatableColumnMeta { type: DatatableColumnType; field?: string; + index?: string; params?: SerializableState; + source?: string; + sourceParams?: SerializableState; } /** * This type represents the shape of a column in a `Datatable`. @@ -63,17 +66,11 @@ export interface DatatableColumn { meta: DatatableColumnMeta; } -export interface DatatableMeta { - type?: string; - source?: string; -} - /** * A `Datatable` in Canvas is a unique structure that represents tabulated data. */ export interface Datatable { type: typeof name; - meta?: DatatableMeta; columns: DatatableColumn[]; rows: DatatableRow[]; } diff --git a/src/plugins/expressions/common/expression_types/specs/num.ts b/src/plugins/expressions/common/expression_types/specs/num.ts index 041747f39740b..d208a9dcf73c8 100644 --- a/src/plugins/expressions/common/expression_types/specs/num.ts +++ b/src/plugins/expressions/common/expression_types/specs/num.ts @@ -73,7 +73,6 @@ export const num: ExpressionTypeDefinition<'num', ExpressionValueNum> = { }, datatable: ({ value }): Datatable => ({ type: 'datatable', - meta: {}, columns: [{ id: 'value', name: 'value', meta: { type: 'number' } }], rows: [{ value }], }), diff --git a/src/plugins/expressions/common/expression_types/specs/number.ts b/src/plugins/expressions/common/expression_types/specs/number.ts index c5fdacf3408a1..c30d3fe943d42 100644 --- a/src/plugins/expressions/common/expression_types/specs/number.ts +++ b/src/plugins/expressions/common/expression_types/specs/number.ts @@ -55,7 +55,6 @@ export const number: ExpressionTypeDefinition = { }, datatable: (value): Datatable => ({ type: 'datatable', - meta: {}, columns: [{ id: 'value', name: 'value', meta: { type: 'number' } }], rows: [{ value }], }), diff --git a/src/plugins/expressions/common/expression_types/specs/string.ts b/src/plugins/expressions/common/expression_types/specs/string.ts index 3d52707279bfc..0869e21e455f7 100644 --- a/src/plugins/expressions/common/expression_types/specs/string.ts +++ b/src/plugins/expressions/common/expression_types/specs/string.ts @@ -40,7 +40,6 @@ export const string: ExpressionTypeDefinition = { }, datatable: (value): Datatable => ({ type: 'datatable', - meta: {}, columns: [{ id: 'value', name: 'value', meta: { type: 'string' } }], rows: [{ value }], }), diff --git a/src/plugins/home/kibana.json b/src/plugins/home/kibana.json index 74bd3625ca964..81bfc57a00363 100644 --- a/src/plugins/home/kibana.json +++ b/src/plugins/home/kibana.json @@ -3,7 +3,7 @@ "version": "kibana", "server": true, "ui": true, - "requiredPlugins": ["data", "kibanaLegacy"], + "requiredPlugins": ["data", "urlForwarding"], "optionalPlugins": ["usageCollection", "telemetry"], "requiredBundles": [ "kibanaReact" diff --git a/src/plugins/home/public/application/application.tsx b/src/plugins/home/public/application/application.tsx index 5d71bf8651d88..a4a158bc7dbde 100644 --- a/src/plugins/home/public/application/application.tsx +++ b/src/plugins/home/public/application/application.tsx @@ -47,6 +47,13 @@ export const renderApp = async ( chrome.setBreadcrumbs([{ text: homeTitle }]); + // dispatch synthetic hash change event to update hash history objects + // this is necessary because hash updates triggered by using popState won't trigger this event naturally. + // This must be called before the app is mounted to avoid call this after the redirect to default app logic kicks in + const unlisten = history.listen((location) => { + window.dispatchEvent(new HashChangeEvent('hashchange')); + }); + render( @@ -54,12 +61,6 @@ export const renderApp = async ( element ); - // dispatch synthetic hash change event to update hash history objects - // this is necessary because hash updates triggered by using popState won't trigger this event naturally. - const unlisten = history.listen(() => { - window.dispatchEvent(new HashChangeEvent('hashchange')); - }); - return () => { unmountComponentAtNode(element); unlisten(); diff --git a/src/plugins/home/public/application/components/home_app.js b/src/plugins/home/public/application/components/home_app.js index 90e549c873436..69cd68d553d03 100644 --- a/src/plugins/home/public/application/components/home_app.js +++ b/src/plugins/home/public/application/components/home_app.js @@ -32,8 +32,8 @@ import { useMount } from 'react-use'; const RedirectToDefaultApp = () => { useMount(() => { - const { kibanaLegacy } = getServices(); - kibanaLegacy.navigateToDefaultApp(); + const { urlForwarding } = getServices(); + urlForwarding.navigateToDefaultApp(); }); return null; }; diff --git a/src/plugins/home/public/application/components/welcome.tsx b/src/plugins/home/public/application/components/welcome.tsx index cacb507009c70..404185de3d2ea 100644 --- a/src/plugins/home/public/application/components/welcome.tsx +++ b/src/plugins/home/public/application/components/welcome.tsx @@ -76,7 +76,7 @@ export class Welcome extends React.Component { componentDidMount() { const { telemetry } = this.props; this.services.trackUiMetric(METRIC_TYPE.LOADED, 'welcomeScreenMount'); - if (telemetry) { + if (telemetry?.telemetryService.userCanChangeSettings) { telemetry.telemetryNotifications.setOptedInNoticeSeen(); } document.addEventListener('keydown', this.hideOnEsc); @@ -88,7 +88,7 @@ export class Welcome extends React.Component { private renderTelemetryEnabledOrDisabledText = () => { const { telemetry } = this.props; - if (!telemetry) { + if (!telemetry || !telemetry.telemetryService.userCanChangeSettings) { return null; } diff --git a/src/plugins/home/public/application/kibana_services.ts b/src/plugins/home/public/application/kibana_services.ts index 8bd651d038128..74b2bf8d4f6a4 100644 --- a/src/plugins/home/public/application/kibana_services.ts +++ b/src/plugins/home/public/application/kibana_services.ts @@ -29,7 +29,7 @@ import { } from 'kibana/public'; import { UiStatsMetricType } from '@kbn/analytics'; import { TelemetryPluginStart } from '../../../telemetry/public'; -import { KibanaLegacyStart } from '../../../kibana_legacy/public'; +import { UrlForwardingStart } from '../../../url_forwarding/public'; import { TutorialService } from '../services/tutorials'; import { FeatureCatalogueRegistry } from '../services/feature_catalogue'; import { EnvironmentService } from '../services/environment'; @@ -41,7 +41,7 @@ export interface HomeKibanaServices { chrome: ChromeStart; application: ApplicationStart; uiSettings: IUiSettingsClient; - kibanaLegacy: KibanaLegacyStart; + urlForwarding: UrlForwardingStart; homeConfig: ConfigSchema; featureCatalogue: FeatureCatalogueRegistry; http: HttpStart; diff --git a/src/plugins/home/public/plugin.test.ts b/src/plugins/home/public/plugin.test.ts index 0ebba06e6bea9..7b56c6ec89b77 100644 --- a/src/plugins/home/public/plugin.test.ts +++ b/src/plugins/home/public/plugin.test.ts @@ -20,7 +20,7 @@ import { registryMock, environmentMock, tutorialMock } from './plugin.test.mocks'; import { HomePublicPlugin } from './plugin'; import { coreMock } from '../../../core/public/mocks'; -import { kibanaLegacyPluginMock } from '../../kibana_legacy/public/mocks'; +import { urlForwardingPluginMock } from '../../url_forwarding/public/mocks'; const mockInitializerContext = coreMock.createPluginInitializerContext(); @@ -37,7 +37,7 @@ describe('HomePublicPlugin', () => { const setup = await new HomePublicPlugin(mockInitializerContext).setup( coreMock.createSetup() as any, { - kibanaLegacy: kibanaLegacyPluginMock.createSetupContract(), + urlForwarding: urlForwardingPluginMock.createSetupContract(), } ); expect(setup).toHaveProperty('featureCatalogue'); @@ -56,7 +56,7 @@ describe('HomePublicPlugin', () => { const setup = await new HomePublicPlugin(mockInitializerContext).setup( coreMock.createSetup() as any, { - kibanaLegacy: kibanaLegacyPluginMock.createSetupContract(), + urlForwarding: urlForwardingPluginMock.createSetupContract(), } ); expect(setup).toHaveProperty('featureCatalogue'); @@ -73,7 +73,7 @@ describe('HomePublicPlugin', () => { const setup = await new HomePublicPlugin(mockInitializerContext).setup( coreMock.createSetup() as any, { - kibanaLegacy: kibanaLegacyPluginMock.createSetupContract(), + urlForwarding: urlForwardingPluginMock.createSetupContract(), } ); expect(setup).toHaveProperty('featureCatalogue'); @@ -84,7 +84,7 @@ describe('HomePublicPlugin', () => { const setup = await new HomePublicPlugin(mockInitializerContext).setup( coreMock.createSetup() as any, { - kibanaLegacy: kibanaLegacyPluginMock.createSetupContract(), + urlForwarding: urlForwardingPluginMock.createSetupContract(), } ); expect(setup).toHaveProperty('environment'); @@ -95,7 +95,7 @@ describe('HomePublicPlugin', () => { const setup = await new HomePublicPlugin(mockInitializerContext).setup( coreMock.createSetup() as any, { - kibanaLegacy: kibanaLegacyPluginMock.createSetupContract(), + urlForwarding: urlForwardingPluginMock.createSetupContract(), } ); expect(setup).toHaveProperty('tutorials'); diff --git a/src/plugins/home/public/plugin.ts b/src/plugins/home/public/plugin.ts index ba2f537e7c5de..b62ceae3d0d37 100644 --- a/src/plugins/home/public/plugin.ts +++ b/src/plugins/home/public/plugin.ts @@ -41,19 +41,19 @@ import { setServices } from './application/kibana_services'; import { DataPublicPluginStart } from '../../data/public'; import { TelemetryPluginStart } from '../../telemetry/public'; import { UsageCollectionSetup } from '../../usage_collection/public'; -import { KibanaLegacySetup, KibanaLegacyStart } from '../../kibana_legacy/public'; +import { UrlForwardingSetup, UrlForwardingStart } from '../../url_forwarding/public'; import { AppNavLinkStatus } from '../../../core/public'; import { PLUGIN_ID, HOME_APP_BASE_PATH } from '../common/constants'; export interface HomePluginStartDependencies { data: DataPublicPluginStart; telemetry?: TelemetryPluginStart; - kibanaLegacy: KibanaLegacyStart; + urlForwarding: UrlForwardingStart; } export interface HomePluginSetupDependencies { usageCollection?: UsageCollectionSetup; - kibanaLegacy: KibanaLegacySetup; + urlForwarding: UrlForwardingSetup; } export class HomePublicPlugin @@ -67,7 +67,7 @@ export class HomePublicPlugin public setup( core: CoreSetup, - { kibanaLegacy, usageCollection }: HomePluginSetupDependencies + { urlForwarding, usageCollection }: HomePluginSetupDependencies ): HomePublicPluginSetup { core.application.register({ id: PLUGIN_ID, @@ -79,7 +79,7 @@ export class HomePublicPlugin : () => {}; const [ coreStart, - { telemetry, data, kibanaLegacy: kibanaLegacyStart }, + { telemetry, data, urlForwarding: urlForwardingStart }, ] = await core.getStartServices(); setServices({ trackUiMetric, @@ -97,7 +97,7 @@ export class HomePublicPlugin getBasePath: core.http.basePath.get, indexPatternService: data.indexPatterns, environmentService: this.environmentService, - kibanaLegacy: kibanaLegacyStart, + urlForwarding: urlForwardingStart, homeConfig: this.initializerContext.config.get(), tutorialService: this.tutorialService, featureCatalogue: this.featuresCatalogueRegistry, @@ -109,7 +109,7 @@ export class HomePublicPlugin return await renderApp(params.element, coreStart, params.history); }, }); - kibanaLegacy.forwardApp('home', 'home'); + urlForwarding.forwardApp('home', 'home'); const featureCatalogue = { ...this.featuresCatalogueRegistry.setup() }; @@ -170,7 +170,7 @@ export class HomePublicPlugin public start( { application: { capabilities, currentAppId$ }, http }: CoreStart, - { kibanaLegacy }: HomePluginStartDependencies + { urlForwarding }: HomePluginStartDependencies ) { this.featuresCatalogueRegistry.start({ capabilities }); @@ -184,7 +184,7 @@ export class HomePublicPlugin if (appId === 'home') { // ...navigate to default app set by `kibana.defaultAppId`. // This doesn't do anything as along as the default settings are kept. - kibanaLegacy.navigateToDefaultApp({ overwriteHash: false }); + urlForwarding.navigateToDefaultApp({ overwriteHash: false }); } }); } diff --git a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts index 4c31ccee1243a..37657912deb95 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts @@ -248,11 +248,11 @@ export const getSavedObjects = (): SavedObject[] => [ version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('home.sampleData.ecommerceSpec.averageSalesPerRegionTitle', { - defaultMessage: '[eCommerce] Average Sales Per Region', + title: i18n.translate('home.sampleData.ecommerceSpec.salesCountMapTitle', { + defaultMessage: '[eCommerce] Sales Count Map', }), visState: - '{"title":"[eCommerce] Average Sales Per Region","type":"region_map","params":{"legendPosition":"bottomright","addTooltip":true,"colorSchema":"Blues","selectedLayer":{"attribution":"

    Made with NaturalEarth|Elastic Maps Service

    ","weight":1,"name":"World Countries","url":"https://vector.maps.elastic.co/blob/5659313586569216?elastic_tile_service_tos=agree&my_app_version=7.0.0-alpha1&license=f6c534b8-91b9-4499-8804-a2e9789ecc95","format":{"type":"geojson"},"fields":[{"name":"iso2","description":"Two letter abbreviation"},{"name":"name","description":"Country name"},{"name":"iso3","description":"Three letter abbreviation"}],"created_at":"2017-04-26T17:12:15.978370","tags":[],"id":5659313586569216,"layerId":"elastic_maps_service.World Countries","isEMS":true},"emsHotLink":"https://maps.elastic.co/v2#file/World Countries","selectedJoinField":{"name":"iso2","description":"Two letter abbreviation"},"isDisplayWarning":true,"wms":{"enabled":false,"options":{"format":"image/png","transparent":true}},"mapZoom":2,"mapCenter":[0,0],"outlineWeight":1,"showAllShapes":true},"aggs":[{"id":"1","enabled":true,"type":"avg","schema":"metric","params":{"field":"taxful_total_price","customLabel":"Average Sale"}},{"id":"2","enabled":true,"type":"terms","schema":"segment","params":{"field":"geoip.country_iso_code","size":100,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing"}}]}', + '{"title":"[eCommerce] Sales Count Map","type":"vega","aggs":[],"params":{"spec":"{\\n $schema: https://vega.github.io/schema/vega/v5.json\\n config: {\\n kibana: {type: \\"map\\", latitude: 25, longitude: -40, zoom: 3}\\n }\\n data: [\\n {\\n name: table\\n url: {\\n index: kibana_sample_data_ecommerce\\n %context%: true\\n %timefield%: order_date\\n body: {\\n size: 0\\n aggs: {\\n gridSplit: {\\n geotile_grid: {field: \\"geoip.location\\", precision: 4, size: 10000}\\n aggs: {\\n gridCentroid: {\\n geo_centroid: {\\n field: \\"geoip.location\\"\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n format: {property: \\"aggregations.gridSplit.buckets\\"}\\n transform: [\\n {\\n type: geopoint\\n projection: projection\\n fields: [\\n gridCentroid.location.lon\\n gridCentroid.location.lat\\n ]\\n }\\n ]\\n }\\n ]\\n scales: [\\n {\\n name: gridSize\\n type: linear\\n domain: {data: \\"table\\", field: \\"doc_count\\"}\\n range: [\\n 50\\n 1000\\n ]\\n }\\n ]\\n marks: [\\n {\\n name: gridMarker\\n type: symbol\\n from: {data: \\"table\\"}\\n encode: {\\n update: {\\n size: {scale: \\"gridSize\\", field: \\"doc_count\\"}\\n xc: {signal: \\"datum.x\\"}\\n yc: {signal: \\"datum.y\\"}\\n }\\n }\\n },\\n {\\n name: gridLabel\\n type: text\\n from: {data: \\"table\\"}\\n encode: {\\n enter: {\\n fill: {value: \\"firebrick\\"}\\n text: {signal: \\"datum.doc_count\\"}\\n }\\n update: {\\n x: {signal: \\"datum.x\\"}\\n y: {signal: \\"datum.y\\"}\\n dx: {value: -6}\\n dy: {value: 6}\\n fontSize: {value: 18}\\n fontWeight: {value: \\"bold\\"}\\n }\\n }\\n }\\n ]\\n}"}}', uiStateJSON: '{}', description: '', version: 1, diff --git a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts index c1f7fcb75b149..6f701d75e7d52 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts @@ -281,11 +281,10 @@ export const getSavedObjects = (): SavedObject[] => [ version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('home.sampleData.flightsSpec.originCountryTicketPricesTitle', { - defaultMessage: '[Flights] Origin Country Ticket Prices', + title: i18n.translate('home.sampleData.flightsSpec.departuresCountMapTitle', { + defaultMessage: '[Flights] Departures Count Map', }), - visState: - '{"title":"[Flights] Origin Country Ticket Prices","type":"region_map","params":{"legendPosition":"bottomright","addTooltip":true,"colorSchema":"Blues","selectedLayer":{"attribution":"

    Made with NaturalEarth | Elastic Maps Service

    ","name":"World Countries","weight":1,"format":{"type":"geojson"},"url":"https://vector.maps.elastic.co/blob/5659313586569216?elastic_tile_service_tos=agree&my_app_version=6.3.0&license=686f9ec6-d775-44f0-b334-38caf85da617","fields":[{"name":"iso2","description":"Two letter abbreviation"},{"name":"name","description":"Country name"},{"name":"iso3","description":"Three letter abbreviation"}],"created_at":"2017-04-26T17:12:15.978370","tags":[],"id":5659313586569216,"layerId":"elastic_maps_service.World Countries"},"selectedJoinField":{"name":"iso2","description":"Two letter abbreviation"},"isDisplayWarning":false,"wms":{"enabled":false,"options":{"format":"image/png","transparent":true},"baseLayersAreLoaded":{},"tmsLayers":[{"id":"road_map","url":"https://tiles.maps.elastic.co/v2/default/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana&my_app_version=6.3.0&license=686f9ec6-d775-44f0-b334-38caf85da617","minZoom":0,"maxZoom":18,"attribution":"

    © OpenStreetMap contributors | Elastic Maps Service

    ","subdomains":[]}],"selectedTmsLayer":{"id":"road_map","url":"https://tiles.maps.elastic.co/v2/default/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana&my_app_version=6.3.0&license=686f9ec6-d775-44f0-b334-38caf85da617","minZoom":0,"maxZoom":18,"attribution":"

    © OpenStreetMap contributors | Elastic Maps Service

    ","subdomains":[]}},"mapZoom":2,"mapCenter":[0,0],"outlineWeight":1,"showAllShapes":true},"aggs":[{"id":"1","enabled":true,"type":"avg","schema":"metric","params":{"field":"AvgTicketPrice"}},{"id":"2","enabled":true,"type":"terms","schema":"segment","params":{"field":"OriginCountry","size":100,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing"}}]}', + visState: '{\"title\":\"[Flights] Departure Count Map\",\"type\":\"vega\",\"aggs\":[],\"params\":{\"spec\":\"{\\n $schema: https://vega.github.io/schema/vega/v5.json\\n config: {\\n kibana: {type: \\\"map\\\", latitude: 25, longitude: -40, zoom: 3}\\n }\\n data: [\\n {\\n name: table\\n url: {\\n index: kibana_sample_data_flights\\n %context%: true\\n %timefield%: timestamp\\n body: {\\n size: 0\\n aggs: {\\n gridSplit: {\\n geotile_grid: {field: \\\"OriginLocation\\\", precision: 4, size: 10000}\\n aggs: {\\n gridCentroid: {\\n geo_centroid: {\\n field: \\\"OriginLocation\\\"\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n format: {property: \\\"aggregations.gridSplit.buckets\\\"}\\n transform: [\\n {\\n type: geopoint\\n projection: projection\\n fields: [\\n gridCentroid.location.lon\\n gridCentroid.location.lat\\n ]\\n }\\n ]\\n }\\n ]\\n scales: [\\n {\\n name: gridSize\\n type: linear\\n domain: {data: \\\"table\\\", field: \\\"doc_count\\\"}\\n range: [\\n 50\\n 1000\\n ]\\n }\\n ]\\n marks: [\\n {\\n name: gridMarker\\n type: symbol\\n from: {data: \\\"table\\\"}\\n encode: {\\n update: {\\n size: {scale: \\\"gridSize\\\", field: \\\"doc_count\\\"}\\n xc: {signal: \\\"datum.x\\\"}\\n yc: {signal: \\\"datum.y\\\"}\\n tooltip: {\\n signal: \\\"{flights: datum.doc_count}\\\"\\n }\\n }\\n }\\n }\\n ]\\n}\"}}', uiStateJSON: '{}', description: '', version: 1, diff --git a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts index 97258c21bc8f0..f8d39e6689fa8 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts @@ -51,11 +51,11 @@ export const getSavedObjects = (): SavedObject[] => [ version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('home.sampleData.logsSpec.uniqueVisitorsByCountryTitle', { - defaultMessage: '[Logs] Unique Visitors by Country', + title: i18n.translate('home.sampleData.logsSpec.visitorsMapTitle', { + defaultMessage: '[Logs] Visitors Map', }), visState: - '{"title":"[Logs] Unique Visitors by Country","type":"region_map","params":{"legendPosition":"bottomright","addTooltip":true,"colorSchema":"Reds","selectedLayer":{"attribution":"

    Made with NaturalEarth | Elastic Maps Service

    ","name":"World Countries","weight":1,"format":{"type":"geojson"},"url":"https://vector.maps.elastic.co/blob/5659313586569216?elastic_tile_service_tos=agree&my_app_version=6.2.3&license=77ab0ecf-a521-499d-bd52-fbd740bb81d0","fields":[{"name":"iso2","description":"Two letter abbreviation"},{"name":"name","description":"Country name"},{"name":"iso3","description":"Three letter abbreviation"}],"created_at":"2017-04-26T17:12:15.978370","tags":[],"id":5659313586569216,"layerId":"elastic_maps_service.World Countries"},"selectedJoinField":{"name":"iso2","description":"Two letter abbreviation"},"isDisplayWarning":false,"wms":{"enabled":false,"options":{"format":"image/png","transparent":true},"baseLayersAreLoaded":{},"tmsLayers":[{"id":"road_map","url":"https://tiles.maps.elastic.co/v2/default/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana&my_app_version=6.2.3&license=77ab0ecf-a521-499d-bd52-fbd740bb81d0","minZoom":0,"maxZoom":18,"attribution":"

    © OpenStreetMap contributors | Elastic Maps Service

    ","subdomains":[]}],"selectedTmsLayer":{"id":"road_map","url":"https://tiles.maps.elastic.co/v2/default/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana&my_app_version=6.2.3&license=77ab0ecf-a521-499d-bd52-fbd740bb81d0","minZoom":0,"maxZoom":18,"attribution":"

    © OpenStreetMap contributors | Elastic Maps Service

    ","subdomains":[]}},"mapZoom":2,"mapCenter":[0,0],"outlineWeight":1,"showAllShapes":true,"emsHotLink":null},"aggs":[{"id":"1","enabled":true,"type":"cardinality","schema":"metric","params":{"field":"clientip","customLabel":"Unique Visitors"}},{"id":"2","enabled":true,"type":"terms","schema":"segment","params":{"field":"geo.src","size":50,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing"}}]}', + '{"title":"[Logs] Visitors Map","type":"vega","aggs":[],"params":{"spec":"{\\n $schema: https://vega.github.io/schema/vega/v5.json\\n config: {\\n kibana: {type: \\"map\\", latitude: 30, longitude: -120, zoom: 3}\\n }\\n data: [\\n {\\n name: table\\n url: {\\n index: kibana_sample_data_logs\\n %context%: true\\n %timefield%: timestamp\\n body: {\\n size: 0\\n aggs: {\\n gridSplit: {\\n geotile_grid: {field: \\"geo.coordinates\\", precision: 5, size: 10000}\\n aggs: {\\n gridCentroid: {\\n geo_centroid: {\\n field: \\"geo.coordinates\\"\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n format: {property: \\"aggregations.gridSplit.buckets\\"}\\n transform: [\\n {\\n type: geopoint\\n projection: projection\\n fields: [\\n gridCentroid.location.lon\\n gridCentroid.location.lat\\n ]\\n }\\n ]\\n }\\n ]\\n scales: [\\n {\\n name: gridSize\\n type: linear\\n domain: {data: \\"table\\", field: \\"doc_count\\"}\\n range: [\\n 50\\n 1000\\n ]\\n }\\n {\\n name: bubbleColor\\n type: linear\\n domain: {\\n data: table\\n field: doc_count\\n }\\n range: [\\"rgb(249, 234, 197)\\",\\"rgb(243, 200, 154)\\",\\"rgb(235, 166, 114)\\", \\"rgb(231, 102, 76)\\"]\\n }\\n ]\\n marks: [\\n {\\n name: gridMarker\\n type: symbol\\n from: {data: \\"table\\"}\\n encode: {\\n update: {\\n fill: {\\n scale: bubbleColor\\n field: doc_count\\n }\\n size: {scale: \\"gridSize\\", field: \\"doc_count\\"}\\n xc: {signal: \\"datum.x\\"}\\n yc: {signal: \\"datum.y\\"}\\n tooltip: {\\n signal: \\"{flights: datum.doc_count}\\"\\n }\\n }\\n }\\n }\\n ]\\n}"}}', uiStateJSON: '{}', description: '', version: 1, diff --git a/src/plugins/index_pattern_management/kibana.json b/src/plugins/index_pattern_management/kibana.json index d0ad6a96065c3..6c3025485bbd7 100644 --- a/src/plugins/index_pattern_management/kibana.json +++ b/src/plugins/index_pattern_management/kibana.json @@ -3,6 +3,6 @@ "version": "kibana", "server": true, "ui": true, - "requiredPlugins": ["management", "data", "kibanaLegacy"], + "requiredPlugins": ["management", "data", "urlForwarding"], "requiredBundles": ["kibanaReact", "kibanaUtils"] } diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx index 22bc78ee0538e..13be9ca6c9c25 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx @@ -44,7 +44,7 @@ const newFieldPlaceholder = i18n.translate( export const CreateEditField = withRouter( ({ indexPattern, mode, fieldName, history }: CreateEditFieldProps) => { - const { uiSettings, chrome, notifications } = useKibana< + const { uiSettings, chrome, notifications, data } = useKibana< IndexPatternManagmentContext >().services; const spec = @@ -96,6 +96,7 @@ export const CreateEditField = withRouter( indexPattern={indexPattern} spec={spec} services={{ + saveIndexPattern: data.indexPatterns.save.bind(data.indexPatterns), redirectAway, }} /> diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx index a0eecef66ff93..d09836019b0bc 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx @@ -234,7 +234,13 @@ export const EditIndexPattern = withRouter( )} - + ); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/__snapshots__/indexed_fields_table.test.tsx.snap b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/__snapshots__/indexed_fields_table.test.tsx.snap index 47cabc4df662f..45253f6ad27c0 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/__snapshots__/indexed_fields_table.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/__snapshots__/indexed_fields_table.test.tsx.snap @@ -15,13 +15,10 @@ exports[`IndexedFieldsTable should filter based on the query bar 1`] = ` "displayName": "Elastic", "excluded": false, "format": undefined, - "indexPattern": Object { - "getNonScriptedFields": [Function], - }, "info": Array [], "name": "Elastic", "searchable": true, - "type": "name", + "type": "string", }, ] } @@ -44,9 +41,6 @@ exports[`IndexedFieldsTable should filter based on the type filter 1`] = ` "displayName": "timestamp", "excluded": false, "format": undefined, - "indexPattern": Object { - "getNonScriptedFields": [Function], - }, "info": Array [], "name": "timestamp", "type": "date", @@ -72,21 +66,15 @@ exports[`IndexedFieldsTable should render normally 1`] = ` "displayName": "Elastic", "excluded": false, "format": undefined, - "indexPattern": Object { - "getNonScriptedFields": [Function], - }, "info": Array [], "name": "Elastic", "searchable": true, - "type": "name", + "type": "string", }, Object { "displayName": "timestamp", "excluded": false, "format": undefined, - "indexPattern": Object { - "getNonScriptedFields": [Function], - }, "info": Array [], "name": "timestamp", "type": "date", @@ -95,9 +83,6 @@ exports[`IndexedFieldsTable should render normally 1`] = ` "displayName": "conflictingField", "excluded": false, "format": undefined, - "indexPattern": Object { - "getNonScriptedFields": [Function], - }, "info": Array [], "name": "conflictingField", "type": "conflict", diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx index 411bbe23e4761..319b9b2b3fce2 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { IndexPatternField, IIndexPattern, IndexPattern } from 'src/plugins/data/public'; +import { IndexPatternField, IIndexPattern } from 'src/plugins/data/public'; import { IndexedFieldsTable } from './indexed_fields_table'; jest.mock('@elastic/eui', () => ({ @@ -47,10 +47,8 @@ const indexPattern = ({ const mockFieldToIndexPatternField = (spec: Record) => { return new IndexPatternField( - indexPattern as IndexPattern, (spec as unknown) as IndexPatternField['spec'], - spec.displayName as string, - () => {} + spec.displayName as string ); }; @@ -59,7 +57,7 @@ const fields = [ name: 'Elastic', displayName: 'Elastic', searchable: true, - type: 'name', + type: 'string', }, { name: 'timestamp', displayName: 'timestamp', type: 'date' }, { name: 'conflictingField', displayName: 'conflictingField', type: 'conflict' }, diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx index 90f81a88b3da0..23977aac7fa7a 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx @@ -77,7 +77,6 @@ export class IndexedFieldsTable extends Component< return { ...field.spec, displayName: field.displayName, - indexPattern: field.indexPattern, format: getFieldFormat(indexPattern, field.name), excluded: fieldWildcardMatch ? fieldWildcardMatch(field.name) : false, info: helpers.getFieldInfo && helpers.getFieldInfo(indexPattern, field), diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx index ed50317aed6a0..84469a7e1fbd9 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_field_table.test.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { ScriptedFieldsTable } from '../scripted_fields_table'; -import { IIndexPattern } from '../../../../../../plugins/data/common/index_patterns'; +import { IIndexPattern, IndexPattern } from '../../../../../../plugins/data/common/index_patterns'; jest.mock('@elastic/eui', () => ({ EuiTitle: 'eui-title', @@ -54,7 +54,7 @@ const helpers = { const getIndexPatternMock = (mockedFields: any = {}) => ({ ...mockedFields } as IIndexPattern); describe('ScriptedFieldsTable', () => { - let indexPattern: IIndexPattern; + let indexPattern: IndexPattern; beforeEach(() => { indexPattern = getIndexPatternMock({ @@ -62,7 +62,7 @@ describe('ScriptedFieldsTable', () => { { name: 'ScriptedField', lang: 'painless', script: 'x++' }, { name: 'JustATest', lang: 'painless', script: 'z++' }, ], - }); + }) as IndexPattern; }); test('should render normally', async () => { @@ -71,6 +71,7 @@ describe('ScriptedFieldsTable', () => { indexPattern={indexPattern} helpers={helpers} painlessDocLink={'painlessDoc'} + saveIndexPattern={async () => {}} /> ); @@ -88,6 +89,7 @@ describe('ScriptedFieldsTable', () => { indexPattern={indexPattern} helpers={helpers} painlessDocLink={'painlessDoc'} + saveIndexPattern={async () => {}} /> ); @@ -105,15 +107,18 @@ describe('ScriptedFieldsTable', () => { test('should filter based on the lang filter', async () => { const component = shallow( [ - { name: 'ScriptedField', lang: 'painless', script: 'x++' }, - { name: 'JustATest', lang: 'painless', script: 'z++' }, - { name: 'Bad', lang: 'somethingElse', script: 'z++' }, - ], - })} + indexPattern={ + getIndexPatternMock({ + getScriptedFields: () => [ + { name: 'ScriptedField', lang: 'painless', script: 'x++' }, + { name: 'JustATest', lang: 'painless', script: 'z++' }, + { name: 'Bad', lang: 'somethingElse', script: 'z++' }, + ], + }) as IndexPattern + } painlessDocLink={'painlessDoc'} helpers={helpers} + saveIndexPattern={async () => {}} /> ); @@ -131,11 +136,14 @@ describe('ScriptedFieldsTable', () => { test('should hide the table if there are no scripted fields', async () => { const component = shallow( [], - })} + indexPattern={ + getIndexPatternMock({ + getScriptedFields: () => [], + }) as IndexPattern + } painlessDocLink={'painlessDoc'} helpers={helpers} + saveIndexPattern={async () => {}} /> ); @@ -153,6 +161,7 @@ describe('ScriptedFieldsTable', () => { indexPattern={indexPattern} helpers={helpers} painlessDocLink={'painlessDoc'} + saveIndexPattern={async () => {}} /> ); @@ -168,12 +177,15 @@ describe('ScriptedFieldsTable', () => { const removeScriptedField = jest.fn(); const component = shallow( {}} /> ); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx index 532af2757915b..08cc90faf75fa 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx @@ -27,10 +27,10 @@ import { import { Table, Header, CallOuts, DeleteScritpedFieldConfirmationModal } from './components'; import { ScriptedFieldItem } from './types'; -import { IIndexPattern } from '../../../../../../plugins/data/public'; +import { IndexPattern, DataPublicPluginStart } from '../../../../../../plugins/data/public'; interface ScriptedFieldsTableProps { - indexPattern: IIndexPattern; + indexPattern: IndexPattern; fieldFilter?: string; scriptedFieldLanguageFilter?: string; helpers: { @@ -39,6 +39,7 @@ interface ScriptedFieldsTableProps { }; onRemoveField?: () => void; painlessDocLink: string; + saveIndexPattern: DataPublicPluginStart['indexPatterns']['save']; } interface ScriptedFieldsTableState { @@ -68,7 +69,7 @@ export class ScriptedFieldsTable extends Component< } fetchFields = async () => { - const fields = await this.props.indexPattern.getScriptedFields(); + const fields = await (this.props.indexPattern.getScriptedFields() as ScriptedFieldItem[]); const deprecatedLangsInUse = []; const deprecatedLangs = getDeprecatedScriptingLanguages(); @@ -121,10 +122,11 @@ export class ScriptedFieldsTable extends Component< }; deleteField = () => { - const { indexPattern, onRemoveField } = this.props; + const { indexPattern, onRemoveField, saveIndexPattern } = this.props; const { fieldToDelete } = this.state; - indexPattern.removeScriptedField(fieldToDelete); + indexPattern.removeScriptedField(fieldToDelete!.name); + saveIndexPattern(indexPattern); if (onRemoveField) { onRemoveField(); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/__snapshots__/source_filters_table.test.tsx.snap b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/__snapshots__/source_filters_table.test.tsx.snap index a7b73624c4665..6a2b208c47987 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/__snapshots__/source_filters_table.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/__snapshots__/source_filters_table.test.tsx.snap @@ -14,17 +14,6 @@ exports[`SourceFiltersTable should add a filter 1`] = ` fieldWildcardMatcher={[Function]} indexPattern={ Object { - "save": [MockFunction] { - "calls": Array [ - Array [], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], - }, "sourceFilters": Array [ Object { "value": "tim*", @@ -108,17 +97,6 @@ exports[`SourceFiltersTable should remove a filter 1`] = ` fieldWildcardMatcher={[Function]} indexPattern={ Object { - "save": [MockFunction] { - "calls": Array [ - Array [], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], - }, "sourceFilters": Array [ Object { "clientId": 2, @@ -279,17 +257,6 @@ exports[`SourceFiltersTable should update a filter 1`] = ` fieldWildcardMatcher={[Function]} indexPattern={ Object { - "save": [MockFunction] { - "calls": Array [ - Array [], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], - }, "sourceFilters": Array [ Object { "clientId": 1, diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.test.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.test.tsx index fa048af7c7a70..395e1f3744e94 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.test.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.test.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { SourceFiltersTable } from './source_filters_table'; -import { IIndexPattern } from 'src/plugins/data/public'; +import { IndexPattern } from 'src/plugins/data/public'; jest.mock('@elastic/eui', () => ({ EuiButton: 'eui-button', @@ -52,7 +52,7 @@ const getIndexPatternMock = (mockedFields: any = {}) => ({ sourceFilters: [{ value: 'time*' }, { value: 'nam*' }, { value: 'age*' }], ...mockedFields, - } as IIndexPattern); + } as IndexPattern); describe('SourceFiltersTable', () => { test('should render normally', () => { @@ -61,6 +61,7 @@ describe('SourceFiltersTable', () => { indexPattern={getIndexPatternMock()} fieldWildcardMatcher={() => {}} filterFilter={''} + saveIndexPattern={async () => {}} /> ); @@ -73,6 +74,7 @@ describe('SourceFiltersTable', () => { indexPattern={getIndexPatternMock()} fieldWildcardMatcher={() => {}} filterFilter={''} + saveIndexPattern={async () => {}} /> ); @@ -88,6 +90,7 @@ describe('SourceFiltersTable', () => { })} filterFilter={''} fieldWildcardMatcher={() => {}} + saveIndexPattern={async () => {}} /> ); @@ -98,11 +101,14 @@ describe('SourceFiltersTable', () => { test('should show a delete modal', () => { const component = shallow( {}} + saveIndexPattern={async () => {}} /> ); @@ -112,15 +118,17 @@ describe('SourceFiltersTable', () => { }); test('should remove a filter', async () => { - const save = jest.fn(); + const saveIndexPattern = jest.fn(async () => {}); const component = shallow( {}} + saveIndexPattern={saveIndexPattern} /> ); @@ -129,47 +137,49 @@ describe('SourceFiltersTable', () => { await component.instance().deleteFilter(); component.update(); // We are not calling `.setState` directly so we need to re-render - expect(save).toBeCalled(); + expect(saveIndexPattern).toBeCalled(); expect(component).toMatchSnapshot(); }); test('should add a filter', async () => { - const save = jest.fn(); + const saveIndexPattern = jest.fn(async () => {}); const component = shallow( {}} + saveIndexPattern={saveIndexPattern} /> ); await component.instance().onAddFilter('na*'); component.update(); // We are not calling `.setState` directly so we need to re-render - expect(save).toBeCalled(); + expect(saveIndexPattern).toBeCalled(); expect(component).toMatchSnapshot(); }); test('should update a filter', async () => { - const save = jest.fn(); + const saveIndexPattern = jest.fn(async () => {}); const component = shallow( {}} + saveIndexPattern={saveIndexPattern} /> ); await component.instance().saveFilter({ clientId: 'tim*', value: 'ti*' }); component.update(); // We are not calling `.setState` directly so we need to re-render - expect(save).toBeCalled(); + expect(saveIndexPattern).toBeCalled(); expect(component).toMatchSnapshot(); }); }); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.tsx index e5c753886ea9f..b00648f124716 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.tsx @@ -22,14 +22,15 @@ import { createSelector } from 'reselect'; import { EuiSpacer } from '@elastic/eui'; import { AddFilter, Table, Header, DeleteFilterConfirmationModal } from './components'; -import { IIndexPattern } from '../../../../../../plugins/data/public'; +import { IndexPattern, DataPublicPluginStart } from '../../../../../../plugins/data/public'; import { SourceFiltersTableFilter } from './types'; export interface SourceFiltersTableProps { - indexPattern: IIndexPattern; + indexPattern: IndexPattern; filterFilter: string; fieldWildcardMatcher: Function; onAddOrRemoveFilter?: Function; + saveIndexPattern: DataPublicPluginStart['indexPatterns']['save']; } export interface SourceFiltersTableState { @@ -104,7 +105,7 @@ export class SourceFiltersTable extends Component< }; deleteFilter = async () => { - const { indexPattern, onAddOrRemoveFilter } = this.props; + const { indexPattern, onAddOrRemoveFilter, saveIndexPattern } = this.props; const { filterToDelete, filters } = this.state; indexPattern.sourceFilters = filters.filter((filter) => { @@ -112,7 +113,7 @@ export class SourceFiltersTable extends Component< }); this.setState({ isSaving: true }); - await indexPattern.save(); + await saveIndexPattern(indexPattern); if (onAddOrRemoveFilter) { onAddOrRemoveFilter(); @@ -124,12 +125,12 @@ export class SourceFiltersTable extends Component< }; onAddFilter = async (value: string) => { - const { indexPattern, onAddOrRemoveFilter } = this.props; + const { indexPattern, onAddOrRemoveFilter, saveIndexPattern } = this.props; indexPattern.sourceFilters = [...(indexPattern.sourceFilters || []), { value }]; this.setState({ isSaving: true }); - await indexPattern.save(); + await saveIndexPattern(indexPattern); if (onAddOrRemoveFilter) { onAddOrRemoveFilter(); @@ -140,7 +141,7 @@ export class SourceFiltersTable extends Component< }; saveFilter = async ({ clientId, value }: SourceFiltersTableFilter) => { - const { indexPattern } = this.props; + const { indexPattern, saveIndexPattern } = this.props; const { filters } = this.state; indexPattern.sourceFilters = filters.map((filter) => { @@ -155,7 +156,7 @@ export class SourceFiltersTable extends Component< }); this.setState({ isSaving: true }); - await indexPattern.save(); + await saveIndexPattern(indexPattern); this.updateFilters(); this.setState({ isSaving: false }); }; diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx index 6c9d6db8de130..101399ef02b73 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx @@ -35,6 +35,7 @@ import { IndexPattern, IndexPatternField, UI_SETTINGS, + DataPublicPluginStart, } from '../../../../../../plugins/data/public'; import { useKibana } from '../../../../../../plugins/kibana_react/public'; import { IndexPatternManagmentContext } from '../../../types'; @@ -48,6 +49,7 @@ import { getTabs, getPath, convertToEuiSelectOption } from './utils'; interface TabsProps extends Pick { indexPattern: IndexPattern; fields: IndexPatternField[]; + saveIndexPattern: DataPublicPluginStart['indexPatterns']['save']; } const searchAriaLabel = i18n.translate( @@ -71,7 +73,7 @@ const filterPlaceholder = i18n.translate( } ); -export function Tabs({ indexPattern, fields, history, location }: TabsProps) { +export function Tabs({ indexPattern, saveIndexPattern, fields, history, location }: TabsProps) { const { uiSettings, indexPatternManagementStart, docLinks } = useKibana< IndexPatternManagmentContext >().services; @@ -176,7 +178,7 @@ export function Tabs({ indexPattern, fields, history, location }: TabsProps) { indexedFieldTypeFilter={indexedFieldTypeFilter} helpers={{ redirectToRoute: (field: IndexPatternField) => { - history.push(getPath(field)); + history.push(getPath(field, indexPattern)); }, getFieldInfo: indexPatternManagementStart.list.getFieldInfo, }} @@ -191,11 +193,12 @@ export function Tabs({ indexPattern, fields, history, location }: TabsProps) { { - history.push(getPath(field)); + history.push(getPath(field, indexPattern)); }, }} onRemoveField={refreshFilters} @@ -210,6 +213,7 @@ export function Tabs({ indexPattern, fields, history, location }: TabsProps) { {getFilterSection(type)} + + + } + onChange={[Function]} + /> + {!(format as DurationFormat).isHuman() ? ( - - } - isInvalid={!!error} - error={hasDecimalError ? error : null} - > - { - this.onChange({ outputPrecision: e.target.value ? Number(e.target.value) : null }); - }} + <> + + } isInvalid={!!error} - /> - + error={hasDecimalError ? error : null} + > + { + this.onChange({ + outputPrecision: e.target.value ? Number(e.target.value) : null, + }); + }} + isInvalid={!!error} + /> + + + + } + checked={Boolean(formatParams.showSuffix)} + onChange={(e) => { + this.onChange({ showSuffix: !formatParams.showSuffix }); + }} + /> + + ) : null} diff --git a/src/plugins/index_pattern_management/public/components/field_editor/field_editor.test.tsx b/src/plugins/index_pattern_management/public/components/field_editor/field_editor.test.tsx index 96d3fc549ece0..23f52475d413d 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/field_editor.test.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/field_editor.test.tsx @@ -94,6 +94,8 @@ const field = { format: new Format(), }; +const services = { redirectAway: () => {}, saveIndexPattern: async () => {} }; + describe('FieldEditor', () => { let indexPattern: IndexPattern; @@ -122,7 +124,7 @@ describe('FieldEditor', () => { { indexPattern, spec: (field as unknown) as IndexPatternField, - services: { redirectAway: () => {} }, + services, }, mockContext ); @@ -138,12 +140,12 @@ describe('FieldEditor', () => { name: 'test', script: 'doc.test.value', }; - fieldList.push(testField as IndexPatternField); + fieldList.push((testField as unknown) as IndexPatternField); indexPattern.fields.getByName = (name) => { const flds = { [testField.name]: testField, }; - return flds[name] as IndexPatternField; + return (flds[name] as unknown) as IndexPatternField; }; const component = createComponentWithContext( @@ -151,7 +153,7 @@ describe('FieldEditor', () => { { indexPattern, spec: (testField as unknown) as IndexPatternField, - services: { redirectAway: () => {} }, + services, }, mockContext ); @@ -173,7 +175,7 @@ describe('FieldEditor', () => { const flds = { [testField.name]: testField, }; - return flds[name] as IndexPatternField; + return (flds[name] as unknown) as IndexPatternField; }; const component = createComponentWithContext( @@ -181,7 +183,7 @@ describe('FieldEditor', () => { { indexPattern, spec: (testField as unknown) as IndexPatternField, - services: { redirectAway: () => {} }, + services, }, mockContext ); @@ -198,7 +200,7 @@ describe('FieldEditor', () => { { indexPattern, spec: (testField as unknown) as IndexPatternField, - services: { redirectAway: () => {} }, + services, }, mockContext ); @@ -223,7 +225,7 @@ describe('FieldEditor', () => { { indexPattern, spec: (testField as unknown) as IndexPatternField, - services: { redirectAway: () => {} }, + services, }, mockContext ); diff --git a/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx b/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx index 6a3f632a9582e..4857a402cc4b2 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx @@ -133,6 +133,7 @@ export interface FieldEdiorProps { spec: IndexPatternField['spec']; services: { redirectAway: () => void; + saveIndexPattern: DataPublicPluginStart['indexPatterns']['save']; }; } @@ -757,23 +758,18 @@ export class FieldEditor extends PureComponent { - const { redirectAway } = this.props.services; + const { redirectAway, saveIndexPattern } = this.props.services; const { indexPattern } = this.props; const { spec } = this.state; - const remove = indexPattern.removeScriptedField(spec.name); - - if (remove) { - remove.then(() => { - const message = i18n.translate('indexPatternManagement.deleteField.deletedHeader', { - defaultMessage: "Deleted '{fieldName}'", - values: { fieldName: spec.name }, - }); - this.context.services.notifications.toasts.addSuccess(message); - redirectAway(); + indexPattern.removeScriptedField(spec.name); + saveIndexPattern(indexPattern).then(() => { + const message = i18n.translate('indexPatternManagement.deleteField.deletedHeader', { + defaultMessage: "Deleted '{fieldName}'", + values: { fieldName: spec.name }, }); - } else { + this.context.services.notifications.toasts.addSuccess(message); redirectAway(); - } + }); }; saveField = async () => { @@ -803,7 +799,7 @@ export class FieldEditor extends PureComponent { const message = i18n.translate('indexPatternManagement.deleteField.savedHeader', { defaultMessage: "Saved '{fieldName}'", diff --git a/src/plugins/index_pattern_management/public/mocks.ts b/src/plugins/index_pattern_management/public/mocks.ts index 6a9ef23e3732e..24aea961764a9 100644 --- a/src/plugins/index_pattern_management/public/mocks.ts +++ b/src/plugins/index_pattern_management/public/mocks.ts @@ -20,7 +20,7 @@ import { PluginInitializerContext } from 'src/core/public'; import { coreMock } from '../../../core/public/mocks'; import { managementPluginMock } from '../../management/public/mocks'; -import { kibanaLegacyPluginMock } from '../../kibana_legacy/public/mocks'; +import { urlForwardingPluginMock } from '../../url_forwarding/public/mocks'; import { dataPluginMock } from '../../data/public/mocks'; import { IndexPatternManagementSetup, @@ -65,7 +65,7 @@ const createInstance = async () => { const setup = plugin.setup(coreMock.createSetup(), { management: managementPluginMock.createSetupContract(), - kibanaLegacy: kibanaLegacyPluginMock.createSetupContract(), + urlForwarding: urlForwardingPluginMock.createSetupContract(), }); const doStart = () => plugin.start(coreMock.createStart(), { diff --git a/src/plugins/index_pattern_management/public/plugin.ts b/src/plugins/index_pattern_management/public/plugin.ts index ee1e00fcafd98..cfe0a23eb14dd 100644 --- a/src/plugins/index_pattern_management/public/plugin.ts +++ b/src/plugins/index_pattern_management/public/plugin.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; -import { KibanaLegacySetup } from '../../kibana_legacy/public'; +import { UrlForwardingSetup } from '../../url_forwarding/public'; import { IndexPatternManagementService, IndexPatternManagementServiceSetup, @@ -31,7 +31,7 @@ import { ManagementSetup } from '../../management/public'; export interface IndexPatternManagementSetupDependencies { management: ManagementSetup; - kibanaLegacy: KibanaLegacySetup; + urlForwarding: UrlForwardingSetup; } export interface IndexPatternManagementStartDependencies { @@ -62,7 +62,7 @@ export class IndexPatternManagementPlugin public setup( core: CoreSetup, - { management, kibanaLegacy }: IndexPatternManagementSetupDependencies + { management, urlForwarding }: IndexPatternManagementSetupDependencies ) { const kibanaSection = management.sections.section.kibana; @@ -73,8 +73,8 @@ export class IndexPatternManagementPlugin const newAppPath = `management/kibana/${IPM_APP_ID}`; const legacyPatternsPath = 'management/kibana/index_patterns'; - kibanaLegacy.forwardApp('management/kibana/index_pattern', newAppPath, (path) => '/create'); - kibanaLegacy.forwardApp(legacyPatternsPath, newAppPath, (path) => { + urlForwarding.forwardApp('management/kibana/index_pattern', newAppPath, (path) => '/create'); + urlForwarding.forwardApp(legacyPatternsPath, newAppPath, (path) => { const pathInApp = path.substr(legacyPatternsPath.length + 1); return pathInApp && `/patterns${pathInApp}`; }); diff --git a/src/plugins/input_control_vis/public/control/control.ts b/src/plugins/input_control_vis/public/control/control.ts index 91e8f1b26164b..da2dc7bab7cf7 100644 --- a/src/plugins/input_control_vis/public/control/control.ts +++ b/src/plugins/input_control_vis/public/control/control.ts @@ -81,9 +81,10 @@ export abstract class Control { abstract destroy(): void; format = (value: any) => { + const indexPattern = this.filterManager.getIndexPattern(); const field = this.filterManager.getField(); - if (field?.format?.convert) { - return field.format.convert(value); + if (field) { + return indexPattern.getFormatterForField(field).convert(value); } return value; diff --git a/src/plugins/input_control_vis/public/vis_controller.tsx b/src/plugins/input_control_vis/public/vis_controller.tsx index e4310960851ca..faea98b792291 100644 --- a/src/plugins/input_control_vis/public/vis_controller.tsx +++ b/src/plugins/input_control_vis/public/vis_controller.tsx @@ -18,8 +18,10 @@ */ import React from 'react'; +import { isEqual } from 'lodash'; import { render, unmountComponentAtNode } from 'react-dom'; +import { Subscription } from 'rxjs'; import { I18nStart } from 'kibana/public'; import { InputControlVis } from './components/vis/input_control_vis'; import { getControlFactory } from './control/control_factory'; @@ -34,11 +36,13 @@ import { VisParams, Vis } from '../../visualizations/public'; export const createInputControlVisController = (deps: InputControlVisDependencies) => { return class InputControlVisController { private I18nContext?: I18nStart['Context']; + private isLoaded = false; controls: Array; queryBarUpdateHandler: () => void; filterManager: FilterManager; updateSubsciption: any; + timeFilterSubscription: Subscription; visParams?: VisParams; constructor(public el: Element, public vis: Vis) { @@ -50,19 +54,32 @@ export const createInputControlVisController = (deps: InputControlVisDependencie this.updateSubsciption = this.filterManager .getUpdates$() .subscribe(this.queryBarUpdateHandler); + this.timeFilterSubscription = deps.data.query.timefilter.timefilter + .getTimeUpdate$() + .subscribe(() => { + if (this.visParams?.useTimeFilter) { + this.isLoaded = false; + } + }); } async render(visData: any, visParams: VisParams) { - this.visParams = visParams; - this.controls = []; - this.controls = await this.initControls(); - const [{ i18n }] = await deps.core.getStartServices(); - this.I18nContext = i18n.Context; + if (!this.I18nContext) { + const [{ i18n }] = await deps.core.getStartServices(); + this.I18nContext = i18n.Context; + } + if (!this.isLoaded || !isEqual(visParams, this.visParams)) { + this.visParams = visParams; + this.controls = []; + this.controls = await this.initControls(); + this.isLoaded = true; + } this.drawVis(); } destroy() { this.updateSubsciption.unsubscribe(); + this.timeFilterSubscription.unsubscribe(); unmountComponentAtNode(this.el); this.controls.forEach((control) => control.destroy()); } diff --git a/src/plugins/kibana_legacy/README.md b/src/plugins/kibana_legacy/README.md index 82bf3270589db..d66938cca6d13 100644 --- a/src/plugins/kibana_legacy/README.md +++ b/src/plugins/kibana_legacy/README.md @@ -1,6 +1,7 @@ # kibana-legacy -This plugin will contain several helpers and services to integrate pieces of the legacy Kibana app with the new Kibana platform. +This plugin contains several helpers and services to integrate pieces of the legacy Kibana app with the new Kibana platform. -Currently, the only service offered is the ability to register apps which are rendered in the legacy "kibana" plugin. +This plugin will be removed once all parts of legacy Kibana are removed from other plugins. +All of this plugin should be considered deprecated. New code should never integrate with the services provided from this plugin. \ No newline at end of file diff --git a/src/plugins/kibana_legacy/common/index.ts b/src/plugins/kibana_legacy/common/index.ts deleted file mode 100644 index 9c16d7b273862..0000000000000 --- a/src/plugins/kibana_legacy/common/index.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. - */ - -export * from './kbn_base_url'; -export * from './migrate_legacy_query'; diff --git a/src/plugins/kibana_legacy/common/kbn_base_url.ts b/src/plugins/kibana_legacy/common/kbn_base_url.ts deleted file mode 100644 index 69711626750ea..0000000000000 --- a/src/plugins/kibana_legacy/common/kbn_base_url.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 const kbnBaseUrl = '/app/kibana'; diff --git a/src/plugins/kibana_legacy/kibana.json b/src/plugins/kibana_legacy/kibana.json index 79264d95dcc27..e96b4859a36d0 100644 --- a/src/plugins/kibana_legacy/kibana.json +++ b/src/plugins/kibana_legacy/kibana.json @@ -2,6 +2,5 @@ "id": "kibanaLegacy", "version": "kibana", "server": true, - "ui": true, - "extraPublicDirs": ["common", "common/kbn_base_url"] + "ui": true } diff --git a/src/plugins/kibana_legacy/public/angular/angular_config.tsx b/src/plugins/kibana_legacy/public/angular/angular_config.tsx index eafcbfda3db00..9dae615bc3848 100644 --- a/src/plugins/kibana_legacy/public/angular/angular_config.tsx +++ b/src/plugins/kibana_legacy/public/angular/angular_config.tsx @@ -27,12 +27,12 @@ import { } from 'angular'; import $ from 'jquery'; import { set } from '@elastic/safer-lodash-set'; -import { cloneDeep, forOwn, get } from 'lodash'; +import { get } from 'lodash'; import * as Rx from 'rxjs'; import { ChromeBreadcrumb, EnvironmentMode, PackageInfo } from 'kibana/public'; import { History } from 'history'; -import { CoreStart, LegacyCoreStart } from 'kibana/public'; +import { CoreStart } from 'kibana/public'; import { isSystemApiRequest } from '../utils'; import { formatAngularHttpError, isAngularHttpError } from '../notify/lib'; @@ -72,32 +72,18 @@ function isDummyRoute($route: any, isLocalAngular: boolean) { export const configureAppAngularModule = ( angularModule: IModule, - newPlatform: - | LegacyCoreStart - | { - core: CoreStart; - readonly env: { - mode: Readonly; - packageInfo: Readonly; - }; - }, + newPlatform: { + core: CoreStart; + readonly env: { + mode: Readonly; + packageInfo: Readonly; + }; + }, isLocalAngular: boolean, getHistory?: () => History ) => { const core = 'core' in newPlatform ? newPlatform.core : newPlatform; - const packageInfo = - 'env' in newPlatform - ? newPlatform.env.packageInfo - : newPlatform.injectedMetadata.getLegacyMetadata(); - - if ('injectedMetadata' in newPlatform) { - forOwn(newPlatform.injectedMetadata.getInjectedVars(), (val, name) => { - if (name !== undefined) { - // The legacy platform modifies some of these values, clone to an unfrozen object. - angularModule.value(name, cloneDeep(val)); - } - }); - } + const packageInfo = newPlatform.env.packageInfo; angularModule .value('kbnVersion', packageInfo.version) @@ -105,13 +91,7 @@ export const configureAppAngularModule = ( .value('buildSha', packageInfo.buildSha) .value('esUrl', getEsUrl(core)) .value('uiCapabilities', core.application.capabilities) - .config( - setupCompileProvider( - 'injectedMetadata' in newPlatform - ? newPlatform.injectedMetadata.getLegacyMetadata().devMode - : newPlatform.env.mode.dev - ) - ) + .config(setupCompileProvider(newPlatform.env.mode.dev)) .config(setupLocationProvider()) .config($setupXsrfRequestInterceptor(packageInfo.version)) .run(capture$httpLoadingCount(core)) diff --git a/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js b/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js index b3fbe8baadec3..c34e2487b32d4 100644 --- a/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js +++ b/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js @@ -74,6 +74,7 @@ export function createTopNavDirective() { export const createTopNavHelper = ({ TopNavMenu }) => (reactDirective) => { return reactDirective(TopNavMenu, [ ['config', { watchDepth: 'value' }], + ['setMenuMountPoint', { watchDepth: 'reference' }], ['disabledButtons', { watchDepth: 'reference' }], ['query', { watchDepth: 'reference' }], diff --git a/src/plugins/kibana_legacy/public/index.ts b/src/plugins/kibana_legacy/public/index.ts index 27b940b0a456b..030dfd585fefb 100644 --- a/src/plugins/kibana_legacy/public/index.ts +++ b/src/plugins/kibana_legacy/public/index.ts @@ -24,7 +24,6 @@ export const plugin = (initializerContext: PluginInitializerContext) => new KibanaLegacyPlugin(initializerContext); export * from './plugin'; -export { kbnBaseUrl, migrateLegacyQuery } from '../common'; export { initAngularBootstrap } from './angular_bootstrap'; export { PaginateDirectiveProvider, PaginateControlsDirectiveProvider } from './paginate/paginate'; diff --git a/src/plugins/kibana_legacy/public/mocks.ts b/src/plugins/kibana_legacy/public/mocks.ts index a3cdb2106523c..f3aa015b6000b 100644 --- a/src/plugins/kibana_legacy/public/mocks.ts +++ b/src/plugins/kibana_legacy/public/mocks.ts @@ -22,12 +22,9 @@ import { KibanaLegacyPlugin } from './plugin'; export type Setup = jest.Mocked>; export type Start = jest.Mocked>; -const createSetupContract = (): Setup => ({ - forwardApp: jest.fn(), -}); +const createSetupContract = (): Setup => ({}); const createStartContract = (): Start => ({ - getForwards: jest.fn(), config: { defaultAppId: 'home', }, @@ -35,8 +32,6 @@ const createStartContract = (): Start => ({ turnHideWriteControlsOn: jest.fn(), getHideWriteControls: jest.fn(), }, - navigateToDefaultApp: jest.fn(), - navigateToLegacyKibanaUrl: jest.fn(), loadFontAwesome: jest.fn(), }); diff --git a/src/plugins/kibana_legacy/public/plugin.ts b/src/plugins/kibana_legacy/public/plugin.ts index 59ce88c07f4f4..8e62411fc34e9 100644 --- a/src/plugins/kibana_legacy/public/plugin.ts +++ b/src/plugins/kibana_legacy/public/plugin.ts @@ -18,78 +18,18 @@ */ import { PluginInitializerContext, CoreStart, CoreSetup } from 'kibana/public'; -import { Subscription } from 'rxjs'; import { ConfigSchema } from '../config'; import { getDashboardConfig } from './dashboard_config'; -import { navigateToDefaultApp } from './navigate_to_default_app'; -import { createLegacyUrlForwardApp } from './forward_app'; import { injectHeaderStyle } from './utils/inject_header_style'; -import { navigateToLegacyKibanaUrl } from './forward_app/navigate_to_legacy_kibana_url'; - -export interface ForwardDefinition { - legacyAppId: string; - newAppId: string; - rewritePath: (legacyPath: string) => string; -} export class KibanaLegacyPlugin { - private forwardDefinitions: ForwardDefinition[] = []; - private currentAppId: string | undefined; - private currentAppIdSubscription: Subscription | undefined; - constructor(private readonly initializerContext: PluginInitializerContext) {} public setup(core: CoreSetup<{}, KibanaLegacyStart>) { - core.application.register(createLegacyUrlForwardApp(core, this.forwardDefinitions)); - return { - /** - * Forwards URLs within the legacy `kibana` app to a new platform application. - * - * @param legacyAppId The name of the old app to forward URLs from - * @param newAppId The name of the new app that handles the URLs now - * @param rewritePath Function to rewrite the legacy sub path of the app to the new path in the core app. - * If none is provided, it will just strip the prefix of the legacyAppId away - * - * path into the new path - * - * Example usage: - * ``` - * kibanaLegacy.forwardApp( - * 'old', - * 'new', - * path => { - * const [, id] = /old/item\/(.*)$/.exec(path) || []; - * if (!id) { - * return '#/home'; - * } - * return '#/items/${id}'; - * } - * ); - * ``` - * This will cause the following redirects: - * - * * app/kibana#/old/ -> app/new#/home - * * app/kibana#/old/item/123 -> app/new#/items/123 - * - */ - forwardApp: ( - legacyAppId: string, - newAppId: string, - rewritePath?: (legacyPath: string) => string - ) => { - this.forwardDefinitions.push({ - legacyAppId, - newAppId, - rewritePath: rewritePath || ((path) => `#${path.replace(`/${legacyAppId}`, '') || '/'}`), - }); - }, - }; + return {}; } public start({ application, http: { basePath }, uiSettings }: CoreStart) { - this.currentAppIdSubscription = application.currentAppId$.subscribe((currentAppId) => { - this.currentAppId = currentAppId; - }); injectHeaderStyle(uiSettings); return { /** @@ -97,31 +37,6 @@ export class KibanaLegacyPlugin { * @deprecated */ dashboardConfig: getDashboardConfig(!application.capabilities.dashboard.showWriteControls), - /** - * Navigates to the app defined as kibana.defaultAppId. - * This takes redirects into account and uses the right mechanism to navigate. - */ - navigateToDefaultApp: ( - { overwriteHash }: { overwriteHash: boolean } = { overwriteHash: true } - ) => { - navigateToDefaultApp( - this.initializerContext.config.get().defaultAppId, - this.forwardDefinitions, - application, - basePath, - this.currentAppId, - overwriteHash - ); - }, - /** - * Resolves the provided hash using the registered forwards and navigates to the target app. - * If a navigation happened, `{ navigated: true }` will be returned. - * If no matching forward is found, `{ navigated: false }` will be returned. - * @param hash - */ - navigateToLegacyKibanaUrl: (hash: string) => { - return navigateToLegacyKibanaUrl(hash, this.forwardDefinitions, basePath, application); - }, /** * Loads the font-awesome icon font. Should be removed once the last consumer has migrated to EUI * @deprecated @@ -129,11 +44,6 @@ export class KibanaLegacyPlugin { loadFontAwesome: async () => { await import('./font_awesome'); }, - /** - * @deprecated - * Just exported for wiring up with legacy platform, should not be used. - */ - getForwards: () => this.forwardDefinitions, /** * @deprecated * Just exported for wiring up with dashboard mode, should not be used. @@ -141,12 +51,6 @@ export class KibanaLegacyPlugin { config: this.initializerContext.config.get(), }; } - - public stop() { - if (this.currentAppIdSubscription) { - this.currentAppIdSubscription.unsubscribe(); - } - } } export type KibanaLegacySetup = ReturnType; diff --git a/src/plugins/kibana_legacy/public/utils/index.ts b/src/plugins/kibana_legacy/public/utils/index.ts index a32cd5e40a047..590a75ffeed9e 100644 --- a/src/plugins/kibana_legacy/public/utils/index.ts +++ b/src/plugins/kibana_legacy/public/utils/index.ts @@ -18,7 +18,6 @@ */ export * from './system_api'; -export * from './normalize_path'; // @ts-ignore export { KbnAccessibleClickProvider } from './kbn_accessible_click'; // @ts-ignore diff --git a/src/plugins/kibana_legacy/server/index.ts b/src/plugins/kibana_legacy/server/index.ts index 3ddcac1517f74..c447f44c16a89 100644 --- a/src/plugins/kibana_legacy/server/index.ts +++ b/src/plugins/kibana_legacy/server/index.ts @@ -50,8 +50,6 @@ export const config: PluginConfigDescriptor = { ], }; -export { kbnBaseUrl, migrateLegacyQuery } from '../common'; - class Plugin { public setup(core: CoreSetup) {} diff --git a/src/plugins/kibana_react/public/field_button/field_button.tsx b/src/plugins/kibana_react/public/field_button/field_button.tsx index e5833b261946a..26e6453e4c48b 100644 --- a/src/plugins/kibana_react/public/field_button/field_button.tsx +++ b/src/plugins/kibana_react/public/field_button/field_button.tsx @@ -100,7 +100,16 @@ export function FieldButton({ return (
    -