diff --git a/.eslintrc.js b/.eslintrc.js index 6155d182f7cd7..60d26cbfbab73 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -49,9 +49,9 @@ const ELASTIC_LICENSE_HEADER = ` */ `; -const allMochaRules = {}; +const allMochaRulesOff = {}; Object.keys(require('eslint-plugin-mocha').rules).forEach(k => { - allMochaRules['mocha/' + k] = 'off'; + allMochaRulesOff['mocha/' + k] = 'off'; }); module.exports = { @@ -524,7 +524,7 @@ module.exports = { */ { files: ['test/harden/*.js'], - rules: allMochaRules, + rules: allMochaRulesOff, }, /** diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 552b2666eb3ea..a9af160d02084 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -88,7 +88,6 @@ /x-pack/test/functional/services/ml.ts @elastic/ml-ui # ML team owns the transform plugin, ES team added here for visibility # because the plugin lives in Kibana's Elasticsearch management section. -/x-pack/legacy/plugins/transform/ @elastic/ml-ui @elastic/es-ui /x-pack/plugins/transform/ @elastic/ml-ui @elastic/es-ui /x-pack/test/functional/apps/transform/ @elastic/ml-ui /x-pack/test/functional/services/transform_ui/ @elastic/ml-ui diff --git a/.github/ISSUE_TEMPLATE/v8_breaking_change.md b/.github/ISSUE_TEMPLATE/v8_breaking_change.md new file mode 100644 index 0000000000000..99f779c288f5b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/v8_breaking_change.md @@ -0,0 +1,36 @@ +--- +name: 8.0 Breaking change +about: Breaking changes from 7.x -> 8.0 +title: "[Breaking change]" +labels: Team:Elasticsearch UI, Feature:Upgrade Assistant +assignees: '' + +--- + +## Change description + +**Which release will ship the breaking change?** + + + +**Describe the change. How will it manifest to users?** + +**What percentage of users will be affected?** + + + +**What can users to do to address the change manually?** + + + +**How could we make migration easier with the Upgrade Assistant?** + +**Are there any edge cases?** + +## Test Data + +Provide test data. We can’t build a solution without data to test it against. + +## Cross links + +Cross-link to relevant [Elasticsearch breaking changes](https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-8.0.html). \ No newline at end of file diff --git a/.gitignore b/.gitignore index efb5c57774633..02c9057e3f83a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /.es .DS_Store .node_binaries +.native_modules node_modules !/src/dev/npm/integration_tests/__fixtures__/fixture1/node_modules !/src/dev/notice/__fixtures__/node_modules diff --git a/Jenkinsfile b/Jenkinsfile index 742aec1d4e7ab..d43da6e0bee04 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -3,7 +3,7 @@ library 'kibana-pipeline-library' kibanaLibrary.load() -kibanaPipeline(timeoutMinutes: 135) { +kibanaPipeline(timeoutMinutes: 135, checkPrChanges: true) { githubPr.withDefaultPrComments { catchError { retryable.enable() diff --git a/docs/developer/visualize/development-create-visualization.asciidoc b/docs/developer/visualize/development-create-visualization.asciidoc deleted file mode 100644 index e38b76471ab25..0000000000000 --- a/docs/developer/visualize/development-create-visualization.asciidoc +++ /dev/null @@ -1,463 +0,0 @@ -[[development-create-visualization]] -=== Developing Visualizations - -This is a short description of functions and interfaces provided. For more information you should check the kibana -source code and the existing visualizations provided with it. - -- <> -* <> -* <> -- <> -* <> -* <> -- <> -* <> -* <> -* <> -- <> -* <> -* <> -* <> -- <> -* <> -- <> - -[[development-visualization-factory]] -=== Visualization Factory - -Use the `VisualizationFactory` to create a new visualization. -The creation-methods create a new visualization tied to the underlying rendering technology. -You should also register the visualization with `VisTypesRegistryProvider`. - -["source","js"] ------------ -import { VisFactoryProvider } from 'ui/vis/vis_factory'; -import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; - -const MyNewVisType = (Private) => { - const VisFactory = Private(VisFactoryProvider); - - return VisFactory.createBaseVisualization({ - name: 'my_new_vis', - title: 'My New Vis', - icon: 'my_icon', - description: 'Cool new chart', - ... - }); -} - -VisTypesRegistryProvider.register(MyNewVisType); ------------ - -The list of common parameters: - -- *name*: unique visualization name, only lowercase letters and underscore -- *title*: title of your visualization as displayed in kibana -- *icon*: the https://elastic.github.io/eui/#/display/icons[EUI icon] type to use for this visualization -- *image*: instead of an icon you can provide a SVG image (imported) -- *description*: description of your visualization as shown in kibana -- *hidden*: if set to true, will hide the type from showing up in the visualization wizard -- *visConfig*: object holding visualization parameters -- *visConfig.defaults*: object holding default visualization configuration -- *visualization*: A constructor function for a Visualization. -- *requestHandler*: one of the available request handlers or a for a custom request handler -- *responseHandler*: one of the available response handlers or a for a custom response handler -- *editor*: Editor class for custom one -- *editorConfig*: object holding editor parameters -- *options.showTimePicker*: show or hide time filter (defaults to true) -- *options.showQueryBar*: show or hide query bar (defaults to true) -- *options.showFilterBar*: show or hide filter bar (defaults to true) -- *options.showIndexSelection*: show or hide index selection (defaults to true) -- *stage*: Set this to "experimental" to mark your visualization as experimental. -Experimental visualizations can also be disabled from the advanced settings. (defaults to "production") -- *feedbackMessage*: You can provide a message (which can contain HTML), that will be appended -to the experimental notification in visualize, if your visualization is experimental or in lab mode. - - -Each of the factories have some of the custom parameters, which will be described below. - -[[development-base-visualization-type]] -==== Base Visualization Type -The base visualization type does not make any assumptions about the rendering technology you are going to use and -works with pure JavaScript. It is the visualization type we recommend to use. - -You need to provide a type with a constructor function, a render method which will be called every time -options or data change, and a destroy method which will be called to cleanup. - -The render function receives the data object and status object which tells what actually changed. -Render function needs to return a promise, which should be resolved once the visualization is done rendering. - -The status object provides information about changes since the previous render call. -Due to performance reasons you need to opt-in for each status change, that you want -to be informed about by Kibana. This is done by using the `requiresUpdateStatus` key -in your visualization registration object. You pass it an array, that contains all -the status updates you want to receive. By default none of it will be calculated. - -The following snippet shows explain all available status updates. You should only -activate those changes, that you actually use in your `render` method. - -["source","js"] ------------ -import { Status } from 'ui/vis/update_status'; - -// ... -return VisFactory.createBaseVisualization({ - // ... - requiresUpdateStatus: [ - // Check for changes in the aggregation configuration for the visualization - Status.AGGS, - // Check for changes in the actual data returned from Elasticsearch - Status.DATA, - // Check for changes in the parameters (configuration) for the visualization - Status.PARAMS, - // Check if the visualization has changes its size - Status.RESIZE, - // Check if the time range for the visualization has been changed - Status.TIME, - // Check if the UI state of the visualization has been changed - Status.UI_STATE - ] -}); ------------ - -If you activate any of these status updates, the `status` object passed as second -parameter to the `render` method will contain a key for that status (e.g. `status[Status.DATA]`), -that is either `true` if a change has been detected or `false` otherwise. - - -image::images/visualize-flow.png[Main Flow] - -- Your visualizations constructor will get called with `vis` object and the DOM-element to which it should render. -At this point you should prepare everything for rendering, but not render yet -- `` component monitors `appState`, `uiState` and `vis` for changes -- on changes the ``-directive will call your `requestHandler`. -Implementing a request handler is optional, as you might use one of the provided ones. -- response from `requestHandler` will get passed to `responseHandler`. It should convert raw data to something that -can be consumed by visualization. Implementing `responseHandler` is optional, as you might use of of the provided ones. -- On new data from the `responseHandler` or on when the size of the surrounding DOM-element has changed, -your visualization `render`-method gets called. It needs to return a promise which resolves once the visualization -is done rendering. -- the visualization should call `vis.updateState()` any time something has changed that requires to -re-render or fetch new data. - -["source","js"] ------------ -import { VisFactoryProvider } from 'ui/vis/vis_factory'; -import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; - -class MyVisualization { - constructor(el, vis) { - this.el = el; - this.vis = vis; - } - async render(visData, status) { - ... - return 'done rendering'; - } - destroy() { - console.log('destroying'); - } -} - -const MyNewVisType = (Private) => { - const VisFactory = Private(VisFactoryProvider); - - return VisFactory.createBaseVisualization({ - name: 'my_new_vis', - title: 'My New Vis', - icon: 'my_icon', - description: 'Cool new chart', - visualization: MyVisualization - }); -} - -VisTypesRegistryProvider.register(MyNewVisType); ------------ - -[[development-react-visualization-type]] -==== React Visualization Type -React visualization type assumes you are using React as your rendering technology. -Just pass in a React component to `visConfig.component`. - -The visualization will receive `vis`, `appState`, `updateStatus` and `visData` as props. -It also has a `renderComplete` property, which needs to be called once the rendering has completed. - -["source","js"] ------------ -import { ReactComponent } from './my_react_component'; - -const MyNewVisType = (Private) => { - const VisFactory = Private(VisFactoryProvider); - - return VisFactory.createReactVisualization({ - name: 'my_new_vis', - title: 'My New Vis', - icon: 'my_icon', - description: 'Cool new chart', - visConfig: { - component: ReactComponent - } - }); -} ------------ - -[[development-vis-editors]] -=== Visualization Editors -By default, visualizations will use the `default` editor. -This is the sidebar editor you see in many of the Kibana visualizations. You can also write your own editor. - -[[development-default-editor]] -==== `default` editor controller -The default editor controller receives an `optionsTemplate` or `optionTabs` parameter. -These tabs should be React components. - -["source","js"] ------------ -{ - name: 'my_new_vis', - title: 'My New Vis', - icon: 'my_icon', - description: 'Cool new chart', - editorConfig: { - optionsTemplate: MyReactComponent // or if multiple tabs are required: - optionTabs: [ - { title: 'tab 3', editor: MyReactComponent } - ] - } - } ------------ - -[[development-custom-editor]] -==== custom editor controller -You can create a custom editor controller. To do so pass an Editor object (the same format as VisController class). -You can make your controller take extra configuration which is passed to the editorConfig property. - -["source","js"] ------------ -import { VisFactoryProvider } from 'ui/vis/vis_factory'; - -class MyEditorController { - constructor(el, vis) { - this.el = el; - this.vis = vis; - this.config = vis.type.editorConfig; - } - async render(visData) { - console.log(this.config.my); - ... - return 'done rendering'; - } - destroy() { - console.log('destroying'); - } -} - -const MyNewVisType = (Private) => { - const VisFactory = Private(VisFactoryProvider); - - return VisFactory.createAngularVisualization({ - name: 'my_new_vis', - title: 'My New Vis', - icon: 'my_icon', - description: 'Cool new chart', - editor: MyEditorController, - editorConfig: { my: 'custom config' } - }); -} - -VisTypesRegistryProvider.register(MyNewVisType); ------------ - -[[development-visualization-request-handlers]] -=== Visualization Request Handlers -Request handler gets called when one of the following keys on AppState change: -`vis`, `query`, `filters` or `uiState` and when the time filter is updated. On top -of that it will also get called on force refresh. - -By default visualizations will use the `courier` request handler. They can also choose to use any of the other provided -request handlers. It is also possible to define your own request handler -(which you can then register to be used by other visualizations). - -[[development-default-request-handler]] -==== courier request handler -'courier' is the default request handler which works with the 'default' side bar editor. - -[[development-none-request-handler]] -==== `none` request handler -Using 'none' as your request handles means your visualization does not require any data to be requested. - -[[development-custom-request-handler]] -==== custom request handler -You can define your custom request handler by providing a function with the following signature: -`function (vis, { uiState, appState, timeRange }) { ... }` - -The `timeRange` will be an object with a `from` and `to` key, that can contain -datemath expressions, like `now-7d`. You can use the `datemath` library to parse -them. - -This function must return a promise, which should get resolved with new data that will be passed to responseHandler. - -It's up to function to decide when it wants to issue a new request or return previous data -(if none of the objects relevant to the request handler changed). - -["source","js"] ------------ -import { VisFactoryProvider } from 'ui/vis/vis_factory'; - -const myRequestHandler = async (vis, { appState, uiState, timeRange }) => { - const data = ... parse ... - return data; -}; - -const MyNewVisType = (Private) => { - const VisFactory = Private(VisFactoryProvider); - - return VisFactory.createAngularVisualization({ - name: 'my_new_vis', - title: 'My New Vis', - icon: 'my_icon', - description: 'Cool new chart', - requestHandler: myRequestHandler - }); -} - -VisTypesRegistryProvider.register(MyNewVisType); ------------ - -[[development-visualization-response-handlers]] -=== Visualization Response Handlers -The response handler is a function that receives the data from a request handler, as well as an instance of Vis object. -Its job is to convert the data to a format visualization can use. By default 'default' request handler is used -which produces a table representation of the data. The data object will then be passed to visualization. -This response matches the visData property of the directive. - -[[development-default-response-handler]] -==== default response handler -The default response handler converts pure elasticsearch responses into a tabular format. -It is the recommended responseHandler. The response object contains a table property, -which is an array of all the tables in the response. Each of the table objects has two properties: - -- `columns`: array of column objects, where each column object has a title property and an aggConfig property -- `rows`: array of rows, where each row is an array of non formatted cell values - -Here is an example of a response with 1 table, 3 columns and 2 rows: - -["source","js"] ------------ -{ - tables: [{ - columns: [{ - title: 'column1', - aggConfig: ... - },{ - title: 'column2', - aggConfig: ... - },{ - title: 'column3', - aggConfig: ... - }], - rows: [ - [ '404', 1262, 12.5 ] - [ '200', 343546, 60.1 ] - ] - }]; -} ------------ - -[[development-none-response-handler]] -==== none response handler -None response handler is an identity function, which will return the same data it receives. - -[[development-custom-response-handler]] -==== custom response handler -You can define your custom response handler by providing a function with the following definition: -'function (vis, response) { ... }'. - -Function should return the transformed data object that visualization can consume. - -["source","js"] ------------ -import { VisFactoryProvider } from 'ui/vis/vis_factory'; - -const myResponseHandler = (vis, response) => { - // transform the response (based on vis object?) - const response = ... transform data ...; - return response; -}; - -const MyNewVisType(Private) => { - const VisFactory = Private(VisFactoryProvider); - - return VisFactory.createAngularVisualization({ - name: 'my_new_vis', - title: 'My New Vis', - icon: 'my_icon', - description: 'Cool new chart', - responseHandler: myResponseHandler - }); -} - -VisTypesRegistryProvider.register(MyNewVisType); ------------ - -[[development-vis-object]] -=== Vis object -The `vis` object holds the visualization state and is the window into kibana: - -- *vis.params*: holds the visualization parameters -- *vis.indexPattern*: selected index pattern object -- *vis.getState()*: gets current visualization state -- *vis.updateState()*: updates current state with values from `vis.params` -- *vis.resetState()*: resets `vis.params` to the values in the current state -- *vis.forceReload()*: forces whole cycle (request handler gets called) -- *vis.getUiState()*: gets UI state of visualization -- *vis.uiStateVal(name, val)*: updates a property in UI state -- *vis.isEditorMode()*: returns true if in editor mode -- *vis.API.timeFilter*: allows you to access time filter -- *vis.API.queryFilter*: gives you access to queryFilter -- *vis.API.events.click*: default click handler -- *vis.API.events.brush*: default brush handler - -The visualization gets all its parameters in `vis.params`, which are default values merged with the current state. -If the visualization needs to update the current state, it should update the `vis.params` and call `vis.updateState()` -which will inform about the change, which will call request and response handler and then your -visualization's render method. - -For the parameters that should not be saved with the visualization you should use the UI state. -These hold viewer-specific state, such as popup open/closed, custom colors applied to the series etc. - -You can access the filter bar and time filter through the objects defined on `vis.API` - -[[development-vis-timefilter]] -==== timeFilter - -Update the timefilter time values and call update() method on it to update the time filter - -["source","js"] ------------ - timefilter.time.from = moment(ranges.xaxis.from); - timefilter.time.to = moment(ranges.xaxis.to); - timefilter.time.mode = 'absolute'; - timefilter.update(); ------------ - - -[[development-aggconfig]] -=== AggConfig object - -The AggConfig object represents an aggregation search to Elasticsearch, -plus some additional functionality to manage data-values that belong to this aggregation. -This is primarily used internally in Kibana, but you may find you have a need for it -when writing your own visualization. Here we provide short description of some of the methods on it, -however the best reference would be to actually check the source code. - - -- *fieldFormatter()* : returns a function which will format your value according to field formatters defined on -the field. The type can be either 'text' or 'html'. -- *makeLabel()* : gets the label for the aggregation -- *isFilterable()* : return true if aggregation is filterable (you can then call createFilter) -- *createFilter(bucketKey)* : creates a filter for specific bucket key -- *getValue(bucket)* : gets value for a specific bucket -- *getField()* : gets the field used for this aggregation -- *getFieldDisplayName()* : gets field display name -- *getAggParams()* : gets the arguments to the aggregation diff --git a/docs/developer/visualize/development-embedding-visualizations.asciidoc b/docs/developer/visualize/development-embedding-visualizations.asciidoc deleted file mode 100644 index 1c275e7831f74..0000000000000 --- a/docs/developer/visualize/development-embedding-visualizations.asciidoc +++ /dev/null @@ -1,58 +0,0 @@ -[[development-embedding-visualizations]] -=== Embedding Visualizations - -To embed visualization use the `VisualizeLoader`. - -==== VisualizeLoader - -The `VisualizeLoader` class is the easiest way to embed a visualization into your plugin. -It will take care of loading the data and rendering the visualization. - -To get an instance of the loader, do the following: - -["source","js"] ------------ -import { getVisualizeLoader } from 'ui/visualize/loader'; - -getVisualizeLoader().then((loader) => { - // You now have access to the loader -}); ------------ - -The loader exposes the following methods: - -- `getVisualizationList()`: which returns promise which gets resolved with a list of saved visualizations -- `embedVisualizationWithId(container, savedId, params)`: which embeds visualization by id -- `embedVisualizationWithSavedObject(container, savedObject, params)`: which embeds visualization from saved object - -Depending on which embed method you are using, you either pass in the id of the -saved object for the visualization, or a `savedObject`, that you can retrieve via -the `savedVisualizations` Angular service by its id. The `savedObject` give you access -to the filter and query logic and allows you to attach listeners to the visualizations. -For a more complex use-case you usually want to use that method. - -`container` should be a DOM element (jQuery wrapped or regular DOM element) into which the visualization should be embedded -`params` is a parameter object specifying several parameters, that influence rendering. - -You will find a detailed description of all the parameters in the inline docs -in the {repo}blob/{branch}/src/legacy/ui/public/visualize/loader/types.ts[loader source code]. - -Both methods return an `EmbeddedVisualizeHandler`, that gives you some access -to the visualization. The `embedVisualizationWithSavedObject` method will return -the handler immediately from the method call, whereas the `embedVisualizationWithId` -will return a promise, that resolves with the handler, as soon as the `id` could be -found. It will reject, if the `id` is invalid. - -The returned `EmbeddedVisualizeHandler` itself has the following methods and properties: - -- `destroy()`: destroys the embedded visualization. You MUST call that method when navigating away - or destroying the DOM node you have embedded into. -- `getElement()`: a reference to the jQuery wrapped DOM element, that renders the visualization -- `whenFirstRenderComplete()`: will return a promise, that resolves as soon as the visualization has - finished rendering for the first time -- `addRenderCompleteListener(listener)`: will register a listener to be called whenever - a rendering of this visualization finished (not just the first one) -- `removeRenderCompleteListener(listener)`: removes an event listener from the handler again - -You can find the detailed `EmbeddedVisualizeHandler` documentation in its -{repo}blob/{branch}/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.ts[source code]. \ No newline at end of file diff --git a/docs/developer/visualize/development-visualize-index.asciidoc b/docs/developer/visualize/development-visualize-index.asciidoc index 1cdeac7540ce4..daefc434e1f18 100644 --- a/docs/developer/visualize/development-visualize-index.asciidoc +++ b/docs/developer/visualize/development-visualize-index.asciidoc @@ -1,21 +1,26 @@ [[development-visualize-index]] == Developing Visualizations -Kibana Visualizations are the easiest way to add additional functionality to Kibana. -This part of documentation is split into two parts. -The first part tells you all you need to know on how to embed existing Kibana Visualizations in your plugin. -The second step explains how to create your own custom visualization. - [IMPORTANT] ============================================== -These pages document internal APIs and are not guaranteed to be supported across future versions of Kibana. -However, these docs will be kept up-to-date to reflect the current implementation of Visualization plugins in Kibana. +These pages document internal APIs and are not guaranteed to be supported across future versions of Kibana. ============================================== -* <> -* <> +The internal APIs for creating custom visualizations are in a state of heavy churn as +they are being migrated to the new Kibana platform, and large refactorings have been +happening across minor releases in the `7.x` series. In particular, in `7.5` and later +we have made significant changes to the legacy APIs as we work to gradually replace them. +As a result, starting in `7.5` we have removed the documentation for the legacy APIs +to prevent confusion. We expect to be able to create new documentation later in `7.x` +when the visualizations plugin has been completed. -include::development-embedding-visualizations.asciidoc[] +We would recommend waiting until later in `7.x` to upgrade your plugins if possible. +If you would like to keep up with progress on the visualizations plugin in the meantime, +here are a few resources: -include::development-create-visualization.asciidoc[] \ No newline at end of file +* The <> documentation, where we try to capture any changes to the APIs as they occur across minors. +* link:https://github.com/elastic/kibana/issues/44121[Meta issue] which is tracking the move of the plugin to the new Kibana platform +* Our link:https://www.elastic.co/blog/join-our-elastic-stack-workspace-on-slack[Elastic Stack workspace on Slack]. +* The {repo}blob/{branch}/src/plugins/visualizations[source code], which will continue to be +the most accurate source of information. diff --git a/docs/development/core/public/kibana-plugin-core-public.icontextprovider.md b/docs/development/core/public/kibana-plugin-core-public.icontextprovider.md index 4778415ab2391..97f7bad8e9911 100644 --- a/docs/development/core/public/kibana-plugin-core-public.icontextprovider.md +++ b/docs/development/core/public/kibana-plugin-core-public.icontextprovider.md @@ -9,7 +9,7 @@ A function that returns a context value for a specific key of given context type Signature: ```typescript -export declare type IContextProvider, TContextName extends keyof HandlerContextType> = (context: Partial>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; +export declare type IContextProvider, TContextName extends keyof HandlerContextType> = (context: PartialExceptFor, 'core'>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; ``` ## Remarks diff --git a/docs/development/core/server/kibana-plugin-core-server.icontextprovider.md b/docs/development/core/server/kibana-plugin-core-server.icontextprovider.md index b2194c9ac0504..7d124b266bcc1 100644 --- a/docs/development/core/server/kibana-plugin-core-server.icontextprovider.md +++ b/docs/development/core/server/kibana-plugin-core-server.icontextprovider.md @@ -9,7 +9,7 @@ A function that returns a context value for a specific key of given context type Signature: ```typescript -export declare type IContextProvider, TContextName extends keyof HandlerContextType> = (context: Partial>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; +export declare type IContextProvider, TContextName extends keyof HandlerContextType> = (context: PartialExceptFor, 'core'>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; ``` ## Remarks diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.addsearchstrategy.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.addsearchstrategy.md deleted file mode 100644 index 119e7fbe62536..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.addsearchstrategy.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [addSearchStrategy](./kibana-plugin-plugins-data-public.addsearchstrategy.md) - -## addSearchStrategy variable - -Signature: - -```typescript -addSearchStrategy: (searchStrategy: SearchStrategyProvider) => void -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.defaultsearchstrategy.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.defaultsearchstrategy.md deleted file mode 100644 index d6a71cf561bc2..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.defaultsearchstrategy.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [defaultSearchStrategy](./kibana-plugin-plugins-data-public.defaultsearchstrategy.md) - -## defaultSearchStrategy variable - -Signature: - -```typescript -defaultSearchStrategy: SearchStrategyProvider -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.essearchstrategyprovider.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.essearchstrategyprovider.md deleted file mode 100644 index 1394c6b868546..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.essearchstrategyprovider.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [esSearchStrategyProvider](./kibana-plugin-plugins-data-public.essearchstrategyprovider.md) - -## esSearchStrategyProvider variable - -Signature: - -```typescript -esSearchStrategyProvider: TSearchStrategyProvider -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getdefaultquery.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getdefaultquery.md new file mode 100644 index 0000000000000..5e6627880333e --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getdefaultquery.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [getDefaultQuery](./kibana-plugin-plugins-data-public.getdefaultquery.md) + +## getDefaultQuery() function + +Signature: + +```typescript +export declare function getDefaultQuery(language?: QueryLanguage): { + query: string; + language: QueryLanguage; +}; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| language | QueryLanguage | | + +Returns: + +`{ + query: string; + language: QueryLanguage; +}` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.hassearchstategyforindexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.hassearchstategyforindexpattern.md deleted file mode 100644 index 94608e7a86820..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.hassearchstategyforindexpattern.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [hasSearchStategyForIndexPattern](./kibana-plugin-plugins-data-public.hassearchstategyforindexpattern.md) - -## hasSearchStategyForIndexPattern variable - -Signature: - -```typescript -hasSearchStategyForIndexPattern: (indexPattern: IndexPattern) => boolean -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchrequest.indextype.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchrequest.indextype.md new file mode 100644 index 0000000000000..55b43efc52305 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchrequest.indextype.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IEsSearchRequest](./kibana-plugin-plugins-data-public.iessearchrequest.md) > [indexType](./kibana-plugin-plugins-data-public.iessearchrequest.indextype.md) + +## IEsSearchRequest.indexType property + +Signature: + +```typescript +indexType?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchrequest.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchrequest.md index 7a40725a67e5f..ed24ca613cdf6 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchrequest.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchrequest.md @@ -14,5 +14,6 @@ export interface IEsSearchRequest extends IKibanaSearchRequest | Property | Type | Description | | --- | --- | --- | +| [indexType](./kibana-plugin-plugins-data-public.iessearchrequest.indextype.md) | string | | | [params](./kibana-plugin-plugins-data-public.iessearchrequest.params.md) | SearchParams | | 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 4b85461e64097..ce1375d277b75 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 @@ -74,32 +74,26 @@ | [RangeFilterParams](./kibana-plugin-plugins-data-public.rangefilterparams.md) | | | [RefreshInterval](./kibana-plugin-plugins-data-public.refreshinterval.md) | | | [SavedQuery](./kibana-plugin-plugins-data-public.savedquery.md) | | -| [SavedQueryAttributes](./kibana-plugin-plugins-data-public.savedqueryattributes.md) | | | [SavedQueryService](./kibana-plugin-plugins-data-public.savedqueryservice.md) | | | [SearchSourceFields](./kibana-plugin-plugins-data-public.searchsourcefields.md) | | | [SearchStrategyProvider](./kibana-plugin-plugins-data-public.searchstrategyprovider.md) | | -| [TimefilterSetup](./kibana-plugin-plugins-data-public.timefiltersetup.md) | | | [TimeRange](./kibana-plugin-plugins-data-public.timerange.md) | | ## Variables | Variable | Description | | --- | --- | -| [addSearchStrategy](./kibana-plugin-plugins-data-public.addsearchstrategy.md) | | | [baseFormattersPublic](./kibana-plugin-plugins-data-public.baseformatterspublic.md) | | | [castEsToKbnFieldTypeName](./kibana-plugin-plugins-data-public.castestokbnfieldtypename.md) | Get the KbnFieldType name for an esType string | | [connectToQueryState](./kibana-plugin-plugins-data-public.connecttoquerystate.md) | Helper to setup two-way syncing of global data and a state container | | [createSavedQueryService](./kibana-plugin-plugins-data-public.createsavedqueryservice.md) | | -| [defaultSearchStrategy](./kibana-plugin-plugins-data-public.defaultsearchstrategy.md) | | | [ES\_SEARCH\_STRATEGY](./kibana-plugin-plugins-data-public.es_search_strategy.md) | | | [esFilters](./kibana-plugin-plugins-data-public.esfilters.md) | | | [esKuery](./kibana-plugin-plugins-data-public.eskuery.md) | | | [esQuery](./kibana-plugin-plugins-data-public.esquery.md) | | -| [esSearchStrategyProvider](./kibana-plugin-plugins-data-public.essearchstrategyprovider.md) | | | [fieldFormats](./kibana-plugin-plugins-data-public.fieldformats.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} | -| [hasSearchStategyForIndexPattern](./kibana-plugin-plugins-data-public.hassearchstategyforindexpattern.md) | | | [indexPatterns](./kibana-plugin-plugins-data-public.indexpatterns.md) | | | [QueryStringInput](./kibana-plugin-plugins-data-public.querystringinput.md) | | | [SearchBar](./kibana-plugin-plugins-data-public.searchbar.md) | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.savedqueryattributes.description.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.savedqueryattributes.description.md deleted file mode 100644 index 859935480357c..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.savedqueryattributes.description.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SavedQueryAttributes](./kibana-plugin-plugins-data-public.savedqueryattributes.md) > [description](./kibana-plugin-plugins-data-public.savedqueryattributes.description.md) - -## SavedQueryAttributes.description property - -Signature: - -```typescript -description: string; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.savedqueryattributes.filters.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.savedqueryattributes.filters.md deleted file mode 100644 index c2c1ac681802b..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.savedqueryattributes.filters.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SavedQueryAttributes](./kibana-plugin-plugins-data-public.savedqueryattributes.md) > [filters](./kibana-plugin-plugins-data-public.savedqueryattributes.filters.md) - -## SavedQueryAttributes.filters property - -Signature: - -```typescript -filters?: Filter[]; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.savedqueryattributes.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.savedqueryattributes.md deleted file mode 100644 index 612be6a1dabc6..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.savedqueryattributes.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SavedQueryAttributes](./kibana-plugin-plugins-data-public.savedqueryattributes.md) - -## SavedQueryAttributes interface - -Signature: - -```typescript -export interface SavedQueryAttributes -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [description](./kibana-plugin-plugins-data-public.savedqueryattributes.description.md) | string | | -| [filters](./kibana-plugin-plugins-data-public.savedqueryattributes.filters.md) | Filter[] | | -| [query](./kibana-plugin-plugins-data-public.savedqueryattributes.query.md) | Query | | -| [timefilter](./kibana-plugin-plugins-data-public.savedqueryattributes.timefilter.md) | SavedQueryTimeFilter | | -| [title](./kibana-plugin-plugins-data-public.savedqueryattributes.title.md) | string | | - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.savedqueryattributes.query.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.savedqueryattributes.query.md deleted file mode 100644 index 96673fc3a8fde..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.savedqueryattributes.query.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SavedQueryAttributes](./kibana-plugin-plugins-data-public.savedqueryattributes.md) > [query](./kibana-plugin-plugins-data-public.savedqueryattributes.query.md) - -## SavedQueryAttributes.query property - -Signature: - -```typescript -query: Query; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.savedqueryattributes.timefilter.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.savedqueryattributes.timefilter.md deleted file mode 100644 index b4edb059a3dfd..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.savedqueryattributes.timefilter.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SavedQueryAttributes](./kibana-plugin-plugins-data-public.savedqueryattributes.md) > [timefilter](./kibana-plugin-plugins-data-public.savedqueryattributes.timefilter.md) - -## SavedQueryAttributes.timefilter property - -Signature: - -```typescript -timefilter?: SavedQueryTimeFilter; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.savedqueryattributes.title.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.savedqueryattributes.title.md deleted file mode 100644 index 99ae1b83e8834..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.savedqueryattributes.title.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SavedQueryAttributes](./kibana-plugin-plugins-data-public.savedqueryattributes.md) > [title](./kibana-plugin-plugins-data-public.savedqueryattributes.title.md) - -## SavedQueryAttributes.title property - -Signature: - -```typescript -title: string; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.timefiltersetup.history.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.timefiltersetup.history.md deleted file mode 100644 index b2ef4a92c5fef..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.timefiltersetup.history.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [TimefilterSetup](./kibana-plugin-plugins-data-public.timefiltersetup.md) > [history](./kibana-plugin-plugins-data-public.timefiltersetup.history.md) - -## TimefilterSetup.history property - -Signature: - -```typescript -history: TimeHistoryContract; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.timefiltersetup.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.timefiltersetup.md deleted file mode 100644 index 3375b415e923b..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.timefiltersetup.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [TimefilterSetup](./kibana-plugin-plugins-data-public.timefiltersetup.md) - -## TimefilterSetup interface - - -Signature: - -```typescript -export interface TimefilterSetup -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [history](./kibana-plugin-plugins-data-public.timefiltersetup.history.md) | TimeHistoryContract | | -| [timefilter](./kibana-plugin-plugins-data-public.timefiltersetup.timefilter.md) | TimefilterContract | | - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.timefiltersetup.timefilter.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.timefiltersetup.timefilter.md deleted file mode 100644 index 897ace53a282d..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.timefiltersetup.timefilter.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [TimefilterSetup](./kibana-plugin-plugins-data-public.timefiltersetup.md) > [timefilter](./kibana-plugin-plugins-data-public.timefiltersetup.timefilter.md) - -## TimefilterSetup.timefilter property - -Signature: - -```typescript -timefilter: TimefilterContract; -``` diff --git a/docs/epm/index.asciidoc b/docs/epm/index.asciidoc new file mode 100644 index 0000000000000..46d45b85690e3 --- /dev/null +++ b/docs/epm/index.asciidoc @@ -0,0 +1,146 @@ +[role="xpack"] +[[epm]] +== Elastic Package Manager + +These are the docs for the Elastic Package Manager (EPM). + + +=== Configuration + +The Elastic Package Manager by default access `epr.elastic.co` to retrieve the package. The url can be configured with: + +``` +xpack.epm.registryUrl: 'http://localhost:8080' +``` + +=== API + +The Package Manager offers an API. Here an example on how they can be used. + +List installed packages: + +``` +curl localhost:5601/api/ingest_manager/epm/packages +``` + +Install a package: + +``` +curl -X POST localhost:5601/api/ingest_manager/epm/packages/iptables-1.0.4 +``` + +Delete a package: + +``` +curl -X DELETE localhost:5601/api/ingest_manager/epm/packages/iptables-1.0.4 +``` + +=== Definitions + +This section is to define terms used across ingest management. + +==== Elastic Agent +A single, unified agent that users can deploy to hosts or containers. It controls which data is collected from the host or containers and where the data is sent. It will run Beats, Endpoint or other monitoring programs as needed. It can operate standalone or pull a configuration policy from Fleet. + +==== Namespace +A user-specified string that will be used to part of the index name in Elasticsearch. It helps users identify logs coming from a specific environment (like prod or test), an application, or other identifiers. + +==== Package + +A package contains all the assets for the Elastic Stack. A more detailed definition of a package can be found under https://github.com/elastic/package-registry . + + +== Indexing Strategy + +Ingest Management enforces an indexing strategy to allow the system to automically detect indices and run queries on it. In short the indexing strategy looks as following: + +``` +{type}-{dataset}-{namespace} +``` + +The `{type}` can be `logs` or `metrics`. The `{namespace}` is the part where the user can use free form. The only two requirement are that it has only characters allowed in an Elasticsearch index name and does NOT contain a `-`. The `dataset` is defined by the data that is indexed. The same requirements as for the namespace apply. It is expected that the fields for type, namespace and dataset are part of each event and are constant keywords. + +Note: More `{type}`s might be added in the future like `apm` and `endpoint`. + +This indexing strategy has a few advantages: + +* Each index contains only the fields which are relevant for the dataset. This leads to more dense indices and better field completion. +* ILM policies can be applied per namespace per dataset. +* Rollups can be specified per namespace per dataset. +* Having the namespace user configurable makes setting security permissions possible. +* Having a global metrics and logs template, allows to create new indices on demand which still follow the convention. This is common in the case of k8s as an example. +* Constant keywords allow to narrow down the indices we need to access for querying very efficiently. This is especially relevant in environments which a large number of indices or with indices on slower nodes. + +=== Ingest Pipeline + +The ingest pipelines for a specific dataset will have the following naming scheme: + +``` +{type}-{dataset}-{package.version} +``` + +As an example, the ingest pipeline for the Nginx access logs is called `logs-nginx.access-3.4.1`. The same ingest pipeline is used for all namespaces. It is possible that a dataset has multiple ingest pipelines in which case a suffix is added to the name. + +The version is included in each pipeline to allow upgrades. The pipeline itself is listed in the index template and is automatically applied at ingest time. + +=== Templates & ILM Policies + +To make the above strategy possible, alias templates are required. For each type there is a basic alias template with a default ILM policy. These default templates apply to all indices which follow the indexing strategy and do not have a more specific dataset alias template. + +The `metrics` and `logs` alias template contain all the basic fields from ECS. + +Each type template contains an ILM policy. Modifying this default ILM policy will affect all data covered by the default templates. + +The templates for a dataset are called as following: + +``` +{type}-{dataset} +``` + +The pattern used inside the index template is `{type}-{dataset}-*` to match all namespaces. + +=== Defaults + +If the Elastic Agent is used to ingest data and only the type is specified, `default` for the namespace is used and `generic` for the dataset. + +=== Data filtering + +Filtering for data in queries for example in visualizations or dashboards should always be done on the constant keyword fields. Visualizations needing data for the nginx.access dataset should query on `type:logs AND dataset:nginx.access`. As these are constant keywords the prefiltering is very efficient. + +=== Security permissions + +Security permissions can be set on different levels. To set special permissions for the access on the prod namespace an index pattern as below can be used: + +``` +/(logs|metrics)-[^-]+-prod-$/ +``` + +To set specific permissions on the logs index, the following can be used: + +``` +/^(logs|metrics)-.*/ +``` + +Todo: The above queries need to be tested. + + + +== Package Manager + +=== Package Upgrades + +When upgrading a package between a bugfix or a minor version, no breaking changes should happen. Upgrading a package has the following effect: + +* Removal of existing dashboards +* Installation of new dashboards +* Write new ingest pipelines with the version +* Write new Elasticsearch alias templates +* Trigger a rollover for all the affected indices + +The new ingest pipeline is expected to still work with the data coming from older configurations. In most cases this means some of the fields can be missing. For this to work, each event must contain the version of config / package it is coming from to make such a decision. + +In case of a breaking change in the data structure, the new ingest pipeline is also expected to deal with this change. In case there are breaking changes which cannot be dealt with in an ingest pipeline, a new package has to be created. + +Each package lists its minimal required agent version. In case there are agents enrolled with an older version, the user is notified to upgrade these agents as otherwise the new configs cannot be rolled out. + + diff --git a/docs/getting-started/tutorial-discovering.asciidoc b/docs/getting-started/tutorial-discovering.asciidoc index bbffb2187f0cf..355477286d445 100644 --- a/docs/getting-started/tutorial-discovering.asciidoc +++ b/docs/getting-started/tutorial-discovering.asciidoc @@ -1,20 +1,19 @@ [[tutorial-discovering]] === Discover your data -Using *Discover*, you can enter +Using *Discover*, enter an {ref}/query-dsl-query-string-query.html#query-string-syntax[Elasticsearch query] to search your data and filter the results. . Open *Discover*. + -The current index pattern appears below the filter bar, in this case `shakes*`. -You might need to click *New* in the menu bar to refresh the data. +The `shakes*` index pattern appears. -. Click the caret to the right of the current index pattern, and select `ba*`. +. To make `ba*` the current index, click the index pattern dropdown, then select `ba*`. + By default, all fields are shown for each matching document. -. In the search field, enter the following string: +. In the search field, enter: + [source,text] account_number<100 AND balance>47500 @@ -25,11 +24,10 @@ excess of 47,500. Results appear for account numbers 8, 32, 78, 85, and 97. [role="screenshot"] image::images/tutorial-discover-2.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. +. Hover over the list of *Available fields*, then +click *add* next to each field you want include as a column in the table. + -For example, if you add the `account_number` field, the display changes to a list of five +For example, when you add the `account_number` field, the display changes to a list of five account numbers. + [role="screenshot"] diff --git a/docs/management/managing-fields.asciidoc b/docs/management/managing-fields.asciidoc index b54f4fe5194ad..1a1bcec10ab50 100644 --- a/docs/management/managing-fields.asciidoc +++ b/docs/management/managing-fields.asciidoc @@ -1,8 +1,8 @@ [[managing-fields]] -== Index Patterns and Fields +== Index patterns and fields The *Index patterns* UI helps you create and manage -the index patterns that retrieve your data from Elasticsearch. +the index patterns that retrieve your data from {es}. [role="screenshot"] image::images/management-index-patterns.png[] @@ -10,8 +10,8 @@ image::images/management-index-patterns.png[] [float] === Create an index pattern -An index pattern is the glue that connects Kibana to your Elasticsearch data. Create an -index pattern whenever you load your own data into Kibana. To get started, +An index pattern is the glue that connects {kib} to your {es} data. Create an +index pattern whenever you load your own data into {kib}. To get started, click *Create index pattern*, and then follow the guided steps. Refer to <> for the types of index patterns that you can create. @@ -33,7 +33,7 @@ you create is automatically designated as the default pattern. The default index pattern is loaded when you open *Discover*. * *Refresh the index fields list.* You can refresh the index fields list to -pick up any newly-added fields. Doing so also resets Kibana’s popularity counters +pick up any newly-added fields. Doing so also resets the {kib} popularity counters for the fields. The popularity counters are used in *Discover* to sort fields in lists. * [[delete-pattern]]*Delete the index pattern.* This action removes the pattern from the list of @@ -60,7 +60,7 @@ Kibana has field formatters for the following field types: * <> [[field-formatters-string]] -=== String Field Formatters +=== String field formatters String fields support the `String` and `Url` formatters. @@ -69,7 +69,7 @@ include::field-formatters/string-formatter.asciidoc[] include::field-formatters/url-formatter.asciidoc[] [[field-formatters-date]] -=== Date Field Formatters +=== Date field formatters Date fields support the `Date`, `Url`, and `String` formatters. @@ -81,19 +81,19 @@ include::field-formatters/string-formatter.asciidoc[] include::field-formatters/url-formatter.asciidoc[] [[field-formatters-geopoint]] -=== Geographic Point Field Formatters +=== Geographic point field formatters Geographic point fields support the `String` formatter. include::field-formatters/string-formatter.asciidoc[] [[field-formatters-numeric]] -=== Numeric Field Formatters +=== Numeric field formatters Numeric fields support the `Url`, `Bytes`, `Duration`, `Number`, `Percentage`, `String`, and `Color` formatters. The `Bytes`, `Number`, and `Percentage` formatters enable you to choose the display formats of numbers in this field using -the <> syntax that Kibana maintains. +the <> syntax that {kib} maintains. include::field-formatters/url-formatter.asciidoc[] @@ -104,25 +104,22 @@ include::field-formatters/duration-formatter.asciidoc[] include::field-formatters/color-formatter.asciidoc[] [[scripted-fields]] -=== Scripted Fields +=== Scripted fields -Scripted fields compute data on the fly from the data in your Elasticsearch indices. Scripted field data is shown on -the Discover tab as part of the document data, and you can use scripted fields in your visualizations. -Scripted field values are computed at query time so they aren't indexed and cannot be searched using Kibana's default -query language. However they can be queried using Kibana's new <>. Scripted -fields are also supported in the filter bar. +Scripted fields compute data on the fly from the data in your {es} indices. The data is shown on +the Discover tab as part of the document data, and you can use scripted fields in your visualizations. You query scripted fields with the <>, and can filter them using the filter bar. The scripted field values are computed at query time, so they aren't indexed and cannot be searched using the {kib} default +query language. WARNING: Computing data on the fly with scripted fields can be very resource intensive and can have a direct impact on -Kibana's performance. Keep in mind that there's no built-in validation of a scripted field. If your scripts are +{kib} performance. Keep in mind that there's no built-in validation of a scripted field. If your scripts are buggy, you'll get exceptions whenever you try to view the dynamically generated data. -When you define a scripted field in Kibana, you have a choice of scripting languages. Starting with 5.0, the default +When you define a scripted field in {kib}, you have a choice of scripting languages. In 5.0 and later, the default options are {ref}/modules-scripting-expression.html[Lucene expressions] and {ref}/modules-scripting-painless.html[Painless]. -While you can use other scripting languages if you enable dynamic scripting for them in Elasticsearch, this is not recommended +While you can use other scripting languages if you enable dynamic scripting for them in {es}, this is not recommended because they cannot be sufficiently {ref}/modules-scripting-security.html[sandboxed]. -WARNING: Use of Groovy, JavaScript, and Python scripting is deprecated starting in Elasticsearch 5.0, and support for those -scripting languages will be removed in the future. +WARNING: In 5.0 and later, Groovy, JavaScript, and Python scripting are deprecated and unsupported. You can reference any single value numeric field in your expressions, for example: @@ -130,44 +127,40 @@ You can reference any single value numeric field in your expressions, for exampl doc['field_name'].value ---- -For more background on scripted fields and additional examples, refer to this blog: -https://www.elastic.co/blog/using-painless-kibana-scripted-fields[Using Painless in Kibana scripted fields] +For more information on scripted fields and additional examples, refer to +https://www.elastic.co/blog/using-painless-kibana-scripted-fields[Using Painless in {kib} scripted fields] [float] [[create-scripted-field]] -=== Creating a Scripted Field -To create a scripted field: +=== Create a scripted field -. Go to *Management > Kibana > Index Patterns* +. Go to *Management > {kib} > Index Patterns* . Select the index pattern you want to add a scripted field to. -. Go to the pattern's *Scripted fields* tab. -. Click *Add scripted field*. +. Go to the *Scripted fields* tab for the index pattern, then click *Add scripted field*. . Enter a name for the scripted field. . Enter the expression that you want to use to compute a value on the fly from your index data. . Click *Create field*. -For more information about scripted fields in Elasticsearch, see +For more information about scripted fields in {es}, see {ref}/modules-scripting.html[Scripting]. [float] [[update-scripted-field]] -=== Updating a Scripted Field -To modify a scripted field: +=== Update a scripted field -. Go to *Management > Kibana > Index Patterns* -. Click the index pattern's *Scripted fields* tab. +. Go to *Management > {kib} > Index Patterns* +. Click the *Scripted fields* tab for the index pattern. . Click the *Edit* button for the scripted field you want to change. -. Make your changes and then click *Save field* to update the field. +. Make your changes, then click *Save field*. -WARNING: Keep in mind that there's no built-in validation of a scripted field. If your scripts are buggy, you'll get +WARNING: Built-in validation is unsupported for scripted fields. If your scripts are buggy, you'll get exceptions whenever you try to view the dynamically generated data. [float] [[delete-scripted-field]] -=== Deleting a Scripted Field -To delete a scripted field: +=== Delete a scripted field -. Go to *Management > Kibana > Index Patterns* -. Click the index pattern's *Scripted fields* tab. -. Click the *Delete* button for the scripted field you want to remove. -. Click *Delete* in the confirmation window. +. Go to *Management > {kib} > Index Patterns* +. Click the *Scripted fields* tab for the index pattern. +. Click *Delete* for the scripted field you want to remove. +. Click *Delete* on the confirmation window. diff --git a/docs/management/watcher-ui/index.asciidoc b/docs/management/watcher-ui/index.asciidoc index 44610a2fd3426..205e614dc21cd 100644 --- a/docs/management/watcher-ui/index.asciidoc +++ b/docs/management/watcher-ui/index.asciidoc @@ -2,13 +2,13 @@ [[watcher-ui]] == Watcher -Watcher is an {es} feature that you can use to create actions based on -conditions, which are periodically evaluated using queries on your data. -Watches are helpful for analyzing mission-critical and business-critical -streaming data. For example, you might watch application logs for performance +Watcher is an {es} feature that you can use to create actions based on +conditions, which are periodically evaluated using queries on your data. +Watches are helpful for analyzing mission-critical and business-critical +streaming data. For example, you might watch application logs for performance outages or audit access logs for security threats. -To get started with the Watcher UI, go to *Management > Elasticsearch > Watcher*. +To get started with the Watcher UI, go to *Management > Elasticsearch > Watcher*. With this UI, you can: * <> @@ -20,10 +20,10 @@ With this UI, you can: image:management/watcher-ui/images/watches.png["Watcher list"] {ref}/xpack-alerting.html[Alerting on cluster and index events] -is a good source for detailed -information on how watches work. If you are using the UI to create a -threshold watch, take a look at the different watcher actions. If you are -creating an advanced watch, you should be familiar with the parts of a +is a good source for detailed +information on how watches work. If you are using the UI to create a +threshold watch, take a look at the different watcher actions. If you are +creating an advanced watch, you should be familiar with the parts of a watch—input, schedule, condition, and actions. [float] @@ -40,41 +40,40 @@ and either of these watcher roles: * `watcher_admin`. You can perform all Watcher actions, including create and edit watches. * `watcher_user`. You can view watches, but not create or edit them. -You can manage roles in *Management > Security > Roles*, or use the -<>. Watches are shared between -all users with the same role. +You can manage roles in *Management > Security > Roles*, or use the +<>. Watches are shared between +all users with the same role. -NOTE: If you are creating a threshold watch, you must also have index management -privileges. See +NOTE: If you are creating a threshold watch, you must also have the `view_index_metadata` index privilege. See <> for detailed information. [float] [[watcher-create-threshold-alert]] === Create a threshold alert -A threshold alert is one of the most common types of watches that you can create. -This alert periodically checks when your data is above, below, equals, +A threshold alert is one of the most common types of watches that you can create. +This alert periodically checks when your data is above, below, equals, or is in between a certain threshold within a given time interval. -The following example walks you through creating a threshold alert. The alert -is triggered when the maximum total CPU usage on a machine goes above a -certain percentage. The example uses https://www.elastic.co/products/beats/metricbeat[Metricbeat] -to collect metrics from your systems and services. -{metricbeat-ref}/metricbeat-installation.html[Learn more] on how to install +The following example walks you through creating a threshold alert. The alert +is triggered when the maximum total CPU usage on a machine goes above a +certain percentage. The example uses https://www.elastic.co/products/beats/metricbeat[Metricbeat] +to collect metrics from your systems and services. +{metricbeat-ref}/metricbeat-installation.html[Learn more] on how to install and get started with Metricbeat. [float] ==== Define the watch input and schedule -. Click *Create* and then select *Create threshold alert*. +. Click *Create* and then select *Create threshold alert*. + You're navigated to a page where you're asked to define the watch name, the data that you want to evaluate, and how often you want to trigger the watch. . Enter a name that you want to call the alert, for example, `cpu_threshold_alert`. -. In the *Indices to query* field, enter `metricbeat-*` and select `@timestamp` -as the time field. +. In the *Indices to query* field, enter `metricbeat-*` and select `@timestamp` +as the time field. . Use the default schedule to run the watch every 1 minute. + @@ -84,22 +83,22 @@ image:management/watcher-ui/images/threshold-alert/create-threshold-alert-create [float] ==== Add a condition -You should now see a panel with default conditions and a visualization of the -data based on those conditions. The condition evaluates the data you’ve loaded +You should now see a panel with default conditions and a visualization of the +data based on those conditions. The condition evaluates the data you’ve loaded into the watch and determines if any action is required. -. Click the `WHEN` expression and change the value to `max()`. +. Click the `WHEN` expression and change the value to `max()`. + -The `OF` expression now appears. +The `OF` expression now appears. -. Search for `system.process.cpu.total.norm.pct` and select it from the list. +. Search for `system.process.cpu.total.norm.pct` and select it from the list. -. Select the `IS ABOVE` expression and change the value to `.25` to trigger +. Select the `IS ABOVE` expression and change the value to `.25` to trigger an alert whenever the CPU is above 25%. + -As you change the condition, the visualization is automatically updated. The black -line represents the threshold (25%), while the green fluctuating line +As you change the condition, the visualization is automatically updated. The black +line represents the threshold (25%), while the green fluctuating line represents the change in CPU over the set time period. + [role="screenshot"] @@ -108,46 +107,46 @@ image:management/watcher-ui/images/threshold-alert/threshold-alert-condition.png [float] ==== Add an action -Now that the condition is set, you must add an action. The action triggers -when the watch condition is met. For a complete list of actions and how to configure them, see +Now that the condition is set, you must add an action. The action triggers +when the watch condition is met. For a complete list of actions and how to configure them, see {ref}/action-conditions.html[Adding conditions to actions]. In this example, you’ll configure an email action. You must have an {ref}/actions-email.html#configuring-email[email account configured] -in {es} for this example to work. +in {es} for this example to work. . Click *Add action* and select *Email*. -. In the *To email address* field, enter one or more email addresses to whom -you want to send the message when the condition is met. +. In the *To email address* field, enter one or more email addresses to whom +you want to send the message when the condition is met. . Enter a subject and body for the email. + [role="screenshot"] image:management/watcher-ui/images/threshold-alert/threshold-alert-action.png["Action for threshold alert"] -. To test the action before saving the watch, click *Send test email*. +. To test the action before saving the watch, click *Send test email*. + A sample email is sent using the configuration you set up. -. Click *Create alert*. +. Click *Create alert*. + -The alert appears on the Watcher overview page, where you can drill down into +The alert appears on the Watcher overview page, where you can drill down into the watch history and status. [float] ==== Delete the alert -In this example, you set the threshold to 25% so you can see the watch fire. In -a real-world scenario, this threshold is likely too low because the alerts are -so frequent. Once you are done experimenting, you should delete the alert. +In this example, you set the threshold to 25% so you can see the watch fire. In +a real-world scenario, this threshold is likely too low because the alerts are +so frequent. Once you are done experimenting, you should delete the alert. Find the alert on the Watcher overview page and click the trash icon in the *Actions* column. [float] ==== Edit the alert -Alternatively, you can keep the alert and adjust the threshold value. To edit -an alert, find the alert on the Watcher overview page and click the pencil icon -in the *Actions* column. +Alternatively, you can keep the alert and adjust the threshold value. To edit +an alert, find the alert on the Watcher overview page and click the pencil icon +in the *Actions* column. [float] [[watcher-getting-started]] @@ -161,13 +160,13 @@ last fired, and last triggered. A watch has one of four states: * *Disabled.* The watch will not fire under any circumstances. From this page you can drill down into a watch to investigate its history -and status. +and status. [float] ==== View watch history -The *Execution history* tab shows each time the watch is triggered and the -results of the query, whether the condition was met, and what actions were taken. +The *Execution history* tab shows each time the watch is triggered and the +results of the query, whether the condition was met, and what actions were taken. [role="screenshot"] image:management/watcher-ui/images/execution-history.png["Execution history tab"] @@ -175,10 +174,10 @@ image:management/watcher-ui/images/execution-history.png["Execution history tab" [float] ==== Acknowledge action status -The *Action statuses* tab lists all actions associated with the watch and -the state of each action. If the action is firing, you can acknowledge the -watch to prevent too many executions of the same action for the same watch. -See {ref}/actions.html#actions-ack-throttle[Acknowledgement and throttling] for details. +The *Action statuses* tab lists all actions associated with the watch and +the state of each action. If the action is firing, you can acknowledge the +watch to prevent too many executions of the same action for the same watch. +See {ref}/actions.html#actions-ack-throttle[Acknowledgement and throttling] for details. [role="screenshot"] image:management/watcher-ui/images/alerts-status.png["Action status tab"] @@ -189,28 +188,28 @@ image:management/watcher-ui/images/alerts-status.png["Action status tab"] Actions for deactivating and deleting a watch are on each watch detail page: -* *Deactivate a watch* if you know a situation is planned that will -cause a false alarm. You can reactivate the watch when the situation is resolved. -* *Delete a watch* to permanently remove it from the system. You can delete -the watch you are currently viewing, or go to the Watcher overview, and -delete watches in bulk. +* *Deactivate a watch* if you know a situation is planned that will +cause a false alarm. You can reactivate the watch when the situation is resolved. +* *Delete a watch* to permanently remove it from the system. You can delete +the watch you are currently viewing, or go to the Watcher overview, and +delete watches in bulk. [float] [[watcher-create-advanced-watch]] === Create an advanced watch -Advanced watches are for users who are more familiar with {es} query syntax and -the Watcher framework. The UI is aligned with using the REST APIs. +Advanced watches are for users who are more familiar with {es} query syntax and +the Watcher framework. The UI is aligned with using the REST APIs. For more information, see {ref}/query-dsl.html[Query DSL]. [float] ==== Create the watch -On the Watch overview page, click *Create* and choose *Create advanced watch*. -An advanced watch requires a name and ID. Name is a user-friendly way to -identify the watch, and ID refers to the identifier used by {es}. Refer to -{ref}/how-watcher-works.html#watch-definition[Watch definition] for how -to input the watch JSON. +On the Watch overview page, click *Create* and choose *Create advanced watch*. +An advanced watch requires a name and ID. Name is a user-friendly way to +identify the watch, and ID refers to the identifier used by {es}. Refer to +{ref}/how-watcher-works.html#watch-definition[Watch definition] for how +to input the watch JSON. [role="screenshot"] image:management/watcher-ui/images/advanced-watch/advanced-watch-create.png["Create advanced watch"] @@ -218,7 +217,7 @@ image:management/watcher-ui/images/advanced-watch/advanced-watch-create.png["Cre [float] ==== Simulate the watch -The *Simulate* tab allows you to override parts of the watch, and then run a +The *Simulate* tab allows you to override parts of the watch, and then run a simulation. Be aware of these implementation details on overrides: * Trigger overrides use {ref}/common-options.html#date-math[date math]. @@ -226,7 +225,7 @@ simulation. Be aware of these implementation details on overrides: * Condition overrides indicates if you want to force the condition to always be `true`. * Action overrides support {ref}/watcher-api-execute-watch.html#watcher-api-execute-watch-action-mode[multiple options]. -After starting the simulation, you’ll see a results screen. For more information +After starting the simulation, you’ll see a results screen. For more information on the fields in the response, see the {ref}/watcher-api-execute-watch.html[Execute watch API]. [role="screenshot"] @@ -235,7 +234,7 @@ image:management/watcher-ui/images/advanced-watch/advanced-watch-simulate.png["C [float] ==== Examples of advanced watches -Refer to these examples for creating an advanced watch: +Refer to these examples for creating an advanced watch: * {ref}/watch-cluster-status.html[Watch the status of an {es} cluster] * {ref}/watching-meetup-data.html[Watch event data] diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index 8ad5330f3fda5..fd835bde83322 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -64,4 +64,9 @@ This page has moved. Please see <>. [role="exclude",id="tilemap"] == Coordinate map -This page has moved. Please see <>. +This page has moved. Please see <>. + +[role="exclude",id="visualize-maps"] +== Maps + +This page has moved. Please see <>. diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 80d04c260e25f..71bb7b81ea420 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -220,15 +220,13 @@ requests. Supported on Elastic Cloud Enterprise. `map.includeElasticMapsService:`:: *Default: true* Set to false to disable connections to Elastic Maps Service. When `includeElasticMapsService` is turned off, only the vector layers configured by `map.regionmap` -and the tile layer configured by `map.tilemap.url` will be available in the <>, -<>, and <>. +and the tile layer configured by `map.tilemap.url` will be available in <>. `map.proxyElasticMapsServiceInMaps:`:: *Default: false* Set to true to proxy all <> Elastic Maps Service requests through the Kibana server. -This setting does not impact <> and <>. [[regionmap-settings]] `map.regionmap:`:: Specifies additional vector layers for -use in <> visualizations. Supported on {ece}. Each layer +use in <> visualizations. Supported on {ece}. Each layer object points to an external vector file that contains a geojson FeatureCollection. The file must use the https://en.wikipedia.org/wiki/World_Geodetic_System[WGS84 coordinate reference system (ESPG:4326)] diff --git a/docs/user/visualize.asciidoc b/docs/user/visualize.asciidoc index f6be2040e3e8c..ebc2f404d43c1 100644 --- a/docs/user/visualize.asciidoc +++ b/docs/user/visualize.asciidoc @@ -40,11 +40,7 @@ data sets. <>:: * *<>* — Displays geospatial data in {kib}. -* *Coordinate map* — Displays points on a map using a geohash aggregation. - -* *Region map* — Merges any structured map data onto a shape. - -* *Heat map* — Displays shaded cells within a matrix. +* <>:: Display shaded cells within a matrix. <>:: diff --git a/docs/visualize/aggregations.asciidoc b/docs/visualize/aggregations.asciidoc index 95aa586e6ba18..868e66d0f4e36 100644 --- a/docs/visualize/aggregations.asciidoc +++ b/docs/visualize/aggregations.asciidoc @@ -58,8 +58,6 @@ You can also nest these aggregations. For example, if you want to produce a thir {ref}/search-aggregations-pipeline-serialdiff-aggregation.html[Serial diff]:: Values in a time series are subtracted from itself at different time lags or periods. -Custom {kib} plugins can <>, which includes support for adding more aggregations. - [float] [[visualize-sibling-pipeline-aggregations]] === Sibling pipeline aggregations diff --git a/docs/visualize/tilemap.asciidoc b/docs/visualize/tilemap.asciidoc index 349fa681a9777..51342847080e0 100644 --- a/docs/visualize/tilemap.asciidoc +++ b/docs/visualize/tilemap.asciidoc @@ -1,116 +1,14 @@ -[[visualize-maps]] -== Maps - -To tell a story and answer questions about your geographical data, you can create several types of interactive maps with Visualize. - -Visualize supports the following maps: - -* *Coordinate* — Display latitude and longitude coordinates that are associated to the specified bucket aggregation. - -* *Region* — Display colored boundary vector shapes using a gradient. Darker colors indicate larger values, and lighter colors indicate smaller values. - -* *Heat* — Display graphical representations of data where the individual values are represented by colors. - -NOTE: The maps in Visualize have been replaced with <>, which offers more functionality. - -[float] -[[coordinate-map]] -=== Coordinate map - -Use a coordinate map when your data set includes latitude and longitude values. For example, use a coordinate map to see the varying popularity of destination airports using the sample flight data. - -[role="screenshot"] -image::images/visualize_coordinate_map_example.png[] - -[float] -[[build-coordinate-map]] -==== Build a coordinate map - -Configure the `kibana.yml` settings and add the aggregations. - -. Configure the following `kibana.yml` settings: - -* Set `xpack.maps.showMapVisualizationTypes` to `true`. - -* To use a tile service provider for coordinate maps other than https://www.elastic.co/elastic-maps-service[Elastic Maps Service], configure the <>. - -. To display your data on the coordinate map, use the following aggregations: - -* <> - -* <> - -. Specify the geohash bucket aggregation options: - -* *Precision* slider — Determines the granularity of the results displayed on the map. To show the *Precision* slider, deselect *Change precision on map zoom*. For information on the area specified by each precision level, refer to {ref}/search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator[geohash grid]. -+ -NOTE: Higher precisions increase memory usage for the browser that displays {kib} and the underlying -{es} cluster. - -* *Place markers off grid (use {ref}/search-aggregations-metrics-geocentroid-aggregation.html[geocentroid])* — When you selected, the markers are -placed in the center of all documents in the bucket, and a more accurate visualization is created. When deselected, the markers are placed in the center -of the geohash grid cell. -+ -NOTE: When you have multiple values in the geo_point, the coordinate map is unable to accurately calculate the geo_centroid. - -[float] -[[navigate-coordinate-map]] -==== Navigate the coordinate map - -To navigate the coordinate map, use the navigation options. - -* To move the map center, click and hold anywhere on the map and move the cursor. - -* To change the zoom level, click *Zoom In* or *Zoom out* image:images/viz-zoom.png[]. - -* To automatically crop the map boundaries to the -geohash buckets that have at least one result, click *Fit Data Bounds* image:images/viz-fit-bounds.png[]. - -[float] -[[region-map]] -=== Region map - -Use region maps when you want to show statistical data on a geographic area, such as a county, country, province, or state. For example, use a region map if you want to see the average sales for each country with the sample eCommerce order data. - -[role="screenshot"] -image::images/visualize_region_map_example.png[] - -[float] -[[build-region-maps]] -==== Build a region map - -Configure the `kibana.yml` settings and add the aggregations. - -. In `kibana.yml`, set `xpack.maps.showMapVisualizationTypes` to `true`. - -. To display your data on the region map, use the following aggregations: - -* <> -* <> -* <> - -[float] -[[navigate-region-map]] -==== Navigate the region map - -To navigate the region map, use the navigation options. - -* To change the zoom level, click *Zoom In* or *Zoom out* image:images/viz-zoom.png[]. - -* To automatically crop the map boundaries, click *Fit Data Bounds* image:images/viz-fit-bounds.png[]. - -[float] [[heat-map]] -=== Heat map +== Heat map -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. +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] [[build-heat-map]] -==== Build a heat map +=== Build a heat map To display your data on the heat map, use the supported aggregations. @@ -123,7 +21,7 @@ Heat maps support the following aggregations: [float] [[navigate-heatmap]] -==== Change the color ranges +=== Change the color ranges When only one color displays on the heat map, you might need to change the color ranges. diff --git a/package.json b/package.json index 7c82bf8d6f881..3d1faf3bc3478 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "typespec": "typings-tester --config x-pack/legacy/plugins/canvas/public/lib/aeroelastic/tsconfig.json x-pack/legacy/plugins/canvas/public/lib/aeroelastic/__fixtures__/typescript/typespec_tests.ts", "checkLicenses": "node scripts/check_licenses --dev", "build": "node scripts/build --all-platforms", - "start": "node --trace-warnings --throw-deprecation scripts/kibana --dev", + "start": "node scripts/kibana --dev", "debug": "node --nolazy --inspect scripts/kibana --dev", "debug-break": "node --nolazy --inspect-brk scripts/kibana --dev", "lint": "yarn run lint:es && yarn run lint:sass", @@ -138,6 +138,7 @@ "@kbn/test-subj-selector": "0.2.1", "@kbn/ui-framework": "1.0.0", "@kbn/ui-shared-deps": "1.0.0", + "@types/tar": "^4.0.3", "JSONStream": "1.3.5", "abortcontroller-polyfill": "^1.4.0", "angular": "^1.7.9", @@ -203,7 +204,7 @@ "leaflet-responsive-popup": "0.6.4", "leaflet-vega": "^0.8.6", "leaflet.heat": "0.2.0", - "less": "^3.0.2", + "less": "^2.7.3", "less-loader": "5.0.0", "lodash": "npm:@elastic/lodash@3.10.1-kibana4", "lodash.clonedeep": "^4.5.0", @@ -227,6 +228,7 @@ "pug": "^2.0.4", "query-string": "6.10.1", "raw-loader": "3.1.0", + "re2": "1.10.5", "react": "^16.12.0", "react-color": "^2.13.8", "react-dom": "^16.12.0", @@ -454,6 +456,7 @@ "listr": "^0.14.1", "load-grunt-config": "^3.0.1", "mocha": "^6.2.2", + "mock-http-server": "1.3.0", "multistream": "^2.1.1", "murmurhash3js": "3.0.1", "mutation-observer": "^1.0.3", diff --git a/renovate.json5 b/renovate.json5 index ca2cd2e6bcd93..e4836537df703 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -431,6 +431,14 @@ '@types/jquery', ], }, + { + groupSlug: 'js-search', + groupName: 'js-search related packages', + packageNames: [ + 'js-search', + '@types/js-search', + ], + }, { groupSlug: 'js-yaml', groupName: 'js-yaml related packages', @@ -877,6 +885,14 @@ '@types/supertest-as-promised', ], }, + { + groupSlug: 'tar', + groupName: 'tar related packages', + packageNames: [ + 'tar', + '@types/tar', + ], + }, { groupSlug: 'tar-fs', groupName: 'tar-fs related packages', diff --git a/rfcs/images/pulse_diagram.png b/rfcs/images/pulse_diagram.png new file mode 100644 index 0000000000000..a104fad0fe133 Binary files /dev/null and b/rfcs/images/pulse_diagram.png differ diff --git a/rfcs/text/0008_pulse.md b/rfcs/text/0008_pulse.md new file mode 100644 index 0000000000000..e2543d310aa38 --- /dev/null +++ b/rfcs/text/0008_pulse.md @@ -0,0 +1,316 @@ +- Start Date: 2020-02-07 +- RFC PR: [#57108](https://github.com/elastic/kibana/pull/57108) +- Kibana Issue: (leave this empty) + +# Table of contents + +- [Summary](#summary) +- [Motivation](#motivation) +- [Detailed design](#detailed-design) + - [Concepts](#concepts) + - [Architecture](#architecture) + 1. [Remote Pulse Service](#1-remote-pulse-service) + - [Deployment](#deployment) + - [Endpoints](#endpoints) + - [Authenticate](#authenticate) + - [Opt-In|Out](#opt-inout) + - [Inject telemetry](#inject-telemetry) + - [Retrieve instructions](#retrieve-instructions) + - [Data model](#data-model) + - [Access Control](#access-control) + 2. [Local Pulse Service](#2-local-pulse-service) + - [Data storage](#data-storage) + - [Sending telemetry](#sending-telemetry) + - [Instruction polling](#instruction-polling) +- [Drawbacks](#drawbacks) +- [Alternatives](#alternatives) +- [Adoption strategy](#adoption-strategy) +- [How we teach this](#how-we-teach-this) +- [Unresolved questions](#unresolved-questions) + +# Summary + +Evolve our telemetry to collect more diverse data, enhance our products with that data and engage with users by enabling: + +1. _Two-way_ communication link between us and our products. +2. Flexibility to collect diverse data and different granularity based on the type of data. +3. Enhanced features in our products, allowing remote-driven _small tweaks_ to existing builds. +4. All this while still maintaining transparency about what we send and making sure we don't track any of the user's data. + +# Basic example + +There is a POC implemented in the branch [`pulse_poc`](https://github.com/elastic/kibana/tree/pulse_poc) in this repo. + +It covers the following scenarios: + +- Track the behaviour of our users in the UI, reporting UI events throughout our platform. +- Report to Elastic when an unexpected error occurs and keep track of it. When it's fixed, it lets the user know, encouraging them to update to their deployment to the latest release (PR [#56724](https://github.com/elastic/kibana/pull/56724)). +- Keep track of the notifications and news in the newsfeed to know when they are read/kept unseen. This might help us on improving the way we communicate updates to the user (PR [#53596](https://github.com/elastic/kibana/pull/53596)). +- Provide a cost estimate for running that cluster in Elastic Cloud, so the user is well-informed about our up-to-date offering and can decide accordingly (PR [#56324](https://github.com/elastic/kibana/pull/56324)). +- Customised "upgrade guide" from your current version to the latest (PR [#56556](https://github.com/elastic/kibana/pull/56556)). + +![image](../images/pulse_diagram.png) +_Basic example of the architecture_ + +# Motivation + +Based on our current telemetry, we have many _lessons learned_ we want to tackle: + +- It only supports one type of data: + - It makes simple tasks like reporting aggregations of usage based on a number of days [an overengineered solution](https://github.com/elastic/kibana/issues/46599#issuecomment-545024137) + - When reporting arrays (i.e.: `ui_metrics`), it cannot be consumed, making the data useless. +- _One index to rule them all_: +The current unique document structure comes at a price: + - People consuming that information finding it hard to understand each element in the document ([[DISCUSS] Data dictionary for product usage data](https://github.com/elastic/telemetry/issues/211)) + - Maintaining the mappings is a tedious and risky process. It involved increasing the setting for the limit of fields in a mapping and reindexing documents (now millions of them). + - We cannot fully control the data we insert in the documents: If we set `mappings.dynamic: 'strict'`, we'll reject all the documents containing more information than the actually mapped, losing all the other content we do want to receive. +- Opt-out ratio: +We want to reduce the number of `opt-out`s by providing some valuable feedback to our users so that they want to turn telemetry ON because they do benefit from it. + +# Detailed design + +This design is going to be tackled by introducing some common concepts to be used by the main two main components in this architecture: + +1. Remote Pulse Service (RPS) +2. Local Pulse Service (LPS) + +After that, it explains how we envision the architecture and design of each of those components. + +## Concepts + +There are some new concepts we'd like to introduce with this new way of reporting telemetry: + +- **Deployment Hash ID** +This is the _anonymised_ random ID assigned for a deployment. It is used to link multiple pieces of information for further analysis like cross-referencing different bits of information from different sources. +- **Channels** +This is each stream of data that have common information. Typically each channel will have a well defined source of information, different to the rest. It will also result in a different structure to the rest of channels. However, all the channels will maintain a minimum piece of common schema for cross-references (like **Deployment Hash ID** and **timestamp**). +- **Instructions** +These are the messages generated in the form of feedback to the different channels. +Typically, channels will follow a bi-directional communication process _(Local <-> Remote)_ but there might be channels that do not generate any kind of instruction _(Local -> Remote)_ and, similarly, some other channels that do not provide any telemetry at all, but allows Pulse to send updates to our products _(Local <- Remote)_. + +## Phased implementation + +At the moment of writing this document, anyone can push _fake_ telemetry data to our Telemetry cluster. They only need to know the public encryption key, the endpoint and the format of the data, all of that easily retrievable. We take that into consideration when analysing the data we have at the moment and it is a risk we are OK with for now. + +But, given that we aim to provide feedback to the users and clusters in the form of instructions, the **Security and Integrity of the information** is critical. We need to come up with a solution that ensures the instructions are created based on data that was uniquely created (signed?) by the source. If we cannot ensure that, we should not allow that piece of information to be used in the generation of the instructions for that cluster and we should mark it so we know it could be maliciously injected when using it in our analysis. + +But also, we want to be able to ship the benefits of Pulse on every release. That's why we are thinking on a phased release, starting with limited functionality and evolving to the final complete vision of this product. This RFC suggests the following phased implementation: + +1. **Be able to ingest granular data** +With the introduction of the **channels**, we can start receiving granular data that will help us all on our analysis. At this point, the same _security_ features as the current telemetry are considered: The payload is encrypted by the Kibana server so no mediator can spoof the data. +The same risks as the current telemetry still apply at this point: anyone can _impersonate_ and send the data on behalf of another cluster, making the collected information useless. +Because this information cannot be used to generate any instruction, we may not care about the **Deployment Hash ID** at this stage. This means no authentication is required to push data. +The works at this point in time will be focused on creating the initial infraestructure, receiving early data and start with the migration of the current telemetry into the new channel-based model. Finally, start exploring the new visualisations we can provide with this new model of data. + +2. **Secured ingest channel** +In this phase, our efforts will focus on securing the communications and integrity of the data. This includes: + - **Generation of the Deployment Hash ID**: + Discussions on whether it should be self-generated and accepted/rejected by the Remote Pulse Service (RPS) or it should be generated and assigned by the RPS because it is the only one that can ensure uniqueness. + - **Locally store the Deployment Hash ID as an encrypted saved object**: + This comes back with a caveat: OSS versions will not be able to receive instructions. We will need to maintain a fallback mechanism to the phase 1 logic (it may be a desired scenario because it could happen the encrypted saved objects are not recoverable due to an error in the deployment and we should still be able to apply that fallback). + - **Authenticity of the information (Local -> Remote)**: + We need to _sign_ the data in some way the RPS can confirm the information reported as for a _Deployment Hash ID_ comes from the right source. + - **Authenticity of the information (Remote -> Local)**: + We need the Local Pulse Service (LPS) to be able to confirm the responses from the RPS data has not been altered by any mediator. It could be done via encryption using a key provided by the LPS. This should be provided to the RPS inside an encrypted payload in the same fashion we currently encrypt the telemetry. + - **Integrity of the data in the channels**: + We need to ensure an external plugin cannot push data to channels to avoid malicious corruption of the data. We could achieve this by either making this plugin only available to Kibana-shipped plugins or storing the `pluginID` that is pushing the data to have better control of the source of the data (then an ingest pipeline can reject any source of data that should not be accepted). + + All the suggestions in this phase can be further discussed at that point (I will create another RFC to discuss those terms after this RFC is approved and merged). + +3. **Instruction handling** +This final phase we'll implement the instruction generation and handling at the same time we are adding more **channels**. +We can discuss at this point if we want to be able to provide _harmless_ instructions for those deployments that are not _secured_ (i.e.: Cloud cost estimations, User-profiled-based marketing updates, ...). + +## Architecture + +As mentioned earlier, at the beginning of this chapter, there are two main components in this architecture: + +1. Remote Pulse Service +2. Local Pulse Service + +### 1. Remote Pulse Service + +This is the service that will receive and store the telemetry from all the _opted-in_ deployments. It will also generate the messages we want to report back to each deployment (aka: instructions). + +#### Deployment + +- The service will be hosted by Elastic. +- Most likely maintained by the Infra team. +- GCP is contemplated at this moment, but we need to confirm how would it affect us regarding the FedRamp approvals (and similar). +- Exposes an API (check [Endpoints](#endpoints) to know more) to inject the data and retrieve the _instructions_. +- The data will be stored in an ES cluster. + +#### Endpoints + +The following endpoints **will send every payload** detailed in below **encrypted** with a similar mechanism to the current telemetry encryption. + +##### Authenticate + +This Endpoint will be used to retrieve a randomised `deploymentID` and a `token` for the cluster to use in all the subsequent requests. Ideally, it will provide some sort of identifier (like `cluster_uuid` or `license.uuid`) so we can revoke its access to any of the endpoints if explicitly requested ([Blocking telemetry input](https://github.com/elastic/telemetry/pull/221) and [Delete previous telemetry data](https://github.com/elastic/telemetry/issues/209)). + +I'd appreciate some insights here to come up with a strong handshake mechanism to avoid stealing identities. + +In order to _dereference_ the data, we can store these mappings in a Vault or Secrets provider instead of an index in our ES. + +_NB: Not for phase 1_ + +##### Opt-In|Out + +Similar to the current telemetry, we want to keep track of when the user opts in or out of telemetry. The implementation can be very similar to the current one. But we recently learned we need to add the origin to know what application has telemetry disabled (Kibana, Beats, Enterprise Search, ...). This makes me wonder if we will ever want to provide a granular option for the user to be able to cherry-pick about what channels are sent and which ones should be disabled. + +##### Inject telemetry + +In order to minimise the amount of requests, this `POST` should accept bulks of data in the payload (mind the payload size limits if any). It will require authentication based on the `deploymentID` and `token` explained in the [previous endpoint](#authenticate) (_NB: Not for phase 1_). + +The received payload will be pushed to a streaming technology (AWS Firehose, Google Pub/Sub, ...). This way we can maintain a buffer in cases the ingestion of data spikes or we need to stop our ES cluster for any maintenance purposes. + +A subscriber to that stream will receive that info a split the payload into smaller documents per channel and index them into their separate indices. + +This indexing should also trigger some additional processes like the **generation of instructions** and _special views_ (only if needed, check the point [Access control](#access-control) for more details). + +_NB: We might want to consider some sort of piggy-backing to include the instructions in the response. But for the purpose of this RFC, scalability and separation of concerns, I'd rather keep it for future possible improvements._ + +##### Retrieve instructions + +_NB: Only after phase 3_ + +This `GET` endpoint should return the list of instructions generated for that deployment. To control the likely ever-growing list of instructions for each deployment, it will accept a `since` query parameter where the requester can specify the timestamp ever since it was to retrieve the new values. + +This endpoint will read the `instructions-*` indices, filtering `updated-at` by the `since` query parameter (if provided) and it will return the results, grouping them by channels. + +Additionally, we can consider accepting an additional query parameter to retrieve only specific channels. For use cases like distributed components (endpoint, apm, beats, ...) polling instructions themselves. + +#### Data model + +The storage of each of the documents, will be based on monthly-rolling indices split by channels. This means we'll have indices like `pulse-raw-{CHANNEL_NAME}-YYYY.MM` and `pulse-instructions-{CHANNEL_NAME}-YYYY.MM` (final names TBD). + +The first group will be used to index all the incoming documents from the telemetry. While the second one will contain the instructions to be sent to the deployments. + +The mapping for those indices will be **`strict`** to avoid anyone storing unwanted/not-allowed info. The indexer defined in [the _Inject telemetry_ endpoint](#inject-telemetry) will need to handle accordingly the errors derived from the strict mapping. +We'll set up a process to add new mappings and their descriptions before every new release. + +#### Access control + +- The access to _raw_ data indices will be very limited. Only granted to those in need of troubleshooting the service and maintaining mappings (this is the Pulse/Telemetry team at the moment). +- Special views (as in aggregations/visualisations/snapshots of the data stored in special indices via separated indexers/aggregators/ES transform or via _BigQuery_ or similar) will be defined for different roles in the company to help them to take informed decisions based on the data. +This way we'll be able to control "who can see what" on a very granual basis. It will also provide us with more flexibility to change to structure of the _raw_ if needed. + +### 2. Local Pulse Service + +This refers to the plugin running in Kibana in each of our customers' deployments. It will be a core service in NP, available for all plugins to get the existing channels, to send pieces of data, and subscribe to instructions. + +The channel handlers are only defined inside the pulse context and are used to normalise the data for each channel before sending it to the remote service. The CODEOWNERS should notify the Pulse team every time there's an intended change in this context. + +#### Data storage + +For the purpose of transparency, we want the user to be able to retrieve the telemetry we send at any point, so we should store the information we send for each channel in their own local _dot_ internal indices (similar to a copy of the `pulse-raw-*` and `pulse-instructions-*` indices in our remote service). We may want to also sync back from the remote service any updates we do to the documents: enrichment of the document, anonymisation, categorisation when it makes sense in that specific channel, ... + +In the same effort, we could even provide some _dashboards_ in Kibana for specific roles in the cluster to understand more about their deployment. + +Only those specific roles (admin?) should have access to these local indices, unless they grant permissions to other users they want to share this information with. + +The users should be able to control how long they want to keep that information for via ILM. A default ILM policy will be setup during the startup if it doesn't exist. + +#### Sending telemetry + +The telemetry will be sent, preferably, from the server. Only falling back to the browser in case we detect the server is behind firewalls and it cannot reach the service or if the user explicitly sets the behaviour in the config. + +Periodically, the process (either in the server or the browser) will retrieve the telemetry to be sent by the channels, compile it into 1 bulk payload and send it encrypted to the [ingest endpoint](#inject-telemetry) explained earlier. + +How often it sends the data, depends on the channel specifications. We will have 3 levels of periodicity: + +- `URGENT`: The data is sent as soon as possible. +- `HIGH`: Sent every hour. +- `NORMAL`: Sent every 24 hours. +- `LOW`: Sent every 3 days. + +Some throttling policy should be applied to avoid exploiting the exceeded use of `URGENT`. + +#### Instruction polling + +Similarly to the sending of the telemetry, the instruction polling should happen only on one end (either the server or the browser). It will store the responses in the local index for each channel and the plugins reacting to those instructions will be able to consume that information based on their own needs (either load only the new ones or all the historic data at once). + +Depending on the subscriptions to the channels by the plugins, the polling will happen with different periodicity, similar to the one described in the chapter above. + +#### Exposing channels to the plugins + +The plugins will be able to send messages and/or consume instructions for any channel by using the methods provided as part of the `coreContext` in the `setup` and `start` lifecycle methods in a fashion like (types to be properly defined when implementing it): + +```typescript +const coreContext: CoreSetup | CoreStart = { + ...existingCoreContext, + pulse: { + sendToChannel: async (channelName: keyof Channels, payload: Channels[channelName]) => void, + instructionsFromChannel$: (channelName: keyof Channels) => Observable, + }, +} +``` + +Plugins will simply need to call `core.pulse.sendToChannel('errors', myUnexpectedErrorIWantToReport)` whenever they want to report any new data to that channel. This will call the channel's handler to store the data. + +Similarly, they'll be able to subscribe to channels like: + +```typescript +core.pulse.instructionsFromChannel$('ui_behaviour_tracking') + .pipe(filterInstructionsForMyPlugin) // Initially, we won't filter the instructions based on the plugin ID (might not be necessary in all cases) + .subscribe(changeTheOrderOfTheComponents); +``` + +Internally in those methods we should append the `pluginId` to know who is sending/receiving the info. + +##### The _legacy_ collection + +The current telemetry collection via the `UsageCollector` service will be maintained until all the current telemetry is fully migrated into their own channels. In the meantime, the current existing telemetry will be sent to Pulse as the `legacy` channel. This way we can maintain the same architecture for the old and new telemetry to come. At this stage, there is no need for any plugin to update their logic unless they want to send more granular data using other (even specific to that plugin) channels. + +The mapping for this `legacy` channel will be kept `dynamic: false` instead of `strict` to ensure compatibility. + +# Drawbacks + +- Pushing data into telemetry nowadays is as simple as implementing your own `usageCollector`. For consuming, though, the telemetry team needs to update the mappings. But as soon as they do so, the previous data is available. Now we'll be more strict about the mapping. Rejecting any data that does not comply. Changing the structure of the reported data will result in data loss in that channel. +- Hard dependency on the Pulse team's availability to update the metrics and on the Infra team to deploy the instruction handlers. +- Testing architecture: any dockerised way to test the local dev environment? +- We'll increase the local usage of indices. Making it more expensive to users to maintain the cluster. We need be to careful with this! Although it might not change much, compared to the current implementation, if any plugin decides to maintain its own index/saved objects to do aggregations afterwards. Similarly, more granularity per channel, may involve more network usage. +- It is indeed a breaking change, but it can be migrated over-time as new features, making use of the instructions. +- We need to update other products already reporting telemetry from outside Kibana (like Beats, Enterprise Search, Logstash, ...) to use the new way of pushing telemetry. + +# Alternatives + +> What other designs have been considered? + +We currently have the newsfeed to be able to communicate to the user. This is actually pulling in Kibana from a public API to retrieve the list of entries to be shown in the notification bar. But this is only limitted to notifications to the user while the new _intructions_ can provide capabilities like self-update/self-configuration of components like endpoints, elasticsearch, ... + +> What is the impact of not doing this? + +Users might not see any benefit from providing telemetry and will opt-out. The quality of the telemetry will likely not be as good (or it will require a higher effort on the plugin end to provide it like in [the latest lens effort](https://github.com/elastic/kibana/issues/46599#issuecomment-545024137)) + +# Adoption strategy + +Initially, we'll focus on the remote service and move the current telemetry to report as a `"legacy"` channel to the new Pulse service. + +Then, we'll focus on doing the client side, providing new APIs to report the data, aiming for the minimum changes on the public end. For instance, the current usage collectors already report an ID, we can work on those IDs mapping to a channel (only grouping them when it makes sense). Nevertheless, it will require the devs to engage with the Pulse team for the mappings and definitions to be properly set up and updated. And any views to be added. + +Finally, the instruction handling APIs are completely new and it will require development on both _remote_ and _local_ ends for the instruction generation and handling. + +# How we teach this + +> What names and terminology work best for these concepts and why? How is this +idea best presented? As a continuation of existing Kibana patterns? + +We have 3 points of view to show here: + +- From the users perspective, we need to show the value for them to have the telemetry activated. +- From the devs, how to generate data and consume instructions. +- From the PMs, how to consume the views + definitions of the fields. + +> Would the acceptance of this proposal mean the Kibana documentation must be +re-organized or altered? Does it change how Kibana is taught to new developers +at any level? + +This telemetry is supposed to be internal only. Only internal developers will be able to add to this. So the documentation will only be for internal puposes. As mentioned in the _Adoption strategy_, the idea is that the devs to report new data to telemetry will need to engage with the Pulse team. + +> How should this feature be taught to existing Kibana developers? + +# Unresolved questions + +- Pending to define a proper handshake in the authentication mechanism to reduce the chance of a man-in-the-middle attack or DDoS. => We already have some ideas thanks to @jportner and @kobelb but it will be resolved during the _Phase 2_ design. +- Opt-in/out per channel? diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index f71a50e2927d8..69668176a397e 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -735,8 +735,10 @@ export interface IContextContainer> { registerContext>(pluginOpaqueId: PluginOpaqueId, contextName: TContextName, provider: IContextProvider): this; } +// Warning: (ae-forgotten-export) The symbol "PartialExceptFor" needs to be exported by the entry point index.d.ts +// // @public -export type IContextProvider, TContextName extends keyof HandlerContextType> = (context: Partial>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; +export type IContextProvider, TContextName extends keyof HandlerContextType> = (context: PartialExceptFor, 'core'>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; // @public (undocumented) export interface IHttpFetchError extends Error { diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 5ede98a1e6e6d..86e99e0dab550 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -882,8 +882,10 @@ export interface IContextContainer> { registerContext>(pluginOpaqueId: PluginOpaqueId, contextName: TContextName, provider: IContextProvider): this; } +// Warning: (ae-forgotten-export) The symbol "PartialExceptFor" needs to be exported by the entry point index.d.ts +// // @public -export type IContextProvider, TContextName extends keyof HandlerContextType> = (context: Partial>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; +export type IContextProvider, TContextName extends keyof HandlerContextType> = (context: PartialExceptFor, 'core'>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; // @public export interface ICspConfig { diff --git a/src/core/utils/context.ts b/src/core/utils/context.ts index 775c890675410..de311f91d56fa 100644 --- a/src/core/utils/context.ts +++ b/src/core/utils/context.ts @@ -22,6 +22,11 @@ import { ShallowPromise } from '@kbn/utility-types'; import { pick } from '.'; import { CoreId, PluginOpaqueId } from '../server'; +/** + * Make all properties in T optional, except for the properties whose keys are in the union K + */ +type PartialExceptFor = Partial & Pick; + /** * A function that returns a context value for a specific key of given context type. * @@ -39,7 +44,8 @@ export type IContextProvider< THandler extends HandlerFunction, TContextName extends keyof HandlerContextType > = ( - context: Partial>, + // context.core will always be available, but plugin contexts are typed as optional + context: PartialExceptFor, 'core'>, ...rest: HandlerParameters ) => | Promise[TContextName]> @@ -261,7 +267,7 @@ export class ContextContainer> // registered that provider. const exposedContext = pick(resolvedContext, [ ...this.getContextNamesForSource(providerSource), - ]) as Partial>; + ]) as PartialExceptFor, 'core'>; return { ...resolvedContext, diff --git a/src/dev/build/build_distributables.js b/src/dev/build/build_distributables.js index 6c2efeebc60c3..008281db8bb93 100644 --- a/src/dev/build/build_distributables.js +++ b/src/dev/build/build_distributables.js @@ -45,6 +45,7 @@ import { InstallDependenciesTask, BuildKibanaPlatformPluginsTask, OptimizeBuildTask, + PatchNativeModulesTask, RemovePackageJsonDepsTask, RemoveWorkspacesTask, TranspileBabelTask, @@ -132,6 +133,7 @@ export async function buildDistributables(options) { * directories and perform platform-specific steps */ await run(CreateArchivesSourcesTask); + await run(PatchNativeModulesTask); await run(CleanExtraBinScriptsTask); await run(CleanExtraBrowsersTask); await run(CleanNodeBuildsTask); diff --git a/src/dev/build/tasks/nodejs/__tests__/download.js b/src/dev/build/lib/__tests__/download.js similarity index 100% rename from src/dev/build/tasks/nodejs/__tests__/download.js rename to src/dev/build/lib/__tests__/download.js diff --git a/src/dev/build/tasks/nodejs/download.js b/src/dev/build/lib/download.js similarity index 98% rename from src/dev/build/tasks/nodejs/download.js rename to src/dev/build/lib/download.js index 0bd10e5b84015..97f82ed14b409 100644 --- a/src/dev/build/tasks/nodejs/download.js +++ b/src/dev/build/lib/download.js @@ -24,7 +24,7 @@ import chalk from 'chalk'; import { createHash } from 'crypto'; import Axios from 'axios'; -import { mkdirp } from '../../lib'; +import { mkdirp } from './fs'; function tryUnlink(path) { try { diff --git a/src/dev/build/lib/index.js b/src/dev/build/lib/index.js index afebd090d797d..25c4b74eefd22 100644 --- a/src/dev/build/lib/index.js +++ b/src/dev/build/lib/index.js @@ -33,5 +33,6 @@ export { compress, isFileAccessible, } from './fs'; +export { download } from './download'; export { scanDelete } from './scan_delete'; export { scanCopy } from './scan_copy'; diff --git a/src/dev/build/tasks/index.js b/src/dev/build/tasks/index.js index 8105fa8a7d5d4..1faae655c9abb 100644 --- a/src/dev/build/tasks/index.js +++ b/src/dev/build/tasks/index.js @@ -32,6 +32,7 @@ export * from './nodejs_modules'; export * from './notice_file_task'; export * from './optimize_task'; export * from './os_packages'; +export * from './patch_native_modules_task'; export * from './transpile_babel_task'; export * from './transpile_scss_task'; export * from './verify_env_task'; diff --git a/src/dev/build/tasks/nodejs/__tests__/download_node_builds_task.js b/src/dev/build/tasks/nodejs/__tests__/download_node_builds_task.js index 4c94ed776417d..1048a8c7386bc 100644 --- a/src/dev/build/tasks/nodejs/__tests__/download_node_builds_task.js +++ b/src/dev/build/tasks/nodejs/__tests__/download_node_builds_task.js @@ -22,7 +22,7 @@ import expect from '@kbn/expect'; import * as NodeShasumsNS from '../node_shasums'; import * as NodeDownloadInfoNS from '../node_download_info'; -import * as DownloadNS from '../download'; +import * as DownloadNS from '../../../lib/download'; // sinon can't stub '../../../lib' properly import { DownloadNodeBuildsTask } from '../download_node_builds_task'; describe('src/dev/build/tasks/nodejs/download_node_builds_task', () => { diff --git a/src/dev/build/tasks/nodejs/download_node_builds_task.js b/src/dev/build/tasks/nodejs/download_node_builds_task.js index df841960677c1..12e9245262c56 100644 --- a/src/dev/build/tasks/nodejs/download_node_builds_task.js +++ b/src/dev/build/tasks/nodejs/download_node_builds_task.js @@ -17,7 +17,7 @@ * under the License. */ -import { download } from './download'; +import { download } from '../../lib'; import { getNodeShasums } from './node_shasums'; import { getNodeDownloadInfo } from './node_download_info'; diff --git a/src/dev/build/tasks/patch_native_modules_task.js b/src/dev/build/tasks/patch_native_modules_task.js new file mode 100644 index 0000000000000..28fc9ebf3d61f --- /dev/null +++ b/src/dev/build/tasks/patch_native_modules_task.js @@ -0,0 +1,85 @@ +/* + * 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 path from 'path'; +import util from 'util'; +import { deleteAll, download, untar } from '../lib'; + +const BASE_URL = `https://storage.googleapis.com/native-modules`; +const DOWNLOAD_DIRECTORY = '.native_modules'; + +const packages = [ + { + name: 're2', + version: '1.10.5', + destinationPath: 'node_modules/re2/build/Release/', + shas: { + darwin: '066533b592094f91e00412499e44c338ce2466d63c9eaf0dc32be8214bde2099', + linux: '0322cac3c2e106129b650a8eac509f598ed283791d6116984fec4c151b24e574', + windows: '65b5bef7de2352f4787224c2c76a619b6683a868c8d4d71e0fdd500786fc422b', + }, + }, +]; + +async function getInstalledVersion(config, packageName) { + const packageJSONPath = config.resolveFromRepo( + path.join('node_modules', packageName, 'package.json') + ); + const buffer = await util.promisify(fs.readFile)(packageJSONPath); + const packageJSON = JSON.parse(buffer); + return packageJSON.version; +} + +async function patchModule(config, log, build, platform, pkg) { + const installedVersion = await getInstalledVersion(config, pkg.name); + if (installedVersion !== pkg.version) { + throw new Error( + `Can't patch ${pkg.name}'s native module, we were expecting version ${pkg.version} and found ${installedVersion}` + ); + } + const platformName = platform.getName(); + const archiveName = `${pkg.version}-${platformName}.tar.gz`; + const downloadUrl = `${BASE_URL}/${pkg.name}/${archiveName}`; + const downloadPath = config.resolveFromRepo(DOWNLOAD_DIRECTORY, archiveName); + const extractedPath = build.resolvePathForPlatform(platform, pkg.destinationPath); + log.debug(`Patching ${pkg.name} binaries from ${downloadUrl} to ${extractedPath}`); + + await deleteAll([extractedPath], log); + await download({ + log, + url: downloadUrl, + destination: downloadPath, + sha256: pkg.shas[platformName], + retries: 3, + }); + await untar(downloadPath, extractedPath); +} + +export const PatchNativeModulesTask = { + description: 'Patching platform-specific native modules', + async run(config, log, build) { + for (const pkg of packages) { + await Promise.all( + config.getTargetPlatforms().map(async platform => { + await patchModule(config, log, build, platform, pkg); + }) + ); + } + }, +}; diff --git a/src/dev/ci_setup/setup_env.sh b/src/dev/ci_setup/setup_env.sh index dc3fa38f3129c..05840926d35de 100644 --- a/src/dev/ci_setup/setup_env.sh +++ b/src/dev/ci_setup/setup_env.sh @@ -14,7 +14,7 @@ cacheDir="$HOME/.kibana" RED='\033[0;31m' C_RESET='\033[0m' # Reset color -export NODE_OPTIONS="$NODE_OPTIONS --throw-deprecation --max-old-space-size=4096" +export NODE_OPTIONS="$NODE_OPTIONS --max-old-space-size=4096" ### ### Since the Jenkins logging output collector doesn't look like a TTY @@ -168,4 +168,4 @@ if [[ -d "$ES_DIR" && -f "$ES_JAVA_PROP_PATH" ]]; then export JAVA_HOME=$HOME/.java/$ES_BUILD_JAVA fi -export CI_ENV_SETUP=true \ No newline at end of file +export CI_ENV_SETUP=true diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.test.ts deleted file mode 100644 index 08d5955d3fae9..0000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.test.ts +++ /dev/null @@ -1,175 +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 { FilterStateManager } from './filter_state_manager'; - -import { StubState } from './test_helpers/stub_state'; -import { getFilter } from './test_helpers/get_stub_filter'; -import { FilterManager, esFilters } from '../../../../../../plugins/data/public'; - -import { coreMock } from '../../../../../../core/public/mocks'; -const setupMock = coreMock.createSetup(); - -setupMock.uiSettings.get.mockImplementation((key: string) => { - return true; -}); - -describe('filter_state_manager', () => { - let appStateStub: StubState; - let globalStateStub: StubState; - - let filterManager: FilterManager; - - beforeEach(() => { - appStateStub = new StubState(); - globalStateStub = new StubState(); - filterManager = new FilterManager(setupMock.uiSettings); - }); - - describe('app_state_undefined', () => { - beforeEach(() => { - // FilterStateManager is tested indirectly. - // Therefore, we don't need it's instance. - new FilterStateManager( - globalStateStub, - () => { - return undefined; - }, - filterManager - ); - }); - - test('should NOT watch state until both app and global state are defined', done => { - const f1 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); - globalStateStub.filters.push(f1); - - setTimeout(() => { - expect(filterManager.getGlobalFilters()).toHaveLength(0); - done(); - }, 100); - }); - - test('should NOT update app URL when filter manager filters are set', async () => { - appStateStub.save = sinon.stub(); - globalStateStub.save = sinon.stub(); - - const f1 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'age', 34); - const f2 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); - - filterManager.setFilters([f1, f2]); - - sinon.assert.notCalled(appStateStub.save); - sinon.assert.calledOnce(globalStateStub.save); - }); - }); - - describe('app_state_defined', () => { - let filterStateManager: FilterStateManager; - beforeEach(() => { - // FilterStateManager is tested indirectly. - // Therefore, we don't need it's instance. - filterStateManager = new FilterStateManager( - globalStateStub, - () => { - return appStateStub; - }, - filterManager - ); - }); - - afterEach(() => { - filterStateManager.destroy(); - }); - - test('should update filter manager global filters', done => { - const updateSubscription = filterManager.getUpdates$().subscribe(() => { - expect(filterManager.getGlobalFilters()).toHaveLength(1); - if (updateSubscription) { - updateSubscription.unsubscribe(); - } - done(); - }); - - const f1 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, true, true, 'age', 34); - globalStateStub.filters.push(f1); - }); - - test('should update filter manager app filter', done => { - const updateSubscription = filterManager.getUpdates$().subscribe(() => { - expect(filterManager.getAppFilters()).toHaveLength(1); - if (updateSubscription) { - updateSubscription.unsubscribe(); - } - done(); - }); - - const f1 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'age', 34); - appStateStub.filters.push(f1); - }); - - test('should update URL when filter manager filters are set', () => { - appStateStub.save = sinon.stub(); - globalStateStub.save = sinon.stub(); - - const f1 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'age', 34); - const f2 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); - - filterManager.setFilters([f1, f2]); - - sinon.assert.calledOnce(appStateStub.save); - sinon.assert.calledOnce(globalStateStub.save); - }); - - test('should update URL when filter manager filters are added', () => { - appStateStub.save = sinon.stub(); - globalStateStub.save = sinon.stub(); - - const f1 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'age', 34); - const f2 = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, false, false, 'age', 34); - - filterManager.addFilters([f1, f2]); - - sinon.assert.calledOnce(appStateStub.save); - sinon.assert.calledOnce(globalStateStub.save); - }); - }); - - describe('bug fixes', () => { - /* - ** This test is here to reproduce a bug where a filter manager update - ** would cause filter state manager detects those changes - ** And triggers *another* filter manager update. - */ - test('should NOT re-trigger filter manager', done => { - const f1 = getFilter(esFilters.FilterStateStore.APP_STATE, false, false, 'age', 34); - filterManager.setFilters([f1]); - const setFiltersSpy = sinon.spy(filterManager, 'setFilters'); - - f1.meta.negate = true; - filterManager.setFilters([f1]); - - setTimeout(() => { - expect(setFiltersSpy.callCount).toEqual(1); - done(); - }, 100); - }); - }); -}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.ts deleted file mode 100644 index e095493c94c58..0000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.ts +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { Subscription } from 'rxjs'; -import { State } from 'ui/state_management/state'; -import { FilterManager, esFilters, Filter } from '../../../../../../plugins/data/public'; - -type GetAppStateFunc = () => { filters?: Filter[]; save?: () => void } | undefined | null; - -/** - * FilterStateManager is responsible for watching for filter changes - * and syncing with FilterManager, as well as syncing FilterManager changes - * back to the URL. - **/ -export class FilterStateManager { - private filterManagerUpdatesSubscription: Subscription; - - filterManager: FilterManager; - globalState: State; - getAppState: GetAppStateFunc; - interval: number | undefined; - - constructor(globalState: State, getAppState: GetAppStateFunc, filterManager: FilterManager) { - this.getAppState = getAppState; - this.globalState = globalState; - this.filterManager = filterManager; - - this.watchFilterState(); - - this.filterManagerUpdatesSubscription = this.filterManager.getUpdates$().subscribe(() => { - this.updateAppState(); - }); - } - - destroy() { - if (this.interval) { - clearInterval(this.interval); - } - this.filterManagerUpdatesSubscription.unsubscribe(); - } - - private watchFilterState() { - // This is a temporary solution to remove rootscope. - // Moving forward, state should provide observable subscriptions. - this.interval = window.setInterval(() => { - const appState = this.getAppState(); - const stateUndefined = !appState || !this.globalState; - if (stateUndefined) return; - - const globalFilters = this.globalState.filters || []; - const appFilters = (appState && appState.filters) || []; - - const globalFilterChanged = !esFilters.compareFilters( - this.filterManager.getGlobalFilters(), - globalFilters, - esFilters.COMPARE_ALL_OPTIONS - ); - const appFilterChanged = !esFilters.compareFilters( - this.filterManager.getAppFilters(), - appFilters, - esFilters.COMPARE_ALL_OPTIONS - ); - const filterStateChanged = globalFilterChanged || appFilterChanged; - - if (!filterStateChanged) return; - - const newGlobalFilters = _.cloneDeep(globalFilters); - const newAppFilters = _.cloneDeep(appFilters); - FilterManager.setFiltersStore(newAppFilters, esFilters.FilterStateStore.APP_STATE); - FilterManager.setFiltersStore(newGlobalFilters, esFilters.FilterStateStore.GLOBAL_STATE); - - this.filterManager.setFilters(newGlobalFilters.concat(newAppFilters)); - }, 10); - } - - private saveState() { - const appState = this.getAppState(); - if (appState && appState.save) appState.save(); - this.globalState.save(); - } - - private updateAppState() { - // Update Angular state before saving State objects (which save it to URL) - const partitionedFilters = this.filterManager.getPartitionedFilters(); - const appState = this.getAppState(); - if (appState) { - appState.filters = partitionedFilters.appFilters; - } - this.globalState.filters = partitionedFilters.globalFilters; - this.saveState(); - } -} diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/index.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/index.ts deleted file mode 100644 index ebb622783c3d1..0000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/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 { FilterStateManager } from './filter_state_manager'; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/get_stub_filter.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/get_stub_filter.ts deleted file mode 100644 index 74eaad34fe160..0000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/get_stub_filter.ts +++ /dev/null @@ -1,45 +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 { Filter } from '../../../../../../../plugins/data/public'; - -export function getFilter( - store: any, // I don't want to export only for this, as it should move to data plugin - disabled: boolean, - negated: boolean, - queryKey: string, - queryValue: any -): Filter { - return { - $state: { - store, - }, - meta: { - index: 'logstash-*', - disabled, - negate: negated, - alias: null, - }, - query: { - match: { - [queryKey]: queryValue, - }, - }, - }; -} diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index 9187e207ed0d6..61d8621a36843 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -28,11 +28,6 @@ export function plugin() { /** @public types */ export { DataSetup, DataStart } from './plugin'; -export { - SavedQueryAttributes, - SavedQuery, - SavedQueryTimeFilter, -} from '../../../../plugins/data/public'; export { // agg_types AggParam, // only the type is used externally, only in vis editor @@ -50,7 +45,6 @@ export { /** @public static code */ export * from '../common'; -export { FilterStateManager } from './filter/filter_manager'; export { // agg_types TODO need to group these under a namespace or prefix AggConfigs, diff --git a/src/legacy/core_plugins/data/public/plugin.ts b/src/legacy/core_plugins/data/public/plugin.ts index 18230646ab412..f40cda8760bc7 100644 --- a/src/legacy/core_plugins/data/public/plugin.ts +++ b/src/legacy/core_plugins/data/public/plugin.ts @@ -18,12 +18,7 @@ */ import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; -import { - DataPublicPluginStart, - addSearchStrategy, - defaultSearchStrategy, - DataPublicPluginSetup, -} from '../../../../plugins/data/public'; +import { DataPublicPluginStart, DataPublicPluginSetup } from '../../../../plugins/data/public'; import { ExpressionsSetup } from '../../../../plugins/expressions/public'; import { @@ -111,9 +106,6 @@ export class DataPlugin public setup(core: CoreSetup, { data, uiActions }: DataPluginSetupDependencies) { setInjectedMetadata(core.injectedMetadata); - // This is to be deprecated once we switch to the new search service fully - addSearchStrategy(defaultSearchStrategy); - uiActions.attachAction( SELECT_RANGE_TRIGGER, selectRangeAction(data.query.filterManager, data.query.timefilter.timefilter) diff --git a/src/legacy/core_plugins/input_control_vis/public/control/create_search_source.ts b/src/legacy/core_plugins/input_control_vis/public/control/create_search_source.ts index f792796230757..f238a2287ecdb 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/create_search_source.ts +++ b/src/legacy/core_plugins/input_control_vis/public/control/create_search_source.ts @@ -17,7 +17,7 @@ * under the License. */ -import { PhraseFilter, IndexPattern, TimefilterSetup } from '../../../../../plugins/data/public'; +import { PhraseFilter, IndexPattern, TimefilterContract } from '../../../../../plugins/data/public'; import { SearchSource as SearchSourceClass, SearchSourceFields } from '../legacy_imports'; export function createSearchSource( @@ -27,7 +27,7 @@ export function createSearchSource( aggs: any, useTimeFilter: boolean, filters: PhraseFilter[] = [], - timefilter: TimefilterSetup['timefilter'] + timefilter: TimefilterContract ) { const searchSource = initialState ? new SearchSource(initialState) : new SearchSource(); // Do not not inherit from rootSearchSource to avoid picking up time and globals diff --git a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.ts b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.ts index 56b42f295ce15..8364c82efecdb 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.ts +++ b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.ts @@ -26,7 +26,7 @@ import { PhraseFilterManager } from './filter_manager/phrase_filter_manager'; import { createSearchSource } from './create_search_source'; import { ControlParams } from '../editor_utils'; import { InputControlVisDependencies } from '../plugin'; -import { IFieldType, TimefilterSetup } from '../../../../../plugins/data/public'; +import { IFieldType, TimefilterContract } from '../../../../../plugins/data/public'; function getEscapedQuery(query = '') { // https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#_standard_operators @@ -74,7 +74,7 @@ const termsAgg = ({ field, size, direction, query }: TermsAggArgs) => { export class ListControl extends Control { private getInjectedVar: InputControlVisDependencies['core']['injectedMetadata']['getInjectedVar']; - private timefilter: TimefilterSetup['timefilter']; + private timefilter: TimefilterContract; abortController?: AbortController; lastAncestorValues: any; diff --git a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.ts b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.ts index b9191436b5968..d9b43c9dff201 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.ts +++ b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.ts @@ -26,7 +26,7 @@ import { RangeFilterManager } from './filter_manager/range_filter_manager'; import { createSearchSource } from './create_search_source'; import { ControlParams } from '../editor_utils'; import { InputControlVisDependencies } from '../plugin'; -import { IFieldType, TimefilterSetup } from '../.../../../../../../plugins/data/public'; +import { IFieldType, TimefilterContract } from '../.../../../../../../plugins/data/public'; const minMaxAgg = (field?: IFieldType) => { const aggBody: any = {}; @@ -52,7 +52,7 @@ const minMaxAgg = (field?: IFieldType) => { }; export class RangeControl extends Control { - timefilter: TimefilterSetup['timefilter']; + timefilter: TimefilterContract; abortController: any; min: any; max: any; diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_add_filter.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_add_filter.js deleted file mode 100644 index 87eb283639c78..0000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_add_filter.js +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { createStateStub } from './_utils'; -import { getQueryParameterActions } from '../../np_ready/angular/context/query_parameters/actions'; -import { pluginInstance } from 'plugins/kibana/discover/legacy'; -import { npStart } from 'ui/new_platform'; - -describe('context app', function() { - beforeEach(() => pluginInstance.initializeInnerAngular()); - beforeEach(() => pluginInstance.initializeServices()); - beforeEach(ngMock.module('app/discover')); - - describe('action addFilter', function() { - let addFilter; - - beforeEach( - ngMock.inject(function createPrivateStubs() { - addFilter = getQueryParameterActions().addFilter; - }) - ); - - it('should pass the given arguments to the filterManager', function() { - const state = createStateStub(); - const filterManagerAddStub = npStart.plugins.data.query.filterManager.addFilters; - - addFilter(state)('FIELD_NAME', 'FIELD_VALUE', 'FILTER_OPERATION'); - - //get the generated filter - const generatedFilter = filterManagerAddStub.firstCall.args[0][0]; - const queryKeys = Object.keys(generatedFilter.query.match_phrase); - expect(filterManagerAddStub.calledOnce).to.be(true); - expect(queryKeys[0]).to.eql('FIELD_NAME'); - expect(generatedFilter.query.match_phrase[queryKeys[0]]).to.eql('FIELD_VALUE'); - }); - - it('should pass the index pattern id to the filterManager', function() { - const state = createStateStub(); - const filterManagerAddStub = npStart.plugins.data.query.filterManager.addFilters; - - addFilter(state)('FIELD_NAME', 'FIELD_VALUE', 'FILTER_OPERATION'); - - const generatedFilter = filterManagerAddStub.firstCall.args[0][0]; - expect(generatedFilter.meta.index).to.eql('INDEX_PATTERN_ID'); - }); - }); -}); diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_predecessor_count.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_predecessor_count.js deleted file mode 100644 index 9ba425bb0e489..0000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_predecessor_count.js +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { pluginInstance } from 'plugins/kibana/discover/legacy'; -import { createStateStub } from './_utils'; -import { getQueryParameterActions } from '../../np_ready/angular/context/query_parameters/actions'; - -describe('context app', function() { - beforeEach(() => pluginInstance.initializeInnerAngular()); - beforeEach(() => pluginInstance.initializeServices()); - beforeEach(ngMock.module('app/discover')); - - describe('action setPredecessorCount', function() { - let setPredecessorCount; - - beforeEach( - ngMock.inject(function createPrivateStubs() { - setPredecessorCount = getQueryParameterActions().setPredecessorCount; - }) - ); - - it('should set the predecessorCount to the given value', function() { - const state = createStateStub(); - - setPredecessorCount(state)(20); - - expect(state.queryParameters.predecessorCount).to.equal(20); - }); - - it('should limit the predecessorCount to 0 as a lower bound', function() { - const state = createStateStub(); - - setPredecessorCount(state)(-1); - - expect(state.queryParameters.predecessorCount).to.equal(0); - }); - - it('should limit the predecessorCount to 10000 as an upper bound', function() { - const state = createStateStub(); - - setPredecessorCount(state)(20000); - - expect(state.queryParameters.predecessorCount).to.equal(10000); - }); - }); -}); diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_query_parameters.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_query_parameters.js deleted file mode 100644 index 39dde2d8bb7cf..0000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_query_parameters.js +++ /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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { pluginInstance } from 'plugins/kibana/discover/legacy'; - -import { createStateStub } from './_utils'; -import { getQueryParameterActions } from '../../np_ready/angular/context/query_parameters/actions'; - -describe('context app', function() { - beforeEach(() => pluginInstance.initializeInnerAngular()); - beforeEach(() => pluginInstance.initializeServices()); - beforeEach(ngMock.module('app/discover')); - - describe('action setQueryParameters', function() { - let setQueryParameters; - - beforeEach( - ngMock.inject(function createPrivateStubs() { - setQueryParameters = getQueryParameterActions().setQueryParameters; - }) - ); - - it('should update the queryParameters with valid properties from the given object', function() { - const state = createStateStub({ - queryParameters: { - additionalParameter: 'ADDITIONAL_PARAMETER', - }, - }); - - setQueryParameters(state)({ - anchorId: 'ANCHOR_ID', - columns: ['column'], - defaultStepSize: 3, - filters: ['filter'], - indexPatternId: 'INDEX_PATTERN', - predecessorCount: 100, - successorCount: 100, - sort: ['field'], - }); - - expect(state.queryParameters).to.eql({ - additionalParameter: 'ADDITIONAL_PARAMETER', - anchorId: 'ANCHOR_ID', - columns: ['column'], - defaultStepSize: 3, - filters: ['filter'], - indexPatternId: 'INDEX_PATTERN', - predecessorCount: 100, - successorCount: 100, - sort: ['field'], - }); - }); - - it('should ignore invalid properties', function() { - const state = createStateStub(); - - setQueryParameters(state)({ - additionalParameter: 'ADDITIONAL_PARAMETER', - }); - - expect(state.queryParameters).to.eql(createStateStub().queryParameters); - }); - }); -}); diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_successor_count.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_successor_count.js deleted file mode 100644 index c05f5b4aff3bc..0000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_successor_count.js +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { pluginInstance } from 'plugins/kibana/discover/legacy'; - -import { createStateStub } from './_utils'; -import { getQueryParameterActions } from '../../np_ready/angular/context/query_parameters/actions'; - -describe('context app', function() { - beforeEach(() => pluginInstance.initializeInnerAngular()); - beforeEach(() => pluginInstance.initializeServices()); - beforeEach(ngMock.module('app/discover')); - - describe('action setSuccessorCount', function() { - let setSuccessorCount; - - beforeEach( - ngMock.inject(function createPrivateStubs() { - setSuccessorCount = getQueryParameterActions().setSuccessorCount; - }) - ); - - it('should set the successorCount to the given value', function() { - const state = createStateStub(); - - setSuccessorCount(state)(20); - - expect(state.queryParameters.successorCount).to.equal(20); - }); - - it('should limit the successorCount to 0 as a lower bound', function() { - const state = createStateStub(); - - setSuccessorCount(state)(-1); - - expect(state.queryParameters.successorCount).to.equal(0); - }); - - it('should limit the successorCount to 10000 as an upper bound', function() { - const state = createStateStub(); - - setSuccessorCount(state)(20000); - - expect(state.queryParameters.successorCount).to.equal(10000); - }); - }); -}); diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index b7a3974a58865..9687afaaa599d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -66,7 +66,6 @@ export { IIndexPattern, IndexPattern, indexPatterns, - hasSearchStategyForIndexPattern, IFieldType, SearchSource, ISearchSource, diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/_stubs.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/_stubs.js similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/_stubs.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/_stubs.js index 53be4e5bd0f2d..f6ed570be2c37 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/_stubs.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/_stubs.js @@ -19,7 +19,7 @@ import sinon from 'sinon'; import moment from 'moment'; -import { SearchSource } from '../../../../../kibana_services'; +import { SearchSource } from '../../../../../../../../../plugins/data/public'; export function createIndexPatternsStub() { return { diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/anchor.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/anchor.test.js similarity index 73% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/anchor.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/anchor.test.js index 63834fb750e21..0bc2cbacc1eee 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/anchor.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/anchor.test.js @@ -17,28 +17,19 @@ * under the License. */ -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { pluginInstance } from 'plugins/kibana/discover/legacy'; - import { createIndexPatternsStub, createSearchSourceStub } from './_stubs'; -import { fetchAnchorProvider } from '../anchor'; +import { fetchAnchorProvider } from './anchor'; describe('context app', function() { - beforeEach(() => pluginInstance.initializeInnerAngular()); - beforeEach(ngMock.module('app/discover')); - describe('function fetchAnchor', function() { let fetchAnchor; let searchSourceStub; - beforeEach( - ngMock.inject(function createPrivateStubs() { - searchSourceStub = createSearchSourceStub([{ _id: 'hit1' }]); - fetchAnchor = fetchAnchorProvider(createIndexPatternsStub(), searchSourceStub); - }) - ); + beforeEach(() => { + searchSourceStub = createSearchSourceStub([{ _id: 'hit1' }]); + fetchAnchor = fetchAnchorProvider(createIndexPatternsStub(), searchSourceStub); + }); afterEach(() => { searchSourceStub._restore(); @@ -49,7 +40,7 @@ describe('context app', function() { { '@timestamp': 'desc' }, { _doc: 'desc' }, ]).then(() => { - expect(searchSourceStub.fetch.calledOnce).to.be(true); + expect(searchSourceStub.fetch.calledOnce).toBe(true); }); }); @@ -59,8 +50,8 @@ describe('context app', function() { { _doc: 'desc' }, ]).then(() => { const setParentSpy = searchSourceStub.setParent; - expect(setParentSpy.calledOnce).to.be(true); - expect(setParentSpy.firstCall.args[0]).to.be(undefined); + expect(setParentSpy.calledOnce).toBe(true); + expect(setParentSpy.firstCall.args[0]).toBe(undefined); }); }); @@ -70,7 +61,7 @@ describe('context app', function() { { _doc: 'desc' }, ]).then(() => { const setFieldSpy = searchSourceStub.setField; - expect(setFieldSpy.firstCall.args[1].id).to.eql('INDEX_PATTERN_ID'); + expect(setFieldSpy.firstCall.args[1].id).toEqual('INDEX_PATTERN_ID'); }); }); @@ -80,8 +71,8 @@ describe('context app', function() { { _doc: 'desc' }, ]).then(() => { const setVersionSpy = searchSourceStub.setField.withArgs('version'); - expect(setVersionSpy.calledOnce).to.be(true); - expect(setVersionSpy.firstCall.args[1]).to.eql(true); + expect(setVersionSpy.calledOnce).toBe(true); + expect(setVersionSpy.firstCall.args[1]).toEqual(true); }); }); @@ -91,8 +82,8 @@ describe('context app', function() { { _doc: 'desc' }, ]).then(() => { const setSizeSpy = searchSourceStub.setField.withArgs('size'); - expect(setSizeSpy.calledOnce).to.be(true); - expect(setSizeSpy.firstCall.args[1]).to.eql(1); + expect(setSizeSpy.calledOnce).toBe(true); + expect(setSizeSpy.firstCall.args[1]).toEqual(1); }); }); @@ -102,8 +93,8 @@ describe('context app', function() { { _doc: 'desc' }, ]).then(() => { const setQuerySpy = searchSourceStub.setField.withArgs('query'); - expect(setQuerySpy.calledOnce).to.be(true); - expect(setQuerySpy.firstCall.args[1]).to.eql({ + expect(setQuerySpy.calledOnce).toBe(true); + expect(setQuerySpy.firstCall.args[1]).toEqual({ query: { constant_score: { filter: { @@ -124,8 +115,8 @@ describe('context app', function() { { _doc: 'desc' }, ]).then(() => { const setSortSpy = searchSourceStub.setField.withArgs('sort'); - expect(setSortSpy.calledOnce).to.be(true); - expect(setSortSpy.firstCall.args[1]).to.eql([{ '@timestamp': 'desc' }, { _doc: 'desc' }]); + expect(setSortSpy.calledOnce).toBe(true); + expect(setSortSpy.firstCall.args[1]).toEqual([{ '@timestamp': 'desc' }, { _doc: 'desc' }]); }); }); @@ -140,7 +131,7 @@ describe('context app', function() { expect().fail('expected the promise to be rejected'); }, error => { - expect(error).to.be.an(Error); + expect(error).toBeInstanceOf(Error); } ); }); @@ -152,8 +143,8 @@ describe('context app', function() { { '@timestamp': 'desc' }, { _doc: 'desc' }, ]).then(anchorDocument => { - expect(anchorDocument).to.have.property('property1', 'value1'); - expect(anchorDocument).to.have.property('$$_isAnchor', true); + expect(anchorDocument).toHaveProperty('property1', 'value1'); + expect(anchorDocument).toHaveProperty('$$_isAnchor', true); }); }); }); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/predecessors.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.predecessors.test.js similarity index 74% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/predecessors.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.predecessors.test.js index 02d998e8f4529..d6e91e57b22a8 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/predecessors.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.predecessors.test.js @@ -17,15 +17,10 @@ * under the License. */ -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; import moment from 'moment'; import * as _ from 'lodash'; -import { pluginInstance } from 'plugins/kibana/discover/legacy'; - import { createIndexPatternsStub, createContextSearchSourceStub } from './_stubs'; - -import { fetchContextProvider } from '../context'; +import { fetchContextProvider } from './context'; const MS_PER_DAY = 24 * 60 * 60 * 1000; const ANCHOR_TIMESTAMP = new Date(MS_PER_DAY).toJSON(); @@ -34,46 +29,41 @@ const ANCHOR_TIMESTAMP_1000 = new Date(MS_PER_DAY * 1000).toJSON(); const ANCHOR_TIMESTAMP_3000 = new Date(MS_PER_DAY * 3000).toJSON(); describe('context app', function() { - beforeEach(() => pluginInstance.initializeInnerAngular()); - beforeEach(ngMock.module('app/discover')); - describe('function fetchPredecessors', function() { let fetchPredecessors; let searchSourceStub; - beforeEach( - ngMock.inject(function createPrivateStubs() { - searchSourceStub = createContextSearchSourceStub([], '@timestamp', MS_PER_DAY * 8); - fetchPredecessors = ( + beforeEach(() => { + searchSourceStub = createContextSearchSourceStub([], '@timestamp', MS_PER_DAY * 8); + fetchPredecessors = ( + indexPatternId, + timeField, + sortDir, + timeValIso, + timeValNr, + tieBreakerField, + tieBreakerValue, + size + ) => { + const anchor = { + _source: { + [timeField]: timeValIso, + }, + sort: [timeValNr, tieBreakerValue], + }; + + return fetchContextProvider(createIndexPatternsStub()).fetchSurroundingDocs( + 'predecessors', indexPatternId, + anchor, timeField, - sortDir, - timeValIso, - timeValNr, tieBreakerField, - tieBreakerValue, - size - ) => { - const anchor = { - _source: { - [timeField]: timeValIso, - }, - sort: [timeValNr, tieBreakerValue], - }; - - return fetchContextProvider(createIndexPatternsStub()).fetchSurroundingDocs( - 'predecessors', - indexPatternId, - anchor, - timeField, - tieBreakerField, - sortDir, - size, - [] - ); - }; - }) - ); + sortDir, + size, + [] + ); + }; + }); afterEach(() => { searchSourceStub._restore(); @@ -99,8 +89,8 @@ describe('context app', function() { 3, [] ).then(hits => { - expect(searchSourceStub.fetch.calledOnce).to.be(true); - expect(hits).to.eql(searchSourceStub._stubHits.slice(0, 3)); + expect(searchSourceStub.fetch.calledOnce).toBe(true); + expect(hits).toEqual(searchSourceStub._stubHits.slice(0, 3)); }); }); @@ -132,14 +122,14 @@ describe('context app', function() { expect( intervals.every(({ gte, lte }) => (gte && lte ? moment(gte).isBefore(lte) : true)) - ).to.be(true); + ).toBe(true); // should have started at the given time - expect(intervals[0].gte).to.eql(moment(MS_PER_DAY * 3000).toISOString()); + expect(intervals[0].gte).toEqual(moment(MS_PER_DAY * 3000).toISOString()); // should have ended with a half-open interval - expect(_.last(intervals)).to.only.have.keys('gte', 'format'); - expect(intervals.length).to.be.greaterThan(1); + expect(Object.keys(_.last(intervals))).toEqual(['format', 'gte']); + expect(intervals.length).toBeGreaterThan(1); - expect(hits).to.eql(searchSourceStub._stubHits.slice(0, 3)); + expect(hits).toEqual(searchSourceStub._stubHits.slice(0, 3)); }); }); @@ -169,11 +159,11 @@ describe('context app', function() { ); // should have started at the given time - expect(intervals[0].gte).to.eql(moment(MS_PER_DAY * 1000).toISOString()); + expect(intervals[0].gte).toEqual(moment(MS_PER_DAY * 1000).toISOString()); // should have stopped before reaching MS_PER_DAY * 1700 - expect(moment(_.last(intervals).lte).valueOf()).to.be.lessThan(MS_PER_DAY * 1700); - expect(intervals.length).to.be.greaterThan(1); - expect(hits).to.eql(searchSourceStub._stubHits.slice(-3)); + expect(moment(_.last(intervals).lte).valueOf()).toBeLessThan(MS_PER_DAY * 1700); + expect(intervals.length).toBeGreaterThan(1); + expect(hits).toEqual(searchSourceStub._stubHits.slice(-3)); }); }); @@ -189,7 +179,7 @@ describe('context app', function() { 3, [] ).then(hits => { - expect(hits).to.eql([]); + expect(hits).toEqual([]); }); }); @@ -206,8 +196,8 @@ describe('context app', function() { [] ).then(() => { const setParentSpy = searchSourceStub.setParent; - expect(setParentSpy.alwaysCalledWith(undefined)).to.be(true); - expect(setParentSpy.called).to.be(true); + expect(setParentSpy.alwaysCalledWith(undefined)).toBe(true); + expect(setParentSpy.called).toBe(true); }); }); @@ -225,7 +215,7 @@ describe('context app', function() { ).then(() => { expect( searchSourceStub.setField.calledWith('sort', [{ '@timestamp': 'asc' }, { _doc: 'asc' }]) - ).to.be(true); + ).toBe(true); }); }); }); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/successors.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.successors.test.js similarity index 74% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/successors.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.successors.test.js index d4c00930c9383..cc2b6d31cb43b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/successors.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.successors.test.js @@ -17,15 +17,12 @@ * under the License. */ -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; import moment from 'moment'; import * as _ from 'lodash'; -import { pluginInstance } from 'plugins/kibana/discover/legacy'; import { createIndexPatternsStub, createContextSearchSourceStub } from './_stubs'; -import { fetchContextProvider } from '../context'; +import { fetchContextProvider } from './context'; const MS_PER_DAY = 24 * 60 * 60 * 1000; const ANCHOR_TIMESTAMP = new Date(MS_PER_DAY).toJSON(); @@ -33,47 +30,42 @@ const ANCHOR_TIMESTAMP_3 = new Date(MS_PER_DAY * 3).toJSON(); const ANCHOR_TIMESTAMP_3000 = new Date(MS_PER_DAY * 3000).toJSON(); describe('context app', function() { - beforeEach(() => pluginInstance.initializeInnerAngular()); - beforeEach(ngMock.module('app/discover')); - describe('function fetchSuccessors', function() { let fetchSuccessors; let searchSourceStub; - beforeEach( - ngMock.inject(function createPrivateStubs() { - searchSourceStub = createContextSearchSourceStub([], '@timestamp'); + beforeEach(() => { + searchSourceStub = createContextSearchSourceStub([], '@timestamp'); + + fetchSuccessors = ( + indexPatternId, + timeField, + sortDir, + timeValIso, + timeValNr, + tieBreakerField, + tieBreakerValue, + size + ) => { + const anchor = { + _source: { + [timeField]: timeValIso, + }, + sort: [timeValNr, tieBreakerValue], + }; - fetchSuccessors = ( + return fetchContextProvider(createIndexPatternsStub()).fetchSurroundingDocs( + 'successors', indexPatternId, + anchor, timeField, - sortDir, - timeValIso, - timeValNr, tieBreakerField, - tieBreakerValue, - size - ) => { - const anchor = { - _source: { - [timeField]: timeValIso, - }, - sort: [timeValNr, tieBreakerValue], - }; - - return fetchContextProvider(createIndexPatternsStub()).fetchSurroundingDocs( - 'successors', - indexPatternId, - anchor, - timeField, - tieBreakerField, - sortDir, - size, - [] - ); - }; - }) - ); + sortDir, + size, + [] + ); + }; + }); afterEach(() => { searchSourceStub._restore(); @@ -99,8 +91,8 @@ describe('context app', function() { 3, [] ).then(hits => { - expect(searchSourceStub.fetch.calledOnce).to.be(true); - expect(hits).to.eql(searchSourceStub._stubHits.slice(-3)); + expect(searchSourceStub.fetch.calledOnce).toBe(true); + expect(hits).toEqual(searchSourceStub._stubHits.slice(-3)); }); }); @@ -132,14 +124,14 @@ describe('context app', function() { expect( intervals.every(({ gte, lte }) => (gte && lte ? moment(gte).isBefore(lte) : true)) - ).to.be(true); + ).toBe(true); // should have started at the given time - expect(intervals[0].lte).to.eql(moment(MS_PER_DAY * 3000).toISOString()); + expect(intervals[0].lte).toEqual(moment(MS_PER_DAY * 3000).toISOString()); // should have ended with a half-open interval - expect(_.last(intervals)).to.only.have.keys('lte', 'format'); - expect(intervals.length).to.be.greaterThan(1); + expect(Object.keys(_.last(intervals))).toEqual(['format', 'lte']); + expect(intervals.length).toBeGreaterThan(1); - expect(hits).to.eql(searchSourceStub._stubHits.slice(-3)); + expect(hits).toEqual(searchSourceStub._stubHits.slice(-3)); }); }); @@ -171,12 +163,12 @@ describe('context app', function() { ); // should have started at the given time - expect(intervals[0].lte).to.eql(moment(MS_PER_DAY * 3000).toISOString()); + expect(intervals[0].lte).toEqual(moment(MS_PER_DAY * 3000).toISOString()); // should have stopped before reaching MS_PER_DAY * 2200 - expect(moment(_.last(intervals).gte).valueOf()).to.be.greaterThan(MS_PER_DAY * 2200); - expect(intervals.length).to.be.greaterThan(1); + expect(moment(_.last(intervals).gte).valueOf()).toBeGreaterThan(MS_PER_DAY * 2200); + expect(intervals.length).toBeGreaterThan(1); - expect(hits).to.eql(searchSourceStub._stubHits.slice(0, 4)); + expect(hits).toEqual(searchSourceStub._stubHits.slice(0, 4)); }); }); @@ -192,7 +184,7 @@ describe('context app', function() { 3, [] ).then(hits => { - expect(hits).to.eql([]); + expect(hits).toEqual([]); }); }); @@ -209,8 +201,8 @@ describe('context app', function() { [] ).then(() => { const setParentSpy = searchSourceStub.setParent; - expect(setParentSpy.alwaysCalledWith(undefined)).to.be(true); - expect(setParentSpy.called).to.be(true); + expect(setParentSpy.alwaysCalledWith(undefined)).toBe(true); + expect(setParentSpy.called).toBe(true); }); }); @@ -228,7 +220,7 @@ describe('context app', function() { ).then(() => { expect( searchSourceStub.setField.calledWith('sort', [{ '@timestamp': 'desc' }, { _doc: 'desc' }]) - ).to.be(true); + ).toBe(true); }); }); }); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.ts index b91ef5a6b79fb..507f927c608e1 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.ts @@ -17,14 +17,18 @@ * under the License. */ -import { IndexPattern, SearchSource } from '../../../../kibana_services'; import { reverseSortDir, SortDirection } from './utils/sorting'; import { extractNanos, convertIsoToMillis } from './utils/date_conversion'; import { fetchHitsInInterval } from './utils/fetch_hits_in_interval'; import { generateIntervals } from './utils/generate_intervals'; import { getEsQuerySearchAfter } from './utils/get_es_query_search_after'; import { getEsQuerySort } from './utils/get_es_query_sort'; -import { Filter, IndexPatternsContract } from '../../../../../../../../../plugins/data/public'; +import { + Filter, + IndexPatternsContract, + IndexPattern, + SearchSource, +} from '../../../../../../../../../plugins/data/public'; export type SurrDocType = 'successors' | 'predecessors'; export interface EsHitRecord { diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/fetch_hits_in_interval.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/fetch_hits_in_interval.ts index e7df44e6fe61c..8eed5d33ab004 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/fetch_hits_in_interval.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/fetch_hits_in_interval.ts @@ -16,7 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -import { EsQuerySortValue, SortDirection, ISearchSource } from '../../../../../kibana_services'; +import { + ISearchSource, + EsQuerySortValue, + SortDirection, +} from '../../../../../../../../../../plugins/data/public'; import { convertTimeValueToIso } from './date_conversion'; import { EsHitRecordList } from '../context'; import { IntervalValue } from './generate_intervals'; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/generate_intervals.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/generate_intervals.ts index 373dc37e56f6f..b14180d32b4f2 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/generate_intervals.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/generate_intervals.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { SortDirection } from '../../../../../kibana_services'; +import { SortDirection } from '../../../../../../../../../../plugins/data/public'; export type IntervalValue = number | null; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/actions.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/actions.js index 1cebb88cbda5a..674f40d0186e5 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/actions.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/actions.js @@ -29,9 +29,13 @@ import { FAILURE_REASONS, LOADING_STATUS } from './constants'; import { MarkdownSimple } from '../../../../../../../kibana_react/public'; export function QueryActionsProvider(Promise) { - const fetchAnchor = fetchAnchorProvider(getServices().indexPatterns, new SearchSource()); - const { fetchSurroundingDocs } = fetchContextProvider(getServices().indexPatterns); - const { setPredecessorCount, setQueryParameters, setSuccessorCount } = getQueryParameterActions(); + const { filterManager, indexPatterns } = getServices(); + const fetchAnchor = fetchAnchorProvider(indexPatterns, new SearchSource()); + const { fetchSurroundingDocs } = fetchContextProvider(indexPatterns); + const { setPredecessorCount, setQueryParameters, setSuccessorCount } = getQueryParameterActions( + filterManager, + indexPatterns + ); const setFailedStatus = state => (subject, details = {}) => (state.loadingStatus[subject] = { diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.js index 5be1179a9ae09..5c1700e776361 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.js @@ -18,14 +18,11 @@ */ import _ from 'lodash'; -import { getServices } from '../../../../kibana_services'; import { esFilters } from '../../../../../../../../../plugins/data/public'; import { MAX_CONTEXT_SIZE, MIN_CONTEXT_SIZE, QUERY_PARAMETER_KEYS } from './constants'; -export function getQueryParameterActions() { - const filterManager = getServices().filterManager; - +export function getQueryParameterActions(filterManager, indexPatterns) { const setPredecessorCount = state => predecessorCount => (state.queryParameters.predecessorCount = clamp( MIN_CONTEXT_SIZE, @@ -57,8 +54,10 @@ export function getQueryParameterActions() { indexPatternId ); filterManager.addFilters(newFilters); - const indexPattern = await getServices().indexPatterns.get(indexPatternId); - indexPattern.popularizeField(field.name, 1); + if (indexPatterns) { + const indexPattern = await indexPatterns.get(indexPatternId); + indexPattern.popularizeField(field.name, 1); + } }; return { diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.test.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.test.ts new file mode 100644 index 0000000000000..35fbd33fb4bc9 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.test.ts @@ -0,0 +1,157 @@ +/* + * 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 { getQueryParameterActions } from './actions'; +import { FilterManager } from '../../../../../../../../../plugins/data/public'; +import { coreMock } from '../../../../../../../../../core/public/mocks'; +const setupMock = coreMock.createSetup(); + +let state: { + queryParameters: { + defaultStepSize: number; + indexPatternId: string; + predecessorCount: number; + successorCount: number; + }; +}; +let filterManager: FilterManager; +let filterManagerSpy: jest.SpyInstance; + +beforeEach(() => { + filterManager = new FilterManager(setupMock.uiSettings); + filterManagerSpy = jest.spyOn(filterManager, 'addFilters'); + + state = { + queryParameters: { + defaultStepSize: 3, + indexPatternId: 'INDEX_PATTERN_ID', + predecessorCount: 10, + successorCount: 10, + }, + }; +}); + +describe('context query_parameter actions', function() { + describe('action addFilter', () => { + it('should pass the given arguments to the filterManager', () => { + const { addFilter } = getQueryParameterActions(filterManager); + + addFilter(state)('FIELD_NAME', 'FIELD_VALUE', 'FILTER_OPERATION'); + + // get the generated filter + const generatedFilter = filterManagerSpy.mock.calls[0][0][0]; + const queryKeys = Object.keys(generatedFilter.query.match_phrase); + expect(filterManagerSpy.mock.calls.length).toBe(1); + expect(queryKeys[0]).toBe('FIELD_NAME'); + expect(generatedFilter.query.match_phrase[queryKeys[0]]).toBe('FIELD_VALUE'); + }); + + it('should pass the index pattern id to the filterManager', () => { + const { addFilter } = getQueryParameterActions(filterManager); + addFilter(state)('FIELD_NAME', 'FIELD_VALUE', 'FILTER_OPERATION'); + const generatedFilter = filterManagerSpy.mock.calls[0][0][0]; + expect(generatedFilter.meta.index).toBe('INDEX_PATTERN_ID'); + }); + }); + describe('action setPredecessorCount', () => { + it('should set the predecessorCount to the given value', () => { + const { setPredecessorCount } = getQueryParameterActions(filterManager); + setPredecessorCount(state)(20); + expect(state.queryParameters.predecessorCount).toBe(20); + }); + + it('should limit the predecessorCount to 0 as a lower bound', () => { + const { setPredecessorCount } = getQueryParameterActions(filterManager); + setPredecessorCount(state)(-1); + expect(state.queryParameters.predecessorCount).toBe(0); + }); + + it('should limit the predecessorCount to 10000 as an upper bound', () => { + const { setPredecessorCount } = getQueryParameterActions(filterManager); + setPredecessorCount(state)(20000); + expect(state.queryParameters.predecessorCount).toBe(10000); + }); + }); + describe('action setSuccessorCount', () => { + it('should set the successorCount to the given value', function() { + const { setSuccessorCount } = getQueryParameterActions(filterManager); + setSuccessorCount(state)(20); + + expect(state.queryParameters.successorCount).toBe(20); + }); + + it('should limit the successorCount to 0 as a lower bound', () => { + const { setSuccessorCount } = getQueryParameterActions(filterManager); + setSuccessorCount(state)(-1); + expect(state.queryParameters.successorCount).toBe(0); + }); + + it('should limit the successorCount to 10000 as an upper bound', () => { + const { setSuccessorCount } = getQueryParameterActions(filterManager); + setSuccessorCount(state)(20000); + expect(state.queryParameters.successorCount).toBe(10000); + }); + }); + describe('action setQueryParameters', function() { + const { setQueryParameters } = getQueryParameterActions(filterManager); + + it('should update the queryParameters with valid properties from the given object', function() { + const newState = { + ...state, + queryParameters: { + additionalParameter: 'ADDITIONAL_PARAMETER', + }, + }; + + const actualState = setQueryParameters(newState)({ + anchorId: 'ANCHOR_ID', + columns: ['column'], + defaultStepSize: 3, + filters: ['filter'], + indexPatternId: 'INDEX_PATTERN', + predecessorCount: 100, + successorCount: 100, + sort: ['field'], + }); + + expect(actualState).toEqual({ + additionalParameter: 'ADDITIONAL_PARAMETER', + anchorId: 'ANCHOR_ID', + columns: ['column'], + defaultStepSize: 3, + filters: ['filter'], + indexPatternId: 'INDEX_PATTERN', + predecessorCount: 100, + successorCount: 100, + sort: ['field'], + }); + }); + + it('should ignore invalid properties', function() { + const newState = { ...state }; + + setQueryParameters(newState)({ + additionalParameter: 'ADDITIONAL_PARAMETER', + }); + + expect(state.queryParameters).toEqual(newState.queryParameters); + }); + }); +}); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_app.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_app.js index b5ba2844e8b06..345717cafee9a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_app.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_app.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import { getAngularModule } from '../../kibana_services'; +import { getAngularModule, getServices } from '../../kibana_services'; import contextAppTemplate from './context_app.html'; import './context/components/action_bar'; import { getFirstSortableField } from './context/api/utils/sorting'; @@ -58,7 +58,8 @@ module.directive('contextApp', function ContextApp() { }); function ContextAppController($scope, config, Private) { - const queryParameterActions = getQueryParameterActions(); + const { filterManager, indexpatterns } = getServices(); + const queryParameterActions = getQueryParameterActions(filterManager, indexpatterns); const queryActions = Private(QueryActionsProvider); this.state = createInitialState( parseInt(config.get('context:step'), 10), diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/index.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/index.js index 1a3922dfc2008..5482258e06564 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/index.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/index.js @@ -19,7 +19,6 @@ import { DiscoverNoResults } from './no_results'; import { DiscoverUninitialized } from './uninitialized'; -import { DiscoverUnsupportedIndexPattern } from './unsupported_index_pattern'; import { DiscoverHistogram } from './histogram'; import { getAngularModule, wrapInI18nContext } from '../../../kibana_services'; @@ -33,8 +32,4 @@ app.directive('discoverUninitialized', reactDirective => reactDirective(wrapInI18nContext(DiscoverUninitialized)) ); -app.directive('discoverUnsupportedIndexPattern', reactDirective => - reactDirective(wrapInI18nContext(DiscoverUnsupportedIndexPattern), ['unsupportedType']) -); - app.directive('discoverHistogram', reactDirective => reactDirective(DiscoverHistogram)); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/unsupported_index_pattern.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/unsupported_index_pattern.js deleted file mode 100644 index 7cf4fd1d14181..0000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/unsupported_index_pattern.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React, { Fragment } from 'react'; -import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; - -export const DiscoverUnsupportedIndexPattern = ({ unsupportedType }) => { - // This message makes the assumption that X-Pack will support this type, as is the case with - // rollup index patterns. - const message = ( - - ); - - return ( - - - - - - - - - - ); -}; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html index 2d44b12989228..2334e33deadba 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html @@ -38,11 +38,6 @@

{{screenTitle}}

- - !_.isEqual(newStatePartial[prop], oldStatePartial[prop]) ); - if (changes.indexOf('index') !== -1) { - try { - $scope.indexPattern = await indexPatterns.get(newStatePartial.index); - $scope.opts.timefield = getTimeField(); - $scope.enableTimeRangeSelector = !!$scope.opts.timefield; - // is needed to rerender the histogram - $scope.vis = undefined; - - // Taking care of sort when switching index pattern: - // Old indexPattern: sort by A - // If A is not available in the new index pattern, sort has to be adapted and propagated to URL - const sort = getSortArray(newStatePartial.sort, $scope.indexPattern); - if (newStatePartial.sort && !_.isEqual(sort, newStatePartial.sort)) { - return await replaceUrlAppState({ sort }); - } - } catch (e) { - toastNotifications.addWarning({ text: getIndexPatternWarning(newStatePartial.index) }); - } - } if (changes.length) { $fetchObservable.next(); @@ -268,8 +248,9 @@ function discoverController( } }); - $scope.setIndexPattern = id => { - setAppState({ index: id }); + $scope.setIndexPattern = async id => { + await replaceUrlAppState({ index: id }); + $route.reload(); }; // update data source when filters update @@ -805,7 +786,7 @@ function discoverController( title: i18n.translate('kbn.discover.errorLoadingData', { defaultMessage: 'Error loading data', }), - toastMessage: error.shortMessage, + toastMessage: error.shortMessage || error.body?.message, }); } }); @@ -1107,17 +1088,6 @@ function discoverController( return loadedIndexPattern; } - // Block the UI from loading if the user has loaded a rollup index pattern but it isn't - // supported. - $scope.isUnsupportedIndexPattern = - !isDefaultType($route.current.locals.savedObjects.ip.loaded) && - !hasSearchStategyForIndexPattern($route.current.locals.savedObjects.ip.loaded); - - if ($scope.isUnsupportedIndexPattern) { - $scope.unsupportedIndexPatternType = $route.current.locals.savedObjects.ip.loaded.type; - return; - } - addHelpMenuToAppChrome(chrome); init(); diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js index e1a20e3381331..3fab650002c17 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js @@ -115,7 +115,7 @@ function VisualizeAppController( savedVis.vis.on('apply', _applyVis); // vis is instance of src/legacy/ui/public/vis/vis.js. // SearchSource is a promise-based stream of search results that can inherit from other search sources. - const { vis, searchSource } = savedVis; + const { vis, searchSource, savedSearch } = savedVis; $scope.vis = vis; @@ -379,6 +379,17 @@ function VisualizeAppController( }, }; + const handleLinkedSearch = linked => { + if (linked && !savedVis.savedSearchId && savedSearch) { + savedVis.savedSearchId = savedSearch.id; + vis.savedSearchId = savedSearch.id; + searchSource.setParent(savedSearch.searchSource); + } else if (!linked && savedVis.savedSearchId) { + delete savedVis.savedSearchId; + delete vis.savedSearchId; + } + }; + // Create a PersistedState instance for uiState. const { persistedState, unsubscribePersisted, persistOnChange } = makeStateful( 'uiState', @@ -387,9 +398,9 @@ function VisualizeAppController( $scope.uiState = persistedState; $scope.savedVis = savedVis; $scope.query = initialState.query; - $scope.linked = initialState.linked; $scope.searchSource = searchSource; $scope.refreshInterval = timefilter.getRefreshInterval(); + handleLinkedSearch(initialState.linked); const addToDashMode = $route.current.params[DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM]; @@ -468,7 +479,7 @@ function VisualizeAppController( $scope.fetch = function() { const { query, linked, filters } = stateContainer.getState(); $scope.query = query; - $scope.linked = linked; + handleLinkedSearch(linked); savedVis.searchSource.setField('query', query); savedVis.searchSource.setField('filter', filters); $scope.$broadcast('render'); @@ -558,20 +569,6 @@ function VisualizeAppController( updateStateFromSavedQuery(savedQuery); }; - $scope.$watch('linked', linked => { - if (linked && !savedVis.savedSearchId) { - savedVis.savedSearchId = savedVis.searchSource.id; - vis.savedSearchId = savedVis.searchSource.id; - - $scope.$broadcast('render'); - } else if (!linked && savedVis.savedSearchId) { - delete savedVis.savedSearchId; - delete vis.savedSearchId; - - $scope.$broadcast('render'); - } - }); - /** * Called when the user clicks "Save" button. */ @@ -663,33 +660,26 @@ function VisualizeAppController( } const unlinkFromSavedSearch = () => { - const searchSourceParent = searchSource.getParent(); + const searchSourceParent = savedSearch.searchSource; const searchSourceGrandparent = searchSourceParent.getParent(); + const currentIndex = searchSourceParent.getField('index'); - delete savedVis.savedSearchId; - delete vis.savedSearchId; - searchSourceParent.setField( - 'filter', - _.union(searchSource.getOwnField('filter'), searchSourceParent.getOwnField('filter')) - ); - - stateContainer.transitions.unlinkSavedSearch( - searchSourceParent.getField('query'), - searchSourceParent.getField('filter') - ); - searchSource.setField('index', searchSourceParent.getField('index')); + searchSource.setField('index', currentIndex); searchSource.setParent(searchSourceGrandparent); + stateContainer.transitions.unlinkSavedSearch({ + query: searchSourceParent.getField('query'), + parentFilters: searchSourceParent.getOwnField('filter'), + }); + toastNotifications.addSuccess( i18n.translate('kbn.visualize.linkedToSearch.unlinkSuccessNotificationText', { defaultMessage: `Unlinked from saved search '{searchTitle}'`, values: { - searchTitle: savedVis.savedSearch.title, + searchTitle: savedSearch.title, }, }) ); - - $scope.fetch(); }; $scope.getAdditionalMessage = () => { diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/visualize_app_state.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/visualize_app_state.ts index d3fae3d457b63..86f39ea76dd3a 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/visualize_app_state.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/visualize_app_state.ts @@ -17,7 +17,7 @@ * under the License. */ -import { isFunction, omit } from 'lodash'; +import { isFunction, omit, union } from 'lodash'; import { migrateAppState } from './migrate_app_state'; import { @@ -75,10 +75,10 @@ export function useVisualizeAppState({ stateDefaults, kbnUrlStateStorage }: Argu query: defaultQuery, }; }, - unlinkSavedSearch: state => (query, filters) => ({ + unlinkSavedSearch: state => ({ query, parentFilters = [] }) => ({ ...state, - query, - filters, + query: query || state.query, + filters: union(state.filters, parentFilters), linked: false, }), updateVisState: state => newVisState => ({ ...state, vis: toObject(newVisState) }), diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts index 55fccd75361a0..ccb3b3ddbb1da 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts @@ -52,7 +52,7 @@ export interface VisualizeAppStateTransitions { removeSavedQuery: (state: VisualizeAppState) => (defaultQuery: Query) => VisualizeAppState; unlinkSavedSearch: ( state: VisualizeAppState - ) => (query: Query, filters: Filter[]) => VisualizeAppState; + ) => ({ query, parentFilters }: { query?: Query; parentFilters?: Filter[] }) => VisualizeAppState; updateVisState: (state: VisualizeAppState) => (vis: PureVisState) => VisualizeAppState; updateFromSavedQuery: (state: VisualizeAppState) => (savedQuery: SavedQuery) => VisualizeAppState; } diff --git a/src/legacy/core_plugins/telemetry/server/collectors/application_usage/index.test.ts b/src/legacy/core_plugins/telemetry/server/collectors/application_usage/index.test.ts index cdfead2dff3c6..1a64100bda692 100644 --- a/src/legacy/core_plugins/telemetry/server/collectors/application_usage/index.test.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/application_usage/index.test.ts @@ -114,9 +114,11 @@ describe('telemetry_application_usage', () => { expect(await collector.fetch(callCluster)).toStrictEqual({ appId: { clicks_total: total - 1 + 10, + clicks_7_days: total - 1, clicks_30_days: total - 1, clicks_90_days: total - 1, minutes_on_screen_total: total - 1 + 10, + minutes_on_screen_7_days: total - 1, minutes_on_screen_30_days: total - 1, minutes_on_screen_90_days: total - 1, }, diff --git a/src/legacy/core_plugins/telemetry/server/collectors/application_usage/telemetry_application_usage_collector.ts b/src/legacy/core_plugins/telemetry/server/collectors/application_usage/telemetry_application_usage_collector.ts index 5047ebc4b0454..5c862686a37d9 100644 --- a/src/legacy/core_plugins/telemetry/server/collectors/application_usage/telemetry_application_usage_collector.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/application_usage/telemetry_application_usage_collector.ts @@ -20,12 +20,8 @@ import moment from 'moment'; import { APPLICATION_USAGE_TYPE } from '../../../common/constants'; import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server'; -import { - ISavedObjectsRepository, - SavedObjectAttributes, - SavedObjectsFindOptions, - SavedObject, -} from '../../../../../../core/server'; +import { ISavedObjectsRepository, SavedObjectAttributes } from '../../../../../../core/server'; +import { findAll } from '../find_all'; /** * Roll indices every 24h @@ -53,30 +49,16 @@ interface ApplicationUsageTransactional extends ApplicationUsageTotal { interface ApplicationUsageTelemetryReport { [appId: string]: { clicks_total: number; + clicks_7_days: number; clicks_30_days: number; clicks_90_days: number; minutes_on_screen_total: number; + minutes_on_screen_7_days: number; minutes_on_screen_30_days: number; minutes_on_screen_90_days: number; }; } -async function findAll( - savedObjectsClient: ISavedObjectsRepository, - opts: SavedObjectsFindOptions -): Promise>> { - const { page = 1, perPage = 100, ...options } = opts; - const { saved_objects: savedObjects, total } = await savedObjectsClient.find({ - ...options, - page, - perPage, - }); - if (page * perPage >= total) { - return savedObjects; - } - return [...savedObjects, ...(await findAll(savedObjectsClient, { ...opts, page: page + 1 }))]; -} - export function registerApplicationUsageCollector( usageCollection: UsageCollectionSetup, getSavedObjectsClient: () => ISavedObjectsRepository | undefined @@ -103,9 +85,11 @@ export function registerApplicationUsageCollector( ...acc, [appId]: { clicks_total: numberOfClicks + existing.clicks_total, + clicks_7_days: 0, clicks_30_days: 0, clicks_90_days: 0, minutes_on_screen_total: minutesOnScreen + existing.minutes_on_screen_total, + minutes_on_screen_7_days: 0, minutes_on_screen_30_days: 0, minutes_on_screen_90_days: 0, }, @@ -113,7 +97,7 @@ export function registerApplicationUsageCollector( }, {} as ApplicationUsageTelemetryReport ); - + const nowMinus7 = moment().subtract(7, 'days'); const nowMinus30 = moment().subtract(30, 'days'); const nowMinus90 = moment().subtract(90, 'days'); @@ -121,17 +105,24 @@ export function registerApplicationUsageCollector( (acc, { attributes: { appId, minutesOnScreen, numberOfClicks, timestamp } }) => { const existing = acc[appId] || { clicks_total: 0, + clicks_7_days: 0, clicks_30_days: 0, clicks_90_days: 0, minutes_on_screen_total: 0, + minutes_on_screen_7_days: 0, minutes_on_screen_30_days: 0, minutes_on_screen_90_days: 0, }; const timeOfEntry = moment(timestamp as string); + const isInLast7Days = timeOfEntry.isSameOrAfter(nowMinus7); const isInLast30Days = timeOfEntry.isSameOrAfter(nowMinus30); const isInLast90Days = timeOfEntry.isSameOrAfter(nowMinus90); + const last7Days = { + clicks_7_days: existing.clicks_7_days + numberOfClicks, + minutes_on_screen_7_days: existing.minutes_on_screen_7_days + minutesOnScreen, + }; const last30Days = { clicks_30_days: existing.clicks_30_days + numberOfClicks, minutes_on_screen_30_days: existing.minutes_on_screen_30_days + minutesOnScreen, @@ -147,6 +138,7 @@ export function registerApplicationUsageCollector( ...existing, clicks_total: existing.clicks_total + numberOfClicks, minutes_on_screen_total: existing.minutes_on_screen_total + minutesOnScreen, + ...(isInLast7Days ? last7Days : {}), ...(isInLast30Days ? last30Days : {}), ...(isInLast90Days ? last90Days : {}), }, diff --git a/src/legacy/core_plugins/telemetry/server/collectors/find_all.test.ts b/src/legacy/core_plugins/telemetry/server/collectors/find_all.test.ts new file mode 100644 index 0000000000000..012cda395bc6c --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/collectors/find_all.test.ts @@ -0,0 +1,55 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { savedObjectsRepositoryMock } from '../../../../../core/server/mocks'; + +import { findAll } from './find_all'; + +describe('telemetry_application_usage', () => { + test('when savedObjectClient is initialised, return something', async () => { + const savedObjectClient = savedObjectsRepositoryMock.create(); + savedObjectClient.find.mockImplementation( + async () => + ({ + saved_objects: [], + total: 0, + } as any) + ); + + expect(await findAll(savedObjectClient, { type: 'test-type' })).toStrictEqual([]); + }); + + test('paging in findAll works', async () => { + const savedObjectClient = savedObjectsRepositoryMock.create(); + let total = 201; + const doc = { id: 'test-id', attributes: { test: 1 } }; + savedObjectClient.find.mockImplementation(async opts => { + if ((opts.page || 1) > 2) { + return { saved_objects: [], total } as any; + } + const savedObjects = new Array(opts.perPage).fill(doc); + total = savedObjects.length * 2 + 1; + return { saved_objects: savedObjects, total }; + }); + + expect(await findAll(savedObjectClient, { type: 'test-type' })).toStrictEqual( + new Array(total - 1).fill(doc) + ); + }); +}); diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/_utils.js b/src/legacy/core_plugins/telemetry/server/collectors/find_all.ts similarity index 55% rename from src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/_utils.js rename to src/legacy/core_plugins/telemetry/server/collectors/find_all.ts index 63f8ced97e9dc..e6363551eba9c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/_utils.js +++ b/src/legacy/core_plugins/telemetry/server/collectors/find_all.ts @@ -17,18 +17,25 @@ * under the License. */ -import _ from 'lodash'; +import { + SavedObjectAttributes, + ISavedObjectsRepository, + SavedObjectsFindOptions, + SavedObject, +} from 'kibana/server'; -export function createStateStub(overrides) { - return _.merge( - { - queryParameters: { - defaultStepSize: 3, - indexPatternId: 'INDEX_PATTERN_ID', - predecessorCount: 10, - successorCount: 10, - }, - }, - overrides - ); +export async function findAll( + savedObjectsClient: ISavedObjectsRepository, + opts: SavedObjectsFindOptions +): Promise>> { + const { page = 1, perPage = 100, ...options } = opts; + const { saved_objects: savedObjects, total } = await savedObjectsClient.find({ + ...options, + page, + perPage, + }); + if (page * perPage >= total) { + return savedObjects; + } + return [...savedObjects, ...(await findAll(savedObjectsClient, { ...opts, page: page + 1 }))]; } diff --git a/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/index.test.ts b/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/index.test.ts new file mode 100644 index 0000000000000..ddb58a7d09bbd --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/index.test.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 { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server'; +import { savedObjectsRepositoryMock } from '../../../../../../core/server/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { CollectorOptions } from '../../../../../../plugins/usage_collection/server/collector/collector'; + +import { registerUiMetricUsageCollector } from './'; + +describe('telemetry_ui_metric', () => { + let collector: CollectorOptions; + + const usageCollectionMock: jest.Mocked = { + makeUsageCollector: jest.fn().mockImplementation(config => (collector = config)), + registerCollector: jest.fn(), + } as any; + + const getUsageCollector = jest.fn(); + const callCluster = jest.fn(); + + beforeAll(() => registerUiMetricUsageCollector(usageCollectionMock, getUsageCollector)); + + test('registered collector is set', () => { + expect(collector).not.toBeUndefined(); + }); + + test('if no savedObjectClient initialised, return undefined', async () => { + expect(await collector.fetch(callCluster)).toBeUndefined(); + }); + + test('when savedObjectClient is initialised, return something', async () => { + const savedObjectClient = savedObjectsRepositoryMock.create(); + savedObjectClient.find.mockImplementation( + async () => + ({ + saved_objects: [], + total: 0, + } as any) + ); + getUsageCollector.mockImplementation(() => savedObjectClient); + + expect(await collector.fetch(callCluster)).toStrictEqual({}); + expect(savedObjectClient.bulkCreate).not.toHaveBeenCalled(); + }); + + test('results grouped by appName', async () => { + const savedObjectClient = savedObjectsRepositoryMock.create(); + savedObjectClient.find.mockImplementation(async () => { + return { + saved_objects: [ + { id: 'testAppName:testKeyName1', attributes: { count: 3 } }, + { id: 'testAppName:testKeyName2', attributes: { count: 5 } }, + { id: 'testAppName2:testKeyName3', attributes: { count: 1 } }, + ], + total: 3, + } as any; + }); + + getUsageCollector.mockImplementation(() => savedObjectClient); + + expect(await collector.fetch(callCluster)).toStrictEqual({ + testAppName: [ + { key: 'testKeyName1', value: 3 }, + { key: 'testKeyName2', value: 5 }, + ], + testAppName2: [{ key: 'testKeyName3', value: 1 }], + }); + }); +}); diff --git a/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts b/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts index 73157abce8629..a7b6850b0b20a 100644 --- a/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts @@ -17,24 +17,33 @@ * under the License. */ +import { ISavedObjectsRepository, SavedObjectAttributes } from 'kibana/server'; import { UI_METRIC_USAGE_TYPE } from '../../../common/constants'; import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server'; +import { findAll } from '../find_all'; -export function registerUiMetricUsageCollector(usageCollection: UsageCollectionSetup, server: any) { +interface UIMetricsSavedObjects extends SavedObjectAttributes { + count: number; +} + +export function registerUiMetricUsageCollector( + usageCollection: UsageCollectionSetup, + getSavedObjectsClient: () => ISavedObjectsRepository | undefined +) { const collector = usageCollection.makeUsageCollector({ type: UI_METRIC_USAGE_TYPE, fetch: async () => { - const { SavedObjectsClient, getSavedObjectsRepository } = server.savedObjects; - const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); - const internalRepository = getSavedObjectsRepository(callWithInternalUser); - const savedObjectsClient = new SavedObjectsClient(internalRepository); + const savedObjectsClient = getSavedObjectsClient(); + if (typeof savedObjectsClient === 'undefined') { + return; + } - const { saved_objects: rawUiMetrics } = await savedObjectsClient.find({ + const rawUiMetrics = await findAll(savedObjectsClient, { type: 'ui-metric', fields: ['count'], }); - const uiMetricsByAppName = rawUiMetrics.reduce((accum: any, rawUiMetric: any) => { + const uiMetricsByAppName = rawUiMetrics.reduce((accum, rawUiMetric) => { const { id, attributes: { count }, @@ -42,18 +51,16 @@ export function registerUiMetricUsageCollector(usageCollection: UsageCollectionS const [appName, metricType] = id.split(':'); - if (!accum[appName]) { - accum[appName] = []; - } - const pair = { key: metricType, value: count }; - accum[appName].push(pair); - return accum; - }, {}); + return { + ...accum, + [appName]: [...(accum[appName] || []), pair], + }; + }, {} as Record>); return uiMetricsByAppName; }, - isReady: () => true, + isReady: () => typeof getSavedObjectsClient() !== 'undefined', }); usageCollection.registerCollector(collector); diff --git a/src/legacy/core_plugins/telemetry/server/plugin.ts b/src/legacy/core_plugins/telemetry/server/plugin.ts index d859c0cfd4678..0b9f0526988c8 100644 --- a/src/legacy/core_plugins/telemetry/server/plugin.ts +++ b/src/legacy/core_plugins/telemetry/server/plugin.ts @@ -59,7 +59,7 @@ export class TelemetryPlugin { registerTelemetryPluginUsageCollector(usageCollection, server); registerLocalizationUsageCollector(usageCollection, server); registerTelemetryUsageCollector(usageCollection, server); - registerUiMetricUsageCollector(usageCollection, server); + registerUiMetricUsageCollector(usageCollection, getSavedObjectsClient); registerManagementUsageCollector(usageCollection, server); registerApplicationUsageCollector(usageCollection, getSavedObjectsClient); } diff --git a/src/legacy/core_plugins/vis_type_vega/index.ts b/src/legacy/core_plugins/vis_type_vega/index.ts index ccef24f8f9746..ac7e407ca9e4d 100644 --- a/src/legacy/core_plugins/vis_type_vega/index.ts +++ b/src/legacy/core_plugins/vis_type_vega/index.ts @@ -24,10 +24,16 @@ import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy const vegaPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => new Plugin({ - // TODO: ID property should be changed from 'vega' to 'vis_type_vega' - // It is required to change the configuration property - // vega.enableExternalUrls -> vis_type_vega.enableExternalUrls - id: 'vega', + id: 'vis_type_vega', + deprecations: ({ rename }: { rename: any }) => [ + rename('vega.enabled', 'vis_type_vega.enabled'), + ], + config(Joi: any) { + return Joi.object({ + enabled: Joi.boolean().default(true), + enableExternalUrls: Joi.boolean().default(false), + }).default(); + }, require: ['kibana', 'elasticsearch'], publicDir: resolve(__dirname, 'public'), uiExports: { diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.ts index f658f6ef52df8..ba86125f2e246 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.ts @@ -32,6 +32,12 @@ export interface Vis { aggs: Array<{ [key: string]: any }>; }; + /** + * If a visualization based on the saved search, + * the id is necessary for building an expression function in src/plugins/expressions/common/expression_functions/specs/kibana_context.ts + */ + savedSearchId?: string; + // Since we haven't typed everything here yet, we basically "any" the rest // of that interface. This should be removed as soon as this type definition // has been completed. But that way we at least have typing for a couple of diff --git a/src/legacy/ui/public/filter_manager/index.js b/src/legacy/ui/public/filter_manager/index.js deleted file mode 100644 index 9880b336e76e5..0000000000000 --- a/src/legacy/ui/public/filter_manager/index.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ diff --git a/src/legacy/ui/public/filter_manager/query_filter.d.ts b/src/legacy/ui/public/filter_manager/query_filter.d.ts deleted file mode 100644 index b5d7742f51d46..0000000000000 --- a/src/legacy/ui/public/filter_manager/query_filter.d.ts +++ /dev/null @@ -1,22 +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 type QueryFilter = any; - -export const FilterBarQueryFilterProvider: () => QueryFilter; diff --git a/src/legacy/ui/public/filter_manager/query_filter.js b/src/legacy/ui/public/filter_manager/query_filter.js deleted file mode 100644 index 97b3810b7f1c7..0000000000000 --- a/src/legacy/ui/public/filter_manager/query_filter.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 { FilterStateManager } from 'plugins/data'; -import { npStart } from 'ui/new_platform'; - -export function FilterBarQueryFilterProvider(getAppState, globalState) { - const { filterManager } = npStart.plugins.data.query; - const filterStateManager = new FilterStateManager(globalState, getAppState, filterManager); - - const queryFilter = {}; - queryFilter.getUpdates$ = filterManager.getUpdates$.bind(filterManager); - queryFilter.getFetches$ = filterManager.getFetches$.bind(filterManager); - queryFilter.getFilters = filterManager.getFilters.bind(filterManager); - queryFilter.getAppFilters = filterManager.getAppFilters.bind(filterManager); - queryFilter.getGlobalFilters = filterManager.getGlobalFilters.bind(filterManager); - queryFilter.removeFilter = filterManager.removeFilter.bind(filterManager); - queryFilter.addFilters = filterManager.addFilters.bind(filterManager); - queryFilter.setFilters = filterManager.setFilters.bind(filterManager); - queryFilter.removeAll = filterManager.removeAll.bind(filterManager); - - queryFilter.destroy = () => { - filterManager.destroy(); - filterStateManager.destroy(); - }; - - return queryFilter; -} diff --git a/src/plugins/data/common/search/es_search/types.ts b/src/plugins/data/common/search/es_search/types.ts index 1576a6e38e36d..550aac6819d0b 100644 --- a/src/plugins/data/common/search/es_search/types.ts +++ b/src/plugins/data/common/search/es_search/types.ts @@ -23,6 +23,7 @@ export const ES_SEARCH_STRATEGY = 'es'; export interface IEsSearchRequest extends IKibanaSearchRequest { params: SearchParams; + indexType?: string; } export interface IEsSearchResponse extends IKibanaSearchResponse { diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 7487f048525bd..7d739103eb2bb 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -286,11 +286,7 @@ export { export { ES_SEARCH_STRATEGY, SYNC_SEARCH_STRATEGY, - defaultSearchStrategy, - esSearchStrategyProvider, getEsPreference, - addSearchStrategy, - hasSearchStategyForIndexPattern, getSearchErrorType, ISearchContext, TSearchStrategyProvider, @@ -348,9 +344,7 @@ export { SavedQuery, SavedQueryService, SavedQueryTimeFilter, - SavedQueryAttributes, InputTimeRange, - TimefilterSetup, TimeHistory, TimefilterContract, TimeHistoryContract, diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index c41a4ef531443..48125254ff749 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -49,11 +49,6 @@ import { UiSettingsParams } from 'src/core/server/types'; import { UnregisterCallback } from 'history'; import { UserProvidedValues } from 'src/core/server/types'; -// Warning: (ae-missing-release-tag) "addSearchStrategy" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const addSearchStrategy: (searchStrategy: SearchStrategyProvider) => void; - // Warning: (ae-forgotten-export) The symbol "DateFormat" 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) // @@ -153,11 +148,6 @@ export interface DataPublicPluginStart { }; } -// Warning: (ae-missing-release-tag) "defaultSearchStrategy" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const defaultSearchStrategy: SearchStrategyProvider; - // @public (undocumented) export enum ES_FIELD_TYPES { // (undocumented) @@ -313,11 +303,6 @@ export interface EsQueryConfig { // @public (undocumented) export type EsQuerySortValue = Record; -// Warning: (ae-missing-release-tag) "esSearchStrategyProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const esSearchStrategyProvider: TSearchStrategyProvider; - // Warning: (ae-missing-release-tag) "ExistsFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -488,11 +473,6 @@ export function getSearchErrorType({ message }: Pick): " // @public (undocumented) export function getTime(indexPattern: IIndexPattern | undefined, timeRange: TimeRange, forceNow?: Date): import("../..").RangeFilter | undefined; -// Warning: (ae-missing-release-tag) "hasSearchStategyForIndexPattern" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const hasSearchStategyForIndexPattern: (indexPattern: IndexPattern) => boolean; - // Warning: (ae-missing-release-tag) "IDataPluginServices" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -517,6 +497,8 @@ export interface IDataPluginServices extends Partial { // // @public (undocumented) export interface IEsSearchRequest extends IKibanaSearchRequest { + // (undocumented) + indexType?: string; // (undocumented) params: SearchParams; } @@ -1227,28 +1209,14 @@ export interface RefreshInterval { // // @public (undocumented) export interface SavedQuery { + // Warning: (ae-forgotten-export) The symbol "SavedQueryAttributes" needs to be exported by the entry point index.d.ts + // // (undocumented) attributes: SavedQueryAttributes; // (undocumented) id: string; } -// Warning: (ae-missing-release-tag) "SavedQueryAttributes" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export interface SavedQueryAttributes { - // (undocumented) - description: string; - // (undocumented) - filters?: Filter[]; - // (undocumented) - query: Query; - // (undocumented) - timefilter?: SavedQueryTimeFilter; - // (undocumented) - title: string; -} - // Warning: (ae-missing-release-tag) "SavedQueryService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1484,14 +1452,6 @@ export const syncQueryStateWithUrl: (query: Pick<{ // @public (undocumented) export type TimefilterContract = PublicMethodsOf; -// @public (undocumented) -export interface TimefilterSetup { - // (undocumented) - history: TimeHistoryContract; - // (undocumented) - timefilter: TimefilterContract; -} - // Warning: (ae-missing-release-tag) "TimeHistory" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/src/plugins/data/public/query/saved_query/saved_query_service.test.ts b/src/plugins/data/public/query/saved_query/saved_query_service.test.ts index c983cc4ea8fc5..a86a5b4ed401e 100644 --- a/src/plugins/data/public/query/saved_query/saved_query_service.test.ts +++ b/src/plugins/data/public/query/saved_query/saved_query_service.test.ts @@ -18,8 +18,8 @@ */ import { createSavedQueryService } from './saved_query_service'; -import { SavedQueryAttributes } from '../..'; import { FilterStateStore } from '../../../common'; +import { SavedQueryAttributes } from './types'; const savedQueryAttributes: SavedQueryAttributes = { title: 'foo', diff --git a/src/plugins/data/public/search/fetch/call_client.test.ts b/src/plugins/data/public/search/fetch/call_client.test.ts index 6b43157aab83b..7a99b7c064515 100644 --- a/src/plugins/data/public/search/fetch/call_client.test.ts +++ b/src/plugins/data/public/search/fetch/call_client.test.ts @@ -20,60 +20,35 @@ import { callClient } from './call_client'; import { handleResponse } from './handle_response'; import { FetchHandlers } from './types'; -import { SearchRequest } from '../..'; -import { SearchStrategySearchParams } from '../search_strategy'; - -const mockResponses = [{}, {}]; -const mockAbortFns = [jest.fn(), jest.fn()]; -const mockSearchFns = [ - jest.fn(({ searchRequests }: SearchStrategySearchParams) => ({ - searching: Promise.resolve(Array(searchRequests.length).fill(mockResponses[0])), - abort: mockAbortFns[0], - })), - jest.fn(({ searchRequests }: SearchStrategySearchParams) => ({ - searching: Promise.resolve(Array(searchRequests.length).fill(mockResponses[1])), - abort: mockAbortFns[1], - })), -]; -const mockSearchStrategies = mockSearchFns.map((search, i) => ({ search, id: i })); +import { SearchStrategySearchParams, defaultSearchStrategy } from '../search_strategy'; +const mockAbortFn = jest.fn(); jest.mock('./handle_response', () => ({ handleResponse: jest.fn((request, response) => response), })); -jest.mock('../search_strategy', () => ({ - getSearchStrategyForSearchRequest: (request: SearchRequest) => - mockSearchStrategies[request._searchStrategyId], - getSearchStrategyById: (id: number) => mockSearchStrategies[id], -})); +jest.mock('../search_strategy', () => { + return { + defaultSearchStrategy: { + search: jest.fn(({ searchRequests }: SearchStrategySearchParams) => { + return { + searching: Promise.resolve( + searchRequests.map(req => { + return { + id: req._searchStrategyId, + }; + }) + ), + abort: mockAbortFn, + }; + }), + }, + }; +}); describe('callClient', () => { beforeEach(() => { (handleResponse as jest.Mock).mockClear(); - mockAbortFns.forEach(fn => fn.mockClear()); - mockSearchFns.forEach(fn => fn.mockClear()); - }); - - test('Executes each search strategy with its group of matching requests', () => { - const searchRequests = [ - { _searchStrategyId: 0 }, - { _searchStrategyId: 1 }, - { _searchStrategyId: 0 }, - { _searchStrategyId: 1 }, - ]; - - callClient(searchRequests, [], {} as FetchHandlers); - - expect(mockSearchFns[0]).toBeCalled(); - expect(mockSearchFns[0].mock.calls[0][0].searchRequests).toEqual([ - searchRequests[0], - searchRequests[2], - ]); - expect(mockSearchFns[1]).toBeCalled(); - expect(mockSearchFns[1].mock.calls[0][0].searchRequests).toEqual([ - searchRequests[1], - searchRequests[3], - ]); }); test('Passes the additional arguments it is given to the search strategy', () => { @@ -82,8 +57,11 @@ describe('callClient', () => { callClient(searchRequests, [], args); - expect(mockSearchFns[0]).toBeCalled(); - expect(mockSearchFns[0].mock.calls[0][0]).toEqual({ searchRequests, ...args }); + expect(defaultSearchStrategy.search).toBeCalled(); + expect((defaultSearchStrategy.search as any).mock.calls[0][0]).toEqual({ + searchRequests, + ...args, + }); }); test('Returns the responses in the original order', async () => { @@ -91,7 +69,8 @@ describe('callClient', () => { const responses = await Promise.all(callClient(searchRequests, [], {} as FetchHandlers)); - expect(responses).toEqual([mockResponses[1], mockResponses[0]]); + expect(responses[0]).toEqual({ id: searchRequests[0]._searchStrategyId }); + expect(responses[1]).toEqual({ id: searchRequests[1]._searchStrategyId }); }); test('Calls handleResponse with each request and response', async () => { @@ -101,8 +80,12 @@ describe('callClient', () => { await Promise.all(responses); expect(handleResponse).toBeCalledTimes(2); - expect(handleResponse).toBeCalledWith(searchRequests[0], mockResponses[0]); - expect(handleResponse).toBeCalledWith(searchRequests[1], mockResponses[1]); + expect(handleResponse).toBeCalledWith(searchRequests[0], { + id: searchRequests[0]._searchStrategyId, + }); + expect(handleResponse).toBeCalledWith(searchRequests[1], { + id: searchRequests[1]._searchStrategyId, + }); }); test('If passed an abortSignal, calls abort on the strategy if the signal is aborted', () => { @@ -117,7 +100,7 @@ describe('callClient', () => { callClient(searchRequests, requestOptions, {} as FetchHandlers); abortController.abort(); - expect(mockAbortFns[0]).toBeCalled(); - expect(mockAbortFns[1]).not.toBeCalled(); + expect(mockAbortFn).toBeCalled(); + // expect(mockAbortFns[1]).not.toBeCalled(); }); }); diff --git a/src/plugins/data/public/search/fetch/call_client.ts b/src/plugins/data/public/search/fetch/call_client.ts index 6cc58b05ea183..b3c4c682fa60c 100644 --- a/src/plugins/data/public/search/fetch/call_client.ts +++ b/src/plugins/data/public/search/fetch/call_client.ts @@ -17,10 +17,9 @@ * under the License. */ -import { groupBy } from 'lodash'; import { handleResponse } from './handle_response'; import { FetchOptions, FetchHandlers } from './types'; -import { getSearchStrategyForSearchRequest, getSearchStrategyById } from '../search_strategy'; +import { defaultSearchStrategy } from '../search_strategy'; import { SearchRequest } from '..'; export function callClient( @@ -34,34 +33,18 @@ export function callClient( FetchOptions ]> = searchRequests.map((request, i) => [request, requestsOptions[i]]); const requestOptionsMap = new Map(requestOptionEntries); - - // Group the requests by the strategy used to search that specific request - const searchStrategyMap = groupBy(searchRequests, (request, i) => { - const searchStrategy = getSearchStrategyForSearchRequest(request, requestsOptions[i]); - return searchStrategy.id; - }); - - // Execute each search strategy with the group of requests, but return the responses in the same - // order in which they were received. We use a map to correlate the original request with its - // response. const requestResponseMap = new Map(); - Object.keys(searchStrategyMap).forEach(searchStrategyId => { - const searchStrategy = getSearchStrategyById(searchStrategyId); - const requests = searchStrategyMap[searchStrategyId]; - // There's no way `searchStrategy` could be undefined here because if we didn't get a matching strategy for this ID - // then an error would have been thrown above - const { searching, abort } = searchStrategy!.search({ - searchRequests: requests, - ...fetchHandlers, - }); + const { searching, abort } = defaultSearchStrategy.search({ + searchRequests, + ...fetchHandlers, + }); - requests.forEach((request, i) => { - const response = searching.then(results => handleResponse(request, results[i])); - const { abortSignal = null } = requestOptionsMap.get(request) || {}; - if (abortSignal) abortSignal.addEventListener('abort', abort); - requestResponseMap.set(request, response); - }); - }, []); + searchRequests.forEach((request, i) => { + const response = searching.then(results => handleResponse(request, results[i])); + const { abortSignal = null } = requestOptionsMap.get(request) || {}; + if (abortSignal) abortSignal.addEventListener('abort', abort); + requestResponseMap.set(request, response); + }); return searchRequests.map(request => requestResponseMap.get(request)); } diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts index 2a54cfe2be785..6ccd90c6a9eff 100644 --- a/src/plugins/data/public/search/index.ts +++ b/src/plugins/data/public/search/index.ts @@ -42,14 +42,7 @@ export { IKibanaSearchResponse, IKibanaSearchRequest } from '../../common/search export { LegacyApiCaller, SearchRequest, SearchResponse } from './es_client'; -export { - addSearchStrategy, - hasSearchStategyForIndexPattern, - defaultSearchStrategy, - SearchError, - SearchStrategyProvider, - getSearchErrorType, -} from './search_strategy'; +export { SearchError, SearchStrategyProvider, getSearchErrorType } from './search_strategy'; export { ISearchSource, 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 21e5ded6983ac..0c3321f03dabc 100644 --- a/src/plugins/data/public/search/search_source/search_source.ts +++ b/src/plugins/data/public/search/search_source/search_source.ts @@ -73,7 +73,7 @@ import _ from 'lodash'; import { normalizeSortRequest } from './normalize_sort_request'; import { filterDocvalueFields } from './filter_docvalue_fields'; import { fieldWildcardFilter } from '../../../../kibana_utils/public'; -import { SearchRequest } from '../..'; +import { IIndexPattern, SearchRequest } from '../..'; import { SearchSourceOptions, SearchSourceFields } from './types'; import { fetchSoon, FetchOptions, RequestFailure } from '../fetch'; @@ -339,11 +339,20 @@ export class SearchSource { return searchRequest; } + private getIndexType(index: IIndexPattern) { + if (this.searchStrategyId) { + return this.searchStrategyId === 'default' ? undefined : this.searchStrategyId; + } else { + return index?.type; + } + } + private flatten() { const searchRequest = this.mergeProps(); searchRequest.body = searchRequest.body || {}; const { body, index, fields, query, filters, highlightAll } = searchRequest; + searchRequest.indexType = this.getIndexType(index); const computedFields = index ? index.getComputedFields() : {}; diff --git a/src/plugins/data/public/search/search_strategy/default_search_strategy.ts b/src/plugins/data/public/search/search_strategy/default_search_strategy.ts index 6fcb1e6b3e8d2..2bd88f51587a8 100644 --- a/src/plugins/data/public/search/search_strategy/default_search_strategy.ts +++ b/src/plugins/data/public/search/search_strategy/default_search_strategy.ts @@ -74,7 +74,7 @@ function search({ }: SearchStrategySearchParams) { const abortController = new AbortController(); const searchParams = getSearchParams(config, esShardTimeout); - const promises = searchRequests.map(({ index, body }) => { + const promises = searchRequests.map(({ index, indexType, body }) => { const params = { index: index.title || index, body, @@ -82,7 +82,7 @@ function search({ }; const { signal } = abortController; return searchService - .search({ params }, { signal }) + .search({ params, indexType }, { signal }) .toPromise() .then(({ rawResponse }) => rawResponse); }); diff --git a/src/plugins/data/public/search/search_strategy/index.ts b/src/plugins/data/public/search/search_strategy/index.ts index 330e10d7d30e4..e3de2ea46e3ec 100644 --- a/src/plugins/data/public/search/search_strategy/index.ts +++ b/src/plugins/data/public/search/search_strategy/index.ts @@ -17,13 +17,6 @@ * under the License. */ -export { - addSearchStrategy, - hasSearchStategyForIndexPattern, - getSearchStrategyById, - getSearchStrategyForSearchRequest, -} from './search_strategy_registry'; - export { SearchError, getSearchErrorType } from './search_error'; export { SearchStrategyProvider, SearchStrategySearchParams } from './types'; diff --git a/src/plugins/data/public/search/search_strategy/search_strategy_registry.test.ts b/src/plugins/data/public/search/search_strategy/search_strategy_registry.test.ts deleted file mode 100644 index eaf86e1b270d5..0000000000000 --- a/src/plugins/data/public/search/search_strategy/search_strategy_registry.test.ts +++ /dev/null @@ -1,149 +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 { IndexPattern } from '../..'; -import { noOpSearchStrategy } from './no_op_search_strategy'; -import { - searchStrategies, - addSearchStrategy, - getSearchStrategyByViability, - getSearchStrategyById, - getSearchStrategyForSearchRequest, - hasSearchStategyForIndexPattern, -} from './search_strategy_registry'; -import { SearchStrategyProvider } from './types'; - -const mockSearchStrategies: SearchStrategyProvider[] = [ - { - id: '0', - isViable: (index: IndexPattern) => index.id === '0', - search: () => ({ - searching: Promise.resolve([]), - abort: () => void 0, - }), - }, - { - id: '1', - isViable: (index: IndexPattern) => index.id === '1', - search: () => ({ - searching: Promise.resolve([]), - abort: () => void 0, - }), - }, -]; - -describe('Search strategy registry', () => { - beforeEach(() => { - searchStrategies.length = 0; - }); - - describe('addSearchStrategy', () => { - it('adds a search strategy', () => { - addSearchStrategy(mockSearchStrategies[0]); - expect(searchStrategies.length).toBe(1); - }); - - it('does not add a search strategy if it is already included', () => { - addSearchStrategy(mockSearchStrategies[0]); - addSearchStrategy(mockSearchStrategies[0]); - expect(searchStrategies.length).toBe(1); - }); - }); - - describe('getSearchStrategyByViability', () => { - beforeEach(() => { - mockSearchStrategies.forEach(addSearchStrategy); - }); - - it('returns the viable strategy', () => { - expect(getSearchStrategyByViability({ id: '0' } as IndexPattern)).toBe( - mockSearchStrategies[0] - ); - expect(getSearchStrategyByViability({ id: '1' } as IndexPattern)).toBe( - mockSearchStrategies[1] - ); - }); - - it('returns undefined if there is no viable strategy', () => { - expect(getSearchStrategyByViability({ id: '-1' } as IndexPattern)).toBe(undefined); - }); - }); - - describe('getSearchStrategyById', () => { - beforeEach(() => { - mockSearchStrategies.forEach(addSearchStrategy); - }); - - it('returns the strategy by ID', () => { - expect(getSearchStrategyById('0')).toBe(mockSearchStrategies[0]); - expect(getSearchStrategyById('1')).toBe(mockSearchStrategies[1]); - }); - - it('returns undefined if there is no strategy with that ID', () => { - expect(getSearchStrategyById('-1')).toBe(undefined); - }); - - it('returns the noOp search strategy if passed that ID', () => { - expect(getSearchStrategyById('noOp')).toBe(noOpSearchStrategy); - }); - }); - - describe('getSearchStrategyForSearchRequest', () => { - beforeEach(() => { - mockSearchStrategies.forEach(addSearchStrategy); - }); - - it('returns the strategy by ID if provided', () => { - expect(getSearchStrategyForSearchRequest({}, { searchStrategyId: '1' })).toBe( - mockSearchStrategies[1] - ); - }); - - it('throws if there is no strategy by provided ID', () => { - expect(() => - getSearchStrategyForSearchRequest({}, { searchStrategyId: '-1' }) - ).toThrowErrorMatchingInlineSnapshot(`"No strategy with ID -1"`); - }); - - it('returns the strategy by viability if there is one', () => { - expect( - getSearchStrategyForSearchRequest({ - index: { - id: '1', - }, - }) - ).toBe(mockSearchStrategies[1]); - }); - - it('returns the no op strategy if there is no viable strategy', () => { - expect(getSearchStrategyForSearchRequest({ index: '3' })).toBe(noOpSearchStrategy); - }); - }); - - describe('hasSearchStategyForIndexPattern', () => { - beforeEach(() => { - mockSearchStrategies.forEach(addSearchStrategy); - }); - - it('returns whether there is a search strategy for this index pattern', () => { - expect(hasSearchStategyForIndexPattern({ id: '0' } as IndexPattern)).toBe(true); - expect(hasSearchStategyForIndexPattern({ id: '-1' } as IndexPattern)).toBe(false); - }); - }); -}); diff --git a/src/plugins/data/public/search/search_strategy/search_strategy_registry.ts b/src/plugins/data/public/search/search_strategy/search_strategy_registry.ts deleted file mode 100644 index 1ab6f7d4e1eff..0000000000000 --- a/src/plugins/data/public/search/search_strategy/search_strategy_registry.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { IndexPattern } from '../..'; -import { SearchStrategyProvider } from './types'; -import { noOpSearchStrategy } from './no_op_search_strategy'; -import { SearchResponse } from '..'; - -export const searchStrategies: SearchStrategyProvider[] = []; - -export const addSearchStrategy = (searchStrategy: SearchStrategyProvider) => { - if (searchStrategies.includes(searchStrategy)) { - return; - } - - searchStrategies.push(searchStrategy); -}; - -export const getSearchStrategyByViability = (indexPattern: IndexPattern) => { - return searchStrategies.find(searchStrategy => { - return searchStrategy.isViable(indexPattern); - }); -}; - -export const getSearchStrategyById = (searchStrategyId: string) => { - return [...searchStrategies, noOpSearchStrategy].find(searchStrategy => { - return searchStrategy.id === searchStrategyId; - }); -}; - -export const getSearchStrategyForSearchRequest = ( - searchRequest: SearchResponse, - { searchStrategyId }: { searchStrategyId?: string } = {} -) => { - // Allow the searchSource to declare the correct strategy with which to execute its searches. - if (searchStrategyId != null) { - const strategy = getSearchStrategyById(searchStrategyId); - if (!strategy) throw Error(`No strategy with ID ${searchStrategyId}`); - return strategy; - } - - // Otherwise try to match it to a strategy. - const viableSearchStrategy = getSearchStrategyByViability(searchRequest.index); - - if (viableSearchStrategy) { - return viableSearchStrategy; - } - - // This search strategy automatically rejects with an error. - return noOpSearchStrategy; -}; - -export const hasSearchStategyForIndexPattern = (indexPattern: IndexPattern) => { - return Boolean(getSearchStrategyByViability(indexPattern)); -}; diff --git a/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx b/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx index 7183f14bdb255..36dcd4a00c05e 100644 --- a/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx +++ b/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx @@ -35,7 +35,8 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { sortBy, isEqual } from 'lodash'; -import { SavedQuery, SavedQueryAttributes, SavedQueryService } from '../..'; +import { SavedQuery, SavedQueryService } from '../..'; +import { SavedQueryAttributes } from '../../query'; interface Props { savedQuery?: SavedQueryAttributes; 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 26055a3ae41f7..b4ee02eefaf84 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 @@ -31,6 +31,13 @@ export const esSearchStrategyProvider: TSearchStrategyProvider { const config = await context.config$.pipe(first()).toPromise(); const defaultParams = getDefaultSearchParams(config); + + // Only default index pattern type is supported here. + // See data_enhanced for other type support. + if (!!request.indexType) { + throw new Error(`Unsupported index pattern type ${request.indexType}`); + } + const params = { ...defaultParams, ...request.params, diff --git a/src/plugins/data/server/search/routes.ts b/src/plugins/data/server/search/routes.ts index 2b8c4b95ee022..e618f99084aed 100644 --- a/src/plugins/data/server/search/routes.ts +++ b/src/plugins/data/server/search/routes.ts @@ -43,11 +43,11 @@ export function registerSearchRoute(router: IRouter): void { return res.ok({ body: response }); } catch (err) { return res.customError({ - statusCode: err.statusCode, + statusCode: err.statusCode || 500, body: { message: err.message, attributes: { - error: err.body.error, + error: err.body?.error || err.message, }, }, }); diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 09bb150594177..46f90e3c6fc62 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -55,7 +55,7 @@ export class SearchService implements Plugin { core.http.registerRouteHandlerContext<'search'>('search', context => { return createApi({ - caller: context.core!.elasticsearch.dataClient.callAsCurrentUser, + caller: context.core.elasticsearch.dataClient.callAsCurrentUser, searchStrategies: this.searchStrategies, }); }); diff --git a/src/plugins/timelion/server/series_functions/label.js b/src/plugins/timelion/server/series_functions/label.js index 1e4782e5a381e..6e46a92b48add 100644 --- a/src/plugins/timelion/server/series_functions/label.js +++ b/src/plugins/timelion/server/series_functions/label.js @@ -51,7 +51,10 @@ export default new Chainable('label', { const config = args.byName; return alter(args, function(eachSeries) { if (config.regex) { - eachSeries.label = eachSeries.label.replace(new RegExp(config.regex), config.label); + // not using a standard `import` so that if there's an issue with the re2 native module + // that it doesn't prevent Kibana from starting up and we only have an issue using Timelion labels + const RE2 = require('re2'); + eachSeries.label = eachSeries.label.replace(new RE2(config.regex), config.label); } else { eachSeries.label = config.label; } diff --git a/src/plugins/visualizations/public/persisted_state/persisted_state.ts b/src/plugins/visualizations/public/persisted_state/persisted_state.ts index d09dcd5381511..b81b651c73509 100644 --- a/src/plugins/visualizations/public/persisted_state/persisted_state.ts +++ b/src/plugins/visualizations/public/persisted_state/persisted_state.ts @@ -26,10 +26,8 @@ function prepSetParams(key: PersistedStateKey, value: any, path: PersistedStateP // key must be the value, set the entire state using it if (value === undefined && (isPlainObject(key) || path.length > 0)) { // setting entire tree, swap the key and value to write to the state - return { - value: key, - key: undefined, - }; + value = key; + key = undefined; } // ensure the value being passed in is never mutated diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/stub_state.ts b/src/setup_node_env/exit_on_warning.js similarity index 60% rename from src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/stub_state.ts rename to src/setup_node_env/exit_on_warning.js index 272c8a4e19913..5be5ccd72bd02 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/stub_state.ts +++ b/src/setup_node_env/exit_on_warning.js @@ -17,25 +17,22 @@ * under the License. */ -import sinon from 'sinon'; +if (process.noProcessWarnings !== true) { + var ignore = ['MaxListenersExceededWarning']; -import { State } from 'ui/state_management/state'; -import { Filter } from '../../../../../../../plugins/data/public'; + process.on('warning', function(warn) { + if (ignore.includes(warn.name)) return; -export class StubState implements State { - filters: Filter[]; - save: sinon.SinonSpy; + if (process.traceProcessWarnings === true) { + console.error('Node.js process-warning detected - Terminating process...'); + } else { + console.error('Node.js process-warning detected:'); + console.error(); + console.error(warn.stack); + console.error(); + console.error('Terminating process...'); + } - constructor() { - this.save = sinon.stub(); - this.filters = []; - } - - getQueryParamName() { - return '_a'; - } - - translateHashToRison(stateHashOrRison: string | string[]): string | string[] { - return ''; - } + process.exit(1); + }); } diff --git a/src/setup_node_env/index.js b/src/setup_node_env/index.js index 0f51f47572be6..97de5ba76b926 100644 --- a/src/setup_node_env/index.js +++ b/src/setup_node_env/index.js @@ -17,7 +17,11 @@ * under the License. */ -require('./harden'); // this require MUST be executed before any others +// The following require statements MUST be executed before any others - BEGIN +require('./exit_on_warning'); +require('./harden'); +// The following require statements MUST be executed before any others - END + require('symbol-observable'); require('./root'); require('./node_version_validator'); diff --git a/src/test_utils/kbn_server.ts b/src/test_utils/kbn_server.ts index e1b4a823e7e87..f4c3ecd8243ce 100644 --- a/src/test_utils/kbn_server.ts +++ b/src/test_utils/kbn_server.ts @@ -252,7 +252,7 @@ export function createTestServers({ return { startES: async () => { - await es.start(); + await es.start(get(settings, 'es.esArgs', [])); if (['gold', 'trial'].includes(license)) { await setupUsers({ diff --git a/tasks/test.js b/tasks/test.js index 504247f5b5355..5618ebba4e6eb 100644 --- a/tasks/test.js +++ b/tasks/test.js @@ -61,7 +61,7 @@ module.exports = function(grunt) { 'run:apiIntegrationTests', ]); - grunt.registerTask('test:karmaDebug', ['checkPlugins', 'run:karmaDebugServer', 'karma:dev']); + grunt.registerTask('test:karmaDebug', ['checkPlugins', 'run:karmaTestDebugServer', 'karma:dev']); grunt.registerTask('test:mochaCoverage', ['run:mochaCoverage']); grunt.registerTask('test', subTask => { diff --git a/test/accessibility/apps/discover.ts b/test/accessibility/apps/discover.ts index e25d295515971..cf3d37d29b491 100644 --- a/test/accessibility/apps/discover.ts +++ b/test/accessibility/apps/discover.ts @@ -25,7 +25,13 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const inspector = getService('inspector'); + const docTable = getService('docTable'); const filterBar = getService('filterBar'); + const TEST_COLUMN_NAMES = ['@message']; + const TEST_FILTER_COLUMN_NAMES = [ + ['extension', 'jpg'], + ['geo.src', 'IN'], + ]; describe('Discover', () => { before(async () => { @@ -57,7 +63,6 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { await a11y.testAppSnapshot(); }); - // skipping the test for new because we can't fix it right now it.skip('Click on new to clear the search', async () => { await PageObjects.discover.clickNewSearchButton(); await a11y.testAppSnapshot(); @@ -94,7 +99,6 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { await a11y.testAppSnapshot(); }); - // unable to validate on EUI pop-over it('click share button', async () => { await PageObjects.share.clickShareTopNavButton(); await a11y.testAppSnapshot(); @@ -109,5 +113,29 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.closeSidebarFieldFilter(); await a11y.testAppSnapshot(); }); + + it('Add a field from sidebar', async () => { + for (const columnName of TEST_COLUMN_NAMES) { + await PageObjects.discover.clickFieldListItemAdd(columnName); + } + await a11y.testAppSnapshot(); + }); + + it.skip('Add more fields from sidebar', async () => { + for (const [columnName, value] of TEST_FILTER_COLUMN_NAMES) { + await PageObjects.discover.clickFieldListItem(columnName); + await PageObjects.discover.clickFieldListPlusFilter(columnName, value); + } + await a11y.testAppSnapshot(); + }); + + // Context view test + it('should open context view on a doc', async () => { + await docTable.clickRowToggle(); + await (await docTable.getRowActions())[0].click(); + await a11y.testAppSnapshot(); + }); + + // Adding rest of the tests after https://github.com/elastic/kibana/issues/53888 is resolved }); } diff --git a/test/functional/apps/management/_handle_alias.js b/test/functional/apps/management/_handle_alias.js index 3d9368f8d4680..55f6b56d9f0d1 100644 --- a/test/functional/apps/management/_handle_alias.js +++ b/test/functional/apps/management/_handle_alias.js @@ -25,7 +25,8 @@ export default function({ getService, getPageObjects }) { const retry = getService('retry'); const PageObjects = getPageObjects(['common', 'home', 'settings', 'discover', 'timePicker']); - describe('Index patterns on aliases', function() { + // FLAKY: https://github.com/elastic/kibana/issues/59717 + describe.skip('Index patterns on aliases', function() { before(async function() { await esArchiver.loadIfNeeded('alias'); await esArchiver.load('empty_kibana'); diff --git a/test/functional/apps/management/_scripted_fields.js b/test/functional/apps/management/_scripted_fields.js index b1a14cd18f557..65291c3c4772c 100644 --- a/test/functional/apps/management/_scripted_fields.js +++ b/test/functional/apps/management/_scripted_fields.js @@ -53,7 +53,7 @@ export default function({ getService, getPageObjects }) { ]); describe('scripted fields', function() { - this.tags(['skipFirefox', 'skipCoverage']); + this.tags(['skipFirefox']); before(async function() { await browser.setWindowSize(1200, 800); diff --git a/test/plugin_functional/plugins/core_plugin_a/server/plugin.ts b/test/plugin_functional/plugins/core_plugin_a/server/plugin.ts index e057e63c03f4a..6535a54f6b744 100644 --- a/test/plugin_functional/plugins/core_plugin_a/server/plugin.ts +++ b/test/plugin_functional/plugins/core_plugin_a/server/plugin.ts @@ -34,7 +34,7 @@ export class CorePluginAPlugin implements Plugin { core.http.registerRouteHandlerContext('pluginA', context => { return { ping: () => - context.core!.elasticsearch.adminClient.callAsInternalUser('ping') as Promise, + context.core.elasticsearch.adminClient.callAsInternalUser('ping') as Promise, }; }); } diff --git a/vars/githubPr.groovy b/vars/githubPr.groovy index 7759edbbf5bfc..0176424452d07 100644 --- a/vars/githubPr.groovy +++ b/vars/githubPr.groovy @@ -194,14 +194,6 @@ def getNextCommentMessage(previousCommentInfo = [:]) { .join("\n\n") } -def withGithubCredentials(closure) { - withCredentials([ - string(credentialsId: '2a9602aa-ab9f-4e52-baf3-b71ca88469c7', variable: 'GITHUB_TOKEN'), - ]) { - closure() - } -} - def postComment(message) { if (!isPr()) { error "Trying to post a GitHub PR comment on a non-PR or non-elastic PR build" diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy index 2b9b0eba38f46..cb5508642711a 100644 --- a/vars/kibanaPipeline.groovy +++ b/vars/kibanaPipeline.groovy @@ -202,12 +202,20 @@ def runErrorReporter() { } def call(Map params = [:], Closure closure) { - def config = [timeoutMinutes: 135] + params + def config = [timeoutMinutes: 135, checkPrChanges: false] + params stage("Kibana Pipeline") { timeout(time: config.timeoutMinutes, unit: 'MINUTES') { timestamps { ansiColor('xterm') { + if (config.checkPrChanges && githubPr.isPr()) { + print "Checking PR for changes to determine if CI needs to be run..." + + if (prChanges.areChangesSkippable()) { + print "No changes requiring CI found in PR, skipping." + return + } + } closure() } } @@ -215,4 +223,5 @@ def call(Map params = [:], Closure closure) { } } + return this diff --git a/vars/prChanges.groovy b/vars/prChanges.groovy new file mode 100644 index 0000000000000..a9eb9027a0597 --- /dev/null +++ b/vars/prChanges.groovy @@ -0,0 +1,52 @@ + +def getSkippablePaths() { + return [ + /^docs\//, + /^rfcs\//, + /^.ci\/.+\.yml$/, + /^\.github\//, + /\.md$/, + ] +} + +def areChangesSkippable() { + if (!githubPr.isPr()) { + return false + } + + try { + def skippablePaths = getSkippablePaths() + def files = getChangedFiles() + + // 3000 is the max files GH API will return + if (files.size() >= 3000) { + return false + } + + files = files.findAll { file -> + return !skippablePaths.find { regex -> file =~ regex} + } + + return files.size() < 1 + } catch (ex) { + buildUtils.printStacktrace(ex) + print "Error while checking to see if CI is skippable based on changes. Will run CI." + return false + } +} + +def getChanges() { + withGithubCredentials { + return githubPrs.getChanges(env.ghprbPullId) + } +} + +def getChangedFiles() { + def changes = getChanges() + def changedFiles = changes.collect { it.filename } + def renamedFiles = changes.collect { it.previousFilename }.findAll { it } + + return changedFiles + renamedFiles +} + +return this diff --git a/vars/withGithubCredentials.groovy b/vars/withGithubCredentials.groovy new file mode 100644 index 0000000000000..224e49af1bd6f --- /dev/null +++ b/vars/withGithubCredentials.groovy @@ -0,0 +1,9 @@ +def call(closure) { + withCredentials([ + string(credentialsId: '2a9602aa-ab9f-4e52-baf3-b71ca88469c7', variable: 'GITHUB_TOKEN'), + ]) { + closure() + } +} + +return this diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 53628ea970fb6..60a8d1fcbf229 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -26,7 +26,7 @@ "xpack.licensing": "plugins/licensing", "xpack.logstash": "legacy/plugins/logstash", "xpack.main": "legacy/plugins/xpack_main", - "xpack.maps": "legacy/plugins/maps", + "xpack.maps": ["plugins/maps", "legacy/plugins/maps"], "xpack.ml": ["plugins/ml", "legacy/plugins/ml"], "xpack.monitoring": "legacy/plugins/monitoring", "xpack.remoteClusters": "plugins/remote_clusters", diff --git a/x-pack/index.js b/x-pack/index.js index 893802ea81621..c917befb4b3dd 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -31,7 +31,6 @@ import { crossClusterReplication } from './legacy/plugins/cross_cluster_replicat import { upgradeAssistant } from './legacy/plugins/upgrade_assistant'; import { uptime } from './legacy/plugins/uptime'; import { encryptedSavedObjects } from './legacy/plugins/encrypted_saved_objects'; -import { transform } from './legacy/plugins/transform'; import { actions } from './legacy/plugins/actions'; import { alerting } from './legacy/plugins/alerting'; import { lens } from './legacy/plugins/lens'; @@ -61,7 +60,6 @@ module.exports = function(kibana) { infra(kibana), taskManager(kibana), rollup(kibana), - transform(kibana), siem(kibana), remoteClusters(kibana), crossClusterReplication(kibana), diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Controls.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Controls.tsx index 38e86e4a0d1c9..a2e7b2c76031e 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Controls.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Controls.tsx @@ -4,13 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButtonIcon, EuiPanel } from '@elastic/eui'; +import { EuiButtonIcon, EuiPanel, EuiToolTip } from '@elastic/eui'; import theme from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; import React, { useContext, useEffect, useState } from 'react'; import styled from 'styled-components'; import { CytoscapeContext } from './Cytoscape'; import { animationOptions, nodeHeight } from './cytoscapeOptions'; +import { getAPMHref } from '../../shared/Links/apm/APMLink'; +import { useUrlParams } from '../../../hooks/useUrlParams'; +import { APMQueryParams } from '../../shared/Links/url_helpers'; const ControlsContainer = styled('div')` left: ${theme.gutterTypes.gutterMedium}; @@ -28,7 +31,7 @@ const ZoomInButton = styled(Button)` margin-bottom: ${theme.paddingSizes.s}; `; -const ZoomPanel = styled(EuiPanel)` +const Panel = styled(EuiPanel)` margin-bottom: ${theme.paddingSizes.s}; `; @@ -47,7 +50,8 @@ function doZoom(cy: cytoscape.Core | undefined, increment: number) { export function Controls() { const cy = useContext(CytoscapeContext); - + const { urlParams } = useUrlParams(); + const currentSearch = urlParams.kuery ?? ''; const [zoom, setZoom] = useState((cy && cy.zoom()) || 1); useEffect(() => { @@ -86,45 +90,73 @@ export function Controls() { const minZoom = cy.minZoom(); const isMinZoom = zoom === minZoom; const increment = (maxZoom - minZoom) / steps; + + const centerLabel = i18n.translate('xpack.apm.serviceMap.center', { + defaultMessage: 'Center' + }); + const viewFullMapLabel = i18n.translate('xpack.apm.serviceMap.viewFullMap', { + defaultMessage: 'View full service map' + }); const zoomInLabel = i18n.translate('xpack.apm.serviceMap.zoomIn', { defaultMessage: 'Zoom in' }); const zoomOutLabel = i18n.translate('xpack.apm.serviceMap.zoomOut', { defaultMessage: 'Zoom out' }); - const centerLabel = i18n.translate('xpack.apm.serviceMap.center', { - defaultMessage: 'Center' - }); + + const showViewFullMapButton = cy.nodes('.primary').length > 0; return ( - - -