diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 90f88de7d1104..9aa308c0dc58d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -62,6 +62,7 @@ /packages/kbn-interpreter/ @elastic/kibana-app-services /src/plugins/bfetch/ @elastic/kibana-app-services /src/plugins/data/ @elastic/kibana-app-services +/src/plugins/data-views/ @elastic/kibana-app-services /src/plugins/embeddable/ @elastic/kibana-app-services /src/plugins/expressions/ @elastic/kibana-app-services /src/plugins/field_formats/ @elastic/kibana-app-services diff --git a/.i18nrc.json b/.i18nrc.json index 4107772e421ca..45016edc38dcd 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -9,6 +9,7 @@ "bfetch": "src/plugins/bfetch", "dashboard": "src/plugins/dashboard", "data": "src/plugins/data", + "dataViews": "src/plugins/data_views", "embeddableApi": "src/plugins/embeddable", "embeddableExamples": "examples/embeddable_examples", "fieldFormats": "src/plugins/field_formats", diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 14dfaa84cebb6..8a19562eaff47 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,5 @@ # Contributing to Kibana -If you are an employee at Elastic, please check out our Developer Guide [here](https://docs.elastic.dev/kibana-dev-docs/welcome). +If you are an employee at Elastic, please check out our Developer Guide [here](https://docs.elastic.dev/kibana-dev-docs/getting-started/welcome). If you are an external developer, we have a legacy developer guide [here](https://www.elastic.co/guide/en/kibana/master/development.html), or you can view the raw docs from our new, internal Developer Guide [here](./dev_docs/getting_started/dev_welcome.mdx). Eventually, our internal Developer Guide will be opened for public consumption. diff --git a/STYLEGUIDE.mdx b/STYLEGUIDE.mdx index 95f29c674da9b..56117d0fd7e59 100644 --- a/STYLEGUIDE.mdx +++ b/STYLEGUIDE.mdx @@ -1,6 +1,6 @@ --- id: kibStyleGuide -slug: /kibana-dev-docs/styleguide +slug: /kibana-dev-docs/contributing/styleguide title: Style Guide summary: JavaScript/TypeScript styleguide. date: 2021-05-06 diff --git a/dev_docs/api_welcome.mdx b/dev_docs/api_welcome.mdx index be9281113b3d5..00d5bfb9644af 100644 --- a/dev_docs/api_welcome.mdx +++ b/dev_docs/api_welcome.mdx @@ -1,6 +1,6 @@ --- id: kibDevDocsApiWelcome -slug: /kibana-dev-docs/api-welcome +slug: /kibana-dev-docs/api-meta/welcome title: Welcome summary: How to use our automatically generated API documentation date: 2021-02-25 diff --git a/dev_docs/best_practices.mdx b/dev_docs/contributing/best_practices.mdx similarity index 99% rename from dev_docs/best_practices.mdx rename to dev_docs/contributing/best_practices.mdx index 767e525c0afa7..27983d92ed121 100644 --- a/dev_docs/best_practices.mdx +++ b/dev_docs/contributing/best_practices.mdx @@ -1,6 +1,6 @@ --- id: kibBestPractices -slug: /kibana-dev-docs/best-practices +slug: /kibana-dev-docs/contributing/best-practices title: Best practices summary: Best practices to follow when building a Kibana plugin. date: 2021-03-17 diff --git a/dev_docs/dev_principles.mdx b/dev_docs/contributing/dev_principles.mdx similarity index 98% rename from dev_docs/dev_principles.mdx rename to dev_docs/contributing/dev_principles.mdx index 4b238ea24f694..0b8f68d232367 100644 --- a/dev_docs/dev_principles.mdx +++ b/dev_docs/contributing/dev_principles.mdx @@ -1,10 +1,10 @@ --- id: kibDevPrinciples -slug: /kibana-dev-docs/dev-principles +slug: /kibana-dev-docs/contributing/dev-principles title: Developer principles -summary: Follow our development principles to help keep our code base stable, maintainable and scalable. +summary: Follow our development principles to help keep our code base stable, maintainable and scalable. date: 2021-03-04 -tags: ['kibana','onboarding', 'dev', 'architecture'] +tags: ['kibana', 'onboarding', 'dev', 'architecture'] --- Over time, the Kibana project has been shaped by certain principles. Like Kibana itself, some of these principles were formed by intention while others were the result of evolution and circumstance, but today all are important for the continued success and maintainability of Kibana. @@ -117,4 +117,4 @@ The primary consumers of the code we write, the APIs that we create, and the fea Features that we anticipate end users, admins, and plugin developers consuming should be documented through our official docs, but module-level READMEs and code comments are also appropriate. -Documentation is critical part of developing features and code, so an undocumented feature is an incomplete feature. \ No newline at end of file +Documentation is critical part of developing features and code, so an undocumented feature is an incomplete feature. diff --git a/dev_docs/contributing/how_we_use_github.mdx b/dev_docs/contributing/how_we_use_github.mdx index f18bcbcf556f5..ff7901fdf08da 100644 --- a/dev_docs/contributing/how_we_use_github.mdx +++ b/dev_docs/contributing/how_we_use_github.mdx @@ -1,6 +1,6 @@ --- id: kibGitHub -slug: /kibana-dev-docs/github +slug: /kibana-dev-docs/contributing/github title: How we use Github summary: Forking, branching, committing and using labels in the Kibana GitHub repo date: 2021-09-16 diff --git a/dev_docs/contributing/standards.mdx b/dev_docs/contributing/standards.mdx index 3d41de8f229cc..5f61be80ee207 100644 --- a/dev_docs/contributing/standards.mdx +++ b/dev_docs/contributing/standards.mdx @@ -9,7 +9,7 @@ tags: ['contributor', 'dev', 'github', 'getting started', 'onboarding', 'kibana' ## Developer principles -We expect all developers to read and abide by our overarching . +We expect all developers to read and abide by our overarching . ## Style guide diff --git a/dev_docs/getting_started/add_data.mdx b/dev_docs/getting_started/add_data.mdx index b09e3f6262e77..46822b82fc40d 100644 --- a/dev_docs/getting_started/add_data.mdx +++ b/dev_docs/getting_started/add_data.mdx @@ -1,6 +1,6 @@ --- id: kibDevAddData -slug: /kibana-dev-docs/tutorial/sample-data +slug: /kibana-dev-docs/getting-started/sample-data title: Add data summary: Learn how to add data to Kibana date: 2021-08-11 diff --git a/dev_docs/getting_started/dev_welcome.mdx b/dev_docs/getting_started/dev_welcome.mdx index 5e569bd377ee0..4080e0850b946 100644 --- a/dev_docs/getting_started/dev_welcome.mdx +++ b/dev_docs/getting_started/dev_welcome.mdx @@ -1,6 +1,6 @@ --- id: kibDevDocsWelcome -slug: /kibana-dev-docs/welcome +slug: /kibana-dev-docs/getting-started/welcome title: Welcome summary: Build custom solutions and applications on top of Kibana. date: 2021-01-02 diff --git a/dev_docs/getting_started/hello_world_plugin.mdx b/dev_docs/getting_started/hello_world_plugin.mdx index 7c02d2807472c..b95d0cac201fc 100644 --- a/dev_docs/getting_started/hello_world_plugin.mdx +++ b/dev_docs/getting_started/hello_world_plugin.mdx @@ -1,6 +1,6 @@ --- id: kibHelloWorldApp -slug: /kibana-dev-docs/hello-world-app +slug: /kibana-dev-docs/getting-started/hello-world-app title: Hello World summary: Build a very basic plugin that registers an application that says "Hello World!". date: 2021-08-03 diff --git a/dev_docs/getting_started/setting_up_a_development_env.mdx b/dev_docs/getting_started/setting_up_a_development_env.mdx index 04e0511e255b1..4338083b1bc8d 100644 --- a/dev_docs/getting_started/setting_up_a_development_env.mdx +++ b/dev_docs/getting_started/setting_up_a_development_env.mdx @@ -1,6 +1,6 @@ --- id: kibDevTutorialSetupDevEnv -slug: /kibana-dev-docs/tutorial/setup-dev-env +slug: /kibana-dev-docs/getting-started/setup-dev-env title: Set up a Development Environment summary: Learn how to setup a development environment for contributing to the Kibana repository date: 2021-04-26 diff --git a/dev_docs/troubleshooting.mdx b/dev_docs/getting_started/troubleshooting.mdx similarity index 94% rename from dev_docs/troubleshooting.mdx rename to dev_docs/getting_started/troubleshooting.mdx index f624a8cd77507..e0adfbad86a84 100644 --- a/dev_docs/troubleshooting.mdx +++ b/dev_docs/getting_started/troubleshooting.mdx @@ -1,6 +1,6 @@ --- id: kibTroubleshooting -slug: /kibana-dev-docs/troubleshooting +slug: /kibana-dev-docs/getting-started/troubleshooting title: Troubleshooting summary: A collection of tips for working around strange issues. date: 2021-09-08 diff --git a/dev_docs/key_concepts/anatomy_of_a_plugin.mdx b/dev_docs/key_concepts/anatomy_of_a_plugin.mdx index 3739f907c3d87..ca9119f4d21b3 100644 --- a/dev_docs/key_concepts/anatomy_of_a_plugin.mdx +++ b/dev_docs/key_concepts/anatomy_of_a_plugin.mdx @@ -1,6 +1,6 @@ --- id: kibDevAnatomyOfAPlugin -slug: /kibana-dev-docs/anatomy-of-a-plugin +slug: /kibana-dev-docs/key-concepts/anatomy-of-a-plugin title: Anatomy of a plugin summary: Anatomy of a Kibana plugin. date: 2021-08-03 diff --git a/dev_docs/building_blocks.mdx b/dev_docs/key_concepts/building_blocks.mdx similarity index 51% rename from dev_docs/building_blocks.mdx rename to dev_docs/key_concepts/building_blocks.mdx index 6320a7db4558c..da3d0f32780be 100644 --- a/dev_docs/building_blocks.mdx +++ b/dev_docs/key_concepts/building_blocks.mdx @@ -1,26 +1,28 @@ --- id: kibBuildingBlocks -slug: /kibana-dev-docs/building-blocks +slug: /kibana-dev-docs/key-concepts/building-blocks title: Building blocks summary: Consider these building blocks when developing your plugin. date: 2021-02-24 -tags: ['kibana','onboarding', 'dev', 'architecture'] +tags: ['kibana', 'onboarding', 'dev', 'architecture'] --- -When building a plugin in Kibana, there are a handful of architectural "building blocks" you can use. Some of these building blocks are "higher-level", -and some are "lower-level". High-level building blocks come +When building a plugin in Kibana, there are a handful of architectural "building blocks" you can use. Some of these building blocks are "higher-level", +and some are "lower-level". High-level building blocks come with many built-in capabilities, require less maintenance, and evolve new feature sets over time with little to no - impact on consumers. When developers use high-level building blocks, new features are exposed consistently, across all of Kibana, at the same time. - On the downside, they are not as flexible as our low-level building blocks. - - Low-level building blocks - provide greater flexibility, but require more code to stitch them together into a meaningful UX. This results in higher maintenance cost for consumers and greater UI/UX variability - across Kibana. - - For example, if an application is using and - , - their application would automatically support runtime fields. If the app is instead using the - lower-level , additional work would be required. +impact on consumers. When developers use high-level building blocks, new features are exposed consistently, across all of Kibana, at the same time. +On the downside, they are not as flexible as our low-level building blocks. + +Low-level building blocks +provide greater flexibility, but require more code to stitch them together into a meaningful UX. This results in higher maintenance cost for consumers and greater UI/UX variability +across Kibana. + +For example, if an application is using and , their application would +automatically support runtime fields. If the app is instead using the lower-level , additional work would be required. Armed with this knowledge, you can choose what works best for your use case! @@ -32,23 +34,15 @@ The following high-level building blocks can be rendered directly into your appl ### Query Bar -The provides a high-level Query Bar component that comes with support for Lucene, KQL, Saved Queries, -and . - -If you would like to expose the ability to search and filter on Elasticsearch data, the Query Bar provided by the - - is your go-to building block. +The provides a high-level Query Bar component that comes with support for Lucene, KQL, Saved Queries, +and . If you would like to expose the ability to search and filter on Elasticsearch data, the Query Bar provided by the is your go-to building block. **Github labels**: `Team:AppServices`, `Feature:QueryBar` ### Dashboard Embeddable Add a Dashboard Embeddable directly inside your application to provide users with a set of visualizations and graphs that work seamlessly -with the . Every feature that is added to a registered - -(Lens, Maps, Saved Searches and more) will be available automatically, as well as any - that are - added to the Embeddable context menu panel (for example, drilldowns, custom panel time ranges, and "share to" features). +with the . Every feature that is added to a registered (Lens, Maps, Saved Searches and more) will be available automatically, as well as any that are added to the Embeddable context menu panel (for example, drilldowns, custom panel time ranges, and "share to" features). The Dashboard Embeddable is one of the highest-level UI components you can add to your application. @@ -56,11 +50,7 @@ The Dashboard Embeddable is one of the highest-level UI components you can add t ### Lens Embeddable -Check out the Lens Embeddable if you wish to show users visualizations based on Elasticsearch data without worrying about query building and chart rendering. It's built on top of the - , and integrates with - - and . Using the same configuration, it's also possible to link to - a prefilled Lens editor, allowing the user to drill deeper and explore their data. +Check out the Lens Embeddable if you wish to show users visualizations based on Elasticsearch data without worrying about query building and chart rendering. It's built on top of the , and integrates with and . Using the same configuration, it's also possible to link to a prefilled Lens editor, allowing the user to drill deeper and explore their data. **Github labels**: `Team:VisEditors`, `Feature:Lens` @@ -72,7 +62,7 @@ Check out the Map Embeddable if you wish to embed a map in your application. ### KibanaPageTemplate -All Kibana pages should use KibanaPageTemplate to setup their pages. It's a thin wrapper around [EuiPageTemplate](https://elastic.github.io/eui/#/layout/page) that makes setting up common types of Kibana pages quicker and easier while also adhering to any Kibana-specific requirements. +All Kibana pages should use KibanaPageTemplate to setup their pages. It's a thin wrapper around [EuiPageTemplate](https://elastic.github.io/eui/#/layout/page) that makes setting up common types of Kibana pages quicker and easier while also adhering to any Kibana-specific requirements. Check out for more implementation guidance. @@ -82,10 +72,11 @@ Check out are a high-level, space-aware abstraction layer that sits -above Data Streams and Elasticsearch indices. Index Patterns provide users the -ability to define and customize the data they wish to search and filter on, on a per-space basis. For example, users can specify a set of indices, -and they can customize the field list with runtime fields, formatting options and custom labels. + are a high-level, space-aware +abstraction layer that sits above Data Streams and Elasticsearch indices. Index Patterns provide users +the ability to define and customize the data they wish to search and filter on, on a per-space basis. +For example, users can specify a set of indices, and they can customize the field list with runtime fields, +formatting options and custom labels. Index Patterns are used in many other high-level building blocks so we highly recommend you consider this building block for your search needs. @@ -93,18 +84,23 @@ Index Patterns are used in many other high-level building blocks so we highly re ### Search Source - is a high-level search service offered by the -. It requires an -, and abstracts away the raw ES DSL and search endpoint. Internally -it uses the ES . Use Search Source if you need to query data -from Elasticsearch, and you aren't already using one of the high-level UI Components that handles this internally. + is a high-level search service +offered by the . It requires +an , and abstracts away +the raw ES DSL and search endpoint. Internally it uses the ES +. Use Search Source if you need to query data from Elasticsearch, and you aren't already using one of +the high-level UI Components that handles this internally. **Github labels**: `Team:AppServices`, `Feature:Search` ### Search Strategies -Search Strategies are a low-level building block that abstracts away search details, like what REST endpoint is being called. The ES Search Strategy -is a very lightweight abstraction layer that sits just above querying ES with the elasticsearch-js client. Other search stragies are offered for other +Search Strategies are a low-level building block that abstracts away search details, like what REST endpoint is being called. The ES Search Strategy +is a very lightweight abstraction layer that sits just above querying ES with the elasticsearch-js client. Other search stragies are offered for other languages, like EQL and SQL. These are very low-level building blocks so expect a lot of glue work to make these work with the higher-level abstractions. **Github labels**: `Team:AppServices`, `Feature:Search` @@ -112,24 +108,24 @@ languages, like EQL and SQL. These are very low-level building blocks so expect ### Expressions Expressions are a low-level building block that can be used if you have advanced search needs that requiring piping results into additional functionality, like -joining and manipulating data. Lens and Canvas are built on top of Expressions. Most developers should be able to use - or - , rather than need to access the Expression language directly. +joining and manipulating data. Lens and Canvas are built on top of Expressions. Most developers should be able to use or , rather than need to +access the Expression language directly.{' '} **Github labels**: `Team:AppServices`, `Feature:ExpressionLanguage` ## Saved Objects - should be used if you need to persist application-level information. If you were building a TODO -application, each TODO item would be a `Saved Object`. Saved objects come pre-wired with support for bulk export/import, security features like space sharing and -space isolation, and tags. + should be used if you need to persist +application-level information. If you were building a TODO application, each TODO item would be a `Saved +Object`. Saved objects come pre-wired with support for bulk export/import, security features like space +sharing and space isolation, and tags. **Github labels**: `Team:Core`, `Feature:Saved Objects` # Integration building blocks Use the following building blocks to create an inter-connected, cross-application, holistic Kibana experience. These building blocks allow you to expose functionality - that promotes your own application into other applications, as well as help developers of other applications integrate into your app. +that promotes your own application into other applications, as well as help developers of other applications integrate into your app. ## UI Actions & Triggers @@ -141,6 +137,6 @@ application could register a UI Action called "View in Maps" to appear any time ## Embeddables Embeddables help you integrate your application with the Dashboard application. Register your custom UI Widget as an Embeddable and users will -be able to add it as a panel on a Dashboard. With a little extra work, it can also be exposed in Canvas workpads. +be able to add it as a panel on a Dashboard. With a little extra work, it can also be exposed in Canvas workpads. **Github labels**: `Team:AppServices`, `Feature:Embeddables` diff --git a/dev_docs/key_concepts/data_views.mdx b/dev_docs/key_concepts/data_views.mdx index e2b64c8705c48..c514af21c0cf7 100644 --- a/dev_docs/key_concepts/data_views.mdx +++ b/dev_docs/key_concepts/data_views.mdx @@ -1,16 +1,16 @@ --- id: kibDataViewsKeyConcepts -slug: /kibana-dev-docs/data-view-intro +slug: /kibana-dev-docs/key-concepts/data-view-intro title: Data Views summary: Data views are the central method of defining queryable data sets in Kibana date: 2021-08-11 -tags: ['kibana','dev', 'contributor', 'api docs'] +tags: ['kibana', 'dev', 'contributor', 'api docs'] --- -*Note: Kibana index patterns are currently being renamed to data views. There will be some naming inconsistencies until the transition is complete.* +_Note: Kibana index patterns are currently being renamed to data views. There will be some naming inconsistencies until the transition is complete._ Data views (formerly Kibana index patterns or KIPs) are the central method of describing sets of indices for queries. Usage is strongly recommended -as a number of high level rely on them. Further, they provide a consistent view of data across +as a number of high level rely on them. Further, they provide a consistent view of data across a variety Kibana apps. Data views are defined by a wildcard string (an index pattern) which matches indices, data streams, and index aliases, optionally specify a @@ -20,8 +20,6 @@ on the data view via runtime fields. Schema-on-read functionality is provided by ![image](../assets/data_view_diagram.png) - - The data view API is made available via the data plugin (`data.indexPatterns`, soon to be renamed) and most commonly used with (`data.search.search.SearchSource`) to perform queries. SearchSource will apply existing filters and queries from the search bar UI. @@ -29,4 +27,3 @@ Users can create data views via [Data view management](https://www.elastic.co/gu Additionally, they can be created through the data view API. Data views also allow formatters and custom labels to be defined for fields. - diff --git a/dev_docs/kibana_platform_plugin_intro.mdx b/dev_docs/key_concepts/kibana_platform_plugin_intro.mdx similarity index 99% rename from dev_docs/kibana_platform_plugin_intro.mdx rename to dev_docs/key_concepts/kibana_platform_plugin_intro.mdx index 252a6dcd9cd8e..b2255dbc8e5c4 100644 --- a/dev_docs/kibana_platform_plugin_intro.mdx +++ b/dev_docs/key_concepts/kibana_platform_plugin_intro.mdx @@ -1,6 +1,6 @@ --- id: kibPlatformIntro -slug: /kibana-dev-docs/platform-intro +slug: /kibana-dev-docs/key-concepts/platform-intro title: Plugins and the Kibana platform summary: An introduction to the Kibana platform and how to use it to build a plugin. date: 2021-01-06 diff --git a/dev_docs/key_concepts/performance.mdx b/dev_docs/key_concepts/performance.mdx index 2870262825e4a..0201c7774f854 100644 --- a/dev_docs/key_concepts/performance.mdx +++ b/dev_docs/key_concepts/performance.mdx @@ -1,6 +1,6 @@ --- id: kibDevPerformance -slug: /kibana-dev-docs/performance +slug: /kibana-dev-docs/key-concepts/performance title: Performance summary: Performance tips for Kibana development. date: 2021-09-02 @@ -9,13 +9,13 @@ tags: ['kibana', 'onboarding', 'dev', 'performance'] ## Keep Kibana fast -*tl;dr*: Load as much code lazily as possible. Everyone loves snappy +_tl;dr_: Load as much code lazily as possible. Everyone loves snappy applications with a responsive UI and hates spinners. Users deserve the best experience whether they run Kibana locally or in the cloud, regardless of their hardware and environment. There are 2 main aspects of the perceived speed of an application: loading time -and responsiveness to user actions. Kibana loads and bootstraps *all* +and responsiveness to user actions. Kibana loads and bootstraps _all_ the plugins whenever a user lands on any page. It means that every new application affects the overall _loading performance_, as plugin code is loaded _eagerly_ to initialize the plugin and provide plugin API to dependent @@ -60,12 +60,12 @@ export class MyPlugin implements Plugin { ### Understanding plugin bundle size -Kibana Platform plugins are pre-built with `@kbn/optimizer` +Kibana Platform plugins are pre-built with `@kbn/optimizer` and distributed as package artifacts. This means that it is no -longer necessary for us to include the `optimizer` in the +longer necessary for us to include the `optimizer` in the distributable version of Kibana Every plugin artifact contains all plugin dependencies required to run the plugin, except some -stateful dependencies shared across plugin bundles via +stateful dependencies shared across plugin bundles via `@kbn/ui-shared-deps-npm` and `@kbn/ui-shared-deps-src`. This means that plugin artifacts _tend to be larger_ than they were in the legacy platform. To understand the current size of your plugin @@ -101,7 +101,7 @@ node scripts/build_kibana_platform_plugins.js --dist --no-examples --profile Many OSS tools allow you to analyze the generated stats file: -* [An official tool](https://webpack.github.io/analyse/#modules) from -Webpack authors -* [webpack-visualizer](https://chrisbateman.github.io/webpack-visualizer/) -* [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) +- [An official tool](https://webpack.github.io/analyse/#modules) from + Webpack authors +- [webpack-visualizer](https://chrisbateman.github.io/webpack-visualizer/) +- [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) diff --git a/dev_docs/key_concepts/persistable_state.mdx b/dev_docs/key_concepts/persistable_state.mdx index 4368417170eed..189259cf1085b 100644 --- a/dev_docs/key_concepts/persistable_state.mdx +++ b/dev_docs/key_concepts/persistable_state.mdx @@ -1,19 +1,19 @@ --- id: kibDevDocsPersistableStateIntro -slug: /kibana-dev-docs/persistable-state-intro +slug: /kibana-dev-docs/key-concepts/persistable-state-intro title: Persistable State summary: Persitable state is a key concept to understand when building a Kibana plugin. date: 2021-02-02 -tags: ['kibana','dev', 'contributor', 'api docs'] +tags: ['kibana', 'dev', 'contributor', 'api docs'] --- - “Persistable state” is developer-defined state that supports being persisted by a plugin other than the one defining it. Persistable State needs to be serializable and the owner can/should provide utilities to migrate it, extract and inject any it may contain, as well as telemetry collection utilities. - +“Persistable state” is developer-defined state that supports being persisted by a plugin other than the one defining it. Persistable State needs to be serializable and the owner can/should provide utilities to migrate it, extract and inject any it may contain, as well as telemetry collection utilities. + ## Exposing state that can be persisted Any plugin that exposes state that another plugin might persist should implement interface on their `setup` contract. This will allow plugins persisting the state to easily access migrations and other utilities. -Example: Data plugin allows you to generate filters. Those filters can be persisted by applications in their saved +Example: Data plugin allows you to generate filters. Those filters can be persisted by applications in their saved objects or in the URL. In order to allow apps to migrate the filters in case the structure changes in the future, the Data plugin implements `PersistableStateService` on . note: There is currently no obvious way for a plugin to know which state is safe to persist. The developer must manually look for a matching `PersistableStateService`, or ad-hoc provided migration utilities (as is the case with Rule Type Parameters). @@ -26,9 +26,10 @@ interface on their `setup` contract and each item in the collection should imple Example: Embeddable plugin owns the registry of embeddable factories to which other plugins can register new embeddable factories. Dashboard plugin stores a bunch of embeddable panels input in its saved object and URL. Embeddable plugin setup contract implements `PersistableStateService` -interface and each `EmbeddableFactory` needs to implement `PersistableStateDefinition` interface. +interface and each `EmbeddableFactory` needs to implement `PersistableStateDefinition` interface. Embeddable plugin exposes this interfaces: + ``` // EmbeddableInput implements Serializable @@ -45,7 +46,7 @@ If the state your plugin is storing can be provided by other plugins (your plugi ## Storing persistable state as part of saved object -Any plugin that stores any persistable state as part of their saved object should make sure that its saved object migration +Any plugin that stores any persistable state as part of their saved object should make sure that its saved object migration and reference extraction and injection methods correctly use the matching `PersistableStateService` implementation for the state they are storing. Take a look at [example saved object](https://github.com/elastic/kibana/blob/master/examples/embeddable_examples/server/searchable_list_saved_object.ts#L32) which stores an embeddable state. Note how the `migrations`, `extractReferences` and `injectReferences` are defined. @@ -58,13 +59,17 @@ of `PersistableStateService` should be called, which will migrate the state from note: Currently there is no recommended way on how to store version in url and its up to every application to decide on how to implement that. ## Available state operations - + ### Extraction/Injection of References In order to support import and export, and space-sharing capabilities, Saved Objects need to explicitly list any references they contain to other Saved Objects. To support persisting your state in saved objects owned by another plugin, the and methods of Persistable State interface should be implemented. - + [See example embeddable providing extract/inject functions](https://github.com/elastic/kibana/blob/master/examples/embeddable_examples/public/migrations/migrations_embeddable_factory.ts) @@ -72,7 +77,7 @@ To support persisting your state in saved objects owned by another plugin, the < As your plugin evolves, you may need to change your state in a breaking way. If that happens, you should write a migration to upgrade the state that existed prior to the change. -. +. [See an example saved object storing embeddable state implementing saved object migration function](https://github.com/elastic/kibana/blob/master/examples/embeddable_examples/server/searchable_list_saved_object.ts) @@ -80,4 +85,4 @@ As your plugin evolves, you may need to change your state in a breaking way. If ## Telemetry -You might want to collect statistics about how your state is used. If that is the case you should implement the telemetry method of Persistable State interface. +You might want to collect statistics about how your state is used. If that is the case you should implement the telemetry method of Persistable State interface. diff --git a/dev_docs/key_concepts/saved_objects.mdx b/dev_docs/key_concepts/saved_objects.mdx index bef92bf028697..7fe66b9eab95c 100644 --- a/dev_docs/key_concepts/saved_objects.mdx +++ b/dev_docs/key_concepts/saved_objects.mdx @@ -1,39 +1,42 @@ --- id: kibDevDocsSavedObjectsIntro -slug: /kibana-dev-docs/saved-objects-intro +slug: /kibana-dev-docs/key-concepts/saved-objects-intro title: Saved Objects summary: Saved Objects are a key concept to understand when building a Kibana plugin. date: 2021-02-02 -tags: ['kibana','dev', 'contributor', 'api docs'] +tags: ['kibana', 'dev', 'contributor', 'api docs'] --- "Saved Objects" are developer defined, persisted entities, stored in the Kibana system index (which is also sometimes referred to as the `.kibana` index). The Saved Objects service allows Kibana plugins to use Elasticsearch like a primary database. Think of it as an Object Document Mapper for Elasticsearch. - Some examples of Saved Object types are dashboards, lens, canvas workpads, index patterns, cases, ml jobs, and advanced settings. Some Saved Object types are - exposed to the user in the [Saved Object management UI](https://www.elastic.co/guide/en/kibana/current/managing-saved-objects.html), but not all. +Some examples of Saved Object types are dashboards, lens, canvas workpads, index patterns, cases, ml jobs, and advanced settings. Some Saved Object types are +exposed to the user in the [Saved Object management UI](https://www.elastic.co/guide/en/kibana/current/managing-saved-objects.html), but not all. Developers create and manage their Saved Objects using the SavedObjectClient, while other data in Elasticsearch should be accessed via the data plugin's search services. ![image](../assets/saved_object_vs_data_indices.png) - - + ## References In order to support import and export, and space-sharing capabilities, Saved Objects need to explicitly list any references they contain to other Saved Objects. The parent should have a reference to it's children, not the other way around. That way when a "parent" is exported (or shared to a space), - all the "children" will be automatically included. However, when a "child" is exported, it will not include all "parents". +all the "children" will be automatically included. However, when a "child" is exported, it will not include all "parents". - + ## Migrations and Backward compatibility As your plugin evolves, you may need to change your Saved Object type in a breaking way (for example, changing the type of an attribtue, or removing an attribute). If that happens, you should write a migration to upgrade the Saved Objects that existed prior to the change. -. +. ## Security @@ -47,30 +50,30 @@ Saved Objects are "space aware". They exist in the space they were created in, a Feature controls provide another level of isolation and shareability for Saved Objects. Admins can give users and roles read, write or none permissions for each Saved Object type. -### Object level security (OLS) +### Object level security (OLS) OLS is an oft-requested feature that is not implemented yet. When it is, it will provide users with even more sharing and privacy flexibility. Individual objects can be private to the user, shared with a selection of others, or made public. Much like how sharing Google Docs works. - + ## Scalability By default all saved object types go into a single index. If you expect your saved object type to have a lot of unique fields, or if you expect there -to be many of them, you can have your objects go in a separate index by using the `indexPattern` field. Reporting and task manager are two +to be many of them, you can have your objects go in a separate index by using the `indexPattern` field. Reporting and task manager are two examples of features that use this capability. ## Searchability -Because saved objects are stored in system indices, they cannot be searched like other data can. If you see the phrase “[X] as data” it is +Because saved objects are stored in system indices, they cannot be searched like other data can. If you see the phrase “[X] as data” it is referring to this searching limitation. Users will not be able to create custom dashboards using saved object data, like they would for data stored in Elasticsearch data indices. ## Saved Objects by value Sometimes Saved Objects end up persisted inside another Saved Object. We call these Saved Objects “by value”, as opposed to "by - reference". If an end user creates a visualization and adds it to a dashboard without saving it to the visualization - library, the data ends up nested inside the dashboard Saved Object. This helps keep the visualization library smaller. It also avoids - issues with edits propagating - since an entity can only exist in a single place. - Note that from the end user stand point, we don’t use these terms “by reference” and “by value”. +reference". If an end user creates a visualization and adds it to a dashboard without saving it to the visualization +library, the data ends up nested inside the dashboard Saved Object. This helps keep the visualization library smaller. It also avoids +issues with edits propagating - since an entity can only exist in a single place. +Note that from the end user stand point, we don’t use these terms “by reference” and “by value”. ## Sharing Saved Objects @@ -80,7 +83,7 @@ on how it is registered. If you are adding a **new** object type, when you register it: 1. Use `namespaceType: 'multiple-isolated'` to make these objects exist in exactly one space -2. Use `namespaceType: 'multiple'` to make these objects exist in one *or more* spaces +2. Use `namespaceType: 'multiple'` to make these objects exist in one _or more_ spaces 3. Use `namespaceType: 'agnostic'` if you want these objects to always exist in all spaces If you have an **existing** "legacy" object type that is not shareable (using `namespaceType: 'single'`), see the [legacy developer guide diff --git a/dev_docs/tutorials/building_a_kibana_distributable.mdx b/dev_docs/tutorials/building_a_kibana_distributable.mdx index 7b06525a5b977..e73481058ab35 100644 --- a/dev_docs/tutorials/building_a_kibana_distributable.mdx +++ b/dev_docs/tutorials/building_a_kibana_distributable.mdx @@ -1,6 +1,6 @@ --- id: kibDevTutorialBuildingDistributable -slug: /kibana-dev-docs/tutorial/building-distributable +slug: /kibana-dev-docs/tutorials/building-distributable title: Building a Kibana distributable summary: Learn how to build a Kibana distributable date: 2021-05-10 diff --git a/dev_docs/tutorials/data/search.mdx b/dev_docs/tutorials/data/search.mdx index 9cf46bb96c72a..81080b0c27418 100644 --- a/dev_docs/tutorials/data/search.mdx +++ b/dev_docs/tutorials/data/search.mdx @@ -2,14 +2,14 @@ id: kibDevTutorialDataSearchAndSessions slug: /kibana-dev-docs/tutorials/data/search-and-sessions title: Kibana data.search Services -summary: Kibana Search Services +summary: Kibana Search Services date: 2021-02-10 tags: ['kibana', 'onboarding', 'dev', 'tutorials', 'search', 'sessions', 'search-sessions'] --- ## Search service -### Low level search +### Low level search Searching data stored in Elasticsearch can be done in various ways, for example using the Elasticsearch REST API or using an `Elasticsearch Client` for low level access. @@ -50,7 +50,7 @@ export class MyPlugin implements Plugin { } else { // handle partial results if you want. } - }, + }, error: (e) => { // handle error thrown, for example a server hangup }, @@ -69,36 +69,36 @@ Note: The `data` plugin contains services to help you generate the `query` and ` The `search` method can throw several types of errors, for example: - - `EsError` for errors originating in Elasticsearch errors - - `PainlessError` for errors originating from a Painless script - - `AbortError` if the search was aborted via an `AbortController` - - `HttpError` in case of a network error +- `EsError` for errors originating in Elasticsearch errors +- `PainlessError` for errors originating from a Painless script +- `AbortError` if the search was aborted via an `AbortController` +- `HttpError` in case of a network error -To display the errors in the context of an application, use the helper method provided on the `data.search` service. These errors are shown in a toast message, using the `core.notifications` service. +To display the errors in the context of an application, use the helper method provided on the `data.search` service. These errors are shown in a toast message, using the `core.notifications` service. ```ts data.search.search(req).subscribe({ - next: (result) => {}, + next: (result) => {}, error: (e) => { data.search.showError(e); }, -}) +}); ``` If you decide to handle errors by yourself, watch for errors coming from `Elasticsearch`. They have an additional `attributes` property that holds the raw error from `Elasticsearch`. ```ts data.search.search(req).subscribe({ - next: (result) => {}, + next: (result) => {}, error: (e) => { if (e instanceof IEsError) { showErrorReason(e.attributes); } }, -}) +}); ``` -#### Stop a running search +#### Stop a running search The search service `search` method supports a second argument called `options`. One of these options provides an `abortSignal` to stop searches from running to completion, if the result is no longer needed. @@ -106,20 +106,22 @@ The search service `search` method supports a second argument called `options`. import { AbortError } from '../../src/data/public'; const abortController = new AbortController(); -data.search.search(req, { - abortSignal: abortController.signal, -}).subscribe({ - next: (result) => { - // handle result - }, - error: (e) => { - if (e instanceof AbortError) { - // you can ignore this error - return; - } - // handle error, for example a server hangup - }, -}); +data.search + .search(req, { + abortSignal: abortController.signal, + }) + .subscribe({ + next: (result) => { + // handle result + }, + error: (e) => { + if (e instanceof AbortError) { + // you can ignore this error + return; + } + // handle error, for example a server hangup + }, + }); // Abort the search request after a second setTimeout(() => { @@ -135,13 +137,15 @@ For example, to run an EQL query using the `data.search` service, you should to ```ts const req = getEqlRequest(); -data.search.search(req, { - strategy: EQL_SEARCH_STRATEGY, -}).subscribe({ - next: (result) => { - // handle EQL result - }, -}); +data.search + .search(req, { + strategy: EQL_SEARCH_STRATEGY, + }) + .subscribe({ + next: (result) => { + // handle EQL result + }, + }); ``` ##### Custom search strategies @@ -154,18 +158,18 @@ The following example shows how to define, register, and use a search strategy t // ./myPlugin/server/myStrategy.ts /** - * Your custom search strategy should implement the ISearchStrategy interface, requiring at minimum a `search` function. + * Your custom search strategy should implement the ISearchStrategy interface, requiring at minimum a `search` function. */ export const mySearchStrategyProvider = ( data: PluginStart ): ISearchStrategy => { const preprocessRequest = (request: IMyStrategyRequest) => { // Custom preprocessing - } + }; const formatResponse = (response: IMyStrategyResponse) => { // Custom post-processing - } + }; // Get the default search strategy const es = data.search.getSearchStrategy(ES_SEARCH_STRATEGY); @@ -179,16 +183,12 @@ export const mySearchStrategyProvider = ( ```ts // ./myPlugin/server/plugin.ts -import type { - CoreSetup, - CoreStart, - Plugin, -} from 'kibana/server'; +import type { CoreSetup, CoreStart, Plugin } from 'kibana/server'; import { mySearchStrategyProvider } from './my_strategy'; /** - * Your plugin will receive the `data` plugin contact in both the setup and start lifecycle hooks. + * Your plugin will receive the `data` plugin contact in both the setup and start lifecycle hooks. */ export interface MyPluginSetupDeps { data: PluginSetup; @@ -199,13 +199,10 @@ export interface MyPluginStartDeps { } /** - * In your custom server side plugin, register the strategy from the setup contract + * In your custom server side plugin, register the strategy from the setup contract */ export class MyPlugin implements Plugin { - public setup( - core: CoreSetup, - deps: MyPluginSetupDeps - ) { + public setup(core: CoreSetup, deps: MyPluginSetupDeps) { core.getStartServices().then(([_, depsStart]) => { const myStrategy = mySearchStrategyProvider(depsStart.data); deps.data.search.registerSearchStrategy('myCustomStrategy', myStrategy); @@ -217,13 +214,15 @@ export class MyPlugin implements Plugin { ```ts // ./myPlugin/public/plugin.ts const req = getRequest(); -data.search.search(req, { - strategy: 'myCustomStrategy', -}).subscribe({ - next: (result) => { - // handle result - }, -}); +data.search + .search(req, { + strategy: 'myCustomStrategy', + }) + .subscribe({ + next: (result) => { + // handle result + }, + }); ``` ##### Async search and custom async search strategies @@ -234,7 +233,7 @@ This synchronous execution works great in most cases. However, with the introduc The [async_search API](https://www.elastic.co/guide/en/elasticsearch/reference/current/async-search.html) is what drives more advanced `Kibana` `search` features, such as `partial results` and `search sessions`. [When available](https://www.elastic.co/subscriptions), the default search strategy of `Kibana` is automatically set to the **async** default search strategy (`ENHANCED_ES_SEARCH_STRATEGY`), empowering Kibana to run longer queries, with an **optional** duration restriction defined by the UI setting `search:timeout`. -If you are implementing your own async custom search strategy, make sure to implement `cancel` and `extend`, as shown in the following example: +If you are implementing your own async custom search strategy, make sure to implement `cancel` and `extend`, as shown in the following example: ```ts // ./myPlugin/server/myEnhancedStrategy.ts @@ -245,7 +244,7 @@ export const myEnhancedSearchStrategyProvider = ( const ese = data.search.getSearchStrategy(ENHANCED_ES_SEARCH_STRATEGY); return { search: (request, options, deps) => { - // search will be called multiple times, + // search will be called multiple times, // be sure your response formatting is capable of handling partial results, as well as the final result. return formatResponse(ese.search(request, options, deps)); }, @@ -286,7 +285,7 @@ function searchWithSearchSource() { .setField('query', query) .setField('fields', selectedFields.length ? selectedFields.map((f) => f.name) : ['*']) .setField('aggs', getAggsDsl()); - + searchSource.fetch$().subscribe({ next: () => {}, error: () => {}, @@ -296,7 +295,7 @@ function searchWithSearchSource() { ### Partial results -When searching using an `async` strategy (such as async DSL and async EQL), the search service will stream back partial results. +When searching using an `async` strategy (such as async DSL and async EQL), the search service will stream back partial results. Although you can ignore the partial results and wait for the final result before rendering, you can also use the partial results to create a more interactive experience for your users. It is highly advised, however, to make sure users are aware that the results they are seeing are partial. @@ -310,7 +309,7 @@ data.search.search(req).subscribe({ renderPartialResult(res); } }, -}) +}); // Skipping partial results const finalResult = await data.search.search(req).toPromise(); @@ -320,31 +319,29 @@ const finalResult = await data.search.search(req).toPromise(); A search session is a higher level concept than search. A search session describes a grouping of one or more async search requests with additional context. -Search sessions are handy when you want to enable a user to run something asynchronously (for example, a dashboard over a long period of time), and then quickly restore the results at a later time. The `Search Service` transparently fetches results from the `.async-search` index, instead of running each request again. +Search sessions are handy when you want to enable a user to run something asynchronously (for example, a dashboard over a long period of time), and then quickly restore the results at a later time. The `Search Service` transparently fetches results from the `.async-search` index, instead of running each request again. Internally, any search run within a search session is saved into an object, allowing Kibana to manage their lifecycle. Most saved objects are deleted automatically after a short period of time, but if a user chooses to save the search session, the saved object is persisted, so that results can be restored in a later time. -Stored search sessions are listed in the *Management* application, under *Kibana > Search Sessions*, making it easy to find, manage, and restore them. +Stored search sessions are listed in the _Management_ application, under _Kibana > Search Sessions_, making it easy to find, manage, and restore them. As a developer, you might encounter these two common, use cases: - * Running a search inside an existing search session - * Supporting search sessions in your application +- Running a search inside an existing search session +- Supporting search sessions in your application #### Running a search inside an existing search session For this example, assume you are implementing a new type of `Embeddable` that will be shown on dashboards. The same principle applies, however, to any search requests that you are running, as long as the application you are running inside is managing an active session. -Because the Dashboard application is already managing a search session, all you need to do is pass down the `searchSessionId` argument to any `search` call. This applies to both the low and high level search APIs. +Because the Dashboard application is already managing a search session, all you need to do is pass down the `searchSessionId` argument to any `search` call. This applies to both the low and high level search APIs. -The search information will be added to the saved object for the search session. +The search information will be added to the saved object for the search session. ```ts -export class SearchEmbeddable - extends Embeddable { - +export class SearchEmbeddable extends Embeddable { private async fetchData() { - // Every embeddable receives an optional `searchSessionId` input parameter. + // Every embeddable receives an optional `searchSessionId` input parameter. const { searchSessionId } = this.input; // Setup your search source @@ -355,9 +352,11 @@ export class SearchEmbeddable this.updateOutput({ loading: true, error: undefined }); // Make the request, wait for the final result - const {rawResponse: resp} = await searchSource.fetch$({ - sessionId: searchSessionId, - }).toPromise(); + const { rawResponse: resp } = await searchSource + .fetch$({ + sessionId: searchSessionId, + }) + .toPromise(); this.useSearchResult(resp); @@ -368,35 +367,37 @@ export class SearchEmbeddable } } } - -``` +``` You can also retrieve the active `Search Session ID` from the `Search Service` directly: ```ts async function fetchData(data: DataPublicPluginStart) { try { - return await searchSource.fetch$({ + return await searchSource + .fetch$({ sessionId: data.search.sessions.getSessionId(), - }).toPromise(); + }) + .toPromise(); } catch (e) { // handle search errors } } - ``` - Search sessions are initiated by the client. If you are using a route that runs server side searches, you can send the `searchSessionId` to the server, and then pass it down to the server side `data.search` function call. + Search sessions are initiated by the client. If you are using a route that runs server side + searches, you can send the `searchSessionId` to the server, and then pass it down to the server + side `data.search` function call. #### Supporting search sessions in your application Before implementing the ability to create and restore search sessions in your application, ask yourself the following questions: -1. **Does your application normally run long operations?** For example, it makes sense for a user to generate a Dashboard or a Canvas report from data stored in cold storage. However, when editing a single visualization, it is best to work with a shorter timeframe of hot or warm data. +1. **Does your application normally run long operations?** For example, it makes sense for a user to generate a Dashboard or a Canvas report from data stored in cold storage. However, when editing a single visualization, it is best to work with a shorter timeframe of hot or warm data. 2. **Does it make sense for your application to restore a search session?** For example, you might want to restore an interesting configuration of filters of older documents you found in Discover. However, a single Lens or Map visualization might not be as helpful, outside the context of a specific dashboard. -3. **What is a search session in the context of your application?** Although Discover and Dashboard start a new search session every time the time range or filters change, or when the user clicks **Refresh**, you can manage your sessions differently. For example, if your application has tabs, you might group searches from multiple tabs into a single search session. You must be able to clearly define the **state** used to create the search session`. The **state** refers to any setting that might change the queries being set to `Elasticsearch`. +3. **What is a search session in the context of your application?** Although Discover and Dashboard start a new search session every time the time range or filters change, or when the user clicks **Refresh**, you can manage your sessions differently. For example, if your application has tabs, you might group searches from multiple tabs into a single search session. You must be able to clearly define the **state** used to create the search session`. The **state** refers to any setting that might change the queries being set to `Elasticsearch`. Once you answer those questions, proceed to implement the following bits of code in your application. @@ -409,8 +410,8 @@ export class MyPlugin implements Plugin { public start(core: CoreStart, { data }: MyPluginStartDependencies) { const sessionRestorationDataProvider: SearchSessionInfoProvider = { data, - getDashboard - } + getDashboard, + }; data.search.session.enableStorage({ getName: async () => { @@ -430,32 +431,34 @@ export class MyPlugin implements Plugin { ``` - The restore state of a search session may be different from the initial state used to create it. For example, where the initial state may contain relative dates, in the restore state, those must be converted to absolute dates. Read more about the [NowProvider](). + The restore state of a search session may be different from the initial state used to create it. + For example, where the initial state may contain relative dates, in the restore state, those must + be converted to absolute dates. Read more about the [NowProvider](). - Calling `enableStorage` will also enable the `Search Session Indicator` component in the chrome component of your solution. The `Search Session Indicator` is a small button, used by default to engage users and save new search sessions. To implement your own UI, contact the Kibana application services team to decouple this behavior. + Calling `enableStorage` will also enable the `Search Session Indicator` component in the chrome + component of your solution. The `Search Session Indicator` is a small button, used by default to + engage users and save new search sessions. To implement your own UI, contact the Kibana + application services team to decouple this behavior. -##### Start a new search session +##### Start a new search session -Make sure to call `start` when the **state** you previously defined changes. +Make sure to call `start` when the **state** you previously defined changes. ```ts - function onSearchSessionConfigChange() { this.searchSessionId = data.search.sessions.start(); } - ``` -Pass the `searchSessionId` to every `search` call inside your application. If you're using `Embeddables`, pass down the `searchSessionId` as `input`. +Pass the `searchSessionId` to every `search` call inside your application. If you're using `Embeddables`, pass down the `searchSessionId` as `input`. If you can't pass the `searchSessionId` directly, you can retrieve it from the service. ```ts const currentSearchSessionId = data.search.sessions.getSessionId(); - ``` ##### Clear search sessions @@ -466,19 +469,17 @@ Creating a new search session clears the previous one. You must explicitly `clea function onDestroy() { data.search.session.clear(); } - ``` If you don't call `clear`, you will see a warning in the console while developing. However, when running in production, you will get a fatal error. This is done to avoid leakage of unrelated search requests into an existing search session left open by mistake. -##### Restore search sessions +##### Restore search sessions -The last step of the integration is restoring an existing search session. The `searchSessionId` parameter and the rest of the restore state are passed into the application via the URL. Non-URL support is planned for future releases. +The last step of the integration is restoring an existing search session. The `searchSessionId` parameter and the rest of the restore state are passed into the application via the URL. Non-URL support is planned for future releases. If you detect the presense of a `searchSessionId` parameter in the URL, call the `restore` method **instead** of calling `start`. The previous example would now become: ```ts - function onSearchSessionConfigChange(searchSessionIdFromUrl?: string) { if (searchSessionIdFromUrl) { data.search.sessions.restore(searchSessionIdFromUrl); @@ -486,7 +487,6 @@ function onSearchSessionConfigChange(searchSessionIdFromUrl?: string) { data.search.sessions.start(); } } - ``` Once you `restore` the session, as long as all `search` requests run with the same `searchSessionId`, the search session should be seamlessly restored. diff --git a/dev_docs/tutorials/debugging.mdx b/dev_docs/tutorials/debugging.mdx index c0efd249be066..c612893e4f1f9 100644 --- a/dev_docs/tutorials/debugging.mdx +++ b/dev_docs/tutorials/debugging.mdx @@ -1,6 +1,6 @@ --- id: kibDevTutorialDebugging -slug: /kibana-dev-docs/tutorial/debugging +slug: /kibana-dev-docs/tutorials/debugging title: Debugging in development summary: Learn how to debug Kibana while running from source date: 2021-04-26 @@ -27,7 +27,7 @@ You will need to run Jest directly from the Node script: `node --inspect-brk scripts/functional_test_runner` -### Development Server +### Development Server `node --inspect-brk scripts/kibana` @@ -58,4 +58,4 @@ logging: level: debug - name: elasticsearch.query level: debug -``` \ No newline at end of file +``` diff --git a/dev_docs/tutorials/saved_objects.mdx b/dev_docs/tutorials/saved_objects.mdx index 35efbb97a0a03..29a0b60983d90 100644 --- a/dev_docs/tutorials/saved_objects.mdx +++ b/dev_docs/tutorials/saved_objects.mdx @@ -1,6 +1,6 @@ --- id: kibDevTutorialSavedObject -slug: /kibana-dev-docs/tutorial/saved-objects +slug: /kibana-dev-docs/tutorials/saved-objects title: Register a new saved object type summary: Learn how to register a new saved object type. date: 2021-02-05 diff --git a/dev_docs/tutorials/submit_a_pull_request.mdx b/dev_docs/tutorials/submit_a_pull_request.mdx index 2be5973bb3854..5436ebf24e03e 100644 --- a/dev_docs/tutorials/submit_a_pull_request.mdx +++ b/dev_docs/tutorials/submit_a_pull_request.mdx @@ -1,6 +1,6 @@ --- id: kibDevTutorialSubmitPullRequest -slug: /kibana-dev-docs/tutorial/submit-pull-request +slug: /kibana-dev-docs/tutorials/submit-pull-request title: Submitting a Kibana pull request summary: Learn how to submit a Kibana pull request date: 2021-06-24 diff --git a/dev_docs/tutorials/testing_plugins.mdx b/dev_docs/tutorials/testing_plugins.mdx index bc92af33d3493..14089bc3fa315 100644 --- a/dev_docs/tutorials/testing_plugins.mdx +++ b/dev_docs/tutorials/testing_plugins.mdx @@ -1,6 +1,6 @@ --- id: kibDevTutorialTestingPlugins -slug: /kibana-dev-docs/tutorial/testing-plugins +slug: /kibana-dev-docs/tutorials/testing-plugins title: Testing Kibana Plugins summary: Learn how to test different aspects of Kibana plugins date: 2021-07-05 diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index edc1821f3b223..7f7041f7815cd 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -57,6 +57,12 @@ as uiSettings within the code. |The data plugin provides common data access services, such as search and query, for solutions and application developers. +|{kib-repo}blob/{branch}/src/plugins/data_views/README.mdx[dataViews] +|The data views API provides a consistent method of structuring and formatting documents +and field lists across the various Kibana apps. Its typically used in conjunction with + for composing queries. + + |{kib-repo}blob/{branch}/src/plugins/dev_tools/README.md[devTools] |The ui/registry/dev_tools is removed in favor of the devTools plugin which exposes a register method in the setup contract. Registering app works mostly the same as registering apps in core.application.register. diff --git a/examples/expressions_explorer/public/actions_and_expressions.tsx b/examples/expressions_explorer/public/actions_and_expressions.tsx index f802a78faf06e..6d0c8886a79f3 100644 --- a/examples/expressions_explorer/public/actions_and_expressions.tsx +++ b/examples/expressions_explorer/public/actions_and_expressions.tsx @@ -47,11 +47,11 @@ export function ActionsExpressionsExample({ expressions, actions }: Props) { }; const handleEvents = (event: any) => { - if (event.id !== 'NAVIGATE') return; + if (event.name !== 'NAVIGATE') return; // enrich event context with some extra data event.baseUrl = 'http://www.google.com'; - actions.executeTriggerActions(NAVIGATE_TRIGGER_ID, event.value); + actions.executeTriggerActions(NAVIGATE_TRIGGER_ID, event.data); }; return ( diff --git a/examples/expressions_explorer/public/actions_and_expressions2.tsx b/examples/expressions_explorer/public/actions_and_expressions2.tsx index 31ba903ad91ac..e7dc28b8b97cd 100644 --- a/examples/expressions_explorer/public/actions_and_expressions2.tsx +++ b/examples/expressions_explorer/public/actions_and_expressions2.tsx @@ -50,7 +50,7 @@ export function ActionsExpressionsExample2({ expressions, actions }: Props) { }; const handleEvents = (event: any) => { - updateVariables({ color: event.value.href === 'http://www.google.com' ? 'red' : 'blue' }); + updateVariables({ color: event.data.href === 'http://www.google.com' ? 'red' : 'blue' }); }; return ( diff --git a/examples/expressions_explorer/public/renderers/button.tsx b/examples/expressions_explorer/public/renderers/button.tsx index 68add91c3cbc9..557180ab73a35 100644 --- a/examples/expressions_explorer/public/renderers/button.tsx +++ b/examples/expressions_explorer/public/renderers/button.tsx @@ -18,8 +18,8 @@ export const buttonRenderer: ExpressionRenderDefinition = { render(domNode, config, handlers) { const buttonClick = () => { handlers.event({ - id: 'NAVIGATE', - value: { + name: 'NAVIGATE', + data: { href: config.href, }, }); diff --git a/legacy_rfcs/README.md b/legacy_rfcs/README.md index f9f9502ad954b..4ef4db56c004f 100644 --- a/legacy_rfcs/README.md +++ b/legacy_rfcs/README.md @@ -1,3 +1,3 @@ # Kibana RFCs -We no longer follow this RFC process. Internal developers should review the new RFC process in our [internal developer guide](https://docs.elastic.dev/kibana-team/rfc-process) +We no longer follow this RFC process. Internal developers should review the new RFC process in our [internal developer guide](https://docs.elastic.dev/kibana-dev-docs/contributing/rfc-process) diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 8fb31b36f39d5..a0ca88e4e04bd 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -116,3 +116,4 @@ pageLoadAssetSize: expressions: 239290 securitySolution: 231753 customIntegrations: 28810 + dataViews: 42000 diff --git a/src/dev/eslint/lint_files.ts b/src/dev/eslint/lint_files.ts index 77fe941fb7aeb..5c6118edeb2ec 100644 --- a/src/dev/eslint/lint_files.ts +++ b/src/dev/eslint/lint_files.ts @@ -12,41 +12,6 @@ import { REPO_ROOT } from '@kbn/utils'; import { createFailError, ToolingLog } from '@kbn/dev-utils'; import { File } from '../file'; -// For files living on the filesystem -function lintFilesOnFS(cli: CLIEngine, files: File[]) { - const paths = files.map((file) => file.getRelativePath()); - return cli.executeOnFiles(paths); -} - -// For files living somewhere else (ie. git object) -async function lintFilesOnContent(cli: CLIEngine, files: File[]) { - const report: CLIEngine.LintReport = { - results: [], - errorCount: 0, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - usedDeprecatedRules: [], - }; - - for (let i = 0; i < files.length; i++) { - const r = cli.executeOnText(await files[i].getContent(), files[i].getRelativePath()); - // Despite a relative path was given, the result would contain an absolute one. Work around it. - r.results[0].filePath = r.results[0].filePath.replace( - files[i].getAbsolutePath(), - files[i].getRelativePath() - ); - report.results.push(...r.results); - report.errorCount += r.errorCount; - report.warningCount += r.warningCount; - report.fixableErrorCount += r.fixableErrorCount; - report.fixableWarningCount += r.fixableWarningCount; - report.usedDeprecatedRules.push(...r.usedDeprecatedRules); - } - - return report; -} - /** * Lints a list of files with eslint. eslint reports are written to the log * and a FailError is thrown when linting errors occur. @@ -55,16 +20,15 @@ async function lintFilesOnContent(cli: CLIEngine, files: File[]) { * @param {Array} files * @return {undefined} */ -export async function lintFiles(log: ToolingLog, files: File[], { fix }: { fix?: boolean } = {}) { +export function lintFiles(log: ToolingLog, files: File[], { fix }: { fix?: boolean } = {}) { const cli = new CLIEngine({ cache: true, cwd: REPO_ROOT, fix, }); - const virtualFilesCount = files.filter((file) => file.isVirtual()).length; - const report = - virtualFilesCount && !fix ? await lintFilesOnContent(cli, files) : lintFilesOnFS(cli, files); + const paths = files.map((file) => file.getRelativePath()); + const report = cli.executeOnFiles(paths); if (fix) { CLIEngine.outputFixes(report); diff --git a/src/dev/file.ts b/src/dev/file.ts index 01005b257a403..b532a7bb70602 100644 --- a/src/dev/file.ts +++ b/src/dev/file.ts @@ -7,13 +7,11 @@ */ import { dirname, extname, join, relative, resolve, sep, basename } from 'path'; -import { createFailError } from '@kbn/dev-utils'; export class File { private path: string; private relativePath: string; private ext: string; - private fileReader: undefined | (() => Promise); constructor(path: string) { this.path = resolve(path); @@ -57,11 +55,6 @@ export class File { ); } - // Virtual files cannot be read as usual, an helper is needed - public isVirtual() { - return this.fileReader !== undefined; - } - public getRelativeParentDirs() { const parents: string[] = []; @@ -88,15 +81,4 @@ export class File { public toJSON() { return this.relativePath; } - - public setFileReader(fileReader: () => Promise) { - this.fileReader = fileReader; - } - - public getContent() { - if (this.fileReader) { - return this.fileReader(); - } - throw createFailError('getContent() was invoked on a non-virtual File'); - } } diff --git a/src/dev/precommit_hook/get_files_for_commit.js b/src/dev/precommit_hook/get_files_for_commit.js index 52dfab49c5c64..44c8c9d5e6bc0 100644 --- a/src/dev/precommit_hook/get_files_for_commit.js +++ b/src/dev/precommit_hook/get_files_for_commit.js @@ -6,65 +6,12 @@ * Side Public License, v 1. */ -import { format } from 'util'; import SimpleGit from 'simple-git'; import { fromNode as fcb } from 'bluebird'; import { REPO_ROOT } from '@kbn/utils'; import { File } from '../file'; -/** - * Return the `git diff` argument used for building the list of files - * - * @param {String} gitRef - * @return {String} - * - * gitRef return - * '' '--cached' - * '' '~1..' - * '..' '..' - * '...' '...' - * '..' '..' - * '...' '...' - * '..' '..' - * '...' '...' - */ -function getRefForDiff(gitRef) { - if (!gitRef) { - return '--cached'; - } else if (gitRef.includes('..')) { - return gitRef; - } else { - return format('%s~1..%s', gitRef, gitRef); - } -} - -/** - * Return the used for reading files content - * - * @param {String} gitRef - * @return {String} - * - * gitRef return - * '' '' - * '' '' - * '..' 'HEAD' - * '...' 'HEAD' - * '..' '' - * '...' '' - * '..' '' - * '...' '' - */ -function getRefForCat(gitRef) { - if (!gitRef) { - return ''; - } else if (gitRef.includes('..')) { - return gitRef.endsWith('..') ? 'HEAD' : gitRef.slice(gitRef.lastIndexOf('..') + 2); - } else { - return gitRef; - } -} - /** * Get the files that are staged for commit (excluding deleted files) * as `File` objects that are aware of their commit status. @@ -74,23 +21,29 @@ function getRefForCat(gitRef) { */ export async function getFilesForCommit(gitRef) { const simpleGit = new SimpleGit(REPO_ROOT); - const gitRefForDiff = getRefForDiff(gitRef); - const gitRefForCat = getRefForCat(gitRef); - - const output = await fcb((cb) => { - simpleGit.diff(['--diff-filter=d', '--name-only', gitRefForDiff], cb); - }); + const gitRefForDiff = gitRef ? gitRef : '--cached'; + const output = await fcb((cb) => simpleGit.diff(['--name-status', gitRefForDiff], cb)); return ( output .split('\n') // Ignore blank lines .filter((line) => line.trim().length > 0) - .map((path) => { - const file = new File(path); - const object = format('%s:%s', gitRefForCat, path); - file.setFileReader(() => fcb((cb) => simpleGit.catFile(['-p', object], cb))); - return file; + // git diff --name-status outputs lines with two OR three parts + // separated by a tab character + .map((line) => line.trim().split('\t')) + .map(([status, ...paths]) => { + // ignore deleted files + if (status === 'D') { + return undefined; + } + + // the status is always in the first column + // .. If the file is edited the line will only have two columns + // .. If the file is renamed it will have three columns + // .. In any case, the last column is the CURRENT path to the file + return new File(paths[paths.length - 1]); }) + .filter(Boolean) ); } diff --git a/src/dev/run_precommit_hook.js b/src/dev/run_precommit_hook.js index e1eafaf28d95d..a7bd0a9f57f6e 100644 --- a/src/dev/run_precommit_hook.js +++ b/src/dev/run_precommit_hook.js @@ -6,7 +6,9 @@ * Side Public License, v 1. */ -import { run, combineErrors, createFlagError, createFailError } from '@kbn/dev-utils'; +import SimpleGit from 'simple-git/promise'; + +import { run, combineErrors, createFlagError, REPO_ROOT } from '@kbn/dev-utils'; import * as Eslint from './eslint'; import * as Stylelint from './stylelint'; import { getFilesForCommit, checkFileCasing } from './precommit_hook'; @@ -23,11 +25,6 @@ run( throw createFlagError('expected --max-files to be a number greater than 0'); } - const virtualFilesCount = files.filter((file) => file.isVirtual()).length; - if (virtualFilesCount > 0 && virtualFilesCount < files.length) { - throw createFailError('Mixing of virtual and on-filesystem files is unsupported'); - } - if (maxFilesCount && files.length > maxFilesCount) { log.warning( `--max-files is set to ${maxFilesCount} and ${files.length} were discovered. The current script execution will be skipped.` @@ -48,6 +45,11 @@ run( await Linter.lintFiles(log, filesToLint, { fix: flags.fix, }); + + if (flags.fix) { + const simpleGit = new SimpleGit(REPO_ROOT); + await simpleGit.add(filesToLint); + } } catch (error) { errors.push(error); } @@ -71,11 +73,7 @@ run( help: ` --fix Execute eslint in --fix mode --max-files Max files number to check against. If exceeded the script will skip the execution - --ref Run checks against git ref files instead of running against staged ones - Examples: - HEAD~1..HEAD files changed in the commit at HEAD - HEAD equivalent to HEAD~1..HEAD - main... files changed in current branch since the common ancestor with main + --ref Run checks against any git ref files (example HEAD or ) instead of running against staged ones `, }, } diff --git a/src/dev/stylelint/lint_files.js b/src/dev/stylelint/lint_files.js index 1ebc981728814..6e62c85d44ae8 100644 --- a/src/dev/stylelint/lint_files.js +++ b/src/dev/stylelint/lint_files.js @@ -16,51 +16,6 @@ import { createFailError } from '@kbn/dev-utils'; const stylelintPath = path.resolve(__dirname, '..', '..', '..', '.stylelintrc'); const styleLintConfig = safeLoad(fs.readFileSync(stylelintPath)); -// For files living on the filesystem -function lintFilesOnFS(files) { - const paths = files.map((file) => file.getRelativePath()); - - const options = { - files: paths, - config: styleLintConfig, - formatter: 'string', - ignorePath: path.resolve(__dirname, '..', '..', '..', '.stylelintignore'), - }; - - return stylelint.lint(options); -} - -// For files living somewhere else (ie. git object) -async function lintFilesOnContent(files) { - const report = { - errored: false, - output: '', - postcssResults: [], - results: [], - maxWarningsExceeded: { - maxWarnings: 0, - foundWarnings: 0, - }, - }; - - for (let i = 0; i < files.length; i++) { - const options = { - code: await files[i].getContent(), - config: styleLintConfig, - formatter: 'string', - ignorePath: path.resolve(__dirname, '..', '..', '..', '.stylelintignore'), - }; - const r = await stylelint.lint(options); - report.errored = report.errored || r.errored; - report.output += r.output.replace(//, files[i].getRelativePath()).slice(0, -1); - report.postcssResults.push(...(r.postcssResults || [])); - report.maxWarnings = r.maxWarnings; - report.foundWarnings += r.foundWarnings; - } - - return report; -} - /** * Lints a list of files with eslint. eslint reports are written to the log * and a FailError is thrown when linting errors occur. @@ -70,9 +25,16 @@ async function lintFilesOnContent(files) { * @return {undefined} */ export async function lintFiles(log, files) { - const virtualFilesCount = files.filter((file) => file.isVirtual()).length; - const report = virtualFilesCount ? await lintFilesOnContent(files) : await lintFilesOnFS(files); + const paths = files.map((file) => file.getRelativePath()); + + const options = { + files: paths, + config: styleLintConfig, + formatter: 'string', + ignorePath: path.resolve(__dirname, '..', '..', '..', '.stylelintignore'), + }; + const report = await stylelint.lint(options); if (report.errored) { log.error(report.output); throw createFailError('[stylelint] errors'); diff --git a/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.test.ts b/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.test.ts index 8abdc36704b45..86a371afd6912 100644 --- a/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.test.ts +++ b/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.test.ts @@ -12,6 +12,8 @@ import { functionWrapper } from '../../../../expressions/common/expression_funct import { ExpressionValueVisDimension } from '../../../../visualizations/public'; import { Datatable } from '../../../../expressions/common/expression_types/specs'; +type Arguments = Parameters['fn']>[1]; + describe('interpreter/functions#tagcloud', () => { const fn = functionWrapper(tagcloudFunction()); const column1 = 'Count'; @@ -26,7 +28,7 @@ describe('interpreter/functions#tagcloud', () => { { [column1]: 0, [column2]: 'US' }, { [column1]: 10, [column2]: 'UK' }, ], - }; + } as unknown as Datatable; const visConfig = { scale: 'linear', orientation: 'single', @@ -73,12 +75,12 @@ describe('interpreter/functions#tagcloud', () => { }; it('returns an object with the correct structure for number accessors', () => { - const actual = fn(context, { ...visConfig, ...numberAccessors }, undefined); + const actual = fn(context, { ...visConfig, ...numberAccessors } as Arguments, undefined); expect(actual).toMatchSnapshot(); }); it('returns an object with the correct structure for string accessors', () => { - const actual = fn(context, { ...visConfig, ...stringAccessors }, undefined); + const actual = fn(context, { ...visConfig, ...stringAccessors } as Arguments, undefined); expect(actual).toMatchSnapshot(); }); @@ -93,7 +95,7 @@ describe('interpreter/functions#tagcloud', () => { }, }, }; - await fn(context, { ...visConfig, ...numberAccessors }, handlers as any); + await fn(context, { ...visConfig, ...numberAccessors } as Arguments, handlers as any); expect(loggedTable!).toMatchSnapshot(); }); diff --git a/src/plugins/data/README.mdx b/src/plugins/data/README.mdx index 40e82d3034ee2..e24a949a0c2ec 100644 --- a/src/plugins/data/README.mdx +++ b/src/plugins/data/README.mdx @@ -1,6 +1,6 @@ --- id: kibDataPlugin -slug: /kibana-dev-docs/services/data-plugin +slug: /kibana-dev-docs/key-concepts/data-plugin title: Data services image: https://source.unsplash.com/400x175/?Search summary: The data plugin contains services for searching, querying and filtering. @@ -49,15 +49,6 @@ This is helpful when you want to provide a user with options, for example when c ``` -## Data Views - -The data views API provides a consistent method of structuring and formatting documents -and field lists across the various Kibana apps. Its typically used in conjunction with - for composing queries. - -*Note: Kibana index patterns are currently being renamed to data views. There will be some naming inconsistencies until the transition is complete.* - - ## Query The query service is responsible for managing the configuration of a search query (`QueryState`): filters, time range, query string, and settings such as the auto refresh behavior and saved queries. diff --git a/src/plugins/data/common/constants.ts b/src/plugins/data/common/constants.ts index 2c339d1408237..c236be18a8e41 100644 --- a/src/plugins/data/common/constants.ts +++ b/src/plugins/data/common/constants.ts @@ -9,15 +9,6 @@ export const DEFAULT_QUERY_LANGUAGE = 'kuery'; export const KIBANA_USER_QUERY_LANGUAGE_KEY = 'kibana.userQueryLanguage'; -/** @public **/ -export const DATA_VIEW_SAVED_OBJECT_TYPE = 'index-pattern'; - -/** - * @deprecated Use DATA_VIEW_SAVED_OBJECT_TYPE. All index pattern interfaces were renamed. - */ - -export const INDEX_PATTERN_SAVED_OBJECT_TYPE = DATA_VIEW_SAVED_OBJECT_TYPE; - export type ValueSuggestionsMethod = 'terms_enum' | 'terms_agg'; export const UI_SETTINGS = { diff --git a/src/plugins/data/common/data_views/index.ts b/src/plugins/data/common/data_views/index.ts deleted file mode 100644 index fd1df0336815a..0000000000000 --- a/src/plugins/data/common/data_views/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export * from './constants'; -export * from './fields'; -export * from './types'; -export { - IndexPatternsService, - IndexPatternsContract, - DataViewsService, - DataViewsContract, -} from './data_views'; -// todo was trying to export this as type but wasn't working -export { IndexPattern, IndexPatternListItem, DataView, DataViewListItem } from './data_views'; -export * from './errors'; -export * from './expressions'; diff --git a/src/plugins/data/common/index.ts b/src/plugins/data/common/index.ts index 8a9a93f96b68b..195cb9d475314 100644 --- a/src/plugins/data/common/index.ts +++ b/src/plugins/data/common/index.ts @@ -11,18 +11,68 @@ export * from './constants'; export * from './es_query'; -export * from './data_views'; export * from './kbn_field_types'; export * from './query'; export * from './search'; export * from './types'; -export * from './utils'; export * from './exports'; - -/** - * - * @deprecated Use data plugin interface instead - * @removeBy 8.1 - */ - -export { IndexPatternAttributes } from './types'; +export type { + IFieldType, + IIndexPatternFieldList, + FieldFormatMap, + RuntimeType, + RuntimeField, + IIndexPattern, + DataViewAttributes, + IndexPatternAttributes, + FieldAttrs, + FieldAttrSet, + OnNotification, + OnError, + UiSettingsCommon, + SavedObjectsClientCommonFindArgs, + SavedObjectsClientCommon, + GetFieldsOptions, + GetFieldsOptionsTimePattern, + IDataViewsApiClient, + IIndexPatternsApiClient, + SavedObject, + AggregationRestrictions, + TypeMeta, + FieldSpecConflictDescriptions, + FieldSpecExportFmt, + FieldSpec, + DataViewFieldMap, + IndexPatternFieldMap, + DataViewSpec, + IndexPatternSpec, + SourceFilter, + IndexPatternExpressionType, + IndexPatternLoadStartDependencies, + IndexPatternLoadExpressionFunctionDefinition, +} from '../../data_views/common'; +export { + RUNTIME_FIELD_TYPES, + FLEET_ASSETS_TO_IGNORE, + META_FIELDS, + DATA_VIEW_SAVED_OBJECT_TYPE, + INDEX_PATTERN_SAVED_OBJECT_TYPE, + isFilterable, + isNestedField, + fieldList, + DataViewField, + IndexPatternField, + DataViewType, + IndexPatternType, + IndexPatternsService, + IndexPatternsContract, + DataViewsService, + DataViewsContract, + IndexPattern, + IndexPatternListItem, + DataView, + DataViewListItem, + DuplicateDataViewError, + DataViewSavedObjectConflictError, + getIndexPatternLoadMeta, +} from '../../data_views/common'; diff --git a/src/plugins/data/common/mocks.ts b/src/plugins/data/common/mocks.ts index 66ad3b695d24c..c656d9d21346e 100644 --- a/src/plugins/data/common/mocks.ts +++ b/src/plugins/data/common/mocks.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export * from './data_views/fields/fields.mocks'; +export * from '../../data_views/common/fields/fields.mocks'; diff --git a/src/plugins/data/common/search/aggs/param_types/field.ts b/src/plugins/data/common/search/aggs/param_types/field.ts index e1c872ac16701..05e1302475d04 100644 --- a/src/plugins/data/common/search/aggs/param_types/field.ts +++ b/src/plugins/data/common/search/aggs/param_types/field.ts @@ -15,7 +15,7 @@ import { import { BaseParamType } from './base'; import { propFilter } from '../utils'; import { KBN_FIELD_TYPES } from '../../../kbn_field_types/types'; -import { isNestedField, IndexPatternField } from '../../../data_views/fields'; +import { isNestedField, IndexPatternField } from '../../../../../data_views/common'; const filterByType = propFilter('type'); diff --git a/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts b/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts index a9078d6042db8..09d68177c3ec3 100644 --- a/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts +++ b/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts @@ -12,7 +12,7 @@ import { Observable } from 'rxjs'; import type { Datatable, ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { buildExpressionFunction } from '../../../../../../plugins/expressions/common'; -import { IndexPatternExpressionType } from '../../../data_views/expressions'; +import { IndexPatternExpressionType } from '../../../../../data_views/common/expressions'; import { IndexPatternsContract } from '../../..'; import { AggsStart, AggExpressionType, aggCountFnName } from '../../aggs'; diff --git a/src/plugins/data/common/search/search_source/extract_references.ts b/src/plugins/data/common/search/search_source/extract_references.ts index c7f6c53d0f5f7..dfcd1b12cb62f 100644 --- a/src/plugins/data/common/search/search_source/extract_references.ts +++ b/src/plugins/data/common/search/search_source/extract_references.ts @@ -10,7 +10,7 @@ import { SavedObjectReference } from 'src/core/types'; import { Filter } from '@kbn/es-query'; import { SearchSourceFields } from './types'; -import { INDEX_PATTERN_SAVED_OBJECT_TYPE } from '../../constants'; +import { DATA_VIEW_SAVED_OBJECT_TYPE } from '../../../../data/common'; export const extractReferences = ( state: SearchSourceFields @@ -22,7 +22,7 @@ export const extractReferences = ( const refName = 'kibanaSavedObjectMeta.searchSourceJSON.index'; references.push({ name: refName, - type: INDEX_PATTERN_SAVED_OBJECT_TYPE, + type: DATA_VIEW_SAVED_OBJECT_TYPE, id: indexId, }); searchSourceFields = { @@ -42,7 +42,7 @@ export const extractReferences = ( const refName = `kibanaSavedObjectMeta.searchSourceJSON.filter[${i}].meta.index`; references.push({ name: refName, - type: INDEX_PATTERN_SAVED_OBJECT_TYPE, + type: DATA_VIEW_SAVED_OBJECT_TYPE, id: filterRow.meta.index, }); return { diff --git a/src/plugins/data/common/stubs.ts b/src/plugins/data/common/stubs.ts index 5cddcf397f442..ec53b20f6ff3d 100644 --- a/src/plugins/data/common/stubs.ts +++ b/src/plugins/data/common/stubs.ts @@ -6,6 +6,6 @@ * Side Public License, v 1. */ -export * from './data_views/field.stub'; -export * from './data_views/data_view.stub'; +export * from '../../data_views/common/field.stub'; +export * from '../../data_views/common/data_view.stub'; export * from './es_query/stubs'; diff --git a/src/plugins/data/common/types.ts b/src/plugins/data/common/types.ts index c574d4854cfd6..81b47735d8fe2 100644 --- a/src/plugins/data/common/types.ts +++ b/src/plugins/data/common/types.ts @@ -8,7 +8,6 @@ export * from './query/types'; export * from './kbn_field_types/types'; -export * from './data_views/types'; /** * If a service is being shared on both the client and the server, and diff --git a/src/plugins/data/kibana.json b/src/plugins/data/kibana.json index c21955501787d..3d70d138d80ed 100644 --- a/src/plugins/data/kibana.json +++ b/src/plugins/data/kibana.json @@ -3,8 +3,8 @@ "version": "kibana", "server": true, "ui": true, - "requiredPlugins": ["bfetch", "expressions", "uiActions", "share", "inspector", "fieldFormats"], - "serviceFolders": ["search", "data_views", "query", "autocomplete", "ui"], + "requiredPlugins": ["bfetch", "expressions", "uiActions", "share", "inspector", "fieldFormats", "dataViews"], + "serviceFolders": ["search", "query", "autocomplete", "ui"], "optionalPlugins": ["usageCollection"], "extraPublicDirs": ["common"], "requiredBundles": ["kibanaUtils", "kibanaReact", "inspector"], diff --git a/src/plugins/data/public/data_views/index.ts b/src/plugins/data/public/data_views/index.ts index 0125b173989fb..4fb2bbaf08503 100644 --- a/src/plugins/data/public/data_views/index.ts +++ b/src/plugins/data/public/data_views/index.ts @@ -6,25 +6,4 @@ * Side Public License, v 1. */ -export { - ILLEGAL_CHARACTERS_KEY, - CONTAINS_SPACES_KEY, - ILLEGAL_CHARACTERS_VISIBLE, - ILLEGAL_CHARACTERS, - validateDataView, -} from '../../common/data_views/lib'; -export { flattenHitWrapper, formatHitProvider, onRedirectNoIndexPattern } from './data_views'; - -export { IndexPatternField, IIndexPatternFieldList, TypeMeta } from '../../common/data_views'; - -export { - IndexPatternsService, - IndexPatternsContract, - IndexPattern, - DataViewsApiClient, - DataViewsService, - DataViewsContract, - DataView, -} from './data_views'; -export { UiSettingsPublicToCommon } from './ui_settings_wrapper'; -export { SavedObjectsClientPublicToCommon } from './saved_objects_client_wrapper'; +export * from '../../../data_views/public'; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index e1f5b98baca9c..4b6d184f807a4 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -83,14 +83,12 @@ export { IndexPatternLoadExpressionFunctionDefinition, fieldList, GetFieldsOptions, - INDEX_PATTERN_SAVED_OBJECT_TYPE, AggregationRestrictions, IndexPatternType, IndexPatternListItem, + DuplicateDataViewError, } from '../common'; -export { DuplicateDataViewError } from '../common/data_views/errors'; - /* * Autocomplete query suggestions: */ diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index aa766f78a5ecb..4a55cc2a0d511 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -21,12 +21,6 @@ import { AutocompleteService } from './autocomplete'; import { SearchService } from './search/search_service'; import { QueryService } from './query'; import { createIndexPatternSelect } from './ui/index_pattern_select'; -import { - DataViewsService, - onRedirectNoIndexPattern, - DataViewsApiClient, - UiSettingsPublicToCommon, -} from './data_views'; import { setIndexPatterns, setNotifications, @@ -44,8 +38,6 @@ import { createSelectRangeAction, } from './actions'; import { APPLY_FILTER_TRIGGER, applyFilterTrigger } from './triggers'; -import { SavedObjectsClientPublicToCommon } from './data_views'; -import { getIndexPatternLoad } from './data_views/expressions'; import { UsageCollectionSetup } from '../../usage_collection/public'; import { getTableViewDescription } from './utils/table_inspector_view'; import { NowProvider, NowProviderInternalContract } from './now_provider'; @@ -89,8 +81,6 @@ export class DataPublicPlugin ): DataPublicPluginSetup { const startServices = createStartServicesGetter(core.getStartServices); - expressions.registerFunction(getIndexPatternLoad({ getStartServices: core.getStartServices })); - this.usageCollection = usageCollection; const searchService = this.searchService.setup(core, { @@ -138,29 +128,13 @@ export class DataPublicPlugin public start( core: CoreStart, - { uiActions, fieldFormats }: DataStartDependencies + { uiActions, fieldFormats, dataViews }: DataStartDependencies ): DataPublicPluginStart { - const { uiSettings, http, notifications, savedObjects, overlays, application } = core; + const { uiSettings, notifications, savedObjects, overlays } = core; setNotifications(notifications); setOverlays(overlays); setUiSettings(uiSettings); - - const indexPatterns = new DataViewsService({ - uiSettings: new UiSettingsPublicToCommon(uiSettings), - savedObjectsClient: new SavedObjectsClientPublicToCommon(savedObjects.client), - apiClient: new DataViewsApiClient(http), - fieldFormats, - onNotification: (toastInputFields) => { - notifications.toasts.add(toastInputFields); - }, - onError: notifications.toasts.addError.bind(notifications.toasts), - onRedirectNoIndexPattern: onRedirectNoIndexPattern( - application.capabilities, - application.navigateToApp, - overlays - ), - }); - setIndexPatterns(indexPatterns); + setIndexPatterns(dataViews); const query = this.queryService.start({ storage: this.storage, @@ -168,7 +142,7 @@ export class DataPublicPlugin uiSettings, }); - const search = this.searchService.start(core, { fieldFormats, indexPatterns }); + const search = this.searchService.start(core, { fieldFormats, indexPatterns: dataViews }); setSearchService(search); uiActions.addTriggerAction( @@ -197,8 +171,8 @@ export class DataPublicPlugin }, autocomplete: this.autocomplete.start(), fieldFormats, - indexPatterns, - dataViews: indexPatterns, + indexPatterns: dataViews, + dataViews, query, search, nowProvider: this.nowProvider, @@ -214,7 +188,7 @@ export class DataPublicPlugin return { ...dataServices, ui: { - IndexPatternSelect: createIndexPatternSelect(indexPatterns), + IndexPatternSelect: createIndexPatternSelect(dataViews), SearchBar, }, }; diff --git a/src/plugins/data/public/stubs.ts b/src/plugins/data/public/stubs.ts index 3d160a56bd8cf..b81d9c4cc78e4 100644 --- a/src/plugins/data/public/stubs.ts +++ b/src/plugins/data/public/stubs.ts @@ -7,4 +7,5 @@ */ export * from '../common/stubs'; -export { createStubDataView } from './data_views/data_views/data_view.stub'; +// eslint-disable-next-line +export { createStubDataView } from '../../data_views/public/data_views/data_view.stub'; diff --git a/src/plugins/data/public/types.ts b/src/plugins/data/public/types.ts index 7ed13c2096515..9b16ee39f5c80 100644 --- a/src/plugins/data/public/types.ts +++ b/src/plugins/data/public/types.ts @@ -11,6 +11,7 @@ import { CoreStart } from 'src/core/public'; import { BfetchPublicSetup } from 'src/plugins/bfetch/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { ExpressionsSetup } from 'src/plugins/expressions/public'; +import { DataViewsPublicPluginStart } from 'src/plugins/data_views/public'; import { UiActionsSetup, UiActionsStart } from 'src/plugins/ui_actions/public'; import { FieldFormatsSetup, FieldFormatsStart } from 'src/plugins/field_formats/public'; import { AutocompleteSetup, AutocompleteStart } from './autocomplete'; @@ -35,6 +36,7 @@ export interface DataSetupDependencies { export interface DataStartDependencies { uiActions: UiActionsStart; fieldFormats: FieldFormatsStart; + dataViews: DataViewsPublicPluginStart; } /** diff --git a/src/plugins/data/server/data_views/index.ts b/src/plugins/data/server/data_views/index.ts index 7226d6f015cf8..91a61f4bcb7db 100644 --- a/src/plugins/data/server/data_views/index.ts +++ b/src/plugins/data/server/data_views/index.ts @@ -6,12 +6,4 @@ * Side Public License, v 1. */ -export * from './utils'; -export { - IndexPatternsFetcher, - FieldDescriptor, - shouldReadFieldFromDocValues, - mergeCapabilitiesWithFields, - getCapabilitiesForRollupIndices, -} from './fetcher'; -export { IndexPatternsServiceProvider, IndexPatternsServiceStart } from './index_patterns_service'; +export * from '../../../data_views/server'; diff --git a/src/plugins/data/server/data_views/index_patterns_service.ts b/src/plugins/data/server/data_views/index_patterns_service.ts deleted file mode 100644 index 5286d1d64794b..0000000000000 --- a/src/plugins/data/server/data_views/index_patterns_service.ts +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { - CoreSetup, - CoreStart, - Plugin, - Logger, - SavedObjectsClientContract, - ElasticsearchClient, - UiSettingsServiceStart, -} from 'kibana/server'; -import { ExpressionsServerSetup } from 'src/plugins/expressions/server'; -import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { DataPluginStart } from '../plugin'; -import { registerRoutes } from './routes'; -import { indexPatternSavedObjectType } from '../saved_objects'; -import { capabilitiesProvider } from './capabilities_provider'; -import { IndexPatternsCommonService } from '../'; -import { FieldFormatsStart } from '../../../field_formats/server'; -import { getIndexPatternLoad } from './expressions'; -import { UiSettingsServerToCommon } from './ui_settings_wrapper'; -import { IndexPatternsApiServer } from './index_patterns_api_client'; -import { SavedObjectsClientServerToCommon } from './saved_objects_client_wrapper'; -import { registerIndexPatternsUsageCollector } from './register_index_pattern_usage_collection'; -import { createScriptedFieldsDeprecationsConfig } from './deprecations'; - -export interface IndexPatternsServiceStart { - indexPatternsServiceFactory: ( - savedObjectsClient: SavedObjectsClientContract, - elasticsearchClient: ElasticsearchClient - ) => Promise; -} - -export interface IndexPatternsServiceSetupDeps { - expressions: ExpressionsServerSetup; - logger: Logger; - usageCollection?: UsageCollectionSetup; -} - -export interface IndexPatternsServiceStartDeps { - fieldFormats: FieldFormatsStart; - logger: Logger; -} - -export const indexPatternsServiceFactory = - ({ - logger, - uiSettings, - fieldFormats, - }: { - logger: Logger; - uiSettings: UiSettingsServiceStart; - fieldFormats: FieldFormatsStart; - }) => - async ( - savedObjectsClient: SavedObjectsClientContract, - elasticsearchClient: ElasticsearchClient - ) => { - const uiSettingsClient = uiSettings.asScopedToClient(savedObjectsClient); - const formats = await fieldFormats.fieldFormatServiceFactory(uiSettingsClient); - - return new IndexPatternsCommonService({ - uiSettings: new UiSettingsServerToCommon(uiSettingsClient), - savedObjectsClient: new SavedObjectsClientServerToCommon(savedObjectsClient), - apiClient: new IndexPatternsApiServer(elasticsearchClient, savedObjectsClient), - fieldFormats: formats, - onError: (error) => { - logger.error(error); - }, - onNotification: ({ title, text }) => { - logger.warn(`${title}${text ? ` : ${text}` : ''}`); - }, - }); - }; - -export class IndexPatternsServiceProvider implements Plugin { - public setup( - core: CoreSetup, - { expressions, usageCollection }: IndexPatternsServiceSetupDeps - ) { - core.savedObjects.registerType(indexPatternSavedObjectType); - core.capabilities.registerProvider(capabilitiesProvider); - - registerRoutes(core.http, core.getStartServices); - - expressions.registerFunction(getIndexPatternLoad({ getStartServices: core.getStartServices })); - registerIndexPatternsUsageCollector(core.getStartServices, usageCollection); - core.deprecations.registerDeprecations(createScriptedFieldsDeprecationsConfig(core)); - } - - public start(core: CoreStart, { fieldFormats, logger }: IndexPatternsServiceStartDeps) { - const { uiSettings } = core; - - return { - indexPatternsServiceFactory: indexPatternsServiceFactory({ - logger, - uiSettings, - fieldFormats, - }), - }; - } -} diff --git a/src/plugins/data/server/data_views/mocks.ts b/src/plugins/data/server/data_views/mocks.ts index 6435c09cb7ec9..69b57ed079127 100644 --- a/src/plugins/data/server/data_views/mocks.ts +++ b/src/plugins/data/server/data_views/mocks.ts @@ -6,8 +6,4 @@ * Side Public License, v 1. */ -export function createIndexPatternsStartMock() { - return { - indexPatternsServiceFactory: jest.fn().mockResolvedValue({ get: jest.fn() }), - }; -} +export * from '../../../data_views/server/mocks'; diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index a17c66c694b2d..fce73e65dc699 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -30,7 +30,7 @@ export const exporters = { * Field Formats: */ -export { INDEX_PATTERN_SAVED_OBJECT_TYPE } from '../common'; +export { DATA_VIEW_SAVED_OBJECT_TYPE } from '../common'; /* * Index patterns: diff --git a/src/plugins/data/server/plugin.ts b/src/plugins/data/server/plugin.ts index 3342519782d7a..cb52500e78f94 100644 --- a/src/plugins/data/server/plugin.ts +++ b/src/plugins/data/server/plugin.ts @@ -9,8 +9,8 @@ import { CoreSetup, CoreStart, Logger, Plugin, PluginInitializerContext } from 'src/core/server'; import { ExpressionsServerSetup } from 'src/plugins/expressions/server'; import { BfetchServerSetup } from 'src/plugins/bfetch/server'; +import { PluginStart as DataViewsServerPluginStart } from 'src/plugins/data_views/server'; import { ConfigSchema } from '../config'; -import { IndexPatternsServiceProvider, IndexPatternsServiceStart } from './data_views'; import { ISearchSetup, ISearchStart, SearchEnhancements } from './search'; import { SearchService } from './search/search_service'; import { QueryService } from './query/query_service'; @@ -43,7 +43,7 @@ export interface DataPluginStart { * @deprecated - use "fieldFormats" plugin directly instead */ fieldFormats: FieldFormatsStart; - indexPatterns: IndexPatternsServiceStart; + indexPatterns: DataViewsServerPluginStart; } export interface DataPluginSetupDependencies { @@ -56,6 +56,7 @@ export interface DataPluginSetupDependencies { export interface DataPluginStartDependencies { fieldFormats: FieldFormatsStart; logger: Logger; + dataViews: DataViewsServerPluginStart; } export class DataServerPlugin @@ -71,7 +72,6 @@ export class DataServerPlugin private readonly scriptsService: ScriptsService; private readonly kqlTelemetryService: KqlTelemetryService; private readonly autocompleteService: AutocompleteService; - private readonly indexPatterns = new IndexPatternsServiceProvider(); private readonly queryService = new QueryService(); private readonly logger: Logger; @@ -91,11 +91,6 @@ export class DataServerPlugin this.queryService.setup(core); this.autocompleteService.setup(core); this.kqlTelemetryService.setup(core, { usageCollection }); - this.indexPatterns.setup(core, { - expressions, - logger: this.logger.get('indexPatterns'), - usageCollection, - }); core.uiSettings.register(getUiSettings()); @@ -114,16 +109,11 @@ export class DataServerPlugin }; } - public start(core: CoreStart, { fieldFormats }: DataPluginStartDependencies) { - const indexPatterns = this.indexPatterns.start(core, { - fieldFormats, - logger: this.logger.get('indexPatterns'), - }); - + public start(core: CoreStart, { fieldFormats, dataViews }: DataPluginStartDependencies) { return { fieldFormats, - indexPatterns, - search: this.searchService.start(core, { fieldFormats, indexPatterns }), + indexPatterns: dataViews, + search: this.searchService.start(core, { fieldFormats, indexPatterns: dataViews }), }; } diff --git a/src/plugins/data/server/saved_objects/index.ts b/src/plugins/data/server/saved_objects/index.ts index 53d4360782b5b..8bfce1a4d3696 100644 --- a/src/plugins/data/server/saved_objects/index.ts +++ b/src/plugins/data/server/saved_objects/index.ts @@ -7,6 +7,5 @@ */ export { querySavedObjectType } from './query'; -export { indexPatternSavedObjectType } from './index_patterns'; export { kqlTelemetry } from './kql_telemetry'; export { searchTelemetry } from './search_telemetry'; diff --git a/src/plugins/data/tsconfig.json b/src/plugins/data/tsconfig.json index 3687604e05e2b..92f80f47eca64 100644 --- a/src/plugins/data/tsconfig.json +++ b/src/plugins/data/tsconfig.json @@ -23,6 +23,7 @@ { "path": "../usage_collection/tsconfig.json" }, { "path": "../kibana_utils/tsconfig.json" }, { "path": "../kibana_react/tsconfig.json" }, - { "path": "../field_formats/tsconfig.json" } + { "path": "../field_formats/tsconfig.json" }, + { "path": "../data_views/tsconfig.json" } ] } diff --git a/src/plugins/data_views/README.mdx b/src/plugins/data_views/README.mdx new file mode 100644 index 0000000000000..90efdc18d8fdb --- /dev/null +++ b/src/plugins/data_views/README.mdx @@ -0,0 +1,19 @@ +--- +id: kibDataPlugin +slug: /kibana-dev-docs/services/data-plugin +title: Data services +image: https://source.unsplash.com/400x175/?Search +summary: The data plugin contains services for searching, querying and filtering. +date: 2020-12-02 +tags: ['kibana', 'dev', 'contributor', 'api docs'] +--- + +# Data Views + +The data views API provides a consistent method of structuring and formatting documents +and field lists across the various Kibana apps. Its typically used in conjunction with + for composing queries. + +*Note: Kibana index patterns are currently being renamed to data views. There will be some naming inconsistencies until the transition is complete.* + + diff --git a/src/plugins/data/common/data_views/constants.ts b/src/plugins/data_views/common/constants.ts similarity index 81% rename from src/plugins/data/common/data_views/constants.ts rename to src/plugins/data_views/common/constants.ts index 67e266dbd84a2..ca42221806b2e 100644 --- a/src/plugins/data/common/data_views/constants.ts +++ b/src/plugins/data_views/common/constants.ts @@ -29,3 +29,14 @@ export const FLEET_ASSETS_TO_IGNORE = { METRICS_DATA_STREAM_TO_IGNORE: 'metrics-elastic_agent', // ignore ds created by Fleet server itself METRICS_ENDPOINT_INDEX_TO_IGNORE: 'metrics-endpoint.metadata_current_default', // ignore index created by Fleet endpoint package installed by default in Cloud }; + +export const META_FIELDS = 'metaFields'; + +/** @public **/ +export const DATA_VIEW_SAVED_OBJECT_TYPE = 'index-pattern'; + +/** + * @deprecated Use DATA_VIEW_SAVED_OBJECT_TYPE. All index pattern interfaces were renamed. + */ + +export const INDEX_PATTERN_SAVED_OBJECT_TYPE = DATA_VIEW_SAVED_OBJECT_TYPE; diff --git a/src/plugins/data/common/data_views/data_view.stub.ts b/src/plugins/data_views/common/data_view.stub.ts similarity index 94% rename from src/plugins/data/common/data_views/data_view.stub.ts rename to src/plugins/data_views/common/data_view.stub.ts index a3279434c7a0b..2eb6d4f5d7813 100644 --- a/src/plugins/data/common/data_views/data_view.stub.ts +++ b/src/plugins/data_views/common/data_view.stub.ts @@ -12,8 +12,8 @@ export { createStubDataView, createStubDataView as createStubIndexPattern, } from './data_views/data_view.stub'; -import { SavedObject } from '../../../../core/types'; -import { DataViewAttributes } from '../types'; +import { SavedObject } from '../../../core/types'; +import { DataViewAttributes } from './types'; export const stubDataView = createStubDataView({ spec: { diff --git a/src/plugins/data/common/data_views/data_views/__snapshots__/data_view.test.ts.snap b/src/plugins/data_views/common/data_views/__snapshots__/data_view.test.ts.snap similarity index 100% rename from src/plugins/data/common/data_views/data_views/__snapshots__/data_view.test.ts.snap rename to src/plugins/data_views/common/data_views/__snapshots__/data_view.test.ts.snap diff --git a/src/plugins/data/common/data_views/data_views/__snapshots__/data_views.test.ts.snap b/src/plugins/data_views/common/data_views/__snapshots__/data_views.test.ts.snap similarity index 100% rename from src/plugins/data/common/data_views/data_views/__snapshots__/data_views.test.ts.snap rename to src/plugins/data_views/common/data_views/__snapshots__/data_views.test.ts.snap diff --git a/src/plugins/data/common/data_views/data_views/_pattern_cache.ts b/src/plugins/data_views/common/data_views/_pattern_cache.ts similarity index 100% rename from src/plugins/data/common/data_views/data_views/_pattern_cache.ts rename to src/plugins/data_views/common/data_views/_pattern_cache.ts diff --git a/src/plugins/data/common/data_views/data_views/data_view.stub.ts b/src/plugins/data_views/common/data_views/data_view.stub.ts similarity index 91% rename from src/plugins/data/common/data_views/data_views/data_view.stub.ts rename to src/plugins/data_views/common/data_views/data_view.stub.ts index 5ff2d077812a8..bb7696b0e1262 100644 --- a/src/plugins/data/common/data_views/data_views/data_view.stub.ts +++ b/src/plugins/data_views/common/data_views/data_view.stub.ts @@ -8,8 +8,8 @@ import { DataView } from './data_view'; import { DataViewSpec } from '../types'; -import { FieldFormatsStartCommon } from '../../../../field_formats/common'; -import { fieldFormatsMock } from '../../../../field_formats/common/mocks'; +import { FieldFormatsStartCommon } from '../../../field_formats/common'; +import { fieldFormatsMock } from '../../../field_formats/common/mocks'; /** * Create a custom stub index pattern. Use it in your unit tests where an {@link DataView} expected. diff --git a/src/plugins/data/common/data_views/data_views/data_view.test.ts b/src/plugins/data_views/common/data_views/data_view.test.ts similarity index 98% rename from src/plugins/data/common/data_views/data_views/data_view.test.ts rename to src/plugins/data_views/common/data_views/data_view.test.ts index 6aea86a7adae7..990b8fa4d5f35 100644 --- a/src/plugins/data/common/data_views/data_views/data_view.test.ts +++ b/src/plugins/data_views/common/data_views/data_view.test.ts @@ -10,12 +10,12 @@ import { map, last } from 'lodash'; import { IndexPattern } from './data_view'; -import { DuplicateField } from '../../../../kibana_utils/common'; +import { DuplicateField } from '../../../kibana_utils/common'; import { IndexPatternField } from '../fields'; -import { fieldFormatsMock } from '../../../../field_formats/common/mocks'; -import { FieldFormat } from '../../../../field_formats/common'; +import { fieldFormatsMock } from '../../../field_formats/common/mocks'; +import { FieldFormat } from '../../../field_formats/common'; import { RuntimeField } from '../types'; import { stubLogstashFields } from '../field.stub'; import { stubbedSavedObjectIndexPattern } from '../data_view.stub'; diff --git a/src/plugins/data/common/data_views/data_views/data_view.ts b/src/plugins/data_views/common/data_views/data_view.ts similarity index 97% rename from src/plugins/data/common/data_views/data_views/data_view.ts rename to src/plugins/data_views/common/data_views/data_view.ts index 18d301d2f9ea7..00b96cda32ad7 100644 --- a/src/plugins/data/common/data_views/data_views/data_view.ts +++ b/src/plugins/data_views/common/data_views/data_view.ts @@ -9,19 +9,19 @@ /* eslint-disable max-classes-per-file */ import _, { each, reject } from 'lodash'; -import { castEsToKbnFieldTypeName } from '@kbn/field-types'; +import { castEsToKbnFieldTypeName, ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/field-types'; import type { estypes } from '@elastic/elasticsearch'; -import { FieldAttrs, FieldAttrSet, DataViewAttributes } from '../..'; +import { FieldAttrs, FieldAttrSet, DataViewAttributes } from '..'; import type { RuntimeField } from '../types'; -import { DuplicateField } from '../../../../kibana_utils/common'; +import { DuplicateField } from '../../../kibana_utils/common'; -import { ES_FIELD_TYPES, KBN_FIELD_TYPES, IIndexPattern, IFieldType } from '../../../common'; +import { IIndexPattern, IFieldType } from '../../common'; import { DataViewField, IIndexPatternFieldList, fieldList } from '../fields'; import { formatHitProvider } from './format_hit'; import { flattenHitWrapper } from './flatten_hit'; -import { FieldFormatsStartCommon, FieldFormat } from '../../../../field_formats/common'; +import { FieldFormatsStartCommon, FieldFormat } from '../../../field_formats/common'; import { DataViewSpec, TypeMeta, SourceFilter, DataViewFieldMap } from '../types'; -import { SerializedFieldFormat } from '../../../../expressions/common'; +import { SerializedFieldFormat } from '../../../expressions/common'; interface DataViewDeps { spec?: DataViewSpec; diff --git a/src/plugins/data/common/data_views/data_views/data_views.test.ts b/src/plugins/data_views/common/data_views/data_views.test.ts similarity index 99% rename from src/plugins/data/common/data_views/data_views/data_views.test.ts rename to src/plugins/data_views/common/data_views/data_views.test.ts index ef9381f16d934..9a01e52ce48e2 100644 --- a/src/plugins/data/common/data_views/data_views/data_views.test.ts +++ b/src/plugins/data_views/common/data_views/data_views.test.ts @@ -8,7 +8,7 @@ import { defaults } from 'lodash'; import { DataViewsService, DataView } from '.'; -import { fieldFormatsMock } from '../../../../field_formats/common/mocks'; +import { fieldFormatsMock } from '../../../field_formats/common/mocks'; import { UiSettingsCommon, SavedObjectsClientCommon, SavedObject } from '../types'; import { stubbedSavedObjectIndexPattern } from '../data_view.stub'; diff --git a/src/plugins/data/common/data_views/data_views/data_views.ts b/src/plugins/data_views/common/data_views/data_views.ts similarity index 96% rename from src/plugins/data/common/data_views/data_views/data_views.ts rename to src/plugins/data_views/common/data_views/data_views.ts index f9b193d154770..77ce1caaaad84 100644 --- a/src/plugins/data/common/data_views/data_views/data_views.ts +++ b/src/plugins/data_views/common/data_views/data_views.ts @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; import { PublicMethodsOf } from '@kbn/utility-types'; import { castEsToKbnFieldTypeName } from '@kbn/field-types'; -import { DATA_VIEW_SAVED_OBJECT_TYPE, SavedObjectsClientCommon } from '../..'; +import { DATA_VIEW_SAVED_OBJECT_TYPE, SavedObjectsClientCommon } from '..'; import { createDataViewCache } from '.'; import type { RuntimeField } from '../types'; @@ -30,9 +30,9 @@ import { DataViewFieldMap, TypeMeta, } from '../types'; -import { FieldFormatsStartCommon, FORMATS_UI_SETTINGS } from '../../../../field_formats/common/'; -import { UI_SETTINGS, SavedObject } from '../../../common'; -import { SavedObjectNotFound } from '../../../../kibana_utils/common'; +import { FieldFormatsStartCommon, FORMATS_UI_SETTINGS } from '../../../field_formats/common/'; +import { META_FIELDS, SavedObject } from '../../common'; +import { SavedObjectNotFound } from '../../../kibana_utils/common'; import { DataViewMissingIndices } from '../lib'; import { findByTitle } from '../utils'; import { DuplicateDataViewError } from '../errors'; @@ -244,7 +244,7 @@ export class DataViewsService { * @returns FieldSpec[] */ getFieldsForWildcard = async (options: GetFieldsOptions) => { - const metaFields = await this.config.get(UI_SETTINGS.META_FIELDS); + const metaFields = await this.config.get(META_FIELDS); return this.apiClient.getFieldsForWildcard({ pattern: options.pattern, metaFields, @@ -290,7 +290,7 @@ export class DataViewsService { } this.onError(err, { - title: i18n.translate('data.indexPatterns.fetchFieldErrorTitle', { + title: i18n.translate('dataViews.fetchFieldErrorTitle', { defaultMessage: 'Error fetching fields for index pattern {title} (ID: {id})', values: { id: indexPattern.id, title: indexPattern.title }, }), @@ -336,7 +336,7 @@ export class DataViewsService { } this.onError(err, { - title: i18n.translate('data.indexPatterns.fetchFieldErrorTitle', { + title: i18n.translate('dataViews.fetchFieldErrorTitle', { defaultMessage: 'Error fetching fields for index pattern {title} (ID: {id})', values: { id, title }, }), @@ -445,7 +445,7 @@ export class DataViewsService { spec.title as string, { pattern: title as string, - metaFields: await this.config.get(UI_SETTINGS.META_FIELDS), + metaFields: await this.config.get(META_FIELDS), type, rollupIndex: typeMeta?.params?.rollup_index, allowNoIndex: spec.allowNoIndex, @@ -478,7 +478,7 @@ export class DataViewsService { }); } else { this.onError(err, { - title: i18n.translate('data.indexPatterns.fetchFieldErrorTitle', { + title: i18n.translate('dataViews.fetchFieldErrorTitle', { defaultMessage: 'Error fetching fields for index pattern {title} (ID: {id})', values: { id: savedObject.id, title }, }), @@ -520,7 +520,7 @@ export class DataViewsService { */ async create(spec: DataViewSpec, skipFetchFields = false): Promise { const shortDotsEnable = await this.config.get(FORMATS_UI_SETTINGS.SHORT_DOTS_ENABLE); - const metaFields = await this.config.get(UI_SETTINGS.META_FIELDS); + const metaFields = await this.config.get(META_FIELDS); const indexPattern = new DataView({ spec, @@ -648,7 +648,7 @@ export class DataViewsService { if (ignoreErrors) { return; } - const title = i18n.translate('data.indexPatterns.unableWriteLabel', { + const title = i18n.translate('dataViews.unableWriteLabel', { defaultMessage: 'Unable to write index pattern! Refresh the page to get the most up to date changes for this index pattern.', }); diff --git a/src/plugins/data/common/data_views/data_views/ensure_default_data_view.ts b/src/plugins/data_views/common/data_views/ensure_default_data_view.ts similarity index 100% rename from src/plugins/data/common/data_views/data_views/ensure_default_data_view.ts rename to src/plugins/data_views/common/data_views/ensure_default_data_view.ts diff --git a/src/plugins/data/common/data_views/data_views/flatten_hit.test.ts b/src/plugins/data_views/common/data_views/flatten_hit.test.ts similarity index 96% rename from src/plugins/data/common/data_views/data_views/flatten_hit.test.ts rename to src/plugins/data_views/common/data_views/flatten_hit.test.ts index 73232a65b6b72..0946f30b85e39 100644 --- a/src/plugins/data/common/data_views/data_views/flatten_hit.test.ts +++ b/src/plugins/data_views/common/data_views/flatten_hit.test.ts @@ -8,7 +8,7 @@ import { DataView } from './data_view'; -import { fieldFormatsMock } from '../../../../field_formats/common/mocks'; +import { fieldFormatsMock } from '../../../field_formats/common/mocks'; import { flattenHitWrapper } from './flatten_hit'; import { stubbedSavedObjectIndexPattern } from '../data_view.stub'; diff --git a/src/plugins/data/common/data_views/data_views/flatten_hit.ts b/src/plugins/data_views/common/data_views/flatten_hit.ts similarity index 100% rename from src/plugins/data/common/data_views/data_views/flatten_hit.ts rename to src/plugins/data_views/common/data_views/flatten_hit.ts diff --git a/src/plugins/data/common/data_views/data_views/format_hit.ts b/src/plugins/data_views/common/data_views/format_hit.ts similarity index 97% rename from src/plugins/data/common/data_views/data_views/format_hit.ts rename to src/plugins/data_views/common/data_views/format_hit.ts index 39f7fef564eb0..30daf7768c4dc 100644 --- a/src/plugins/data/common/data_views/data_views/format_hit.ts +++ b/src/plugins/data_views/common/data_views/format_hit.ts @@ -8,7 +8,7 @@ import _ from 'lodash'; import { DataView } from './data_view'; -import { FieldFormatsContentType } from '../../../../field_formats/common'; +import { FieldFormatsContentType } from '../../../field_formats/common'; const formattedCache = new WeakMap(); const partialFormattedCache = new WeakMap(); diff --git a/src/plugins/data/common/data_views/data_views/index.ts b/src/plugins/data_views/common/data_views/index.ts similarity index 100% rename from src/plugins/data/common/data_views/data_views/index.ts rename to src/plugins/data_views/common/data_views/index.ts diff --git a/src/plugins/data/common/data_views/errors/data_view_saved_object_conflict.ts b/src/plugins/data_views/common/errors/data_view_saved_object_conflict.ts similarity index 100% rename from src/plugins/data/common/data_views/errors/data_view_saved_object_conflict.ts rename to src/plugins/data_views/common/errors/data_view_saved_object_conflict.ts diff --git a/src/plugins/data/common/data_views/errors/duplicate_index_pattern.ts b/src/plugins/data_views/common/errors/duplicate_index_pattern.ts similarity index 100% rename from src/plugins/data/common/data_views/errors/duplicate_index_pattern.ts rename to src/plugins/data_views/common/errors/duplicate_index_pattern.ts diff --git a/src/plugins/data/common/data_views/errors/index.ts b/src/plugins/data_views/common/errors/index.ts similarity index 100% rename from src/plugins/data/common/data_views/errors/index.ts rename to src/plugins/data_views/common/errors/index.ts diff --git a/src/plugins/data/common/data_views/expressions/index.ts b/src/plugins/data_views/common/expressions/index.ts similarity index 100% rename from src/plugins/data/common/data_views/expressions/index.ts rename to src/plugins/data_views/common/expressions/index.ts diff --git a/src/plugins/data/common/data_views/expressions/load_index_pattern.ts b/src/plugins/data_views/common/expressions/load_index_pattern.ts similarity index 90% rename from src/plugins/data/common/data_views/expressions/load_index_pattern.ts rename to src/plugins/data_views/common/expressions/load_index_pattern.ts index dd47a9fc0dfb4..ca0c1090ceea8 100644 --- a/src/plugins/data/common/data_views/expressions/load_index_pattern.ts +++ b/src/plugins/data_views/common/expressions/load_index_pattern.ts @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { DataViewsContract } from '../data_views'; import { DataViewSpec } from '..'; -import { SavedObjectReference } from '../../../../../core/types'; +import { SavedObjectReference } from '../../../../core/types'; const name = 'indexPatternLoad'; const type = 'index_pattern'; @@ -46,14 +46,14 @@ export const getIndexPatternLoadMeta = (): Omit< name, type, inputTypes: ['null'], - help: i18n.translate('data.functions.indexPatternLoad.help', { + help: i18n.translate('dataViews.indexPatternLoad.help', { defaultMessage: 'Loads an index pattern', }), args: { id: { types: ['string'], required: true, - help: i18n.translate('data.functions.indexPatternLoad.id.help', { + help: i18n.translate('dataViews.functions.indexPatternLoad.id.help', { defaultMessage: 'index pattern id to load', }), }, diff --git a/src/plugins/data/common/data_views/field.stub.ts b/src/plugins/data_views/common/field.stub.ts similarity index 100% rename from src/plugins/data/common/data_views/field.stub.ts rename to src/plugins/data_views/common/field.stub.ts diff --git a/src/plugins/data/common/data_views/fields/__snapshots__/data_view_field.test.ts.snap b/src/plugins/data_views/common/fields/__snapshots__/data_view_field.test.ts.snap similarity index 100% rename from src/plugins/data/common/data_views/fields/__snapshots__/data_view_field.test.ts.snap rename to src/plugins/data_views/common/fields/__snapshots__/data_view_field.test.ts.snap diff --git a/src/plugins/data/common/data_views/fields/data_view_field.test.ts b/src/plugins/data_views/common/fields/data_view_field.test.ts similarity index 97% rename from src/plugins/data/common/data_views/fields/data_view_field.test.ts rename to src/plugins/data_views/common/fields/data_view_field.test.ts index 9107036c15c1a..9c611354683c2 100644 --- a/src/plugins/data/common/data_views/fields/data_view_field.test.ts +++ b/src/plugins/data_views/common/fields/data_view_field.test.ts @@ -8,9 +8,9 @@ import { IndexPatternField } from './data_view_field'; import { IndexPattern } from '..'; -import { KBN_FIELD_TYPES } from '../../../common'; +import { KBN_FIELD_TYPES } from '@kbn/field-types'; import { FieldSpec, RuntimeField } from '../types'; -import { FieldFormat } from '../../../../field_formats/common'; +import { FieldFormat } from '../../../field_formats/common'; describe('Field', function () { function flatten(obj: Record) { diff --git a/src/plugins/data/common/data_views/fields/data_view_field.ts b/src/plugins/data_views/common/fields/data_view_field.ts similarity index 97% rename from src/plugins/data/common/data_views/fields/data_view_field.ts rename to src/plugins/data_views/common/fields/data_view_field.ts index fae0e14b95c05..3ad92a3a7e53d 100644 --- a/src/plugins/data/common/data_views/fields/data_view_field.ts +++ b/src/plugins/data_views/common/fields/data_view_field.ts @@ -9,11 +9,11 @@ /* eslint-disable max-classes-per-file */ import { KbnFieldType, getKbnFieldType, castEsToKbnFieldTypeName } from '@kbn/field-types'; +import { KBN_FIELD_TYPES } from '@kbn/field-types'; import type { RuntimeField } from '../types'; -import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import type { IFieldType } from './types'; -import { FieldSpec, DataView } from '../..'; -import { shortenDottedString } from '../../utils'; +import { FieldSpec, DataView } from '..'; +import { shortenDottedString } from './utils'; /** @public */ export class DataViewField implements IFieldType { diff --git a/src/plugins/data/common/data_views/fields/field_list.ts b/src/plugins/data_views/common/fields/field_list.ts similarity index 100% rename from src/plugins/data/common/data_views/fields/field_list.ts rename to src/plugins/data_views/common/fields/field_list.ts diff --git a/src/plugins/data/common/data_views/fields/fields.mocks.ts b/src/plugins/data_views/common/fields/fields.mocks.ts similarity index 100% rename from src/plugins/data/common/data_views/fields/fields.mocks.ts rename to src/plugins/data_views/common/fields/fields.mocks.ts diff --git a/src/plugins/data/common/data_views/fields/index.ts b/src/plugins/data_views/common/fields/index.ts similarity index 100% rename from src/plugins/data/common/data_views/fields/index.ts rename to src/plugins/data_views/common/fields/index.ts diff --git a/src/plugins/data/common/data_views/fields/types.ts b/src/plugins/data_views/common/fields/types.ts similarity index 95% rename from src/plugins/data/common/data_views/fields/types.ts rename to src/plugins/data_views/common/fields/types.ts index 2c5934a8e7b3f..2bd1cf5834548 100644 --- a/src/plugins/data/common/data_views/fields/types.ts +++ b/src/plugins/data_views/common/fields/types.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ import { DataViewFieldBase } from '@kbn/es-query'; -import { FieldSpec, DataView } from '../..'; +import { FieldSpec, DataView } from '..'; /** * @deprecated Use {@link IndexPatternField} diff --git a/src/plugins/data/common/utils/shorten_dotted_string.test.ts b/src/plugins/data_views/common/fields/utils.test.ts similarity index 92% rename from src/plugins/data/common/utils/shorten_dotted_string.test.ts rename to src/plugins/data_views/common/fields/utils.test.ts index 33a44925982ec..0f2ff280eb61b 100644 --- a/src/plugins/data/common/utils/shorten_dotted_string.test.ts +++ b/src/plugins/data_views/common/fields/utils.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { shortenDottedString } from './shorten_dotted_string'; +import { shortenDottedString } from './utils'; describe('shortenDottedString', () => { test('should convert a dot.notated.string into a short string', () => { diff --git a/src/plugins/data/common/data_views/fields/utils.ts b/src/plugins/data_views/common/fields/utils.ts similarity index 75% rename from src/plugins/data/common/data_views/fields/utils.ts rename to src/plugins/data_views/common/fields/utils.ts index 9e05bebc746f0..8344a32d78556 100644 --- a/src/plugins/data/common/data_views/fields/utils.ts +++ b/src/plugins/data_views/common/fields/utils.ts @@ -22,3 +22,15 @@ export function isFilterable(field: IFieldType): boolean { export function isNestedField(field: IFieldType): boolean { return !!field.subType?.nested; } + +const DOT_PREFIX_RE = /(.).+?\./g; + +/** + * Convert a dot.notated.string into a short + * version (d.n.string) + * + * @return {any} + */ +export function shortenDottedString(input: any) { + return typeof input !== 'string' ? input : input.replace(DOT_PREFIX_RE, '$1.'); +} diff --git a/src/plugins/data_views/common/index.ts b/src/plugins/data_views/common/index.ts new file mode 100644 index 0000000000000..e8b36ab3e8fc1 --- /dev/null +++ b/src/plugins/data_views/common/index.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { + RUNTIME_FIELD_TYPES, + FLEET_ASSETS_TO_IGNORE, + META_FIELDS, + DATA_VIEW_SAVED_OBJECT_TYPE, + INDEX_PATTERN_SAVED_OBJECT_TYPE, +} from './constants'; +export type { IFieldType, IIndexPatternFieldList } from './fields'; +export { isFilterable, isNestedField, fieldList, DataViewField, IndexPatternField } from './fields'; +export type { + FieldFormatMap, + RuntimeType, + RuntimeField, + IIndexPattern, + DataViewAttributes, + IndexPatternAttributes, + FieldAttrs, + FieldAttrSet, + OnNotification, + OnError, + UiSettingsCommon, + SavedObjectsClientCommonFindArgs, + SavedObjectsClientCommon, + GetFieldsOptions, + GetFieldsOptionsTimePattern, + IDataViewsApiClient, + IIndexPatternsApiClient, + SavedObject, + AggregationRestrictions, + TypeMeta, + FieldSpecConflictDescriptions, + FieldSpecExportFmt, + FieldSpec, + DataViewFieldMap, + IndexPatternFieldMap, + DataViewSpec, + IndexPatternSpec, + SourceFilter, +} from './types'; +export { DataViewType, IndexPatternType } from './types'; +export { + IndexPatternsService, + IndexPatternsContract, + DataViewsService, + DataViewsContract, +} from './data_views'; +export { IndexPattern, IndexPatternListItem, DataView, DataViewListItem } from './data_views'; +export { DuplicateDataViewError, DataViewSavedObjectConflictError } from './errors'; +export type { + IndexPatternExpressionType, + IndexPatternLoadStartDependencies, + IndexPatternLoadExpressionFunctionDefinition, +} from './expressions'; +export { getIndexPatternLoadMeta } from './expressions'; diff --git a/src/plugins/data/common/data_views/lib/errors.ts b/src/plugins/data_views/common/lib/errors.ts similarity index 93% rename from src/plugins/data/common/data_views/lib/errors.ts rename to src/plugins/data_views/common/lib/errors.ts index 83cc7ea56d020..f8422a6e5dd0d 100644 --- a/src/plugins/data/common/data_views/lib/errors.ts +++ b/src/plugins/data_views/common/lib/errors.ts @@ -8,7 +8,7 @@ /* eslint-disable */ -import { KbnError } from '../../../../kibana_utils/common/'; +import { KbnError } from '../../../kibana_utils/common'; /** * Tried to call a method that relies on SearchSource having an indexPattern assigned diff --git a/src/plugins/data/common/data_views/lib/get_title.ts b/src/plugins/data_views/common/lib/get_title.ts similarity index 85% rename from src/plugins/data/common/data_views/lib/get_title.ts rename to src/plugins/data_views/common/lib/get_title.ts index 94185eae46893..69471583f139c 100644 --- a/src/plugins/data/common/data_views/lib/get_title.ts +++ b/src/plugins/data_views/common/lib/get_title.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { SavedObjectsClientContract } from '../../../../../core/public'; -import { DATA_VIEW_SAVED_OBJECT_TYPE } from '../../constants'; +import { SavedObjectsClientContract } from '../../../../core/public'; +import { DATA_VIEW_SAVED_OBJECT_TYPE } from '../constants'; import { DataViewAttributes } from '../types'; export async function getTitle( diff --git a/src/plugins/data/common/data_views/lib/index.ts b/src/plugins/data_views/common/lib/index.ts similarity index 100% rename from src/plugins/data/common/data_views/lib/index.ts rename to src/plugins/data_views/common/lib/index.ts diff --git a/src/plugins/data/common/data_views/lib/types.ts b/src/plugins/data_views/common/lib/types.ts similarity index 100% rename from src/plugins/data/common/data_views/lib/types.ts rename to src/plugins/data_views/common/lib/types.ts diff --git a/src/plugins/data/common/data_views/lib/validate_data_view.test.ts b/src/plugins/data_views/common/lib/validate_data_view.test.ts similarity index 100% rename from src/plugins/data/common/data_views/lib/validate_data_view.test.ts rename to src/plugins/data_views/common/lib/validate_data_view.test.ts diff --git a/src/plugins/data/common/data_views/lib/validate_data_view.ts b/src/plugins/data_views/common/lib/validate_data_view.ts similarity index 100% rename from src/plugins/data/common/data_views/lib/validate_data_view.ts rename to src/plugins/data_views/common/lib/validate_data_view.ts diff --git a/src/plugins/data/common/data_views/mocks.ts b/src/plugins/data_views/common/mocks.ts similarity index 100% rename from src/plugins/data/common/data_views/mocks.ts rename to src/plugins/data_views/common/mocks.ts diff --git a/src/plugins/data_views/common/stubs.ts b/src/plugins/data_views/common/stubs.ts new file mode 100644 index 0000000000000..c6895da9bfb3a --- /dev/null +++ b/src/plugins/data_views/common/stubs.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './field.stub'; +export * from './data_views/data_view.stub'; diff --git a/src/plugins/data/common/data_views/types.ts b/src/plugins/data_views/common/types.ts similarity index 96% rename from src/plugins/data/common/data_views/types.ts rename to src/plugins/data_views/common/types.ts index 85fe98fbcfeb7..2b184bc1ef2a4 100644 --- a/src/plugins/data/common/data_views/types.ts +++ b/src/plugins/data_views/common/types.ts @@ -10,11 +10,12 @@ import type { DataViewFieldBase, IFieldSubType, DataViewBase } from '@kbn/es-que import { ToastInputFields, ErrorToastOptions } from 'src/core/public/notifications'; // eslint-disable-next-line import type { SavedObject } from 'src/core/server'; +import { KBN_FIELD_TYPES } from '@kbn/field-types'; import { IFieldType } from './fields'; import { RUNTIME_FIELD_TYPES } from './constants'; -import { SerializedFieldFormat } from '../../../expressions/common'; -import { KBN_FIELD_TYPES, DataViewField } from '..'; -import { FieldFormat } from '../../../field_formats/common'; +import { SerializedFieldFormat } from '../../expressions/common'; +import { DataViewField } from './fields'; +import { FieldFormat } from '../../field_formats/common'; export type FieldFormatMap = Record; diff --git a/src/plugins/data/common/data_views/utils.test.ts b/src/plugins/data_views/common/utils.test.ts similarity index 100% rename from src/plugins/data/common/data_views/utils.test.ts rename to src/plugins/data_views/common/utils.test.ts diff --git a/src/plugins/data/common/data_views/utils.ts b/src/plugins/data_views/common/utils.ts similarity index 89% rename from src/plugins/data/common/data_views/utils.ts rename to src/plugins/data_views/common/utils.ts index 2d36ab6c72225..77e9bd76b869c 100644 --- a/src/plugins/data/common/data_views/utils.ts +++ b/src/plugins/data_views/common/utils.ts @@ -7,9 +7,9 @@ */ import type { IndexPatternSavedObjectAttrs } from './data_views'; -import type { SavedObjectsClientCommon } from '../types'; +import type { SavedObjectsClientCommon } from './types'; -import { DATA_VIEW_SAVED_OBJECT_TYPE } from '../constants'; +import { DATA_VIEW_SAVED_OBJECT_TYPE } from './constants'; /** * Returns an object matching a given title diff --git a/src/plugins/data_views/jest.config.js b/src/plugins/data_views/jest.config.js new file mode 100644 index 0000000000000..4c1f067835630 --- /dev/null +++ b/src/plugins/data_views/jest.config.js @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/data_views'], + coverageDirectory: '/target/kibana-coverage/jest/src/plugins/data_views', + coverageReporters: ['text', 'html'], + collectCoverageFrom: ['/src/plugins/data_views/{common,public,server}/**/*.{ts,tsx}'], +}; diff --git a/src/plugins/data_views/kibana.json b/src/plugins/data_views/kibana.json new file mode 100644 index 0000000000000..27bf536ef8040 --- /dev/null +++ b/src/plugins/data_views/kibana.json @@ -0,0 +1,15 @@ +{ + "id": "dataViews", + "version": "kibana", + "server": true, + "ui": true, + "requiredPlugins": ["fieldFormats","expressions"], + "optionalPlugins": ["usageCollection"], + "extraPublicDirs": ["common"], + "requiredBundles": ["kibanaUtils","kibanaReact"], + "owner": { + "name": "App Services", + "githubTeam": "kibana-app-services" + }, + "description": "Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters." +} diff --git a/src/plugins/data/public/data_views/data_views/data_view.stub.ts b/src/plugins/data_views/public/data_views/data_view.stub.ts similarity index 84% rename from src/plugins/data/public/data_views/data_views/data_view.stub.ts rename to src/plugins/data_views/public/data_views/data_view.stub.ts index b3d8448064c65..f37a8a78b234b 100644 --- a/src/plugins/data/public/data_views/data_views/data_view.stub.ts +++ b/src/plugins/data_views/public/data_views/data_view.stub.ts @@ -7,11 +7,11 @@ */ import { CoreSetup } from 'kibana/public'; -import { FieldFormatsStartCommon } from '../../../../field_formats/common'; -import { getFieldFormatsRegistry } from '../../../../field_formats/public/mocks'; -import * as commonStubs from '../../../common/stubs'; -import { DataView, DataViewSpec } from '../../../common'; -import { coreMock } from '../../../../../core/public/mocks'; +import { FieldFormatsStartCommon } from '../../../field_formats/common'; +import { getFieldFormatsRegistry } from '../../../field_formats/public/mocks'; +import * as commonStubs from '../../common/stubs'; +import { DataView, DataViewSpec } from '../../common'; +import { coreMock } from '../../../../core/public/mocks'; /** * Create a custom stub index pattern. Use it in your unit tests where an {@link DataView} expected. * @param spec - Serialized index pattern object diff --git a/src/plugins/data/public/data_views/data_views/data_views_api_client.test.mock.ts b/src/plugins/data_views/public/data_views/data_views_api_client.test.mock.ts similarity index 86% rename from src/plugins/data/public/data_views/data_views/data_views_api_client.test.mock.ts rename to src/plugins/data_views/public/data_views/data_views_api_client.test.mock.ts index c53ca7ad89aa9..2fd17b98f7498 100644 --- a/src/plugins/data/public/data_views/data_views/data_views_api_client.test.mock.ts +++ b/src/plugins/data_views/public/data_views/data_views_api_client.test.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { setup } from '../../../../../core/test_helpers/http_test_setup'; +import { setup } from '../../../../core/test_helpers/http_test_setup'; export const { http } = setup((injectedMetadata) => { injectedMetadata.getBasePath.mockReturnValue('/hola/daro/'); diff --git a/src/plugins/data/public/data_views/data_views/data_views_api_client.test.ts b/src/plugins/data_views/public/data_views/data_views_api_client.test.ts similarity index 100% rename from src/plugins/data/public/data_views/data_views/data_views_api_client.test.ts rename to src/plugins/data_views/public/data_views/data_views_api_client.test.ts diff --git a/src/plugins/data/public/data_views/data_views/data_views_api_client.ts b/src/plugins/data_views/public/data_views/data_views_api_client.ts similarity index 90% rename from src/plugins/data/public/data_views/data_views/data_views_api_client.ts rename to src/plugins/data_views/public/data_views/data_views_api_client.ts index d11ec7cfa003d..d4da9a55c25d1 100644 --- a/src/plugins/data/public/data_views/data_views/data_views_api_client.ts +++ b/src/plugins/data_views/public/data_views/data_views_api_client.ts @@ -7,12 +7,8 @@ */ import { HttpSetup } from 'src/core/public'; -import { DataViewMissingIndices } from '../../../common/data_views/lib'; -import { - GetFieldsOptions, - IDataViewsApiClient, - GetFieldsOptionsTimePattern, -} from '../../../common/data_views/types'; +import { DataViewMissingIndices } from '../../common/lib'; +import { GetFieldsOptions, IDataViewsApiClient, GetFieldsOptionsTimePattern } from '../../common'; const API_BASE_URL: string = `/api/index_patterns/`; diff --git a/src/plugins/data/public/data_views/data_views/index.ts b/src/plugins/data_views/public/data_views/index.ts similarity index 88% rename from src/plugins/data/public/data_views/data_views/index.ts rename to src/plugins/data_views/public/data_views/index.ts index e0d18d47f39db..e476d62774f17 100644 --- a/src/plugins/data/public/data_views/data_views/index.ts +++ b/src/plugins/data_views/public/data_views/index.ts @@ -6,6 +6,6 @@ * Side Public License, v 1. */ -export * from '../../../common/data_views/data_views'; +export * from '../../common/data_views'; export * from './redirect_no_index_pattern'; export * from './data_views_api_client'; diff --git a/src/plugins/data/public/data_views/data_views/redirect_no_index_pattern.tsx b/src/plugins/data_views/public/data_views/redirect_no_index_pattern.tsx similarity index 84% rename from src/plugins/data/public/data_views/data_views/redirect_no_index_pattern.tsx rename to src/plugins/data_views/public/data_views/redirect_no_index_pattern.tsx index 88e18060c4d11..456af90a3c6dd 100644 --- a/src/plugins/data/public/data_views/data_views/redirect_no_index_pattern.tsx +++ b/src/plugins/data_views/public/data_views/redirect_no_index_pattern.tsx @@ -10,7 +10,7 @@ import { EuiCallOut } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { CoreStart } from 'kibana/public'; -import { toMountPoint } from '../../../../kibana_react/public'; +import { toMountPoint } from '../../../kibana_react/public'; let bannerId: string; @@ -29,13 +29,10 @@ export const onRedirectNoIndexPattern = clearTimeout(timeoutId); } - const bannerMessage = i18n.translate( - 'data.indexPatterns.ensureDefaultIndexPattern.bannerLabel', - { - defaultMessage: - 'To visualize and explore data in Kibana, you must create an index pattern to retrieve data from Elasticsearch.', - } - ); + const bannerMessage = i18n.translate('dataViews.ensureDefaultIndexPattern.bannerLabel', { + defaultMessage: + 'To visualize and explore data in Kibana, you must create an index pattern to retrieve data from Elasticsearch.', + }); // Avoid being hostile to new users who don't have an index pattern setup yet // give them a friendly info message instead of a terse error message diff --git a/src/plugins/data/public/data_views/expressions/index.ts b/src/plugins/data_views/public/expressions/index.ts similarity index 100% rename from src/plugins/data/public/data_views/expressions/index.ts rename to src/plugins/data_views/public/expressions/index.ts diff --git a/src/plugins/data/public/data_views/expressions/load_index_pattern.test.ts b/src/plugins/data_views/public/expressions/load_index_pattern.test.ts similarity index 92% rename from src/plugins/data/public/data_views/expressions/load_index_pattern.test.ts rename to src/plugins/data_views/public/expressions/load_index_pattern.test.ts index befa78c398984..02a530712e80a 100644 --- a/src/plugins/data/public/data_views/expressions/load_index_pattern.test.ts +++ b/src/plugins/data_views/public/expressions/load_index_pattern.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { IndexPatternLoadStartDependencies } from '../../../common/data_views/expressions'; +import { IndexPatternLoadStartDependencies } from '../../common/expressions'; import { getFunctionDefinition } from './load_index_pattern'; describe('indexPattern expression function', () => { diff --git a/src/plugins/data/public/data_views/expressions/load_index_pattern.ts b/src/plugins/data_views/public/expressions/load_index_pattern.ts similarity index 88% rename from src/plugins/data/public/data_views/expressions/load_index_pattern.ts rename to src/plugins/data_views/public/expressions/load_index_pattern.ts index 979861c7da38e..76119f3e50a45 100644 --- a/src/plugins/data/public/data_views/expressions/load_index_pattern.ts +++ b/src/plugins/data_views/public/expressions/load_index_pattern.ts @@ -11,8 +11,8 @@ import { getIndexPatternLoadMeta, IndexPatternLoadExpressionFunctionDefinition, IndexPatternLoadStartDependencies, -} from '../../../common/data_views/expressions'; -import { DataPublicPluginStart, DataStartDependencies } from '../../types'; +} from '../../common/expressions'; +import { DataViewsPublicPluginStart, DataViewsPublicStartDependencies } from '../types'; /** * Returns the expression function definition. Any stateful dependencies are accessed @@ -60,11 +60,14 @@ export function getFunctionDefinition({ export function getIndexPatternLoad({ getStartServices, }: { - getStartServices: StartServicesAccessor; + getStartServices: StartServicesAccessor< + DataViewsPublicStartDependencies, + DataViewsPublicPluginStart + >; }) { return getFunctionDefinition({ getStartDependencies: async () => { - const [, , { indexPatterns }] = await getStartServices(); + const [, , indexPatterns] = await getStartServices(); return { indexPatterns }; }, }); diff --git a/src/plugins/data_views/public/index.ts b/src/plugins/data_views/public/index.ts new file mode 100644 index 0000000000000..572806df11fa3 --- /dev/null +++ b/src/plugins/data_views/public/index.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { + ILLEGAL_CHARACTERS_KEY, + CONTAINS_SPACES_KEY, + ILLEGAL_CHARACTERS_VISIBLE, + ILLEGAL_CHARACTERS, + validateDataView, +} from '../common/lib'; +export { flattenHitWrapper, formatHitProvider, onRedirectNoIndexPattern } from './data_views'; + +export { IndexPatternField, IIndexPatternFieldList, TypeMeta } from '../common'; + +export { + IndexPatternsService, + IndexPatternsContract, + IndexPattern, + DataViewsApiClient, + DataViewsService, + DataViewsContract, + DataView, +} from './data_views'; +export { UiSettingsPublicToCommon } from './ui_settings_wrapper'; +export { SavedObjectsClientPublicToCommon } from './saved_objects_client_wrapper'; + +/* + * Plugin setup + */ + +import { DataViewsPublicPlugin } from './plugin'; + +export function plugin() { + return new DataViewsPublicPlugin(); +} + +export type { DataViewsPublicPluginSetup, DataViewsPublicPluginStart } from './types'; + +// Export plugin after all other imports +export type { DataViewsPublicPlugin as DataPlugin }; diff --git a/src/plugins/data_views/public/plugin.ts b/src/plugins/data_views/public/plugin.ts new file mode 100644 index 0000000000000..58f66623b64ab --- /dev/null +++ b/src/plugins/data_views/public/plugin.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CoreSetup, CoreStart, Plugin } from 'src/core/public'; +import { getIndexPatternLoad } from './expressions'; +import { + DataViewsPublicPluginSetup, + DataViewsPublicPluginStart, + DataViewsPublicSetupDependencies, + DataViewsPublicStartDependencies, +} from './types'; + +import { + DataViewsService, + onRedirectNoIndexPattern, + DataViewsApiClient, + UiSettingsPublicToCommon, + SavedObjectsClientPublicToCommon, +} from '.'; + +export class DataViewsPublicPlugin + implements + Plugin< + DataViewsPublicPluginSetup, + DataViewsPublicPluginStart, + DataViewsPublicSetupDependencies, + DataViewsPublicStartDependencies + > +{ + public setup( + core: CoreSetup, + { expressions }: DataViewsPublicSetupDependencies + ): DataViewsPublicPluginSetup { + expressions.registerFunction(getIndexPatternLoad({ getStartServices: core.getStartServices })); + + return {}; + } + + public start( + core: CoreStart, + { fieldFormats }: DataViewsPublicStartDependencies + ): DataViewsPublicPluginStart { + const { uiSettings, http, notifications, savedObjects, overlays, application } = core; + + return new DataViewsService({ + uiSettings: new UiSettingsPublicToCommon(uiSettings), + savedObjectsClient: new SavedObjectsClientPublicToCommon(savedObjects.client), + apiClient: new DataViewsApiClient(http), + fieldFormats, + onNotification: (toastInputFields) => { + notifications.toasts.add(toastInputFields); + }, + onError: notifications.toasts.addError.bind(notifications.toasts), + onRedirectNoIndexPattern: onRedirectNoIndexPattern( + application.capabilities, + application.navigateToApp, + overlays + ), + }); + } + + public stop() {} +} diff --git a/src/plugins/data/public/data_views/saved_objects_client_wrapper.test.ts b/src/plugins/data_views/public/saved_objects_client_wrapper.test.ts similarity index 96% rename from src/plugins/data/public/data_views/saved_objects_client_wrapper.test.ts rename to src/plugins/data_views/public/saved_objects_client_wrapper.test.ts index 221a18ac7fab7..124a66eba57f2 100644 --- a/src/plugins/data/public/data_views/saved_objects_client_wrapper.test.ts +++ b/src/plugins/data_views/public/saved_objects_client_wrapper.test.ts @@ -9,7 +9,7 @@ import { SavedObjectsClientPublicToCommon } from './saved_objects_client_wrapper'; import { savedObjectsServiceMock } from 'src/core/public/mocks'; -import { DataViewSavedObjectConflictError } from '../../common/data_views'; +import { DataViewSavedObjectConflictError } from '../common'; describe('SavedObjectsClientPublicToCommon', () => { const soClient = savedObjectsServiceMock.createStartContract().client; diff --git a/src/plugins/data/public/data_views/saved_objects_client_wrapper.ts b/src/plugins/data_views/public/saved_objects_client_wrapper.ts similarity index 98% rename from src/plugins/data/public/data_views/saved_objects_client_wrapper.ts rename to src/plugins/data_views/public/saved_objects_client_wrapper.ts index 1db4e3b1ccd24..beaae6ac3fc21 100644 --- a/src/plugins/data/public/data_views/saved_objects_client_wrapper.ts +++ b/src/plugins/data_views/public/saved_objects_client_wrapper.ts @@ -13,7 +13,7 @@ import { SavedObjectsClientCommonFindArgs, SavedObject, DataViewSavedObjectConflictError, -} from '../../common/data_views'; +} from '../common'; type SOClient = Pick; diff --git a/src/plugins/data_views/public/types.ts b/src/plugins/data_views/public/types.ts new file mode 100644 index 0000000000000..20b1cbaf090fa --- /dev/null +++ b/src/plugins/data_views/public/types.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ExpressionsSetup } from 'src/plugins/expressions/public'; +import { FieldFormatsSetup, FieldFormatsStart } from 'src/plugins/field_formats/public'; +import { PublicMethodsOf } from '@kbn/utility-types'; +import { DataViewsService } from './data_views'; + +export interface DataViewsPublicSetupDependencies { + expressions: ExpressionsSetup; + fieldFormats: FieldFormatsSetup; +} + +export interface DataViewsPublicStartDependencies { + fieldFormats: FieldFormatsStart; +} + +/** + * Data plugin public Setup contract + */ +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface DataViewsPublicPluginSetup {} + +/** + * Data plugin public Start contract + */ +export type DataViewsPublicPluginStart = PublicMethodsOf; diff --git a/src/plugins/data/public/data_views/ui_settings_wrapper.ts b/src/plugins/data_views/public/ui_settings_wrapper.ts similarity index 95% rename from src/plugins/data/public/data_views/ui_settings_wrapper.ts rename to src/plugins/data_views/public/ui_settings_wrapper.ts index f8ae317391fa3..91806867b6730 100644 --- a/src/plugins/data/public/data_views/ui_settings_wrapper.ts +++ b/src/plugins/data_views/public/ui_settings_wrapper.ts @@ -7,7 +7,7 @@ */ import { IUiSettingsClient, PublicUiSettingsParams, UserProvidedValues } from 'src/core/public'; -import { UiSettingsCommon } from '../../common'; +import { UiSettingsCommon } from '../common'; export class UiSettingsPublicToCommon implements UiSettingsCommon { private uiSettings: IUiSettingsClient; diff --git a/src/plugins/data/server/data_views/capabilities_provider.ts b/src/plugins/data_views/server/capabilities_provider.ts similarity index 100% rename from src/plugins/data/server/data_views/capabilities_provider.ts rename to src/plugins/data_views/server/capabilities_provider.ts diff --git a/src/plugins/data_views/server/data_views_service_factory.ts b/src/plugins/data_views/server/data_views_service_factory.ts new file mode 100644 index 0000000000000..2f720cd7388f4 --- /dev/null +++ b/src/plugins/data_views/server/data_views_service_factory.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + Logger, + SavedObjectsClientContract, + ElasticsearchClient, + UiSettingsServiceStart, +} from 'kibana/server'; +import { DataViewsService } from '../common'; +import { FieldFormatsStart } from '../../field_formats/server'; +import { UiSettingsServerToCommon } from './ui_settings_wrapper'; +import { IndexPatternsApiServer } from './index_patterns_api_client'; +import { SavedObjectsClientServerToCommon } from './saved_objects_client_wrapper'; + +export const dataViewsServiceFactory = + ({ + logger, + uiSettings, + fieldFormats, + }: { + logger: Logger; + uiSettings: UiSettingsServiceStart; + fieldFormats: FieldFormatsStart; + }) => + async ( + savedObjectsClient: SavedObjectsClientContract, + elasticsearchClient: ElasticsearchClient + ) => { + const uiSettingsClient = uiSettings.asScopedToClient(savedObjectsClient); + const formats = await fieldFormats.fieldFormatServiceFactory(uiSettingsClient); + + return new DataViewsService({ + uiSettings: new UiSettingsServerToCommon(uiSettingsClient), + savedObjectsClient: new SavedObjectsClientServerToCommon(savedObjectsClient), + apiClient: new IndexPatternsApiServer(elasticsearchClient, savedObjectsClient), + fieldFormats: formats, + onError: (error) => { + logger.error(error); + }, + onNotification: ({ title, text }) => { + logger.warn(`${title}${text ? ` : ${text}` : ''}`); + }, + }); + }; diff --git a/src/plugins/data/server/data_views/deprecations/index.ts b/src/plugins/data_views/server/deprecations/index.ts similarity index 100% rename from src/plugins/data/server/data_views/deprecations/index.ts rename to src/plugins/data_views/server/deprecations/index.ts diff --git a/src/plugins/data/server/data_views/deprecations/scripted_fields.test.ts b/src/plugins/data_views/server/deprecations/scripted_fields.test.ts similarity index 100% rename from src/plugins/data/server/data_views/deprecations/scripted_fields.test.ts rename to src/plugins/data_views/server/deprecations/scripted_fields.test.ts diff --git a/src/plugins/data/server/data_views/deprecations/scripted_fields.ts b/src/plugins/data_views/server/deprecations/scripted_fields.ts similarity index 89% rename from src/plugins/data/server/data_views/deprecations/scripted_fields.ts rename to src/plugins/data_views/server/deprecations/scripted_fields.ts index 65cb962196805..9ee2d64e25cb5 100644 --- a/src/plugins/data/server/data_views/deprecations/scripted_fields.ts +++ b/src/plugins/data_views/server/deprecations/scripted_fields.ts @@ -13,7 +13,7 @@ import { RegisterDeprecationsConfig, } from 'kibana/server'; import { i18n } from '@kbn/i18n'; -import { IndexPatternAttributes } from '../../../common'; +import { IndexPatternAttributes } from '../../common'; type IndexPatternAttributesWithFields = Pick; @@ -41,10 +41,10 @@ export const createScriptedFieldsDeprecationsConfig: ( return [ { - title: i18n.translate('data.deprecations.scriptedFieldsTitle', { + title: i18n.translate('dataViews.deprecations.scriptedFieldsTitle', { defaultMessage: 'Found index patterns using scripted fields', }), - message: i18n.translate('data.deprecations.scriptedFieldsMessage', { + message: i18n.translate('dataViews.deprecations.scriptedFieldsMessage', { defaultMessage: `You have {numberOfIndexPatternsWithScriptedFields} index patterns ({titlesPreview}...) that use scripted fields. Scripted fields are deprecated and will be removed in future. Use runtime fields instead.`, values: { titlesPreview: indexPatternTitles.slice(0, PREVIEW_LIMIT).join('; '), @@ -56,10 +56,10 @@ export const createScriptedFieldsDeprecationsConfig: ( level: 'warning', // warning because it is not set in stone WHEN we remove scripted fields, hence this deprecation is not a blocker for 8.0 upgrade correctiveActions: { manualSteps: [ - i18n.translate('data.deprecations.scriptedFields.manualStepOneMessage', { + i18n.translate('dataViews.deprecations.scriptedFields.manualStepOneMessage', { defaultMessage: 'Navigate to Stack Management > Kibana > Index Patterns.', }), - i18n.translate('data.deprecations.scriptedFields.manualStepTwoMessage', { + i18n.translate('dataViews.deprecations.scriptedFields.manualStepTwoMessage', { defaultMessage: 'Update {numberOfIndexPatternsWithScriptedFields} index patterns that have scripted fields to use runtime fields instead. In most cases, to migrate existing scripts, you will need to change "return ;" to "emit();". Index patterns with at least one scripted field: {allTitles}', values: { diff --git a/src/plugins/data/server/data_views/error.ts b/src/plugins/data_views/server/error.ts similarity index 100% rename from src/plugins/data/server/data_views/error.ts rename to src/plugins/data_views/server/error.ts diff --git a/src/plugins/data/server/data_views/expressions/index.ts b/src/plugins/data_views/server/expressions/index.ts similarity index 100% rename from src/plugins/data/server/data_views/expressions/index.ts rename to src/plugins/data_views/server/expressions/index.ts diff --git a/src/plugins/data/server/data_views/expressions/load_index_pattern.test.ts b/src/plugins/data_views/server/expressions/load_index_pattern.test.ts similarity index 94% rename from src/plugins/data/server/data_views/expressions/load_index_pattern.test.ts rename to src/plugins/data_views/server/expressions/load_index_pattern.test.ts index 370d7dcfd7eba..94bd854e6734c 100644 --- a/src/plugins/data/server/data_views/expressions/load_index_pattern.test.ts +++ b/src/plugins/data_views/server/expressions/load_index_pattern.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { IndexPatternLoadStartDependencies } from '../../../common/data_views/expressions'; +import { IndexPatternLoadStartDependencies } from '../../common/expressions'; import { getFunctionDefinition } from './load_index_pattern'; describe('indexPattern expression function', () => { diff --git a/src/plugins/data/server/data_views/expressions/load_index_pattern.ts b/src/plugins/data_views/server/expressions/load_index_pattern.ts similarity index 85% rename from src/plugins/data/server/data_views/expressions/load_index_pattern.ts rename to src/plugins/data_views/server/expressions/load_index_pattern.ts index 4585101f2812c..8ade41132e144 100644 --- a/src/plugins/data/server/data_views/expressions/load_index_pattern.ts +++ b/src/plugins/data_views/server/expressions/load_index_pattern.ts @@ -13,8 +13,8 @@ import { getIndexPatternLoadMeta, IndexPatternLoadExpressionFunctionDefinition, IndexPatternLoadStartDependencies, -} from '../../../common/data_views/expressions'; -import { DataPluginStartDependencies, DataPluginStart } from '../../plugin'; +} from '../../common/expressions'; +import { DataViewsServerPluginStartDependencies, DataViewsServerPluginStart } from '../types'; /** * Returns the expression function definition. Any stateful dependencies are accessed @@ -39,7 +39,7 @@ export function getFunctionDefinition({ const kibanaRequest = getKibanaRequest ? getKibanaRequest() : null; if (!kibanaRequest) { throw new Error( - i18n.translate('data.indexPatterns.indexPatternLoad.error.kibanaRequest', { + i18n.translate('dataViews.indexPatternLoad.error.kibanaRequest', { defaultMessage: 'A KibanaRequest is required to execute this search on the server. ' + 'Please provide a request object to the expression execution params.', @@ -73,13 +73,17 @@ export function getFunctionDefinition({ export function getIndexPatternLoad({ getStartServices, }: { - getStartServices: StartServicesAccessor; + getStartServices: StartServicesAccessor< + DataViewsServerPluginStartDependencies, + DataViewsServerPluginStart + >; }) { return getFunctionDefinition({ getStartDependencies: async (request: KibanaRequest) => { - const [{ elasticsearch, savedObjects }, , { indexPatterns }] = await getStartServices(); + const [{ elasticsearch, savedObjects }, , { indexPatternsServiceFactory }] = + await getStartServices(); return { - indexPatterns: await indexPatterns.indexPatternsServiceFactory( + indexPatterns: await indexPatternsServiceFactory( savedObjects.getScopedClient(request), elasticsearch.client.asScoped(request).asCurrentUser ), diff --git a/src/plugins/data/server/data_views/fetcher/index.ts b/src/plugins/data_views/server/fetcher/index.ts similarity index 100% rename from src/plugins/data/server/data_views/fetcher/index.ts rename to src/plugins/data_views/server/fetcher/index.ts diff --git a/src/plugins/data_views/server/fetcher/index_not_found_exception.json b/src/plugins/data_views/server/fetcher/index_not_found_exception.json new file mode 100644 index 0000000000000..dc892d95ae397 --- /dev/null +++ b/src/plugins/data_views/server/fetcher/index_not_found_exception.json @@ -0,0 +1,21 @@ +{ + "error" : { + "root_cause" : [ + { + "type" : "index_not_found_exception", + "reason" : "no such index [poop]", + "resource.type" : "index_or_alias", + "resource.id" : "poop", + "index_uuid" : "_na_", + "index" : "poop" + } + ], + "type" : "index_not_found_exception", + "reason" : "no such index [poop]", + "resource.type" : "index_or_alias", + "resource.id" : "poop", + "index_uuid" : "_na_", + "index" : "poop" + }, + "status" : 404 +} diff --git a/src/plugins/data/server/data_views/fetcher/index_patterns_fetcher.test.ts b/src/plugins/data_views/server/fetcher/index_patterns_fetcher.test.ts similarity index 95% rename from src/plugins/data/server/data_views/fetcher/index_patterns_fetcher.test.ts rename to src/plugins/data_views/server/fetcher/index_patterns_fetcher.test.ts index 4bd21fb3a1820..a65d4d551cf7c 100644 --- a/src/plugins/data/server/data_views/fetcher/index_patterns_fetcher.test.ts +++ b/src/plugins/data_views/server/fetcher/index_patterns_fetcher.test.ts @@ -8,7 +8,7 @@ import { IndexPatternsFetcher } from '.'; import { ElasticsearchClient } from 'kibana/server'; -import * as indexNotFoundException from '../../../common/search/test_data/index_not_found_exception.json'; +import * as indexNotFoundException from './index_not_found_exception.json'; describe('Index Pattern Fetcher - server', () => { let indexPatterns: IndexPatternsFetcher; diff --git a/src/plugins/data/server/data_views/fetcher/index_patterns_fetcher.ts b/src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts similarity index 100% rename from src/plugins/data/server/data_views/fetcher/index_patterns_fetcher.ts rename to src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts diff --git a/src/plugins/data/server/data_views/fetcher/lib/errors.ts b/src/plugins/data_views/server/fetcher/lib/errors.ts similarity index 100% rename from src/plugins/data/server/data_views/fetcher/lib/errors.ts rename to src/plugins/data_views/server/fetcher/lib/errors.ts diff --git a/src/plugins/data/server/data_views/fetcher/lib/es_api.test.js b/src/plugins/data_views/server/fetcher/lib/es_api.test.js similarity index 100% rename from src/plugins/data/server/data_views/fetcher/lib/es_api.test.js rename to src/plugins/data_views/server/fetcher/lib/es_api.test.js diff --git a/src/plugins/data/server/data_views/fetcher/lib/es_api.ts b/src/plugins/data_views/server/fetcher/lib/es_api.ts similarity index 100% rename from src/plugins/data/server/data_views/fetcher/lib/es_api.ts rename to src/plugins/data_views/server/fetcher/lib/es_api.ts diff --git a/src/plugins/data/server/data_views/fetcher/lib/field_capabilities/__fixtures__/es_field_caps_response.json b/src/plugins/data_views/server/fetcher/lib/field_capabilities/__fixtures__/es_field_caps_response.json similarity index 100% rename from src/plugins/data/server/data_views/fetcher/lib/field_capabilities/__fixtures__/es_field_caps_response.json rename to src/plugins/data_views/server/fetcher/lib/field_capabilities/__fixtures__/es_field_caps_response.json diff --git a/src/plugins/data/server/data_views/fetcher/lib/field_capabilities/field_capabilities.test.js b/src/plugins/data_views/server/fetcher/lib/field_capabilities/field_capabilities.test.js similarity index 100% rename from src/plugins/data/server/data_views/fetcher/lib/field_capabilities/field_capabilities.test.js rename to src/plugins/data_views/server/fetcher/lib/field_capabilities/field_capabilities.test.js diff --git a/src/plugins/data/server/data_views/fetcher/lib/field_capabilities/field_capabilities.ts b/src/plugins/data_views/server/fetcher/lib/field_capabilities/field_capabilities.ts similarity index 100% rename from src/plugins/data/server/data_views/fetcher/lib/field_capabilities/field_capabilities.ts rename to src/plugins/data_views/server/fetcher/lib/field_capabilities/field_capabilities.ts diff --git a/src/plugins/data/server/data_views/fetcher/lib/field_capabilities/field_caps_response.test.js b/src/plugins/data_views/server/fetcher/lib/field_capabilities/field_caps_response.test.js similarity index 99% rename from src/plugins/data/server/data_views/fetcher/lib/field_capabilities/field_caps_response.test.js rename to src/plugins/data_views/server/fetcher/lib/field_capabilities/field_caps_response.test.js index c12eff1b5a377..f1e3f314351de 100644 --- a/src/plugins/data/server/data_views/fetcher/lib/field_capabilities/field_caps_response.test.js +++ b/src/plugins/data_views/server/fetcher/lib/field_capabilities/field_caps_response.test.js @@ -13,7 +13,7 @@ import sinon from 'sinon'; import * as shouldReadFieldFromDocValuesNS from './should_read_field_from_doc_values'; import { shouldReadFieldFromDocValues } from './should_read_field_from_doc_values'; -import { getKbnFieldType } from '../../../../../common'; +import { getKbnFieldType } from '@kbn/field-types'; import { readFieldCapsResponse } from './field_caps_response'; import esResponse from './__fixtures__/es_field_caps_response.json'; diff --git a/src/plugins/data/server/data_views/fetcher/lib/field_capabilities/field_caps_response.ts b/src/plugins/data_views/server/fetcher/lib/field_capabilities/field_caps_response.ts similarity index 98% rename from src/plugins/data/server/data_views/fetcher/lib/field_capabilities/field_caps_response.ts rename to src/plugins/data_views/server/fetcher/lib/field_capabilities/field_caps_response.ts index 3f83fd71b74e4..6dff343f9e00e 100644 --- a/src/plugins/data/server/data_views/fetcher/lib/field_capabilities/field_caps_response.ts +++ b/src/plugins/data_views/server/fetcher/lib/field_capabilities/field_caps_response.ts @@ -8,7 +8,7 @@ import { uniq } from 'lodash'; import type { estypes } from '@elastic/elasticsearch'; -import { castEsToKbnFieldTypeName } from '../../../../../common'; +import { castEsToKbnFieldTypeName } from '@kbn/field-types'; import { shouldReadFieldFromDocValues } from './should_read_field_from_doc_values'; import { FieldDescriptor } from '../../../fetcher'; diff --git a/src/plugins/data/server/data_views/fetcher/lib/field_capabilities/index.ts b/src/plugins/data_views/server/fetcher/lib/field_capabilities/index.ts similarity index 100% rename from src/plugins/data/server/data_views/fetcher/lib/field_capabilities/index.ts rename to src/plugins/data_views/server/fetcher/lib/field_capabilities/index.ts diff --git a/src/plugins/data/server/data_views/fetcher/lib/field_capabilities/overrides.ts b/src/plugins/data_views/server/fetcher/lib/field_capabilities/overrides.ts similarity index 100% rename from src/plugins/data/server/data_views/fetcher/lib/field_capabilities/overrides.ts rename to src/plugins/data_views/server/fetcher/lib/field_capabilities/overrides.ts diff --git a/src/plugins/data/server/data_views/fetcher/lib/field_capabilities/should_read_field_from_doc_values.test.ts b/src/plugins/data_views/server/fetcher/lib/field_capabilities/should_read_field_from_doc_values.test.ts similarity index 100% rename from src/plugins/data/server/data_views/fetcher/lib/field_capabilities/should_read_field_from_doc_values.test.ts rename to src/plugins/data_views/server/fetcher/lib/field_capabilities/should_read_field_from_doc_values.test.ts diff --git a/src/plugins/data/server/data_views/fetcher/lib/field_capabilities/should_read_field_from_doc_values.ts b/src/plugins/data_views/server/fetcher/lib/field_capabilities/should_read_field_from_doc_values.ts similarity index 100% rename from src/plugins/data/server/data_views/fetcher/lib/field_capabilities/should_read_field_from_doc_values.ts rename to src/plugins/data_views/server/fetcher/lib/field_capabilities/should_read_field_from_doc_values.ts diff --git a/src/plugins/data/server/data_views/fetcher/lib/index.ts b/src/plugins/data_views/server/fetcher/lib/index.ts similarity index 100% rename from src/plugins/data/server/data_views/fetcher/lib/index.ts rename to src/plugins/data_views/server/fetcher/lib/index.ts diff --git a/src/plugins/data/server/data_views/fetcher/lib/jobs_compatibility.test.js b/src/plugins/data_views/server/fetcher/lib/jobs_compatibility.test.js similarity index 100% rename from src/plugins/data/server/data_views/fetcher/lib/jobs_compatibility.test.js rename to src/plugins/data_views/server/fetcher/lib/jobs_compatibility.test.js diff --git a/src/plugins/data/server/data_views/fetcher/lib/jobs_compatibility.ts b/src/plugins/data_views/server/fetcher/lib/jobs_compatibility.ts similarity index 100% rename from src/plugins/data/server/data_views/fetcher/lib/jobs_compatibility.ts rename to src/plugins/data_views/server/fetcher/lib/jobs_compatibility.ts diff --git a/src/plugins/data/server/data_views/fetcher/lib/map_capabilities.ts b/src/plugins/data_views/server/fetcher/lib/map_capabilities.ts similarity index 100% rename from src/plugins/data/server/data_views/fetcher/lib/map_capabilities.ts rename to src/plugins/data_views/server/fetcher/lib/map_capabilities.ts diff --git a/src/plugins/data/server/data_views/fetcher/lib/merge_capabilities_with_fields.ts b/src/plugins/data_views/server/fetcher/lib/merge_capabilities_with_fields.ts similarity index 100% rename from src/plugins/data/server/data_views/fetcher/lib/merge_capabilities_with_fields.ts rename to src/plugins/data_views/server/fetcher/lib/merge_capabilities_with_fields.ts diff --git a/src/plugins/data/server/data_views/fetcher/lib/resolve_time_pattern.test.js b/src/plugins/data_views/server/fetcher/lib/resolve_time_pattern.test.js similarity index 100% rename from src/plugins/data/server/data_views/fetcher/lib/resolve_time_pattern.test.js rename to src/plugins/data_views/server/fetcher/lib/resolve_time_pattern.test.js diff --git a/src/plugins/data/server/data_views/fetcher/lib/resolve_time_pattern.ts b/src/plugins/data_views/server/fetcher/lib/resolve_time_pattern.ts similarity index 100% rename from src/plugins/data/server/data_views/fetcher/lib/resolve_time_pattern.ts rename to src/plugins/data_views/server/fetcher/lib/resolve_time_pattern.ts diff --git a/src/plugins/data/server/data_views/fetcher/lib/time_pattern_to_wildcard.test.ts b/src/plugins/data_views/server/fetcher/lib/time_pattern_to_wildcard.test.ts similarity index 100% rename from src/plugins/data/server/data_views/fetcher/lib/time_pattern_to_wildcard.test.ts rename to src/plugins/data_views/server/fetcher/lib/time_pattern_to_wildcard.test.ts diff --git a/src/plugins/data/server/data_views/fetcher/lib/time_pattern_to_wildcard.ts b/src/plugins/data_views/server/fetcher/lib/time_pattern_to_wildcard.ts similarity index 100% rename from src/plugins/data/server/data_views/fetcher/lib/time_pattern_to_wildcard.ts rename to src/plugins/data_views/server/fetcher/lib/time_pattern_to_wildcard.ts diff --git a/src/plugins/data/server/data_views/has_user_index_pattern.test.ts b/src/plugins/data_views/server/has_user_index_pattern.test.ts similarity index 99% rename from src/plugins/data/server/data_views/has_user_index_pattern.test.ts rename to src/plugins/data_views/server/has_user_index_pattern.test.ts index efc149b409375..aeaa64b949dbc 100644 --- a/src/plugins/data/server/data_views/has_user_index_pattern.test.ts +++ b/src/plugins/data_views/server/has_user_index_pattern.test.ts @@ -7,7 +7,7 @@ */ import { hasUserIndexPattern } from './has_user_index_pattern'; -import { elasticsearchServiceMock, savedObjectsClientMock } from '../../../../core/server/mocks'; +import { elasticsearchServiceMock, savedObjectsClientMock } from '../../../core/server/mocks'; describe('hasUserIndexPattern', () => { const esClient = elasticsearchServiceMock.createScopedClusterClient().asCurrentUser; diff --git a/src/plugins/data/server/data_views/has_user_index_pattern.ts b/src/plugins/data_views/server/has_user_index_pattern.ts similarity index 91% rename from src/plugins/data/server/data_views/has_user_index_pattern.ts rename to src/plugins/data_views/server/has_user_index_pattern.ts index 97abd0892b836..6566f75ebc52e 100644 --- a/src/plugins/data/server/data_views/has_user_index_pattern.ts +++ b/src/plugins/data_views/server/has_user_index_pattern.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { ElasticsearchClient, SavedObjectsClientContract } from '../../../../core/server'; -import { IndexPatternSavedObjectAttrs } from '../../common/data_views/data_views'; -import { FLEET_ASSETS_TO_IGNORE } from '../../common/data_views/constants'; +import { ElasticsearchClient, SavedObjectsClientContract } from '../../../core/server'; +import { IndexPatternSavedObjectAttrs } from '../common/data_views'; +import { FLEET_ASSETS_TO_IGNORE } from '../common/constants'; interface Deps { esClient: ElasticsearchClient; diff --git a/src/plugins/data_views/server/index.ts b/src/plugins/data_views/server/index.ts new file mode 100644 index 0000000000000..1c7eeb073bbe2 --- /dev/null +++ b/src/plugins/data_views/server/index.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { getFieldByName, findIndexPatternById } from './utils'; +export { + IndexPatternsFetcher, + FieldDescriptor, + shouldReadFieldFromDocValues, + mergeCapabilitiesWithFields, + getCapabilitiesForRollupIndices, +} from './fetcher'; +export { IndexPatternsServiceStart } from './types'; + +import { PluginInitializerContext } from 'src/core/server'; +import { DataViewsServerPlugin } from './plugin'; +import { DataViewsServerPluginSetup, DataViewsServerPluginStart } from './types'; +export type { dataViewsServiceFactory } from './data_views_service_factory'; + +/** + * Static code to be shared externally + * @public + */ + +export function plugin(initializerContext: PluginInitializerContext) { + return new DataViewsServerPlugin(initializerContext); +} + +export { + DataViewsServerPlugin as Plugin, + DataViewsServerPluginSetup as PluginSetup, + DataViewsServerPluginStart as PluginStart, +}; diff --git a/src/plugins/data/server/data_views/index_patterns_api_client.ts b/src/plugins/data_views/server/index_patterns_api_client.ts similarity index 94% rename from src/plugins/data/server/data_views/index_patterns_api_client.ts rename to src/plugins/data_views/server/index_patterns_api_client.ts index 4f71bf218dd4d..26ccdd7e02b4c 100644 --- a/src/plugins/data/server/data_views/index_patterns_api_client.ts +++ b/src/plugins/data_views/server/index_patterns_api_client.ts @@ -11,8 +11,8 @@ import { GetFieldsOptions, IIndexPatternsApiClient, GetFieldsOptionsTimePattern, -} from '../../common/data_views/types'; -import { DataViewMissingIndices } from '../../common/data_views/lib'; +} from '../common/types'; +import { DataViewMissingIndices } from '../common/lib'; import { IndexPatternsFetcher } from './fetcher'; import { hasUserIndexPattern } from './has_user_index_pattern'; diff --git a/src/plugins/data_views/server/mocks.ts b/src/plugins/data_views/server/mocks.ts new file mode 100644 index 0000000000000..70a582810a1e2 --- /dev/null +++ b/src/plugins/data_views/server/mocks.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export function createIndexPatternsStartMock() { + const dataViewsServiceFactory = jest.fn().mockResolvedValue({ get: jest.fn() }); + return { + indexPatternsServiceFactory: dataViewsServiceFactory, + dataViewsServiceFactory, + }; +} diff --git a/src/plugins/data_views/server/plugin.ts b/src/plugins/data_views/server/plugin.ts new file mode 100644 index 0000000000000..7285e74847e58 --- /dev/null +++ b/src/plugins/data_views/server/plugin.ts @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CoreSetup, CoreStart, Logger, Plugin, PluginInitializerContext } from 'src/core/server'; +import { dataViewsServiceFactory } from './data_views_service_factory'; +import { registerRoutes } from './routes'; +import { dataViewSavedObjectType } from './saved_objects'; +import { capabilitiesProvider } from './capabilities_provider'; +import { getIndexPatternLoad } from './expressions'; +import { registerIndexPatternsUsageCollector } from './register_index_pattern_usage_collection'; +import { createScriptedFieldsDeprecationsConfig } from './deprecations'; +import { + DataViewsServerPluginSetup, + DataViewsServerPluginStart, + DataViewsServerPluginSetupDependencies, + DataViewsServerPluginStartDependencies, +} from './types'; + +export class DataViewsServerPlugin + implements + Plugin< + DataViewsServerPluginSetup, + DataViewsServerPluginStart, + DataViewsServerPluginSetupDependencies, + DataViewsServerPluginStartDependencies + > +{ + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get('dataView'); + } + + public setup( + core: CoreSetup, + { expressions, usageCollection }: DataViewsServerPluginSetupDependencies + ) { + core.savedObjects.registerType(dataViewSavedObjectType); + core.capabilities.registerProvider(capabilitiesProvider); + + registerRoutes(core.http, core.getStartServices); + + expressions.registerFunction(getIndexPatternLoad({ getStartServices: core.getStartServices })); + registerIndexPatternsUsageCollector(core.getStartServices, usageCollection); + core.deprecations.registerDeprecations(createScriptedFieldsDeprecationsConfig(core)); + + return {}; + } + + public start( + { uiSettings }: CoreStart, + { fieldFormats }: DataViewsServerPluginStartDependencies + ) { + const serviceFactory = dataViewsServiceFactory({ + logger: this.logger.get('indexPatterns'), + uiSettings, + fieldFormats, + }); + + return { + indexPatternsServiceFactory: serviceFactory, + dataViewsServiceFactory: serviceFactory, + }; + } + + public stop() {} +} + +export { DataViewsServerPlugin as Plugin }; diff --git a/src/plugins/data/server/data_views/register_index_pattern_usage_collection.test.ts b/src/plugins/data_views/server/register_index_pattern_usage_collection.test.ts similarity index 98% rename from src/plugins/data/server/data_views/register_index_pattern_usage_collection.test.ts rename to src/plugins/data_views/server/register_index_pattern_usage_collection.test.ts index 2c826185757d6..01d3a574a58cb 100644 --- a/src/plugins/data/server/data_views/register_index_pattern_usage_collection.test.ts +++ b/src/plugins/data_views/server/register_index_pattern_usage_collection.test.ts @@ -12,7 +12,7 @@ import { updateMax, getIndexPatternTelemetry, } from './register_index_pattern_usage_collection'; -import { IndexPatternsCommonService } from '..'; +import { DataViewsService } from '../common'; const scriptA = 'emit(0);'; const scriptB = 'emit(1);\nemit(2);'; @@ -32,7 +32,7 @@ const indexPatterns = { getScriptedFields: () => [], fields: [], }), -} as any as IndexPatternsCommonService; +} as any as DataViewsService; describe('index pattern usage collection', () => { it('minMaxAvgLoC calculates min, max, and average ', () => { diff --git a/src/plugins/data/server/data_views/register_index_pattern_usage_collection.ts b/src/plugins/data_views/server/register_index_pattern_usage_collection.ts similarity index 90% rename from src/plugins/data/server/data_views/register_index_pattern_usage_collection.ts rename to src/plugins/data_views/server/register_index_pattern_usage_collection.ts index 36c2a59ce2753..6af2f6df6725e 100644 --- a/src/plugins/data/server/data_views/register_index_pattern_usage_collection.ts +++ b/src/plugins/data_views/server/register_index_pattern_usage_collection.ts @@ -8,9 +8,9 @@ import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { StartServicesAccessor } from 'src/core/server'; -import { IndexPatternsCommonService } from '..'; -import { SavedObjectsClient } from '../../../../core/server'; -import { DataPluginStartDependencies, DataPluginStart } from '../plugin'; +import { DataViewsService } from '../common'; +import { SavedObjectsClient } from '../../../core/server'; +import { DataViewsServerPluginStartDependencies, DataViewsServerPluginStart } from './types'; interface CountSummary { min?: number; @@ -57,7 +57,7 @@ export const updateMax = (currentMax: number | undefined, newVal: number): numbe } }; -export async function getIndexPatternTelemetry(indexPatterns: IndexPatternsCommonService) { +export async function getIndexPatternTelemetry(indexPatterns: DataViewsService) { const ids = await indexPatterns.getIds(); const countSummaryDefaults: CountSummary = { @@ -139,7 +139,10 @@ export async function getIndexPatternTelemetry(indexPatterns: IndexPatternsCommo } export function registerIndexPatternsUsageCollector( - getStartServices: StartServicesAccessor, + getStartServices: StartServicesAccessor< + DataViewsServerPluginStartDependencies, + DataViewsServerPluginStart + >, usageCollection?: UsageCollectionSetup ): void { if (!usageCollection) { @@ -150,8 +153,9 @@ export function registerIndexPatternsUsageCollector( type: 'index-patterns', isReady: () => true, fetch: async () => { - const [{ savedObjects, elasticsearch }, , { indexPatterns }] = await getStartServices(); - const indexPatternService = await indexPatterns.indexPatternsServiceFactory( + const [{ savedObjects, elasticsearch }, , { indexPatternsServiceFactory }] = + await getStartServices(); + const indexPatternService = await indexPatternsServiceFactory( new SavedObjectsClient(savedObjects.createInternalRepository()), elasticsearch.client.asInternalUser ); diff --git a/src/plugins/data/server/data_views/routes.ts b/src/plugins/data_views/server/routes.ts similarity index 96% rename from src/plugins/data/server/data_views/routes.ts rename to src/plugins/data_views/server/routes.ts index 9488285fc7e2c..48c359cd9d852 100644 --- a/src/plugins/data/server/data_views/routes.ts +++ b/src/plugins/data_views/server/routes.ts @@ -19,7 +19,7 @@ import { registerPutScriptedFieldRoute } from './routes/scripted_fields/put_scri import { registerGetScriptedFieldRoute } from './routes/scripted_fields/get_scripted_field'; import { registerDeleteScriptedFieldRoute } from './routes/scripted_fields/delete_scripted_field'; import { registerUpdateScriptedFieldRoute } from './routes/scripted_fields/update_scripted_field'; -import type { DataPluginStart, DataPluginStartDependencies } from '../plugin'; +import type { DataViewsServerPluginStart, DataViewsServerPluginStartDependencies } from './types'; import { registerManageDefaultIndexPatternRoutes } from './routes/default_index_pattern'; import { registerCreateRuntimeFieldRoute } from './routes/runtime_fields/create_runtime_field'; import { registerGetRuntimeFieldRoute } from './routes/runtime_fields/get_runtime_field'; @@ -30,7 +30,10 @@ import { registerHasUserIndexPatternRoute } from './routes/has_user_index_patter export function registerRoutes( http: HttpServiceSetup, - getStartServices: StartServicesAccessor + getStartServices: StartServicesAccessor< + DataViewsServerPluginStartDependencies, + DataViewsServerPluginStart + > ) { const parseMetaFields = (metaFields: string | string[]) => { let parsedFields: string[] = []; diff --git a/src/plugins/data/server/data_views/routes/create_index_pattern.ts b/src/plugins/data_views/server/routes/create_index_pattern.ts similarity index 84% rename from src/plugins/data/server/data_views/routes/create_index_pattern.ts rename to src/plugins/data_views/server/routes/create_index_pattern.ts index 7049903f84e8c..b87b03f8bd4a1 100644 --- a/src/plugins/data/server/data_views/routes/create_index_pattern.ts +++ b/src/plugins/data_views/server/routes/create_index_pattern.ts @@ -7,15 +7,15 @@ */ import { schema } from '@kbn/config-schema'; -import { IndexPatternSpec } from 'src/plugins/data/common'; +import { IndexPatternSpec } from 'src/plugins/data_views/common'; import { handleErrors } from './util/handle_errors'; import { fieldSpecSchema, runtimeFieldSpecSchema, serializedFieldFormatSchema, } from './util/schemas'; -import { IRouter, StartServicesAccessor } from '../../../../../core/server'; -import type { DataPluginStart, DataPluginStartDependencies } from '../../plugin'; +import { IRouter, StartServicesAccessor } from '../../../../core/server'; +import type { DataViewsServerPluginStart, DataViewsServerPluginStartDependencies } from '../types'; const indexPatternSpecSchema = schema.object({ title: schema.string(), @@ -48,7 +48,10 @@ const indexPatternSpecSchema = schema.object({ export const registerCreateIndexPatternRoute = ( router: IRouter, - getStartServices: StartServicesAccessor + getStartServices: StartServicesAccessor< + DataViewsServerPluginStartDependencies, + DataViewsServerPluginStart + > ) => { router.post( { @@ -65,8 +68,8 @@ export const registerCreateIndexPatternRoute = ( handleErrors(async (ctx, req, res) => { const savedObjectsClient = ctx.core.savedObjects.client; const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; - const [, , { indexPatterns }] = await getStartServices(); - const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + const [, , { indexPatternsServiceFactory }] = await getStartServices(); + const indexPatternsService = await indexPatternsServiceFactory( savedObjectsClient, elasticsearchClient ); diff --git a/src/plugins/data/server/data_views/routes/default_index_pattern.ts b/src/plugins/data_views/server/routes/default_index_pattern.ts similarity index 76% rename from src/plugins/data/server/data_views/routes/default_index_pattern.ts rename to src/plugins/data_views/server/routes/default_index_pattern.ts index cf5986943eb37..620e201a4850d 100644 --- a/src/plugins/data/server/data_views/routes/default_index_pattern.ts +++ b/src/plugins/data_views/server/routes/default_index_pattern.ts @@ -7,13 +7,16 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter, StartServicesAccessor } from '../../../../../core/server'; -import type { DataPluginStart, DataPluginStartDependencies } from '../../plugin'; +import { IRouter, StartServicesAccessor } from '../../../../core/server'; +import type { DataViewsServerPluginStart, DataViewsServerPluginStartDependencies } from '../types'; import { handleErrors } from './util/handle_errors'; export const registerManageDefaultIndexPatternRoutes = ( router: IRouter, - getStartServices: StartServicesAccessor + getStartServices: StartServicesAccessor< + DataViewsServerPluginStartDependencies, + DataViewsServerPluginStart + > ) => { router.get( { @@ -23,8 +26,8 @@ export const registerManageDefaultIndexPatternRoutes = ( handleErrors(async (ctx, req, res) => { const savedObjectsClient = ctx.core.savedObjects.client; const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; - const [, , { indexPatterns }] = await getStartServices(); - const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + const [, , { indexPatternsServiceFactory }] = await getStartServices(); + const indexPatternsService = await indexPatternsServiceFactory( savedObjectsClient, elasticsearchClient ); @@ -57,8 +60,8 @@ export const registerManageDefaultIndexPatternRoutes = ( handleErrors(async (ctx, req, res) => { const savedObjectsClient = ctx.core.savedObjects.client; const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; - const [, , { indexPatterns }] = await getStartServices(); - const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + const [, , { indexPatternsServiceFactory }] = await getStartServices(); + const indexPatternsService = await indexPatternsServiceFactory( savedObjectsClient, elasticsearchClient ); diff --git a/src/plugins/data/server/data_views/routes/delete_index_pattern.ts b/src/plugins/data_views/server/routes/delete_index_pattern.ts similarity index 75% rename from src/plugins/data/server/data_views/routes/delete_index_pattern.ts rename to src/plugins/data_views/server/routes/delete_index_pattern.ts index 14de079470dcb..0d3f929cdccc3 100644 --- a/src/plugins/data/server/data_views/routes/delete_index_pattern.ts +++ b/src/plugins/data_views/server/routes/delete_index_pattern.ts @@ -8,12 +8,15 @@ import { schema } from '@kbn/config-schema'; import { handleErrors } from './util/handle_errors'; -import { IRouter, StartServicesAccessor } from '../../../../../core/server'; -import type { DataPluginStart, DataPluginStartDependencies } from '../../plugin'; +import { IRouter, StartServicesAccessor } from '../../../../core/server'; +import type { DataViewsServerPluginStart, DataViewsServerPluginStartDependencies } from '../types'; export const registerDeleteIndexPatternRoute = ( router: IRouter, - getStartServices: StartServicesAccessor + getStartServices: StartServicesAccessor< + DataViewsServerPluginStartDependencies, + DataViewsServerPluginStart + > ) => { router.delete( { @@ -34,8 +37,8 @@ export const registerDeleteIndexPatternRoute = ( handleErrors(async (ctx, req, res) => { const savedObjectsClient = ctx.core.savedObjects.client; const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; - const [, , { indexPatterns }] = await getStartServices(); - const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + const [, , { indexPatternsServiceFactory }] = await getStartServices(); + const indexPatternsService = await indexPatternsServiceFactory( savedObjectsClient, elasticsearchClient ); diff --git a/src/plugins/data/server/data_views/routes/fields/update_fields.ts b/src/plugins/data_views/server/routes/fields/update_fields.ts similarity index 87% rename from src/plugins/data/server/data_views/routes/fields/update_fields.ts rename to src/plugins/data_views/server/routes/fields/update_fields.ts index a510fdaa6e1d8..3e45ee46f2bb7 100644 --- a/src/plugins/data/server/data_views/routes/fields/update_fields.ts +++ b/src/plugins/data_views/server/routes/fields/update_fields.ts @@ -9,12 +9,18 @@ import { schema } from '@kbn/config-schema'; import { handleErrors } from '../util/handle_errors'; import { serializedFieldFormatSchema } from '../util/schemas'; -import { IRouter, StartServicesAccessor } from '../../../../../../core/server'; -import type { DataPluginStart, DataPluginStartDependencies } from '../../../plugin'; +import { IRouter, StartServicesAccessor } from '../../../../../core/server'; +import type { + DataViewsServerPluginStart, + DataViewsServerPluginStartDependencies, +} from '../../types'; export const registerUpdateFieldsRoute = ( router: IRouter, - getStartServices: StartServicesAccessor + getStartServices: StartServicesAccessor< + DataViewsServerPluginStartDependencies, + DataViewsServerPluginStart + > ) => { router.post( { @@ -55,8 +61,8 @@ export const registerUpdateFieldsRoute = ( handleErrors(async (ctx, req, res) => { const savedObjectsClient = ctx.core.savedObjects.client; const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; - const [, , { indexPatterns }] = await getStartServices(); - const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + const [, , { indexPatternsServiceFactory }] = await getStartServices(); + const indexPatternsService = await indexPatternsServiceFactory( savedObjectsClient, elasticsearchClient ); diff --git a/src/plugins/data/server/data_views/routes/get_index_pattern.ts b/src/plugins/data_views/server/routes/get_index_pattern.ts similarity index 76% rename from src/plugins/data/server/data_views/routes/get_index_pattern.ts rename to src/plugins/data_views/server/routes/get_index_pattern.ts index 268fd3da8cd6a..7fea748ca3389 100644 --- a/src/plugins/data/server/data_views/routes/get_index_pattern.ts +++ b/src/plugins/data_views/server/routes/get_index_pattern.ts @@ -8,12 +8,15 @@ import { schema } from '@kbn/config-schema'; import { handleErrors } from './util/handle_errors'; -import { IRouter, StartServicesAccessor } from '../../../../../core/server'; -import type { DataPluginStart, DataPluginStartDependencies } from '../../plugin'; +import { IRouter, StartServicesAccessor } from '../../../../core/server'; +import type { DataViewsServerPluginStart, DataViewsServerPluginStartDependencies } from '../types'; export const registerGetIndexPatternRoute = ( router: IRouter, - getStartServices: StartServicesAccessor + getStartServices: StartServicesAccessor< + DataViewsServerPluginStartDependencies, + DataViewsServerPluginStart + > ) => { router.get( { @@ -34,8 +37,8 @@ export const registerGetIndexPatternRoute = ( handleErrors(async (ctx, req, res) => { const savedObjectsClient = ctx.core.savedObjects.client; const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; - const [, , { indexPatterns }] = await getStartServices(); - const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + const [, , { indexPatternsServiceFactory }] = await getStartServices(); + const indexPatternsService = await indexPatternsServiceFactory( savedObjectsClient, elasticsearchClient ); diff --git a/src/plugins/data/server/data_views/routes/has_user_index_pattern.ts b/src/plugins/data_views/server/routes/has_user_index_pattern.ts similarity index 69% rename from src/plugins/data/server/data_views/routes/has_user_index_pattern.ts rename to src/plugins/data_views/server/routes/has_user_index_pattern.ts index 7d67e96f39f6e..af0ad1cc88d2e 100644 --- a/src/plugins/data/server/data_views/routes/has_user_index_pattern.ts +++ b/src/plugins/data_views/server/routes/has_user_index_pattern.ts @@ -7,12 +7,15 @@ */ import { handleErrors } from './util/handle_errors'; -import { IRouter, StartServicesAccessor } from '../../../../../core/server'; -import type { DataPluginStart, DataPluginStartDependencies } from '../../plugin'; +import { IRouter, StartServicesAccessor } from '../../../../core/server'; +import type { DataViewsServerPluginStart, DataViewsServerPluginStartDependencies } from '../types'; export const registerHasUserIndexPatternRoute = ( router: IRouter, - getStartServices: StartServicesAccessor + getStartServices: StartServicesAccessor< + DataViewsServerPluginStartDependencies, + DataViewsServerPluginStart + > ) => { router.get( { @@ -23,8 +26,8 @@ export const registerHasUserIndexPatternRoute = ( handleErrors(async (ctx, req, res) => { const savedObjectsClient = ctx.core.savedObjects.client; const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; - const [, , { indexPatterns }] = await getStartServices(); - const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + const [, , { indexPatternsServiceFactory }] = await getStartServices(); + const indexPatternsService = await indexPatternsServiceFactory( savedObjectsClient, elasticsearchClient ); diff --git a/src/plugins/data/server/data_views/routes/runtime_fields/create_runtime_field.ts b/src/plugins/data_views/server/routes/runtime_fields/create_runtime_field.ts similarity index 82% rename from src/plugins/data/server/data_views/routes/runtime_fields/create_runtime_field.ts rename to src/plugins/data_views/server/routes/runtime_fields/create_runtime_field.ts index faf6d87b6d10b..04b661d14732f 100644 --- a/src/plugins/data/server/data_views/routes/runtime_fields/create_runtime_field.ts +++ b/src/plugins/data_views/server/routes/runtime_fields/create_runtime_field.ts @@ -9,12 +9,18 @@ import { schema } from '@kbn/config-schema'; import { handleErrors } from '../util/handle_errors'; import { runtimeFieldSpecSchema } from '../util/schemas'; -import { IRouter, StartServicesAccessor } from '../../../../../../core/server'; -import type { DataPluginStart, DataPluginStartDependencies } from '../../../plugin'; +import { IRouter, StartServicesAccessor } from '../../../../../core/server'; +import type { + DataViewsServerPluginStart, + DataViewsServerPluginStartDependencies, +} from '../../types'; export const registerCreateRuntimeFieldRoute = ( router: IRouter, - getStartServices: StartServicesAccessor + getStartServices: StartServicesAccessor< + DataViewsServerPluginStartDependencies, + DataViewsServerPluginStart + > ) => { router.post( { @@ -39,8 +45,8 @@ export const registerCreateRuntimeFieldRoute = ( handleErrors(async (ctx, req, res) => { const savedObjectsClient = ctx.core.savedObjects.client; const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; - const [, , { indexPatterns }] = await getStartServices(); - const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + const [, , { indexPatternsServiceFactory }] = await getStartServices(); + const indexPatternsService = await indexPatternsServiceFactory( savedObjectsClient, elasticsearchClient ); diff --git a/src/plugins/data/server/data_views/routes/runtime_fields/delete_runtime_field.ts b/src/plugins/data_views/server/routes/runtime_fields/delete_runtime_field.ts similarity index 79% rename from src/plugins/data/server/data_views/routes/runtime_fields/delete_runtime_field.ts rename to src/plugins/data_views/server/routes/runtime_fields/delete_runtime_field.ts index 58b8529d7cf5a..e5c6b03a64224 100644 --- a/src/plugins/data/server/data_views/routes/runtime_fields/delete_runtime_field.ts +++ b/src/plugins/data_views/server/routes/runtime_fields/delete_runtime_field.ts @@ -9,12 +9,18 @@ import { schema } from '@kbn/config-schema'; import { ErrorIndexPatternFieldNotFound } from '../../error'; import { handleErrors } from '../util/handle_errors'; -import { IRouter, StartServicesAccessor } from '../../../../../../core/server'; -import type { DataPluginStart, DataPluginStartDependencies } from '../../../plugin'; +import { IRouter, StartServicesAccessor } from '../../../../../core/server'; +import type { + DataViewsServerPluginStart, + DataViewsServerPluginStartDependencies, +} from '../../types'; export const registerDeleteRuntimeFieldRoute = ( router: IRouter, - getStartServices: StartServicesAccessor + getStartServices: StartServicesAccessor< + DataViewsServerPluginStartDependencies, + DataViewsServerPluginStart + > ) => { router.delete( { @@ -35,8 +41,8 @@ export const registerDeleteRuntimeFieldRoute = ( handleErrors(async (ctx, req, res) => { const savedObjectsClient = ctx.core.savedObjects.client; const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; - const [, , { indexPatterns }] = await getStartServices(); - const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + const [, , { indexPatternsServiceFactory }] = await getStartServices(); + const indexPatternsService = await indexPatternsServiceFactory( savedObjectsClient, elasticsearchClient ); diff --git a/src/plugins/data/server/data_views/routes/runtime_fields/get_runtime_field.ts b/src/plugins/data_views/server/routes/runtime_fields/get_runtime_field.ts similarity index 79% rename from src/plugins/data/server/data_views/routes/runtime_fields/get_runtime_field.ts rename to src/plugins/data_views/server/routes/runtime_fields/get_runtime_field.ts index 6bc2bf396c0b4..b457ae6b0159b 100644 --- a/src/plugins/data/server/data_views/routes/runtime_fields/get_runtime_field.ts +++ b/src/plugins/data_views/server/routes/runtime_fields/get_runtime_field.ts @@ -9,12 +9,18 @@ import { schema } from '@kbn/config-schema'; import { ErrorIndexPatternFieldNotFound } from '../../error'; import { handleErrors } from '../util/handle_errors'; -import { IRouter, StartServicesAccessor } from '../../../../../../core/server'; -import type { DataPluginStart, DataPluginStartDependencies } from '../../../plugin'; +import { IRouter, StartServicesAccessor } from '../../../../../core/server'; +import type { + DataViewsServerPluginStart, + DataViewsServerPluginStartDependencies, +} from '../../types'; export const registerGetRuntimeFieldRoute = ( router: IRouter, - getStartServices: StartServicesAccessor + getStartServices: StartServicesAccessor< + DataViewsServerPluginStartDependencies, + DataViewsServerPluginStart + > ) => { router.get( { @@ -36,8 +42,8 @@ export const registerGetRuntimeFieldRoute = ( handleErrors(async (ctx, req, res) => { const savedObjectsClient = ctx.core.savedObjects.client; const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; - const [, , { indexPatterns }] = await getStartServices(); - const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + const [, , { indexPatternsServiceFactory }] = await getStartServices(); + const indexPatternsService = await indexPatternsServiceFactory( savedObjectsClient, elasticsearchClient ); diff --git a/src/plugins/data/server/data_views/routes/runtime_fields/put_runtime_field.ts b/src/plugins/data_views/server/routes/runtime_fields/put_runtime_field.ts similarity index 82% rename from src/plugins/data/server/data_views/routes/runtime_fields/put_runtime_field.ts rename to src/plugins/data_views/server/routes/runtime_fields/put_runtime_field.ts index a5e92fa5a36ec..1c3ed99fdf67e 100644 --- a/src/plugins/data/server/data_views/routes/runtime_fields/put_runtime_field.ts +++ b/src/plugins/data_views/server/routes/runtime_fields/put_runtime_field.ts @@ -9,12 +9,18 @@ import { schema } from '@kbn/config-schema'; import { handleErrors } from '../util/handle_errors'; import { runtimeFieldSpecSchema } from '../util/schemas'; -import { IRouter, StartServicesAccessor } from '../../../../../../core/server'; -import type { DataPluginStart, DataPluginStartDependencies } from '../../../plugin'; +import { IRouter, StartServicesAccessor } from '../../../../../core/server'; +import type { + DataViewsServerPluginStart, + DataViewsServerPluginStartDependencies, +} from '../../types'; export const registerPutRuntimeFieldRoute = ( router: IRouter, - getStartServices: StartServicesAccessor + getStartServices: StartServicesAccessor< + DataViewsServerPluginStartDependencies, + DataViewsServerPluginStart + > ) => { router.put( { @@ -38,8 +44,8 @@ export const registerPutRuntimeFieldRoute = ( handleErrors(async (ctx, req, res) => { const savedObjectsClient = ctx.core.savedObjects.client; const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; - const [, , { indexPatterns }] = await getStartServices(); - const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + const [, , { indexPatternsServiceFactory }] = await getStartServices(); + const indexPatternsService = await indexPatternsServiceFactory( savedObjectsClient, elasticsearchClient ); diff --git a/src/plugins/data/server/data_views/routes/runtime_fields/update_runtime_field.ts b/src/plugins/data_views/server/routes/runtime_fields/update_runtime_field.ts similarity index 83% rename from src/plugins/data/server/data_views/routes/runtime_fields/update_runtime_field.ts rename to src/plugins/data_views/server/routes/runtime_fields/update_runtime_field.ts index 3f3aae46c4388..ca92f310ff281 100644 --- a/src/plugins/data/server/data_views/routes/runtime_fields/update_runtime_field.ts +++ b/src/plugins/data_views/server/routes/runtime_fields/update_runtime_field.ts @@ -7,16 +7,22 @@ */ import { schema } from '@kbn/config-schema'; -import { RuntimeField } from 'src/plugins/data/common'; +import { RuntimeField } from 'src/plugins/data_views/common'; import { ErrorIndexPatternFieldNotFound } from '../../error'; import { handleErrors } from '../util/handle_errors'; import { runtimeFieldSpec, runtimeFieldSpecTypeSchema } from '../util/schemas'; -import { IRouter, StartServicesAccessor } from '../../../../../../core/server'; -import type { DataPluginStart, DataPluginStartDependencies } from '../../../plugin'; +import { IRouter, StartServicesAccessor } from '../../../../../core/server'; +import type { + DataViewsServerPluginStart, + DataViewsServerPluginStartDependencies, +} from '../../types'; export const registerUpdateRuntimeFieldRoute = ( router: IRouter, - getStartServices: StartServicesAccessor + getStartServices: StartServicesAccessor< + DataViewsServerPluginStartDependencies, + DataViewsServerPluginStart + > ) => { router.post( { @@ -46,8 +52,8 @@ export const registerUpdateRuntimeFieldRoute = ( handleErrors(async (ctx, req, res) => { const savedObjectsClient = ctx.core.savedObjects.client; const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; - const [, , { indexPatterns }] = await getStartServices(); - const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + const [, , { indexPatternsServiceFactory }] = await getStartServices(); + const indexPatternsService = await indexPatternsServiceFactory( savedObjectsClient, elasticsearchClient ); diff --git a/src/plugins/data/server/data_views/routes/scripted_fields/create_scripted_field.ts b/src/plugins/data_views/server/routes/scripted_fields/create_scripted_field.ts similarity index 83% rename from src/plugins/data/server/data_views/routes/scripted_fields/create_scripted_field.ts rename to src/plugins/data_views/server/routes/scripted_fields/create_scripted_field.ts index 4d7b1d87cd9eb..e620960afbe13 100644 --- a/src/plugins/data/server/data_views/routes/scripted_fields/create_scripted_field.ts +++ b/src/plugins/data_views/server/routes/scripted_fields/create_scripted_field.ts @@ -9,12 +9,18 @@ import { schema } from '@kbn/config-schema'; import { handleErrors } from '../util/handle_errors'; import { fieldSpecSchema } from '../util/schemas'; -import { IRouter, StartServicesAccessor } from '../../../../../../core/server'; -import type { DataPluginStart, DataPluginStartDependencies } from '../../../plugin'; +import { IRouter, StartServicesAccessor } from '../../../../../core/server'; +import type { + DataViewsServerPluginStart, + DataViewsServerPluginStartDependencies, +} from '../../types'; export const registerCreateScriptedFieldRoute = ( router: IRouter, - getStartServices: StartServicesAccessor + getStartServices: StartServicesAccessor< + DataViewsServerPluginStartDependencies, + DataViewsServerPluginStart + > ) => { router.post( { @@ -38,8 +44,8 @@ export const registerCreateScriptedFieldRoute = ( handleErrors(async (ctx, req, res) => { const savedObjectsClient = ctx.core.savedObjects.client; const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; - const [, , { indexPatterns }] = await getStartServices(); - const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + const [, , { indexPatternsServiceFactory }] = await getStartServices(); + const indexPatternsService = await indexPatternsServiceFactory( savedObjectsClient, elasticsearchClient ); diff --git a/src/plugins/data/server/data_views/routes/scripted_fields/delete_scripted_field.ts b/src/plugins/data_views/server/routes/scripted_fields/delete_scripted_field.ts similarity index 81% rename from src/plugins/data/server/data_views/routes/scripted_fields/delete_scripted_field.ts rename to src/plugins/data_views/server/routes/scripted_fields/delete_scripted_field.ts index 169351c220ecf..bd1bfe0ec4e25 100644 --- a/src/plugins/data/server/data_views/routes/scripted_fields/delete_scripted_field.ts +++ b/src/plugins/data_views/server/routes/scripted_fields/delete_scripted_field.ts @@ -9,12 +9,18 @@ import { schema } from '@kbn/config-schema'; import { ErrorIndexPatternFieldNotFound } from '../../error'; import { handleErrors } from '../util/handle_errors'; -import { IRouter, StartServicesAccessor } from '../../../../../../core/server'; -import type { DataPluginStart, DataPluginStartDependencies } from '../../../plugin'; +import { IRouter, StartServicesAccessor } from '../../../../../core/server'; +import type { + DataViewsServerPluginStart, + DataViewsServerPluginStartDependencies, +} from '../../types'; export const registerDeleteScriptedFieldRoute = ( router: IRouter, - getStartServices: StartServicesAccessor + getStartServices: StartServicesAccessor< + DataViewsServerPluginStartDependencies, + DataViewsServerPluginStart + > ) => { router.delete( { @@ -39,8 +45,8 @@ export const registerDeleteScriptedFieldRoute = ( handleErrors(async (ctx, req, res) => { const savedObjectsClient = ctx.core.savedObjects.client; const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; - const [, , { indexPatterns }] = await getStartServices(); - const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + const [, , { indexPatternsServiceFactory }] = await getStartServices(); + const indexPatternsService = await indexPatternsServiceFactory( savedObjectsClient, elasticsearchClient ); diff --git a/src/plugins/data/server/data_views/routes/scripted_fields/get_scripted_field.ts b/src/plugins/data_views/server/routes/scripted_fields/get_scripted_field.ts similarity index 81% rename from src/plugins/data/server/data_views/routes/scripted_fields/get_scripted_field.ts rename to src/plugins/data_views/server/routes/scripted_fields/get_scripted_field.ts index 28f3f75a7aa1b..ae9cca2c79b48 100644 --- a/src/plugins/data/server/data_views/routes/scripted_fields/get_scripted_field.ts +++ b/src/plugins/data_views/server/routes/scripted_fields/get_scripted_field.ts @@ -9,12 +9,18 @@ import { schema } from '@kbn/config-schema'; import { ErrorIndexPatternFieldNotFound } from '../../error'; import { handleErrors } from '../util/handle_errors'; -import { IRouter, StartServicesAccessor } from '../../../../../../core/server'; -import type { DataPluginStart, DataPluginStartDependencies } from '../../../plugin'; +import { IRouter, StartServicesAccessor } from '../../../../../core/server'; +import type { + DataViewsServerPluginStart, + DataViewsServerPluginStartDependencies, +} from '../../types'; export const registerGetScriptedFieldRoute = ( router: IRouter, - getStartServices: StartServicesAccessor + getStartServices: StartServicesAccessor< + DataViewsServerPluginStartDependencies, + DataViewsServerPluginStart + > ) => { router.get( { @@ -39,8 +45,8 @@ export const registerGetScriptedFieldRoute = ( handleErrors(async (ctx, req, res) => { const savedObjectsClient = ctx.core.savedObjects.client; const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; - const [, , { indexPatterns }] = await getStartServices(); - const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + const [, , { indexPatternsServiceFactory }] = await getStartServices(); + const indexPatternsService = await indexPatternsServiceFactory( savedObjectsClient, elasticsearchClient ); diff --git a/src/plugins/data/server/data_views/routes/scripted_fields/put_scripted_field.ts b/src/plugins/data_views/server/routes/scripted_fields/put_scripted_field.ts similarity index 83% rename from src/plugins/data/server/data_views/routes/scripted_fields/put_scripted_field.ts rename to src/plugins/data_views/server/routes/scripted_fields/put_scripted_field.ts index 368ad53eb2258..a6cee3762513e 100644 --- a/src/plugins/data/server/data_views/routes/scripted_fields/put_scripted_field.ts +++ b/src/plugins/data_views/server/routes/scripted_fields/put_scripted_field.ts @@ -9,12 +9,18 @@ import { schema } from '@kbn/config-schema'; import { handleErrors } from '../util/handle_errors'; import { fieldSpecSchema } from '../util/schemas'; -import { IRouter, StartServicesAccessor } from '../../../../../../core/server'; -import type { DataPluginStart, DataPluginStartDependencies } from '../../../plugin'; +import { IRouter, StartServicesAccessor } from '../../../../../core/server'; +import type { + DataViewsServerPluginStart, + DataViewsServerPluginStartDependencies, +} from '../../types'; export const registerPutScriptedFieldRoute = ( router: IRouter, - getStartServices: StartServicesAccessor + getStartServices: StartServicesAccessor< + DataViewsServerPluginStartDependencies, + DataViewsServerPluginStart + > ) => { router.put( { @@ -38,8 +44,8 @@ export const registerPutScriptedFieldRoute = ( handleErrors(async (ctx, req, res) => { const savedObjectsClient = ctx.core.savedObjects.client; const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; - const [, , { indexPatterns }] = await getStartServices(); - const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + const [, , { indexPatternsServiceFactory }] = await getStartServices(); + const indexPatternsService = await indexPatternsServiceFactory( savedObjectsClient, elasticsearchClient ); diff --git a/src/plugins/data/server/data_views/routes/scripted_fields/update_scripted_field.ts b/src/plugins/data_views/server/routes/scripted_fields/update_scripted_field.ts similarity index 86% rename from src/plugins/data/server/data_views/routes/scripted_fields/update_scripted_field.ts rename to src/plugins/data_views/server/routes/scripted_fields/update_scripted_field.ts index bf10a3ee6389e..2917838293ec8 100644 --- a/src/plugins/data/server/data_views/routes/scripted_fields/update_scripted_field.ts +++ b/src/plugins/data_views/server/routes/scripted_fields/update_scripted_field.ts @@ -7,16 +7,22 @@ */ import { schema } from '@kbn/config-schema'; -import { FieldSpec } from 'src/plugins/data/common'; +import { FieldSpec } from 'src/plugins/data_views/common'; import { ErrorIndexPatternFieldNotFound } from '../../error'; import { handleErrors } from '../util/handle_errors'; import { fieldSpecSchemaFields } from '../util/schemas'; -import { IRouter, StartServicesAccessor } from '../../../../../../core/server'; -import type { DataPluginStart, DataPluginStartDependencies } from '../../../plugin'; +import { IRouter, StartServicesAccessor } from '../../../../../core/server'; +import type { + DataViewsServerPluginStart, + DataViewsServerPluginStartDependencies, +} from '../../types'; export const registerUpdateScriptedFieldRoute = ( router: IRouter, - getStartServices: StartServicesAccessor + getStartServices: StartServicesAccessor< + DataViewsServerPluginStartDependencies, + DataViewsServerPluginStart + > ) => { router.post( { @@ -59,8 +65,8 @@ export const registerUpdateScriptedFieldRoute = ( handleErrors(async (ctx, req, res) => { const savedObjectsClient = ctx.core.savedObjects.client; const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; - const [, , { indexPatterns }] = await getStartServices(); - const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + const [, , { indexPatternsServiceFactory }] = await getStartServices(); + const indexPatternsService = await indexPatternsServiceFactory( savedObjectsClient, elasticsearchClient ); diff --git a/src/plugins/data/server/data_views/routes/update_index_pattern.ts b/src/plugins/data_views/server/routes/update_index_pattern.ts similarity index 91% rename from src/plugins/data/server/data_views/routes/update_index_pattern.ts rename to src/plugins/data_views/server/routes/update_index_pattern.ts index 1c88550c154c5..1421057d65d26 100644 --- a/src/plugins/data/server/data_views/routes/update_index_pattern.ts +++ b/src/plugins/data_views/server/routes/update_index_pattern.ts @@ -13,8 +13,8 @@ import { runtimeFieldSpecSchema, serializedFieldFormatSchema, } from './util/schemas'; -import { IRouter, StartServicesAccessor } from '../../../../../core/server'; -import type { DataPluginStart, DataPluginStartDependencies } from '../../plugin'; +import { IRouter, StartServicesAccessor } from '../../../../core/server'; +import type { DataViewsServerPluginStart, DataViewsServerPluginStartDependencies } from '../types'; const indexPatternUpdateSchema = schema.object({ title: schema.maybe(schema.string()), @@ -37,7 +37,10 @@ const indexPatternUpdateSchema = schema.object({ export const registerUpdateIndexPatternRoute = ( router: IRouter, - getStartServices: StartServicesAccessor + getStartServices: StartServicesAccessor< + DataViewsServerPluginStartDependencies, + DataViewsServerPluginStart + > ) => { router.post( { @@ -62,8 +65,8 @@ export const registerUpdateIndexPatternRoute = ( handleErrors(async (ctx, req, res) => { const savedObjectsClient = ctx.core.savedObjects.client; const elasticsearchClient = ctx.core.elasticsearch.client.asCurrentUser; - const [, , { indexPatterns }] = await getStartServices(); - const indexPatternsService = await indexPatterns.indexPatternsServiceFactory( + const [, , { indexPatternsServiceFactory }] = await getStartServices(); + const indexPatternsService = await indexPatternsServiceFactory( savedObjectsClient, elasticsearchClient ); diff --git a/src/plugins/data/server/data_views/routes/util/handle_errors.ts b/src/plugins/data_views/server/routes/util/handle_errors.ts similarity index 100% rename from src/plugins/data/server/data_views/routes/util/handle_errors.ts rename to src/plugins/data_views/server/routes/util/handle_errors.ts diff --git a/src/plugins/data/server/data_views/routes/util/schemas.ts b/src/plugins/data_views/server/routes/util/schemas.ts similarity index 96% rename from src/plugins/data/server/data_views/routes/util/schemas.ts rename to src/plugins/data_views/server/routes/util/schemas.ts index 79ee1ffa1ab97..79f493f303801 100644 --- a/src/plugins/data/server/data_views/routes/util/schemas.ts +++ b/src/plugins/data_views/server/routes/util/schemas.ts @@ -7,7 +7,7 @@ */ import { schema, Type } from '@kbn/config-schema'; -import { RUNTIME_FIELD_TYPES, RuntimeType } from '../../../../common'; +import { RUNTIME_FIELD_TYPES, RuntimeType } from '../../../common'; export const serializedFieldFormatSchema = schema.object({ id: schema.maybe(schema.string()), diff --git a/src/plugins/data/server/saved_objects/index_patterns.ts b/src/plugins/data_views/server/saved_objects/data_views.ts similarity index 88% rename from src/plugins/data/server/saved_objects/index_patterns.ts rename to src/plugins/data_views/server/saved_objects/data_views.ts index a809f2ce73e1b..d340732873235 100644 --- a/src/plugins/data/server/saved_objects/index_patterns.ts +++ b/src/plugins/data_views/server/saved_objects/data_views.ts @@ -8,10 +8,10 @@ import type { SavedObjectsType } from 'kibana/server'; import { indexPatternSavedObjectTypeMigrations } from './index_pattern_migrations'; -import { INDEX_PATTERN_SAVED_OBJECT_TYPE } from '../../common'; +import { DATA_VIEW_SAVED_OBJECT_TYPE } from '../../common'; -export const indexPatternSavedObjectType: SavedObjectsType = { - name: INDEX_PATTERN_SAVED_OBJECT_TYPE, +export const dataViewSavedObjectType: SavedObjectsType = { + name: DATA_VIEW_SAVED_OBJECT_TYPE, hidden: false, namespaceType: 'single', management: { diff --git a/src/plugins/data/common/utils/index.ts b/src/plugins/data_views/server/saved_objects/index.ts similarity index 81% rename from src/plugins/data/common/utils/index.ts rename to src/plugins/data_views/server/saved_objects/index.ts index e07fd18594471..ff0f524ae961c 100644 --- a/src/plugins/data/common/utils/index.ts +++ b/src/plugins/data_views/server/saved_objects/index.ts @@ -6,5 +6,4 @@ * Side Public License, v 1. */ -/** @internal */ -export { shortenDottedString } from './shorten_dotted_string'; +export { dataViewSavedObjectType } from './data_views'; diff --git a/src/plugins/data/server/saved_objects/index_pattern_migrations.test.ts b/src/plugins/data_views/server/saved_objects/index_pattern_migrations.test.ts similarity index 100% rename from src/plugins/data/server/saved_objects/index_pattern_migrations.test.ts rename to src/plugins/data_views/server/saved_objects/index_pattern_migrations.test.ts diff --git a/src/plugins/data/server/saved_objects/index_pattern_migrations.ts b/src/plugins/data_views/server/saved_objects/index_pattern_migrations.ts similarity index 100% rename from src/plugins/data/server/saved_objects/index_pattern_migrations.ts rename to src/plugins/data_views/server/saved_objects/index_pattern_migrations.ts diff --git a/src/plugins/data/common/utils/shorten_dotted_string.ts b/src/plugins/data_views/server/saved_objects/migrations/to_v7_12_0.ts similarity index 57% rename from src/plugins/data/common/utils/shorten_dotted_string.ts rename to src/plugins/data_views/server/saved_objects/migrations/to_v7_12_0.ts index 53f7471913dc3..955028c0f9bf2 100644 --- a/src/plugins/data/common/utils/shorten_dotted_string.ts +++ b/src/plugins/data_views/server/saved_objects/migrations/to_v7_12_0.ts @@ -6,14 +6,12 @@ * Side Public License, v 1. */ -const DOT_PREFIX_RE = /(.).+?\./g; +import type { SavedObjectMigrationFn } from 'kibana/server'; /** - * Convert a dot.notated.string into a short - * version (d.n.string) - * - * @return {any} + * Drop the previous document's attributes, which report `averageDuration` incorrectly. + * @param doc */ -export function shortenDottedString(input: any) { - return typeof input !== 'string' ? input : input.replace(DOT_PREFIX_RE, '$1.'); -} +export const migrate712: SavedObjectMigrationFn = (doc) => { + return { ...doc, attributes: {} }; +}; diff --git a/src/plugins/data/server/data_views/saved_objects_client_wrapper.test.ts b/src/plugins/data_views/server/saved_objects_client_wrapper.test.ts similarity index 96% rename from src/plugins/data/server/data_views/saved_objects_client_wrapper.test.ts rename to src/plugins/data_views/server/saved_objects_client_wrapper.test.ts index bbe857894b3f0..b03532421ecab 100644 --- a/src/plugins/data/server/data_views/saved_objects_client_wrapper.test.ts +++ b/src/plugins/data_views/server/saved_objects_client_wrapper.test.ts @@ -9,7 +9,7 @@ import { SavedObjectsClientServerToCommon } from './saved_objects_client_wrapper'; import { SavedObjectsClientContract } from 'src/core/server'; -import { DataViewSavedObjectConflictError } from '../../common/data_views'; +import { DataViewSavedObjectConflictError } from '../common'; describe('SavedObjectsClientPublicToCommon', () => { const soClient = { resolve: jest.fn() } as unknown as SavedObjectsClientContract; diff --git a/src/plugins/data/server/data_views/saved_objects_client_wrapper.ts b/src/plugins/data_views/server/saved_objects_client_wrapper.ts similarity index 98% rename from src/plugins/data/server/data_views/saved_objects_client_wrapper.ts rename to src/plugins/data_views/server/saved_objects_client_wrapper.ts index b37648a3f038e..dc7163c405d4f 100644 --- a/src/plugins/data/server/data_views/saved_objects_client_wrapper.ts +++ b/src/plugins/data_views/server/saved_objects_client_wrapper.ts @@ -11,7 +11,7 @@ import { SavedObjectsClientCommon, SavedObjectsClientCommonFindArgs, DataViewSavedObjectConflictError, -} from '../../common/data_views'; +} from '../common'; export class SavedObjectsClientServerToCommon implements SavedObjectsClientCommon { private savedObjectClient: SavedObjectsClientContract; diff --git a/src/plugins/data_views/server/types.ts b/src/plugins/data_views/server/types.ts new file mode 100644 index 0000000000000..4a57a1d01b9c3 --- /dev/null +++ b/src/plugins/data_views/server/types.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Logger, SavedObjectsClientContract, ElasticsearchClient } from 'kibana/server'; +import { ExpressionsServerSetup } from 'src/plugins/expressions/server'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { DataViewsService } from '../common'; +import { FieldFormatsSetup, FieldFormatsStart } from '../../field_formats/server'; + +type ServiceFactory = ( + savedObjectsClient: SavedObjectsClientContract, + elasticsearchClient: ElasticsearchClient +) => Promise; +export interface DataViewsServerPluginStart { + dataViewsServiceFactory: ServiceFactory; + /** + * @deprecated Renamed to dataViewsServiceFactory + */ + indexPatternsServiceFactory: ServiceFactory; +} + +export interface IndexPatternsServiceSetupDeps { + expressions: ExpressionsServerSetup; + usageCollection?: UsageCollectionSetup; +} + +export interface IndexPatternsServiceStartDeps { + fieldFormats: FieldFormatsStart; + logger: Logger; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface DataViewsServerPluginSetup {} + +export type IndexPatternsServiceStart = DataViewsServerPluginStart; + +export interface DataViewsServerPluginSetupDependencies { + fieldFormats: FieldFormatsSetup; + expressions: ExpressionsServerSetup; + usageCollection?: UsageCollectionSetup; +} + +export interface DataViewsServerPluginStartDependencies { + fieldFormats: FieldFormatsStart; + logger: Logger; +} diff --git a/src/plugins/data/server/data_views/ui_settings_wrapper.ts b/src/plugins/data_views/server/ui_settings_wrapper.ts similarity index 95% rename from src/plugins/data/server/data_views/ui_settings_wrapper.ts rename to src/plugins/data_views/server/ui_settings_wrapper.ts index dce552205db2e..f42d43c1c24f4 100644 --- a/src/plugins/data/server/data_views/ui_settings_wrapper.ts +++ b/src/plugins/data_views/server/ui_settings_wrapper.ts @@ -7,7 +7,7 @@ */ import { IUiSettingsClient } from 'src/core/server'; -import { UiSettingsCommon } from '../../common'; +import { UiSettingsCommon } from '../common'; export class UiSettingsServerToCommon implements UiSettingsCommon { private uiSettings: IUiSettingsClient; diff --git a/src/plugins/data/server/data_views/utils.ts b/src/plugins/data_views/server/utils.ts similarity index 92% rename from src/plugins/data/server/data_views/utils.ts rename to src/plugins/data_views/server/utils.ts index 7f1a953c482d0..bb7d23f832233 100644 --- a/src/plugins/data/server/data_views/utils.ts +++ b/src/plugins/data_views/server/utils.ts @@ -9,10 +9,10 @@ import { SavedObjectsClientContract } from 'kibana/server'; import { IFieldType, - INDEX_PATTERN_SAVED_OBJECT_TYPE, + DATA_VIEW_SAVED_OBJECT_TYPE, IndexPatternAttributes, SavedObject, -} from '../../common'; +} from '../common'; export const getFieldByName = ( fieldName: string, @@ -29,7 +29,7 @@ export const findIndexPatternById = async ( index: string ): Promise | undefined> => { const savedObjectsResponse = await savedObjectsClient.find({ - type: INDEX_PATTERN_SAVED_OBJECT_TYPE, + type: DATA_VIEW_SAVED_OBJECT_TYPE, fields: ['fields'], search: `"${index}"`, searchFields: ['title'], diff --git a/src/plugins/data_views/tsconfig.json b/src/plugins/data_views/tsconfig.json new file mode 100644 index 0000000000000..f5c80ce30cce0 --- /dev/null +++ b/src/plugins/data_views/tsconfig.json @@ -0,0 +1,26 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*", + "config.ts", + "common/**/*.json", + "public/**/*.json", + "server/**/*.json" + ], + "references": [ + { "path": "../../core/tsconfig.json" }, + { "path": "../usage_collection/tsconfig.json" }, + { "path": "../kibana_utils/tsconfig.json" }, + { "path": "../kibana_react/tsconfig.json" }, + { "path": "../field_formats/tsconfig.json" }, + { "path": "../expressions/tsconfig.json" } + ] +} diff --git a/src/plugins/expressions/.eslintrc.json b/src/plugins/expressions/.eslintrc.json index 2aab6c2d9093b..d1dbca41acc81 100644 --- a/src/plugins/expressions/.eslintrc.json +++ b/src/plugins/expressions/.eslintrc.json @@ -1,5 +1,6 @@ { "rules": { - "@typescript-eslint/consistent-type-definitions": 0 + "@typescript-eslint/consistent-type-definitions": 0, + "@typescript-eslint/no-explicit-any": ["error", { "ignoreRestArgs": true }] } } diff --git a/src/plugins/expressions/common/ast/build_expression.ts b/src/plugins/expressions/common/ast/build_expression.ts index 0f4618b3e699c..6e84594022fdf 100644 --- a/src/plugins/expressions/common/ast/build_expression.ts +++ b/src/plugins/expressions/common/ast/build_expression.ts @@ -32,13 +32,13 @@ import { parse } from './parse'; * @param val Value you want to check. * @return boolean */ -export function isExpressionAstBuilder(val: any): val is ExpressionAstExpressionBuilder { - return val?.type === 'expression_builder'; +export function isExpressionAstBuilder(val: unknown): val is ExpressionAstExpressionBuilder { + return (val as Record | undefined)?.type === 'expression_builder'; } /** @internal */ -export function isExpressionAst(val: any): val is ExpressionAstExpression { - return val?.type === 'expression'; +export function isExpressionAst(val: unknown): val is ExpressionAstExpression { + return (val as Record | undefined)?.type === 'expression'; } export interface ExpressionAstExpressionBuilder { diff --git a/src/plugins/expressions/common/ast/types.ts b/src/plugins/expressions/common/ast/types.ts index e5a79a0a5ddaf..8f376ac547d26 100644 --- a/src/plugins/expressions/common/ast/types.ts +++ b/src/plugins/expressions/common/ast/types.ts @@ -64,7 +64,7 @@ export type ExpressionAstFunctionDebug = { /** * Raw error that was thrown by the function, if any. */ - rawError?: any | Error; + rawError?: any | Error; // eslint-disable-line @typescript-eslint/no-explicit-any /** * Time in milliseconds it took to execute the function. Duration can be diff --git a/src/plugins/expressions/common/execution/execution.abortion.test.ts b/src/plugins/expressions/common/execution/execution.abortion.test.ts index 798558ba7ffb6..fca030fb9a080 100644 --- a/src/plugins/expressions/common/execution/execution.abortion.test.ts +++ b/src/plugins/expressions/common/execution/execution.abortion.test.ts @@ -90,7 +90,7 @@ describe('Execution abortion tests', () => { const completed = jest.fn(); const aborted = jest.fn(); - const defer: ExpressionFunctionDefinition<'defer', any, { time: number }, any> = { + const defer: ExpressionFunctionDefinition<'defer', unknown, { time: number }, unknown> = { name: 'defer', args: { time: { diff --git a/src/plugins/expressions/common/execution/execution.test.ts b/src/plugins/expressions/common/execution/execution.test.ts index c478977f60764..9b889c62e9ff5 100644 --- a/src/plugins/expressions/common/execution/execution.test.ts +++ b/src/plugins/expressions/common/execution/execution.test.ts @@ -17,7 +17,7 @@ import { ExecutionContract } from './execution_contract'; beforeAll(() => { if (typeof performance === 'undefined') { - (global as any).performance = { now: Date.now }; + global.performance = { now: Date.now } as typeof performance; } }); @@ -41,7 +41,7 @@ const createExecution = ( const run = async ( expression: string = 'foo bar=123', context?: Record, - input: any = null + input: unknown = null ) => { const execution = createExecution(expression, context); execution.start(input); @@ -262,45 +262,45 @@ describe('Execution', () => { describe('execution context', () => { test('context.variables is an object', async () => { - const { result } = (await run('introspectContext key="variables"')) as any; + const { result } = await run('introspectContext key="variables"'); expect(result).toHaveProperty('result', expect.any(Object)); }); test('context.types is an object', async () => { - const { result } = (await run('introspectContext key="types"')) as any; + const { result } = await run('introspectContext key="types"'); expect(result).toHaveProperty('result', expect.any(Object)); }); test('context.abortSignal is an object', async () => { - const { result } = (await run('introspectContext key="abortSignal"')) as any; + const { result } = await run('introspectContext key="abortSignal"'); expect(result).toHaveProperty('result', expect.any(Object)); }); test('context.inspectorAdapters is an object', async () => { - const { result } = (await run('introspectContext key="inspectorAdapters"')) as any; + const { result } = await run('introspectContext key="inspectorAdapters"'); expect(result).toHaveProperty('result', expect.any(Object)); }); test('context.getKibanaRequest is a function if provided', async () => { - const { result } = (await run('introspectContext key="getKibanaRequest"', { + const { result } = await run('introspectContext key="getKibanaRequest"', { kibanaRequest: {}, - })) as any; + }); expect(result).toHaveProperty('result', expect.any(Function)); }); test('context.getKibanaRequest is undefined if not provided', async () => { - const { result } = (await run('introspectContext key="getKibanaRequest"')) as any; + const { result } = await run('introspectContext key="getKibanaRequest"'); expect(result).toHaveProperty('result', undefined); }); test('unknown context key is undefined', async () => { - const { result } = (await run('introspectContext key="foo"')) as any; + const { result } = await run('introspectContext key="foo"'); expect(result).toHaveProperty('result', undefined); }); @@ -314,7 +314,7 @@ describe('Execution', () => { describe('inspector adapters', () => { test('by default, "tables" and "requests" inspector adapters are available', async () => { - const { result } = (await run('introspectContext key="inspectorAdapters"')) as any; + const { result } = await run('introspectContext key="inspectorAdapters"'); expect(result).toHaveProperty( 'result', expect.objectContaining({ @@ -326,9 +326,9 @@ describe('Execution', () => { test('can set custom inspector adapters', async () => { const inspectorAdapters = {}; - const { result } = (await run('introspectContext key="inspectorAdapters"', { + const { result } = await run('introspectContext key="inspectorAdapters"', { inspectorAdapters, - })) as any; + }); expect(result).toHaveProperty('result', inspectorAdapters); }); @@ -351,7 +351,7 @@ describe('Execution', () => { describe('expression abortion', () => { test('context has abortSignal object', async () => { - const { result } = (await run('introspectContext key="abortSignal"')) as any; + const { result } = await run('introspectContext key="abortSignal"'); expect(result).toHaveProperty('result.aborted', false); }); @@ -400,7 +400,7 @@ describe('Execution', () => { testScheduler.run(({ cold, expectObservable }) => { const arg = cold(' -a-b-c|', { a: 1, b: 2, c: 3 }); const expected = ' -a-b-c|'; - const observable: ExpressionFunctionDefinition<'observable', any, {}, any> = { + const observable: ExpressionFunctionDefinition<'observable', unknown, {}, unknown> = { name: 'observable', args: {}, help: '', @@ -468,7 +468,7 @@ describe('Execution', () => { }); test('does not execute remaining functions in pipeline', async () => { - const spy: ExpressionFunctionDefinition<'spy', any, {}, any> = { + const spy: ExpressionFunctionDefinition<'spy', unknown, {}, unknown> = { name: 'spy', args: {}, help: '', @@ -621,7 +621,12 @@ describe('Execution', () => { help: '', fn: () => arg2, }; - const max: ExpressionFunctionDefinition<'max', any, { val1: number; val2: number }, any> = { + const max: ExpressionFunctionDefinition< + 'max', + unknown, + { val1: number; val2: number }, + unknown + > = { name: 'max', args: { val1: { help: '', types: ['number'] }, @@ -679,7 +684,12 @@ describe('Execution', () => { describe('when arguments are missing', () => { it('when required argument is missing and has not alias, returns error', async () => { - const requiredArg: ExpressionFunctionDefinition<'requiredArg', any, { arg: any }, any> = { + const requiredArg: ExpressionFunctionDefinition< + 'requiredArg', + unknown, + { arg: unknown }, + unknown + > = { name: 'requiredArg', args: { arg: { diff --git a/src/plugins/expressions/common/execution/execution.ts b/src/plugins/expressions/common/execution/execution.ts index 0bb12951202a5..54a4800ec7c34 100644 --- a/src/plugins/expressions/common/execution/execution.ts +++ b/src/plugins/expressions/common/execution/execution.ts @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import { isPromise } from '@kbn/std'; +import { ObservableLike, UnwrapObservable, UnwrapPromiseOrReturn } from '@kbn/utility-types'; import { keys, last, mapValues, reduce, zipObject } from 'lodash'; import { combineLatest, @@ -44,6 +45,18 @@ import { ExecutionContract } from './execution_contract'; import { ExpressionExecutionParams } from '../service'; import { createDefaultInspectorAdapters } from '../util/create_default_inspector_adapters'; +type UnwrapReturnType unknown> = + ReturnType extends ObservableLike + ? UnwrapObservable> + : UnwrapPromiseOrReturn>; + +// type ArgumentsOf = Function extends ExpressionFunction< +// unknown, +// infer Arguments +// > +// ? Arguments +// : never; + /** * The result returned after an expression function execution. */ @@ -83,7 +96,7 @@ const createAbortErrorValue = () => }); export interface ExecutionParams { - executor: Executor; + executor: Executor; ast?: ExpressionAstExpression; expression?: string; params: ExpressionExecutionParams; @@ -107,7 +120,7 @@ export class Execution< * N.B. It is initialized to `null` rather than `undefined` for legacy reasons, * because in legacy interpreter it was set to `null` by default. */ - public input: Input = null as any; + public input = null as unknown as Input; /** * Input of the started execution. @@ -186,13 +199,13 @@ export class Execution< }); const inspectorAdapters = - execution.params.inspectorAdapters || createDefaultInspectorAdapters(); + (execution.params.inspectorAdapters as InspectorAdapters) || createDefaultInspectorAdapters(); this.context = { getSearchContext: () => this.execution.params.searchContext || {}, getSearchSessionId: () => execution.params.searchSessionId, getKibanaRequest: execution.params.kibanaRequest - ? () => execution.params.kibanaRequest + ? () => execution.params.kibanaRequest! : undefined, variables: execution.params.variables || {}, types: executor.getTypes(), @@ -201,14 +214,14 @@ export class Execution< logDatatable: (name: string, datatable: Datatable) => { inspectorAdapters.tables[name] = datatable; }, - isSyncColorsEnabled: () => execution.params.syncColors, - ...(execution.params as any).extraContext, + isSyncColorsEnabled: () => execution.params.syncColors!, + ...execution.params.extraContext, getExecutionContext: () => execution.params.executionContext, }; this.result = this.input$.pipe( switchMap((input) => - this.race(this.invokeChain(this.state.get().ast.chain, input)).pipe( + this.race(this.invokeChain(this.state.get().ast.chain, input)).pipe( (source) => new Observable>((subscriber) => { let latest: ExecutionResult | undefined; @@ -270,8 +283,8 @@ export class Execution< * N.B. `input` is initialized to `null` rather than `undefined` for legacy reasons, * because in legacy interpreter it was set to `null` by default. */ - public start( - input: Input = null as any, + start( + input = null as unknown as Input, isSubExpression?: boolean ): Observable> { if (this.hasStarted) throw new Error('Execution already started.'); @@ -294,7 +307,10 @@ export class Execution< return this.result; } - invokeChain(chainArr: ExpressionAstFunction[], input: unknown): Observable { + invokeChain( + chainArr: ExpressionAstFunction[], + input: unknown + ): Observable { return of(input).pipe( ...(chainArr.map((link) => switchMap((currentInput) => { @@ -364,19 +380,24 @@ export class Execution< }) ) as Parameters['pipe']>), catchError((error) => of(error)) - ); + ) as Observable; } - invokeFunction( - fn: ExpressionFunction, + invokeFunction( + fn: Fn, input: unknown, args: Record - ): Observable { + ): Observable> { return of(input).pipe( map((currentInput) => this.cast(currentInput, fn.inputTypes)), switchMap((normalizedInput) => this.race(of(fn.fn(normalizedInput, args, this.context)))), - switchMap((fnResult: any) => - isObservable(fnResult) ? fnResult : from(isPromise(fnResult) ? fnResult : [fnResult]) + switchMap( + (fnResult) => + (isObservable(fnResult) + ? fnResult + : from(isPromise(fnResult) ? fnResult : [fnResult])) as Observable< + UnwrapReturnType + > ), map((output) => { // Validate that the function returned the type it said it would. @@ -405,39 +426,49 @@ export class Execution< ); } - public cast(value: any, toTypeNames?: string[]) { + public cast(value: unknown, toTypeNames?: string[]): Type { // If you don't give us anything to cast to, you'll get your input back - if (!toTypeNames || toTypeNames.length === 0) return value; + if (!toTypeNames?.length) { + return value as Type; + } // No need to cast if node is already one of the valid types const fromTypeName = getType(value); - if (toTypeNames.includes(fromTypeName)) return value; + if (toTypeNames.includes(fromTypeName)) { + return value as Type; + } const { types } = this.state.get(); const fromTypeDef = types[fromTypeName]; for (const toTypeName of toTypeNames) { // First check if the current type can cast to this type - if (fromTypeDef && fromTypeDef.castsTo(toTypeName)) { + if (fromTypeDef?.castsTo(toTypeName)) { return fromTypeDef.to(value, toTypeName, types); } // If that isn't possible, check if this type can cast from the current type const toTypeDef = types[toTypeName]; - if (toTypeDef && toTypeDef.castsFrom(fromTypeName)) return toTypeDef.from(value, types); + if (toTypeDef?.castsFrom(fromTypeName)) { + return toTypeDef.from(value, types); + } } throw new Error(`Can not cast '${fromTypeName}' to any of '${toTypeNames.join(', ')}'`); } // Processes the multi-valued AST argument values into arguments that can be passed to the function - resolveArgs(fnDef: ExpressionFunction, input: unknown, argAsts: any): Observable { + resolveArgs( + fnDef: Fn, + input: unknown, + argAsts: Record + ): Observable> { return defer(() => { const { args: argDefs } = fnDef; // Use the non-alias name from the argument definition const dealiasedArgAsts = reduce( - argAsts as Record, + argAsts, (acc, argAst, argName) => { const argDef = getByAlias(argDefs, argName); if (!argDef) { @@ -452,7 +483,7 @@ export class Execution< // Check for missing required arguments. for (const { aliases, default: argDefault, name, required } of Object.values(argDefs)) { if (!(name in dealiasedArgAsts) && typeof argDefault !== 'undefined') { - dealiasedArgAsts[name] = [parse(argDefault, 'argument')]; + dealiasedArgAsts[name] = [parse(argDefault as string, 'argument')]; } if (!required || name in dealiasedArgAsts) { @@ -490,7 +521,7 @@ export class Execution< const argNames = keys(resolveArgFns); if (!argNames.length) { - return from([[]]); + return from([{}]); } const resolvedArgValuesObservable = combineLatest( @@ -523,7 +554,7 @@ export class Execution< }); } - public interpret(ast: ExpressionAstNode, input: T): Observable> { + interpret(ast: ExpressionAstNode, input: T): Observable> { switch (getType(ast)) { case 'expression': const execution = this.execution.executor.createExecution( diff --git a/src/plugins/expressions/common/execution/types.ts b/src/plugins/expressions/common/execution/types.ts index 06eac98feba67..9264891b2e0b8 100644 --- a/src/plugins/expressions/common/execution/types.ts +++ b/src/plugins/expressions/common/execution/types.ts @@ -11,7 +11,7 @@ import type { SerializableRecord } from '@kbn/utility-types'; import type { KibanaRequest } from 'src/core/server'; import type { KibanaExecutionContext } from 'src/core/public'; -import { ExpressionType } from '../expression_types'; +import { Datatable, ExpressionType } from '../expression_types'; import { Adapters, RequestAdapter } from '../../../inspector/common'; import { TablesAdapter } from '../util/tables_adapter'; @@ -69,6 +69,11 @@ export interface ExecutionContext< * Contains the meta-data about the source of the expression. */ getExecutionContext: () => KibanaExecutionContext | undefined; + + /** + * Logs datatable. + */ + logDatatable?(name: string, datatable: Datatable): void; } /** diff --git a/src/plugins/expressions/common/executor/container.ts b/src/plugins/expressions/common/executor/container.ts index 9d3796ac64f45..c8e24974126ff 100644 --- a/src/plugins/expressions/common/executor/container.ts +++ b/src/plugins/expressions/common/executor/container.ts @@ -19,7 +19,7 @@ export interface ExecutorState = Record< context: Context; } -export const defaultState: ExecutorState = { +export const defaultState: ExecutorState = { functions: {}, types: {}, context: {}, @@ -61,7 +61,7 @@ export type ExecutorContainer = Record = Record >( - state: ExecutorState = defaultState + state = defaultState as ExecutorState ): ExecutorContainer => { const container = createStateContainer< ExecutorState, diff --git a/src/plugins/expressions/common/executor/executor.execution.test.ts b/src/plugins/expressions/common/executor/executor.execution.test.ts index 38022c0f7dc4b..ad7e6e6a014c1 100644 --- a/src/plugins/expressions/common/executor/executor.execution.test.ts +++ b/src/plugins/expressions/common/executor/executor.execution.test.ts @@ -8,20 +8,14 @@ import { Executor } from './executor'; import { parseExpression } from '../ast'; +import { Execution } from '../execution/execution'; -// eslint-disable-next-line -const { __getArgs } = require('../execution/execution'); +jest.mock('../execution/execution', () => ({ + Execution: jest.fn(), +})); -jest.mock('../execution/execution', () => { - const mockedModule = { - args: undefined, - __getArgs: () => mockedModule.args, - Execution: function ExecutionMock(...args: any) { - mockedModule.args = args; - }, - }; - - return mockedModule; +beforeEach(() => { + jest.clearAllMocks(); }); describe('Executor mocked execution tests', () => { @@ -31,7 +25,9 @@ describe('Executor mocked execution tests', () => { const executor = new Executor(); executor.createExecution('foo bar="baz"'); - expect(__getArgs()[0].expression).toBe('foo bar="baz"'); + expect(Execution).toHaveBeenCalledWith( + expect.objectContaining({ expression: 'foo bar="baz"' }) + ); }); }); @@ -41,7 +37,9 @@ describe('Executor mocked execution tests', () => { const ast = parseExpression('foo bar="baz"'); executor.createExecution(ast); - expect(__getArgs()[0].expression).toBe(undefined); + expect(Execution).toHaveBeenCalledWith( + expect.not.objectContaining({ expression: expect.anything() }) + ); }); }); }); diff --git a/src/plugins/expressions/common/executor/executor.test.ts b/src/plugins/expressions/common/executor/executor.test.ts index 4a3d6045a7b4a..60f0f0da4e152 100644 --- a/src/plugins/expressions/common/executor/executor.test.ts +++ b/src/plugins/expressions/common/executor/executor.test.ts @@ -145,7 +145,7 @@ describe('Executor', () => { executor.extendContext({ foo }); const execution = executor.createExecution('foo bar="baz"'); - expect((execution.context as any).foo).toBe(foo); + expect(execution.context).toHaveProperty('foo', foo); }); }); }); @@ -175,10 +175,10 @@ describe('Executor', () => { migrations: { '7.10.0': ((state: ExpressionAstFunction, version: string): ExpressionAstFunction => { return migrateFn(state, version); - }) as any as MigrateFunction, + }) as unknown as MigrateFunction, '7.10.1': ((state: ExpressionAstFunction, version: string): ExpressionAstFunction => { return migrateFn(state, version); - }) as any as MigrateFunction, + }) as unknown as MigrateFunction, }, fn: jest.fn(), }; diff --git a/src/plugins/expressions/common/executor/executor.ts b/src/plugins/expressions/common/executor/executor.ts index ce411ea94eafe..f4913c4953bac 100644 --- a/src/plugins/expressions/common/executor/executor.ts +++ b/src/plugins/expressions/common/executor/executor.ts @@ -40,7 +40,7 @@ export interface ExpressionExecOptions { } export class TypesRegistry implements IRegistry { - constructor(private readonly executor: Executor) {} + constructor(private readonly executor: Executor) {} public register( typeDefinition: AnyExpressionTypeDefinition | (() => AnyExpressionTypeDefinition) @@ -62,7 +62,7 @@ export class TypesRegistry implements IRegistry { } export class FunctionsRegistry implements IRegistry { - constructor(private readonly executor: Executor) {} + constructor(private readonly executor: Executor) {} public register( functionDefinition: AnyExpressionFunctionDefinition | (() => AnyExpressionFunctionDefinition) @@ -100,12 +100,12 @@ export class Executor = Record; @@ -207,15 +207,15 @@ export class Executor = Record { - const executionParams: ExecutionParams = { + const executionParams = { executor: this, params: { ...params, // for canvas we are passing this in, // canvas should be refactored to not pass any extra context in extraContext: this.context, - } as any, - }; + }, + } as ExecutionParams; if (typeof ast === 'string') executionParams.expression = ast; else executionParams.ast = ast; @@ -273,7 +273,7 @@ export class Executor = Record) { + public telemetry(ast: ExpressionAstExpression, telemetryData: Record) { this.walkAst(cloneDeep(ast), (fn, link) => { telemetryData = fn.telemetry(link.arguments, telemetryData); }); diff --git a/src/plugins/expressions/common/expression_functions/arguments.ts b/src/plugins/expressions/common/expression_functions/arguments.ts index 67f83cabc6450..7e39f019f00c2 100644 --- a/src/plugins/expressions/common/expression_functions/arguments.ts +++ b/src/plugins/expressions/common/expression_functions/arguments.ts @@ -24,9 +24,9 @@ export type ArgumentType = * representation of the type. */ // prettier-ignore -type ArrayTypeToArgumentString = - T extends Array ? TypeString : - T extends null ? 'null' : +type ArrayTypeToArgumentString = + T extends Array ? TypeString : + T extends null ? 'null' : never; /** @@ -34,9 +34,9 @@ type ArrayTypeToArgumentString = * string-based representation of the return type. */ // prettier-ignore -type UnresolvedTypeToArgumentString = - T extends (...args: any) => infer ElementType ? TypeString : - T extends null ? 'null' : +type UnresolvedTypeToArgumentString = + T extends (...args: any[]) => infer ElementType ? TypeString : + T extends null ? 'null' : never; /** @@ -44,10 +44,10 @@ type UnresolvedTypeToArgumentString = * string-based representation of the return type. */ // prettier-ignore -type UnresolvedArrayTypeToArgumentString = - T extends Array<(...args: any) => infer ElementType> ? TypeString : - T extends (...args: any) => infer ElementType ? ArrayTypeToArgumentString : - T extends null ? 'null' : +type UnresolvedArrayTypeToArgumentString = + T extends Array<(...args: any[]) => infer ElementType> ? TypeString : + T extends (...args: any[]) => infer ElementType ? ArrayTypeToArgumentString : + T extends null ? 'null' : never; /** A type containing properties common to all Function Arguments. */ diff --git a/src/plugins/expressions/common/expression_functions/expression_function.ts b/src/plugins/expressions/common/expression_functions/expression_function.ts index 05a5dbb638c01..8154534b32ab1 100644 --- a/src/plugins/expressions/common/expression_functions/expression_function.ts +++ b/src/plugins/expressions/common/expression_functions/expression_function.ts @@ -36,7 +36,11 @@ export class ExpressionFunction implements PersistableState, handlers: object) => ExpressionValue; + fn: ( + input: ExpressionValue, + params: Record, + handlers: object + ) => ExpressionValue; /** * A short help text. @@ -56,8 +60,8 @@ export class ExpressionFunction implements PersistableState - ) => Record; + telemetryData: Record + ) => Record; extract: (state: ExpressionAstFunction['arguments']) => { state: ExpressionAstFunction['arguments']; references: SavedObjectReference[]; @@ -100,13 +104,12 @@ export class ExpressionFunction implements PersistableState { // If you don't tell us input types, we'll assume you don't care what you get. - if (!this.inputTypes) return true; - return this.inputTypes.indexOf(type) > -1; + return this.inputTypes?.includes(type) ?? true; }; } diff --git a/src/plugins/expressions/common/expression_functions/expression_function_parameter.ts b/src/plugins/expressions/common/expression_functions/expression_function_parameter.ts index bfc45d65f1c93..9942c9af7ff71 100644 --- a/src/plugins/expressions/common/expression_functions/expression_function_parameter.ts +++ b/src/plugins/expressions/common/expression_functions/expression_function_parameter.ts @@ -6,20 +6,21 @@ * Side Public License, v 1. */ +import { KnownTypeToString } from '../types'; import { ArgumentType } from './arguments'; -export class ExpressionFunctionParameter { +export class ExpressionFunctionParameter { name: string; required: boolean; help: string; - types: string[]; - default: any; + types: ArgumentType['types']; + default?: ArgumentType['default']; aliases: string[]; multi: boolean; resolve: boolean; - options: any[]; + options: T[]; - constructor(name: string, arg: ArgumentType) { + constructor(name: string, arg: ArgumentType) { const { required, help, types, aliases, multi, resolve, options } = arg; if (name === '_') { @@ -38,7 +39,6 @@ export class ExpressionFunctionParameter { } accepts(type: string) { - if (!this.types.length) return true; - return this.types.indexOf(type) > -1; + return !this.types?.length || this.types.includes(type as KnownTypeToString); } } diff --git a/src/plugins/expressions/common/expression_functions/expression_function_parameters.test.ts b/src/plugins/expressions/common/expression_functions/expression_function_parameters.test.ts index 6e47634f5aac2..28ef5243e0fed 100644 --- a/src/plugins/expressions/common/expression_functions/expression_function_parameters.test.ts +++ b/src/plugins/expressions/common/expression_functions/expression_function_parameters.test.ts @@ -21,7 +21,7 @@ describe('ExpressionFunctionParameter', () => { const param = new ExpressionFunctionParameter('foo', { help: 'bar', types: ['baz', 'quux'], - }); + } as ConstructorParameters[1]); expect(param.accepts('baz')).toBe(true); expect(param.accepts('quux')).toBe(true); diff --git a/src/plugins/expressions/common/expression_functions/specs/create_table.ts b/src/plugins/expressions/common/expression_functions/specs/create_table.ts index 5174b258a4d9b..0ce427e817e24 100644 --- a/src/plugins/expressions/common/expression_functions/specs/create_table.ts +++ b/src/plugins/expressions/common/expression_functions/specs/create_table.ts @@ -11,9 +11,9 @@ import { ExpressionFunctionDefinition } from '../types'; import { Datatable, DatatableColumn } from '../../expression_types'; export interface CreateTableArguments { - ids: string[]; - names: string[] | null; - rowCount: number; + ids?: string[]; + names?: string[] | null; + rowCount?: number; } export const createTable: ExpressionFunctionDefinition< diff --git a/src/plugins/expressions/common/expression_functions/specs/font.ts b/src/plugins/expressions/common/expression_functions/specs/font.ts index fa8fc8d387af0..3d189a68119d5 100644 --- a/src/plugins/expressions/common/expression_functions/specs/font.ts +++ b/src/plugins/expressions/common/expression_functions/specs/font.ts @@ -30,7 +30,7 @@ const inlineStyle = (obj: Record) => { return styles.join(';'); }; -interface Arguments { +export interface FontArguments { align?: TextAlignment; color?: string; family?: FontFamily; @@ -41,7 +41,12 @@ interface Arguments { weight?: FontWeight; } -export type ExpressionFunctionFont = ExpressionFunctionDefinition<'font', null, Arguments, Style>; +export type ExpressionFunctionFont = ExpressionFunctionDefinition< + 'font', + null, + FontArguments, + Style +>; export const font: ExpressionFunctionFont = { name: 'font', diff --git a/src/plugins/expressions/common/expression_functions/specs/map_column.ts b/src/plugins/expressions/common/expression_functions/specs/map_column.ts index 23aeee6f9581b..7b2266637bfb5 100644 --- a/src/plugins/expressions/common/expression_functions/specs/map_column.ts +++ b/src/plugins/expressions/common/expression_functions/specs/map_column.ts @@ -110,7 +110,7 @@ export const mapColumn: ExpressionFunctionDefinition< map((rows) => { let type: DatatableColumnType = 'null'; for (const row of rows) { - const rowType = getType(row[id]); + const rowType = getType(row[id]) as DatatableColumnType; if (rowType !== 'null') { type = rowType; break; diff --git a/src/plugins/expressions/common/expression_functions/specs/math.ts b/src/plugins/expressions/common/expression_functions/specs/math.ts index 92a10976428a3..f843f53e4dd88 100644 --- a/src/plugins/expressions/common/expression_functions/specs/math.ts +++ b/src/plugins/expressions/common/expression_functions/specs/math.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { map, zipObject } from 'lodash'; +import { map, zipObject, isString } from 'lodash'; import { i18n } from '@kbn/i18n'; import { evaluate } from '@kbn/tinymath'; import { ExpressionFunctionDefinition } from '../types'; @@ -23,19 +23,18 @@ const TINYMATH = '`TinyMath`'; const TINYMATH_URL = 'https://www.elastic.co/guide/en/kibana/current/canvas-tinymath-functions.html'; -const isString = (val: any): boolean => typeof val === 'string'; - function pivotObjectArray< - RowType extends { [key: string]: any }, - ReturnColumns extends string | number | symbol = keyof RowType ->(rows: RowType[], columns?: string[]): Record { + RowType extends { [key: string]: unknown }, + ReturnColumns extends keyof RowType & string +>(rows: RowType[], columns?: ReturnColumns[]) { const columnNames = columns || Object.keys(rows[0]); if (!columnNames.every(isString)) { throw new Error('Columns should be an array of strings'); } const columnValues = map(columnNames, (name) => map(rows, name)); - return zipObject(columnNames, columnValues); + + return zipObject(columnNames, columnValues) as { [K in ReturnColumns]: Array }; } export const errors = { diff --git a/src/plugins/expressions/common/expression_functions/specs/math_column.ts b/src/plugins/expressions/common/expression_functions/specs/math_column.ts index c59016cd260ab..a2a79ef3f0286 100644 --- a/src/plugins/expressions/common/expression_functions/specs/math_column.ts +++ b/src/plugins/expressions/common/expression_functions/specs/math_column.ts @@ -107,7 +107,7 @@ export const mathColumn: ExpressionFunctionDefinition< let type: DatatableColumnType = 'null'; if (newRows.length) { for (const row of newRows) { - const rowType = getType(row[args.id]); + const rowType = getType(row[args.id]) as DatatableColumnType; if (rowType !== 'null') { type = rowType; break; diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/font.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/font.test.ts index 03b610126660f..e095f4e1bec69 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/font.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/font.test.ts @@ -7,7 +7,8 @@ */ import { openSans } from '../../../fonts'; -import { font } from '../font'; +import { FontWeight, TextAlignment } from '../../../types'; +import { font, FontArguments } from '../font'; import { functionWrapper } from './utils'; describe('font', () => { @@ -22,7 +23,7 @@ describe('font', () => { size: 14, underline: false, weight: 'normal', - }; + } as unknown as FontArguments; describe('default output', () => { const result = fn(null, args); @@ -63,7 +64,7 @@ describe('font', () => { describe('family', () => { it('sets font family', () => { - const result = fn(null, { ...args, family: 'Optima, serif' }); + const result = fn(null, { ...args, family: 'Optima, serif' } as unknown as FontArguments); expect(result.spec.fontFamily).toBe('Optima, serif'); expect(result.css).toContain('font-family:Optima, serif'); }); @@ -79,29 +80,29 @@ describe('font', () => { describe('weight', () => { it('sets font weight', () => { - let result = fn(null, { ...args, weight: 'normal' }); + let result = fn(null, { ...args, weight: FontWeight.NORMAL }); expect(result.spec.fontWeight).toBe('normal'); expect(result.css).toContain('font-weight:normal'); - result = fn(null, { ...args, weight: 'bold' }); + result = fn(null, { ...args, weight: FontWeight.BOLD }); expect(result.spec.fontWeight).toBe('bold'); expect(result.css).toContain('font-weight:bold'); - result = fn(null, { ...args, weight: 'bolder' }); + result = fn(null, { ...args, weight: FontWeight.BOLDER }); expect(result.spec.fontWeight).toBe('bolder'); expect(result.css).toContain('font-weight:bolder'); - result = fn(null, { ...args, weight: 'lighter' }); + result = fn(null, { ...args, weight: FontWeight.LIGHTER }); expect(result.spec.fontWeight).toBe('lighter'); expect(result.css).toContain('font-weight:lighter'); - result = fn(null, { ...args, weight: '400' }); + result = fn(null, { ...args, weight: FontWeight.FOUR }); expect(result.spec.fontWeight).toBe('400'); expect(result.css).toContain('font-weight:400'); }); it('throws when provided an invalid weight', () => { - expect(() => fn(null, { ...args, weight: 'foo' })).toThrow(); + expect(() => fn(null, { ...args, weight: 'foo' as FontWeight })).toThrow(); }); }); @@ -131,25 +132,25 @@ describe('font', () => { describe('align', () => { it('sets text alignment', () => { - let result = fn(null, { ...args, align: 'left' }); + let result = fn(null, { ...args, align: TextAlignment.LEFT }); expect(result.spec.textAlign).toBe('left'); expect(result.css).toContain('text-align:left'); - result = fn(null, { ...args, align: 'center' }); + result = fn(null, { ...args, align: TextAlignment.CENTER }); expect(result.spec.textAlign).toBe('center'); expect(result.css).toContain('text-align:center'); - result = fn(null, { ...args, align: 'right' }); + result = fn(null, { ...args, align: TextAlignment.RIGHT }); expect(result.spec.textAlign).toBe('right'); expect(result.css).toContain('text-align:right'); - result = fn(null, { ...args, align: 'justify' }); + result = fn(null, { ...args, align: TextAlignment.JUSTIFY }); expect(result.spec.textAlign).toBe('justify'); expect(result.css).toContain('text-align:justify'); }); it('throws when provided an invalid alignment', () => { - expect(() => fn(null, { ...args, align: 'foo' })).toThrow(); + expect(() => fn(null, { ...args, align: 'foo' as TextAlignment })).toThrow(); }); }); }); diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/math.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/math.test.ts index 6da00061244da..3761fe0a4f909 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/math.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/math.test.ts @@ -6,19 +6,19 @@ * Side Public License, v 1. */ -import { errors, math } from '../math'; +import { errors, math, MathArguments, MathInput } from '../math'; import { emptyTable, functionWrapper, testTable } from './utils'; describe('math', () => { - const fn = functionWrapper(math); + const fn = functionWrapper(math); it('evaluates math expressions without reference to context', () => { - expect(fn(null, { expression: '10.5345' })).toBe(10.5345); - expect(fn(null, { expression: '123 + 456' })).toBe(579); - expect(fn(null, { expression: '100 - 46' })).toBe(54); + expect(fn(null as unknown as MathInput, { expression: '10.5345' })).toBe(10.5345); + expect(fn(null as unknown as MathInput, { expression: '123 + 456' })).toBe(579); + expect(fn(null as unknown as MathInput, { expression: '100 - 46' })).toBe(54); expect(fn(1, { expression: '100 / 5' })).toBe(20); - expect(fn('foo', { expression: '100 / 5' })).toBe(20); - expect(fn(true, { expression: '100 / 5' })).toBe(20); + expect(fn('foo' as unknown as MathInput, { expression: '100 / 5' })).toBe(20); + expect(fn(true as unknown as MathInput, { expression: '100 / 5' })).toBe(20); expect(fn(testTable, { expression: '100 * 5' })).toBe(500); expect(fn(emptyTable, { expression: '100 * 5' })).toBe(500); }); @@ -54,7 +54,7 @@ describe('math', () => { describe('args', () => { describe('expression', () => { it('sets the math expression to be evaluted', () => { - expect(fn(null, { expression: '10' })).toBe(10); + expect(fn(null as unknown as MathInput, { expression: '10' })).toBe(10); expect(fn(23.23, { expression: 'floor(value)' })).toBe(23); expect(fn(testTable, { expression: 'count(price)' })).toBe(9); expect(fn(testTable, { expression: 'count(name)' })).toBe(9); @@ -99,11 +99,11 @@ describe('math', () => { it('throws when missing expression', () => { expect(() => fn(testTable)).toThrow(new RegExp(errors.emptyExpression().message)); - expect(() => fn(testTable, { expession: '' })).toThrow( + expect(() => fn(testTable, { expession: '' } as unknown as MathArguments)).toThrow( new RegExp(errors.emptyExpression().message) ); - expect(() => fn(testTable, { expession: ' ' })).toThrow( + expect(() => fn(testTable, { expession: ' ' } as unknown as MathArguments)).toThrow( new RegExp(errors.emptyExpression().message) ); }); diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/theme.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/theme.test.ts index 0733b1c77bf40..3f535b7fb7aca 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/theme.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/theme.test.ts @@ -26,14 +26,14 @@ describe('expression_functions', () => { }; context = { - getSearchContext: () => ({} as any), + getSearchContext: () => ({}), getSearchSessionId: () => undefined, getExecutionContext: () => undefined, types: {}, variables: { theme: themeProps }, - abortSignal: {} as any, - inspectorAdapters: {} as any, - }; + abortSignal: {}, + inspectorAdapters: {}, + } as unknown as typeof context; }); it('returns the selected variable', () => { diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/ui_setting.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/ui_setting.test.ts index 6b3a458aa7e57..053f97ffc8fb0 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/ui_setting.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/ui_setting.test.ts @@ -10,13 +10,15 @@ jest.mock('../../../../common'); import { IUiSettingsClient } from 'src/core/public'; import { getUiSettingFn } from '../ui_setting'; +import { functionWrapper } from './utils'; describe('uiSetting', () => { describe('fn', () => { let getStartDependencies: jest.MockedFunction< Parameters[0]['getStartDependencies'] >; - let uiSetting: ReturnType; + const uiSettingWrapper = () => functionWrapper(getUiSettingFn({ getStartDependencies })); + let uiSetting: ReturnType; let uiSettings: jest.Mocked; beforeEach(() => { @@ -27,13 +29,13 @@ describe('uiSetting', () => { uiSettings, })) as unknown as typeof getStartDependencies; - uiSetting = getUiSettingFn({ getStartDependencies }); + uiSetting = uiSettingWrapper(); }); it('should return a value', () => { uiSettings.get.mockReturnValueOnce('value'); - expect(uiSetting.fn(null, { parameter: 'something' }, {} as any)).resolves.toEqual({ + expect(uiSetting(null, { parameter: 'something' })).resolves.toEqual({ type: 'ui_setting', key: 'something', value: 'value', @@ -41,7 +43,7 @@ describe('uiSetting', () => { }); it('should pass a default value', async () => { - await uiSetting.fn(null, { parameter: 'something', default: 'default' }, {} as any); + await uiSetting(null, { parameter: 'something', default: 'default' }); expect(uiSettings.get).toHaveBeenCalledWith('something', 'default'); }); @@ -51,16 +53,16 @@ describe('uiSetting', () => { throw new Error(); }); - expect(uiSetting.fn(null, { parameter: 'something' }, {} as any)).rejects.toEqual( + expect(uiSetting(null, { parameter: 'something' })).rejects.toEqual( new Error('Invalid parameter "something".') ); }); it('should get a request instance on the server-side', async () => { const request = {}; - await uiSetting.fn(null, { parameter: 'something' }, { + await uiSetting(null, { parameter: 'something' }, { getKibanaRequest: () => request, - } as any); + } as Parameters[2]); const [[getKibanaRequest]] = getStartDependencies.mock.calls; @@ -68,7 +70,7 @@ describe('uiSetting', () => { }); it('should throw an error if request is not provided on the server-side', async () => { - await uiSetting.fn(null, { parameter: 'something' }, {} as any); + await uiSetting(null, { parameter: 'something' }); const [[getKibanaRequest]] = getStartDependencies.mock.calls; diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts b/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts index ca41b427a28f7..e3f581d1ae35f 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts @@ -15,13 +15,15 @@ import { Datatable } from '../../../expression_types'; * Takes a function spec and passes in default args, * overriding with any provided args. */ -export const functionWrapper = ( - spec: AnyExpressionFunctionDefinition +export const functionWrapper = < + ExpressionFunctionDefinition extends AnyExpressionFunctionDefinition +>( + spec: ExpressionFunctionDefinition ) => { const defaultArgs = mapValues(spec.args, (argSpec) => argSpec.default); return ( - context: ContextType, - args: Record = {}, + context?: Parameters[0] | null, + args: Parameters[1] = {}, handlers: ExecutionContext = {} as ExecutionContext ) => spec.fn(context, { ...defaultArgs, ...args }, handlers); }; diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/var.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/var.test.ts index 0c5c6b1480201..ca9c9c257f706 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/var.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/var.test.ts @@ -24,9 +24,9 @@ describe('expression_functions', () => { getExecutionContext: () => undefined, types: {}, variables: { test: 1 }, - abortSignal: {} as any, - inspectorAdapters: {} as any, - }; + abortSignal: {}, + inspectorAdapters: {}, + } as unknown as typeof context; }); it('returns the selected variable', () => { diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/var_set.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/var_set.test.ts index 95287f71907a4..b98e8285a1a84 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/var_set.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/var_set.test.ts @@ -17,7 +17,7 @@ describe('expression_functions', () => { const fn = functionWrapper(variableSet); let input: Partial>; let context: ExecutionContext; - let variables: Record; + let variables: Record; beforeEach(() => { input = { timeRange: { from: '0', to: '1' } }; @@ -27,9 +27,9 @@ describe('expression_functions', () => { getExecutionContext: () => undefined, types: {}, variables: { test: 1 }, - abortSignal: {} as any, - inspectorAdapters: {} as any, - }; + abortSignal: {}, + inspectorAdapters: {}, + } as unknown as typeof context; variables = context.variables; }); diff --git a/src/plugins/expressions/common/expression_functions/specs/theme.ts b/src/plugins/expressions/common/expression_functions/specs/theme.ts index 914e5d330bddb..76e97b12a967d 100644 --- a/src/plugins/expressions/common/expression_functions/specs/theme.ts +++ b/src/plugins/expressions/common/expression_functions/specs/theme.ts @@ -12,10 +12,10 @@ import { ExpressionFunctionDefinition } from '../types'; interface Arguments { variable: string; - default: string | number | boolean; + default?: string | number | boolean; } -type Output = any; +type Output = unknown; export type ExpressionFunctionTheme = ExpressionFunctionDefinition< 'theme', diff --git a/src/plugins/expressions/common/expression_functions/specs/var.ts b/src/plugins/expressions/common/expression_functions/specs/var.ts index d0e43a6871478..0ba4d5f439358 100644 --- a/src/plugins/expressions/common/expression_functions/specs/var.ts +++ b/src/plugins/expressions/common/expression_functions/specs/var.ts @@ -36,7 +36,8 @@ export const variable: ExpressionFunctionVar = { }, }, fn(input, args, context) { - const variables: Record = context.variables; + const { variables } = context; + return variables[args.name]; }, }; diff --git a/src/plugins/expressions/common/expression_functions/specs/var_set.ts b/src/plugins/expressions/common/expression_functions/specs/var_set.ts index f3ac6a2ab80d4..aa257940f9ad6 100644 --- a/src/plugins/expressions/common/expression_functions/specs/var_set.ts +++ b/src/plugins/expressions/common/expression_functions/specs/var_set.ts @@ -7,11 +7,12 @@ */ import { i18n } from '@kbn/i18n'; +import type { Serializable } from '@kbn/utility-types'; import { ExpressionFunctionDefinition } from '../types'; interface Arguments { name: string[]; - value: any[]; + value: Serializable[]; } export type ExpressionFunctionVarSet = ExpressionFunctionDefinition< @@ -46,10 +47,11 @@ export const variableSet: ExpressionFunctionVarSet = { }, }, fn(input, args, context) { - const variables: Record = context.variables; + const { variables } = context; args.name.forEach((name, i) => { variables[name] = args.value[i] === undefined ? input : args.value[i]; }); + return input; }, }; diff --git a/src/plugins/expressions/common/expression_functions/types.ts b/src/plugins/expressions/common/expression_functions/types.ts index 0ec61b39608a0..cb3677ed1668c 100644 --- a/src/plugins/expressions/common/expression_functions/types.ts +++ b/src/plugins/expressions/common/expression_functions/types.ts @@ -30,7 +30,7 @@ import { PersistableStateDefinition } from '../../../kibana_utils/common'; export interface ExpressionFunctionDefinition< Name extends string, Input, - Arguments extends Record, + Arguments extends Record, Output, Context extends ExecutionContext = ExecutionContext > extends PersistableStateDefinition { @@ -99,12 +99,14 @@ export interface ExpressionFunctionDefinition< /** * Type to capture every possible expression function definition. */ +/* eslint-disable @typescript-eslint/no-explicit-any */ export type AnyExpressionFunctionDefinition = ExpressionFunctionDefinition< string, any, Record, any >; +/* eslint-enable @typescript-eslint/no-explicit-any */ /** * A mapping of `ExpressionFunctionDefinition`s for functions which the diff --git a/src/plugins/expressions/common/expression_renderers/types.ts b/src/plugins/expressions/common/expression_renderers/types.ts index 8547c1a1bec92..6c889a81a1f80 100644 --- a/src/plugins/expressions/common/expression_renderers/types.ts +++ b/src/plugins/expressions/common/expression_renderers/types.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +import { ExpressionAstExpression } from '../ast'; + export interface ExpressionRenderDefinition { /** * Technical name of the renderer, used as ID to identify renderer in @@ -46,6 +48,7 @@ export interface ExpressionRenderDefinition { ) => void | Promise; } +// eslint-disable-next-line @typescript-eslint/no-explicit-any export type AnyExpressionRenderDefinition = ExpressionRenderDefinition; /** @@ -59,24 +62,34 @@ export type AnyExpressionRenderDefinition = ExpressionRenderDefinition; */ export type RenderMode = 'edit' | 'preview' | 'view'; +export interface IInterpreterRenderUpdateParams { + newExpression?: string | ExpressionAstExpression; + newParams: Params; +} + +export interface IInterpreterRenderEvent { + name: string; + data?: Context; +} + export interface IInterpreterRenderHandlers { /** * Done increments the number of rendering successes */ - done: () => void; - onDestroy: (fn: () => void) => void; - reload: () => void; - update: (params: any) => void; - event: (event: any) => void; - hasCompatibleActions?: (event: any) => Promise; - getRenderMode: () => RenderMode; + done(): void; + onDestroy(fn: () => void): void; + reload(): void; + update(params: IInterpreterRenderUpdateParams): void; + event(event: IInterpreterRenderEvent): void; + hasCompatibleActions?(event: IInterpreterRenderEvent): Promise; + getRenderMode(): RenderMode; /** * The chart is rendered in a non-interactive environment and should not provide any affordances for interaction like brushing. */ - isInteractive: () => boolean; + isInteractive(): boolean; - isSyncColorsEnabled: () => boolean; + isSyncColorsEnabled(): boolean; /** * This uiState interface is actually `PersistedState` from the visualizations plugin, * but expressions cannot know about vis or it creates a mess of circular dependencies. diff --git a/src/plugins/expressions/common/expression_types/expression_type.ts b/src/plugins/expressions/common/expression_types/expression_type.ts index 1c22b9f13b975..d179beeb76860 100644 --- a/src/plugins/expressions/common/expression_types/expression_type.ts +++ b/src/plugins/expressions/common/expression_types/expression_type.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import type { Serializable } from '@kbn/utility-types'; import { AnyExpressionTypeDefinition, ExpressionValue, ExpressionValueConverter } from './types'; import { getType } from './get_type'; @@ -20,15 +21,15 @@ export class ExpressionType { /** * Type validation, useful for checking function output. */ - validate: (type: any) => void | Error; + validate: (type: unknown) => void | Error; create: unknown; /** * Optional serialization (used when passing context around client/server). */ - serialize?: (value: ExpressionValue) => any; - deserialize?: (serialized: any) => ExpressionValue; + serialize?: (value: Serializable) => unknown; + deserialize?: (serialized: unknown[]) => Serializable; constructor(private readonly definition: AnyExpressionTypeDefinition) { const { name, help, deserialize, serialize, validate } = definition; @@ -38,7 +39,7 @@ export class ExpressionType { this.validate = validate || (() => {}); // Optional - this.create = (definition as any).create; + this.create = (definition as unknown as Record<'create', unknown>).create; this.serialize = serialize; this.deserialize = deserialize; diff --git a/src/plugins/expressions/common/expression_types/get_type.ts b/src/plugins/expressions/common/expression_types/get_type.ts index 052508df41329..17089c78816b6 100644 --- a/src/plugins/expressions/common/expression_types/get_type.ts +++ b/src/plugins/expressions/common/expression_types/get_type.ts @@ -6,14 +6,24 @@ * Side Public License, v 1. */ -export function getType(node: any) { - if (node == null) return 'null'; +export function getType(node: unknown): string { + if (node == null) { + return 'null'; + } + if (Array.isArray(node)) { throw new Error('Unexpected array value encountered.'); } - if (typeof node === 'object') { - if (!node.type) throw new Error('Objects must have a type property'); - return node.type; + + if (typeof node !== 'object') { + return typeof node; } - return typeof node; + + const { type } = node as Record; + + if (!type) { + throw new Error('Objects must have a type property'); + } + + return type as string; } diff --git a/src/plugins/expressions/common/expression_types/specs/datatable.ts b/src/plugins/expressions/common/expression_types/specs/datatable.ts index c268557936ac5..b45c36950f870 100644 --- a/src/plugins/expressions/common/expression_types/specs/datatable.ts +++ b/src/plugins/expressions/common/expression_types/specs/datatable.ts @@ -9,7 +9,7 @@ import type { SerializableRecord } from '@kbn/utility-types'; import { map, pick, zipObject } from 'lodash'; -import { ExpressionTypeDefinition } from '../types'; +import { ExpressionTypeDefinition, ExpressionValueBoxed } from '../types'; import { PointSeries, PointSeriesColumn } from './pointseries'; import { ExpressionValueRender } from './render'; import { SerializedFieldFormat } from '../../types'; @@ -21,7 +21,7 @@ const name = 'datatable'; * @param datatable */ export const isDatatable = (datatable: unknown): datatable is Datatable => - !!datatable && typeof datatable === 'object' && (datatable as any).type === 'datatable'; + (datatable as ExpressionValueBoxed | undefined)?.type === 'datatable'; /** * This type represents the `type` of any `DatatableColumn` in a `Datatable`. @@ -48,6 +48,7 @@ export type DatatableColumnType = /** * This type represents a row in a `Datatable`. */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any export type DatatableRow = Record; /** @@ -112,7 +113,7 @@ interface RenderedDatatable { export const datatable: ExpressionTypeDefinition = { name, - validate: (table) => { + validate: (table: Record) => { // TODO: Check columns types. Only string, boolean, number, date, allowed for now. if (!table.columns) { throw new Error('datatable must have a columns array, even if it is empty'); diff --git a/src/plugins/expressions/common/expression_types/specs/error.ts b/src/plugins/expressions/common/expression_types/specs/error.ts index 75e49633866f7..b170675b8f489 100644 --- a/src/plugins/expressions/common/expression_types/specs/error.ts +++ b/src/plugins/expressions/common/expression_types/specs/error.ts @@ -22,7 +22,7 @@ export type ExpressionValueError = ExpressionValueBoxed< } >; -export const isExpressionValueError = (value: any): value is ExpressionValueError => +export const isExpressionValueError = (value: unknown): value is ExpressionValueError => getType(value) === 'error'; /** diff --git a/src/plugins/expressions/common/expression_types/specs/pointseries.ts b/src/plugins/expressions/common/expression_types/specs/pointseries.ts index 1c8bddf14cffe..ef2079bd387a0 100644 --- a/src/plugins/expressions/common/expression_types/specs/pointseries.ts +++ b/src/plugins/expressions/common/expression_types/specs/pointseries.ts @@ -7,7 +7,7 @@ */ import { ExpressionTypeDefinition, ExpressionValueBoxed } from '../types'; -import { Datatable } from './datatable'; +import { Datatable, DatatableRow } from './datatable'; import { ExpressionValueRender } from './render'; const name = 'pointseries'; @@ -31,7 +31,7 @@ export interface PointSeriesColumn { */ export type PointSeriesColumns = Record | {}; -export type PointSeriesRow = Record; +export type PointSeriesRow = DatatableRow; /** * A `PointSeries` is a unique structure that represents dots on a chart. diff --git a/src/plugins/expressions/common/expression_types/specs/shape.ts b/src/plugins/expressions/common/expression_types/specs/shape.ts index 2b62dc6458c15..5ad86366a26cc 100644 --- a/src/plugins/expressions/common/expression_types/specs/shape.ts +++ b/src/plugins/expressions/common/expression_types/specs/shape.ts @@ -11,7 +11,7 @@ import { ExpressionValueRender } from './render'; const name = 'shape'; -export const shape: ExpressionTypeDefinition> = { +export const shape: ExpressionTypeDefinition> = { name: 'shape', to: { render: (input) => { diff --git a/src/plugins/expressions/common/expression_types/types.ts b/src/plugins/expressions/common/expression_types/types.ts index a829c2adc923c..15ec82e40314d 100644 --- a/src/plugins/expressions/common/expression_types/types.ts +++ b/src/plugins/expressions/common/expression_types/types.ts @@ -6,6 +6,9 @@ * Side Public License, v 1. */ +import type { ExpressionType } from './expression_type'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any export type ExpressionValueUnboxed = any; export type ExpressionValueBoxed = { @@ -16,7 +19,7 @@ export type ExpressionValue = ExpressionValueUnboxed | ExpressionValueBoxed; export type ExpressionValueConverter = ( input: I, - availableTypes: Record + availableTypes: Record ) => O; /** @@ -29,18 +32,19 @@ export interface ExpressionTypeDefinition< SerializedType = undefined > { name: Name; - validate?: (type: any) => void | Error; - serialize?: (type: Value) => SerializedType; - deserialize?: (type: SerializedType) => Value; + validate?(type: unknown): void | Error; + serialize?(type: Value): SerializedType; + deserialize?(type: SerializedType): Value; // TODO: Update typings for the `availableTypes` parameter once interfaces for this // have been added elsewhere in the interpreter. from?: { - [type: string]: ExpressionValueConverter; + [type: string]: ExpressionValueConverter; }; to?: { - [type: string]: ExpressionValueConverter; + [type: string]: ExpressionValueConverter; }; help?: string; } -export type AnyExpressionTypeDefinition = ExpressionTypeDefinition; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type AnyExpressionTypeDefinition = ExpressionTypeDefinition; diff --git a/src/plugins/expressions/common/mocks.ts b/src/plugins/expressions/common/mocks.ts index 681fa28268823..4141da06ec04e 100644 --- a/src/plugins/expressions/common/mocks.ts +++ b/src/plugins/expressions/common/mocks.ts @@ -11,7 +11,7 @@ import { ExecutionContext } from './execution/types'; export const createMockExecutionContext = ( extraContext: ExtraContext = {} as ExtraContext ): ExecutionContext & ExtraContext => { - const executionContext: ExecutionContext = { + const executionContext = { getSearchContext: jest.fn(), getSearchSessionId: jest.fn(), getExecutionContext: jest.fn(), @@ -25,10 +25,10 @@ export const createMockExecutionContext = removeEventListener: jest.fn(), }, inspectorAdapters: { - requests: {} as any, - data: {} as any, + requests: {}, + data: {}, }, - }; + } as unknown as ExecutionContext; return { ...executionContext, diff --git a/src/plugins/expressions/common/service/expressions_services.ts b/src/plugins/expressions/common/service/expressions_services.ts index f21eaa34d7868..453ea656ec43b 100644 --- a/src/plugins/expressions/common/service/expressions_services.ts +++ b/src/plugins/expressions/common/service/expressions_services.ts @@ -125,7 +125,7 @@ export interface ExpressionsServiceSetup { export interface ExpressionExecutionParams { searchContext?: SerializableRecord; - variables?: Record; + variables?: Record; /** * Whether to execute expression in *debug mode*. In *debug mode* inputs and @@ -148,6 +148,8 @@ export interface ExpressionExecutionParams { inspectorAdapters?: Adapters; executionContext?: KibanaExecutionContext; + + extraContext?: object; } /** @@ -375,7 +377,7 @@ export class ExpressionsService */ public readonly telemetry = ( state: ExpressionAstExpression, - telemetryData: Record = {} + telemetryData: Record = {} ) => { return this.executor.telemetry(state, telemetryData); }; diff --git a/src/plugins/expressions/common/test_helpers/expression_functions/access.ts b/src/plugins/expressions/common/test_helpers/expression_functions/access.ts index 5498e6741bd99..6ddc99b13ee57 100644 --- a/src/plugins/expressions/common/test_helpers/expression_functions/access.ts +++ b/src/plugins/expressions/common/test_helpers/expression_functions/access.ts @@ -8,7 +8,7 @@ import { ExpressionFunctionDefinition } from '../../expression_functions'; -export const access: ExpressionFunctionDefinition<'access', any, { key: string }, any> = { +export const access: ExpressionFunctionDefinition<'access', unknown, { key: string }, unknown> = { name: 'access', help: 'Access key on input object or return the input, if it is not an object', args: { @@ -19,6 +19,10 @@ export const access: ExpressionFunctionDefinition<'access', any, { key: string } }, }, fn: (input, { key }, context) => { - return !input ? input : typeof input === 'object' ? input[key] : input; + return !input + ? input + : typeof input === 'object' + ? (input as Record)[key] + : input; }, }; diff --git a/src/plugins/expressions/common/test_helpers/expression_functions/add.ts b/src/plugins/expressions/common/test_helpers/expression_functions/add.ts index 03c72733166b8..5e646ead7b62f 100644 --- a/src/plugins/expressions/common/test_helpers/expression_functions/add.ts +++ b/src/plugins/expressions/common/test_helpers/expression_functions/add.ts @@ -26,11 +26,11 @@ export const add: ExpressionFunctionDefinition< types: ['null', 'number', 'string'], }, }, - fn: ({ value: value1 }, { val: input2 }, context) => { + fn: ({ value: value1 }, { val: input2 }) => { const value2 = !input2 ? 0 : typeof input2 === 'object' - ? (input2 as any).value + ? (input2 as ExpressionValueNum).value : Number(input2); return { diff --git a/src/plugins/expressions/common/test_helpers/expression_functions/introspect_context.ts b/src/plugins/expressions/common/test_helpers/expression_functions/introspect_context.ts index 9556872d57344..8b2c22df8e086 100644 --- a/src/plugins/expressions/common/test_helpers/expression_functions/introspect_context.ts +++ b/src/plugins/expressions/common/test_helpers/expression_functions/introspect_context.ts @@ -10,9 +10,9 @@ import { ExpressionFunctionDefinition } from '../../expression_functions'; export const introspectContext: ExpressionFunctionDefinition< 'introspectContext', - any, + unknown, { key: string }, - any + unknown > = { name: 'introspectContext', args: { @@ -25,7 +25,7 @@ export const introspectContext: ExpressionFunctionDefinition< fn: (input, args, context) => { return { type: 'any', - result: (context as any)[args.key], + result: context[args.key as keyof typeof context], }; }, }; diff --git a/src/plugins/expressions/common/test_helpers/expression_functions/sleep.ts b/src/plugins/expressions/common/test_helpers/expression_functions/sleep.ts index 04b1c9822a3bb..71a14b3dac10c 100644 --- a/src/plugins/expressions/common/test_helpers/expression_functions/sleep.ts +++ b/src/plugins/expressions/common/test_helpers/expression_functions/sleep.ts @@ -8,7 +8,7 @@ import { ExpressionFunctionDefinition } from '../../expression_functions'; -export const sleep: ExpressionFunctionDefinition<'sleep', any, { time: number }, any> = { +export const sleep: ExpressionFunctionDefinition<'sleep', unknown, { time: number }, unknown> = { name: 'sleep', args: { time: { diff --git a/src/plugins/expressions/common/types/common.ts b/src/plugins/expressions/common/types/common.ts index d8d1a9a4b256a..64b3d00895f56 100644 --- a/src/plugins/expressions/common/types/common.ts +++ b/src/plugins/expressions/common/types/common.ts @@ -37,7 +37,7 @@ export type KnownTypeToString = * `someArgument: Promise` results in `types: ['boolean', 'string']` */ export type TypeString = KnownTypeToString< - T extends ObservableLike ? UnwrapObservable : UnwrapPromiseOrReturn + T extends ObservableLike ? UnwrapObservable : UnwrapPromiseOrReturn >; /** @@ -52,6 +52,7 @@ export type UnmappedTypeStrings = 'date' | 'filter'; * Is used to carry information about how to format data in * a data table as part of the column definition. */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any export interface SerializedFieldFormat> { id?: string; params?: TParams; diff --git a/src/plugins/expressions/common/util/expressions_inspector_adapter.ts b/src/plugins/expressions/common/util/expressions_inspector_adapter.ts index 34fa1c8713f5a..bf16635792c1f 100644 --- a/src/plugins/expressions/common/util/expressions_inspector_adapter.ts +++ b/src/plugins/expressions/common/util/expressions_inspector_adapter.ts @@ -7,16 +7,17 @@ */ import { EventEmitter } from 'events'; +import { ExpressionAstNode } from '..'; export class ExpressionsInspectorAdapter extends EventEmitter { - private _ast: any = {}; + private _ast = {} as ExpressionAstNode; - public logAST(ast: any): void { + logAST(ast: ExpressionAstNode): void { this._ast = ast; this.emit('change', this._ast); } - public get ast() { + public get ast(): ExpressionAstNode { return this._ast; } } diff --git a/src/plugins/expressions/common/util/test_utils.ts b/src/plugins/expressions/common/util/test_utils.ts index 59bd0a4235d9b..a6a4771bdf89d 100644 --- a/src/plugins/expressions/common/util/test_utils.ts +++ b/src/plugins/expressions/common/util/test_utils.ts @@ -14,7 +14,7 @@ export const createMockContext = () => { getSearchSessionId: () => undefined, types: {}, variables: {}, - abortSignal: {} as any, - inspectorAdapters: {} as any, + abortSignal: {}, + inspectorAdapters: {}, } as ExecutionContext; }; diff --git a/src/plugins/expressions/public/loader.test.ts b/src/plugins/expressions/public/loader.test.ts index ff960f4a3a80d..f22963cedf612 100644 --- a/src/plugins/expressions/public/loader.test.ts +++ b/src/plugins/expressions/public/loader.test.ts @@ -16,12 +16,14 @@ import { IInterpreterRenderHandlers, RenderMode, AnyExpressionFunctionDefinition, + ExpressionsService, + ExecutionContract, } from '../common'; // eslint-disable-next-line const { __getLastExecution, __getLastRenderMode } = require('./services'); -const element: HTMLElement = null as any; +const element = null as unknown as HTMLElement; let testScheduler: TestScheduler; @@ -36,8 +38,9 @@ jest.mock('./services', () => { }, }; - // eslint-disable-next-line - const service = new (require('../common/service/expressions_services').ExpressionsService as any)(); + const service: ExpressionsService = + // eslint-disable-next-line @typescript-eslint/no-var-requires + new (require('../common/service/expressions_services').ExpressionsService)(); const testFn: AnyExpressionFunctionDefinition = { fn: () => ({ type: 'render', as: 'test' }), @@ -54,9 +57,9 @@ jest.mock('./services', () => { service.start(); + let execution: ExecutionContract; const moduleMock = { - __execution: undefined, - __getLastExecution: () => moduleMock.__execution, + __getLastExecution: () => execution, __getLastRenderMode: () => renderMode, getRenderersRegistry: () => ({ get: (id: string) => renderers[id], @@ -72,13 +75,14 @@ jest.mock('./services', () => { }; const execute = service.execute; - service.execute = (...args: any) => { - const execution = execute(...args); + + jest.spyOn(service, 'execute').mockImplementation((...args) => { + execution = execute(...args); jest.spyOn(execution, 'getData'); jest.spyOn(execution, 'cancel'); - moduleMock.__execution = execution; + return execution; - }; + }); return moduleMock; }); diff --git a/src/plugins/expressions/public/loader.ts b/src/plugins/expressions/public/loader.ts index 3ab7473d8d735..b0a54e3dec35d 100644 --- a/src/plugins/expressions/public/loader.ts +++ b/src/plugins/expressions/public/loader.ts @@ -9,7 +9,7 @@ import { BehaviorSubject, Observable, Subject, Subscription, asyncScheduler, identity } from 'rxjs'; import { filter, map, delay, throttleTime } from 'rxjs/operators'; import { defaults } from 'lodash'; -import { UnwrapObservable } from '@kbn/utility-types'; +import { SerializableRecord, UnwrapObservable } from '@kbn/utility-types'; import { Adapters } from '../../inspector/public'; import { IExpressionLoaderParams } from './types'; import { ExpressionAstExpression } from '../common'; @@ -18,7 +18,7 @@ import { ExecutionContract } from '../common/execution/execution_contract'; import { ExpressionRenderHandler } from './render'; import { getExpressionsService } from './services'; -type Data = any; +type Data = unknown; export class ExpressionLoader { data$: ReturnType; @@ -156,7 +156,7 @@ export class ExpressionLoader { }; private render(data: Data): void { - this.renderHandler.render(data, this.params.uiState); + this.renderHandler.render(data as SerializableRecord, this.params.uiState); } private setParams(params?: IExpressionLoaderParams) { @@ -169,7 +169,7 @@ export class ExpressionLoader { {}, params.searchContext, this.params.searchContext || {} - ) as any; + ); } if (params.uiState && this.params) { this.params.uiState = params.uiState; diff --git a/src/plugins/expressions/public/react_expression_renderer.test.tsx b/src/plugins/expressions/public/react_expression_renderer.test.tsx index d31a4c947b09d..f1932ce7dd6ba 100644 --- a/src/plugins/expressions/public/react_expression_renderer.test.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.test.tsx @@ -14,6 +14,7 @@ import { ReactExpressionRenderer } from './react_expression_renderer'; import { ExpressionLoader } from './loader'; import { mount } from 'enzyme'; import { EuiProgress } from '@elastic/eui'; +import { IInterpreterRenderHandlers } from '../common'; import { RenderErrorHandlerFnType } from './types'; import { ExpressionRendererEvent } from './render'; @@ -234,7 +235,7 @@ describe('ExpressionRenderer', () => { done: () => { renderSubject.next(1); }, - } as any); + } as IInterpreterRenderHandlers); }); instance.update(); diff --git a/src/plugins/expressions/public/render.test.ts b/src/plugins/expressions/public/render.test.ts index 61dc0a25439b4..8d4298785572a 100644 --- a/src/plugins/expressions/public/render.test.ts +++ b/src/plugins/expressions/public/render.test.ts @@ -8,6 +8,7 @@ import { ExpressionRenderHandler, render } from './render'; import { Observable } from 'rxjs'; +import { SerializableRecord } from '@kbn/utility-types'; import { ExpressionRenderError } from './types'; import { getRenderersRegistry } from './services'; import { first, take, toArray } from 'rxjs/operators'; @@ -79,11 +80,11 @@ describe('ExpressionRenderHandler', () => { it('in case of error render$ should emit when error renderer is finished', async () => { const expressionRenderHandler = new ExpressionRenderHandler(element); - expressionRenderHandler.render(false); + expressionRenderHandler.render(false as unknown as SerializableRecord); const promise1 = expressionRenderHandler.render$.pipe(first()).toPromise(); await expect(promise1).resolves.toEqual(1); - expressionRenderHandler.render(false); + expressionRenderHandler.render(false as unknown as SerializableRecord); const promise2 = expressionRenderHandler.render$.pipe(first()).toPromise(); await expect(promise2).resolves.toEqual(2); }); @@ -92,7 +93,7 @@ describe('ExpressionRenderHandler', () => { const expressionRenderHandler = new ExpressionRenderHandler(element, { onRenderError: mockMockErrorRenderFunction, }); - await expressionRenderHandler.render(false); + await expressionRenderHandler.render(false as unknown as SerializableRecord); expect(getHandledError()!.message).toEqual( `invalid data provided to the expression renderer` ); @@ -122,7 +123,8 @@ describe('ExpressionRenderHandler', () => { get: () => ({ render: (domNode: HTMLElement, config: unknown, handlers: IInterpreterRenderHandlers) => { handlers.hasCompatibleActions!({ - foo: 'bar', + name: 'something', + data: 'bar', }); }, }), @@ -136,7 +138,8 @@ describe('ExpressionRenderHandler', () => { await expressionRenderHandler.render({ type: 'render', as: 'something' }); expect(hasCompatibleActions).toHaveBeenCalledTimes(1); expect(hasCompatibleActions.mock.calls[0][0]).toEqual({ - foo: 'bar', + name: 'something', + data: 'bar', }); }); @@ -156,7 +159,7 @@ describe('ExpressionRenderHandler', () => { it('default renderer should use notification service', async () => { const expressionRenderHandler = new ExpressionRenderHandler(element); const promise1 = expressionRenderHandler.render$.pipe(first()).toPromise(); - expressionRenderHandler.render(false); + expressionRenderHandler.render(false as unknown as SerializableRecord); await expect(promise1).resolves.toEqual(1); expect(mockNotificationService.toasts.addError).toBeCalledWith( expect.objectContaining({ @@ -175,7 +178,7 @@ describe('ExpressionRenderHandler', () => { const expressionRenderHandler1 = new ExpressionRenderHandler(element, { onRenderError: mockMockErrorRenderFunction, }); - expressionRenderHandler1.render(false); + expressionRenderHandler1.render(false as unknown as SerializableRecord); const renderPromiseAfterRender = expressionRenderHandler1.render$.pipe(first()).toPromise(); await expect(renderPromiseAfterRender).resolves.toEqual(1); expect(getHandledError()!.message).toEqual( @@ -188,7 +191,7 @@ describe('ExpressionRenderHandler', () => { onRenderError: mockMockErrorRenderFunction, }); const renderPromiseBeforeRender = expressionRenderHandler2.render$.pipe(first()).toPromise(); - expressionRenderHandler2.render(false); + expressionRenderHandler2.render(false as unknown as SerializableRecord); await expect(renderPromiseBeforeRender).resolves.toEqual(1); expect(getHandledError()!.message).toEqual( 'invalid data provided to the expression renderer' @@ -199,9 +202,9 @@ describe('ExpressionRenderHandler', () => { // that observables will emit previous result if subscription happens after render it('should emit previous render and error results', async () => { const expressionRenderHandler = new ExpressionRenderHandler(element); - expressionRenderHandler.render(false); + expressionRenderHandler.render(false as unknown as SerializableRecord); const renderPromise = expressionRenderHandler.render$.pipe(take(2), toArray()).toPromise(); - expressionRenderHandler.render(false); + expressionRenderHandler.render(false as unknown as SerializableRecord); await expect(renderPromise).resolves.toEqual([1, 2]); }); }); diff --git a/src/plugins/expressions/public/render.ts b/src/plugins/expressions/public/render.ts index e9a65d1e8f12e..8635a4033bde5 100644 --- a/src/plugins/expressions/public/render.ts +++ b/src/plugins/expressions/public/render.ts @@ -9,13 +9,20 @@ import * as Rx from 'rxjs'; import { Observable } from 'rxjs'; import { filter } from 'rxjs/operators'; +import { isNumber } from 'lodash'; +import { SerializableRecord } from '@kbn/utility-types'; import { ExpressionRenderError, RenderErrorHandlerFnType, IExpressionLoaderParams } from './types'; import { renderErrorHandler as defaultRenderErrorHandler } from './render_error_handler'; -import { IInterpreterRenderHandlers, ExpressionAstExpression, RenderMode } from '../common'; +import { + IInterpreterRenderHandlers, + IInterpreterRenderEvent, + IInterpreterRenderUpdateParams, + RenderMode, +} from '../common'; import { getRenderersRegistry } from './services'; -export type IExpressionRendererExtraHandlers = Record; +export type IExpressionRendererExtraHandlers = Record; export interface ExpressionRenderHandlerParams { onRenderError?: RenderErrorHandlerFnType; @@ -25,15 +32,10 @@ export interface ExpressionRenderHandlerParams { hasCompatibleActions?: (event: ExpressionRendererEvent) => Promise; } -export interface ExpressionRendererEvent { - name: string; - data: any; -} +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type ExpressionRendererEvent = IInterpreterRenderEvent; -interface UpdateValue { - newExpression?: string | ExpressionAstExpression; - newParams: IExpressionLoaderParams; -} +type UpdateValue = IInterpreterRenderUpdateParams; export class ExpressionRenderHandler { render$: Observable; @@ -41,7 +43,7 @@ export class ExpressionRenderHandler { events$: Observable; private element: HTMLElement; - private destroyFn?: any; + private destroyFn?: Function; private renderCount: number = 0; private renderSubject: Rx.BehaviorSubject; private eventsSubject: Rx.Subject; @@ -66,16 +68,14 @@ export class ExpressionRenderHandler { this.onRenderError = onRenderError || defaultRenderErrorHandler; - this.renderSubject = new Rx.BehaviorSubject(null as any | null); - this.render$ = this.renderSubject - .asObservable() - .pipe(filter((_) => _ !== null)) as Observable; + this.renderSubject = new Rx.BehaviorSubject(null); + this.render$ = this.renderSubject.asObservable().pipe(filter(isNumber)); this.updateSubject = new Rx.Subject(); this.update$ = this.updateSubject.asObservable(); this.handlers = { - onDestroy: (fn: any) => { + onDestroy: (fn: Function) => { this.destroyFn = fn; }, done: () => { @@ -104,14 +104,14 @@ export class ExpressionRenderHandler { }; } - render = async (value: any, uiState?: any) => { + render = async (value: SerializableRecord, uiState?: unknown) => { if (!value || typeof value !== 'object') { return this.handleRenderError(new Error('invalid data provided to the expression renderer')); } if (value.type !== 'render' || !value.as) { if (value.type === 'error') { - return this.handleRenderError(value.error); + return this.handleRenderError(value.error as unknown as ExpressionRenderError); } else { return this.handleRenderError( new Error('invalid data provided to the expression renderer') @@ -119,20 +119,20 @@ export class ExpressionRenderHandler { } } - if (!getRenderersRegistry().get(value.as)) { + if (!getRenderersRegistry().get(value.as as string)) { return this.handleRenderError(new Error(`invalid renderer id '${value.as}'`)); } try { // Rendering is asynchronous, completed by handlers.done() await getRenderersRegistry() - .get(value.as)! + .get(value.as as string)! .render(this.element, value.value, { ...this.handlers, uiState, - } as any); + }); } catch (e) { - return this.handleRenderError(e); + return this.handleRenderError(e as ExpressionRenderError); } }; @@ -156,10 +156,10 @@ export class ExpressionRenderHandler { export function render( element: HTMLElement, - data: any, + data: unknown, options?: ExpressionRenderHandlerParams ): ExpressionRenderHandler { const handler = new ExpressionRenderHandler(element, options); - handler.render(data); + handler.render(data as SerializableRecord); return handler; } diff --git a/src/plugins/expressions/public/types/index.ts b/src/plugins/expressions/public/types/index.ts index 172f322f8892a..ea47403332c74 100644 --- a/src/plugins/expressions/public/types/index.ts +++ b/src/plugins/expressions/public/types/index.ts @@ -36,7 +36,7 @@ export interface ExpressionInterpreter { export interface IExpressionLoaderParams { searchContext?: SerializableRecord; context?: ExpressionValue; - variables?: Record; + variables?: Record; // Enables debug tracking on each expression in the AST debug?: boolean; disableCaching?: boolean; diff --git a/src/plugins/home/server/services/new_instance_status.ts b/src/plugins/home/server/services/new_instance_status.ts index b72ada27ecbad..e19380e928822 100644 --- a/src/plugins/home/server/services/new_instance_status.ts +++ b/src/plugins/home/server/services/new_instance_status.ts @@ -7,7 +7,6 @@ */ import type { IScopedClusterClient, SavedObjectsClientContract } from '../../../../core/server'; -import type { IndexPatternSavedObjectAttrs } from '../../../data/common/data_views/data_views'; const LOGS_INDEX_PATTERN = 'logs-*'; const METRICS_INDEX_PATTERN = 'metrics-*'; @@ -23,7 +22,7 @@ interface Deps { } export const isNewInstance = async ({ esClient, soClient }: Deps): Promise => { - const indexPatterns = await soClient.find({ + const indexPatterns = await soClient.find<{ title: string }>({ type: 'index-pattern', fields: ['title'], search: `*`, diff --git a/src/plugins/usage_collection/README.mdx b/src/plugins/usage_collection/README.mdx index a6f6f6c8e5971..a58f197818bf4 100644 --- a/src/plugins/usage_collection/README.mdx +++ b/src/plugins/usage_collection/README.mdx @@ -1,6 +1,6 @@ --- id: kibUsageCollectionPlugin -slug: /kibana-dev-docs/services/usage-collection-plugin +slug: /kibana-dev-docs/key-concepts/usage-collection-plugin title: Usage collection service summary: The Usage Collection Service defines a set of APIs for other plugins to report the usage of their features. date: 2021-02-24 diff --git a/src/plugins/vis_type_markdown/public/markdown_fn.test.ts b/src/plugins/vis_type_markdown/public/markdown_fn.test.ts index bef0b32e392f0..148a15fb9fc8a 100644 --- a/src/plugins/vis_type_markdown/public/markdown_fn.test.ts +++ b/src/plugins/vis_type_markdown/public/markdown_fn.test.ts @@ -8,6 +8,7 @@ import { functionWrapper } from '../../expressions/common/expression_functions/specs/tests/utils'; import { createMarkdownVisFn } from './markdown_fn'; +import { Arguments } from './types'; describe('interpreter/functions#markdown', () => { const fn = functionWrapper(createMarkdownVisFn()); @@ -15,7 +16,7 @@ describe('interpreter/functions#markdown', () => { font: { spec: { fontSize: 12 } }, openLinksInNewTab: true, markdown: '## hello _markdown_', - }; + } as unknown as Arguments; it('returns an object with the correct structure', async () => { const actual = await fn(null, args, undefined); diff --git a/src/plugins/vis_types/metric/public/metric_vis_fn.test.ts b/src/plugins/vis_types/metric/public/metric_vis_fn.test.ts index 3844c0f21ed05..28124a653629c 100644 --- a/src/plugins/vis_types/metric/public/metric_vis_fn.test.ts +++ b/src/plugins/vis_types/metric/public/metric_vis_fn.test.ts @@ -10,13 +10,15 @@ import { createMetricVisFn } from './metric_vis_fn'; import { functionWrapper } from '../../../expressions/common/expression_functions/specs/tests/utils'; import { Datatable } from '../../../expressions/common/expression_types/specs'; +type Arguments = Parameters['fn']>[1]; + describe('interpreter/functions#metric', () => { const fn = functionWrapper(createMetricVisFn()); const context = { type: 'datatable', rows: [{ 'col-0-1': 0 }], columns: [{ id: 'col-0-1', name: 'Count' }], - }; + } as unknown as Datatable; const args = { percentageMode: false, useRanges: false, @@ -50,7 +52,7 @@ describe('interpreter/functions#metric', () => { aggType: 'count', }, ], - }; + } as unknown as Arguments; it('returns an object with the correct structure', () => { const actual = fn(context, args, undefined); diff --git a/src/plugins/vis_types/pie/public/pie_fn.test.ts b/src/plugins/vis_types/pie/public/pie_fn.test.ts index d0e0af9807f34..9ba21cdc847e5 100644 --- a/src/plugins/vis_types/pie/public/pie_fn.test.ts +++ b/src/plugins/vis_types/pie/public/pie_fn.test.ts @@ -8,6 +8,7 @@ import { functionWrapper } from '../../../expressions/common/expression_functions/specs/tests/utils'; import { createPieVisFn } from './pie_fn'; +import { PieVisConfig } from './types'; import { Datatable } from '../../../expressions/common/expression_types/specs'; describe('interpreter/functions#pie', () => { @@ -16,7 +17,7 @@ describe('interpreter/functions#pie', () => { type: 'datatable', rows: [{ 'col-0-1': 0 }], columns: [{ id: 'col-0-1', name: 'Count' }], - }; + } as unknown as Datatable; const visConfig = { addTooltip: true, addLegend: true, @@ -43,7 +44,7 @@ describe('interpreter/functions#pie', () => { params: {}, aggType: 'count', }, - }; + } as unknown as PieVisConfig; beforeEach(() => { jest.clearAllMocks(); diff --git a/src/plugins/vis_types/table/public/table_vis_fn.test.ts b/src/plugins/vis_types/table/public/table_vis_fn.test.ts index 8b08bca160478..d0d9a414bbcce 100644 --- a/src/plugins/vis_types/table/public/table_vis_fn.test.ts +++ b/src/plugins/vis_types/table/public/table_vis_fn.test.ts @@ -8,6 +8,7 @@ import { createTableVisFn } from './table_vis_fn'; import { tableVisResponseHandler } from './utils'; +import { TableVisConfig } from './types'; import { functionWrapper } from '../../../expressions/common/expression_functions/specs/tests/utils'; import { Datatable } from '../../../expressions/common/expression_types/specs'; @@ -24,7 +25,7 @@ describe('interpreter/functions#table', () => { type: 'datatable', rows: [{ 'col-0-1': 0 }], columns: [{ id: 'col-0-1', name: 'Count' }], - }; + } as unknown as Datatable; const visConfig = { title: 'My Chart title', perPage: 10, @@ -52,7 +53,7 @@ describe('interpreter/functions#table', () => { }, ], buckets: [], - }; + } as unknown as TableVisConfig; beforeEach(() => { jest.clearAllMocks(); diff --git a/src/plugins/vis_types/timeseries/public/application/components/lib/index_pattern_select/index_pattern_select.tsx b/src/plugins/vis_types/timeseries/public/application/components/lib/index_pattern_select/index_pattern_select.tsx index 1029ac67cc43c..d28e2c5e0fb9a 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/lib/index_pattern_select/index_pattern_select.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/lib/index_pattern_select/index_pattern_select.tsx @@ -33,22 +33,28 @@ export interface IndexPatternSelectProps { | null; } -const defaultIndexPatternHelpText = i18n.translate( - 'visTypeTimeseries.indexPatternSelect.defaultIndexPatternText', - { - defaultMessage: 'Default index pattern is used.', - } +const queryAllIndicesHelpText = ( + *, + }} + /> ); -const queryAllIndexesHelpText = i18n.translate( - 'visTypeTimeseries.indexPatternSelect.queryAllIndexesText', - { - defaultMessage: 'To query all indexes use *', - } +const getIndexPatternHelpText = (useKibanaIndices: boolean) => ( + ); const indexPatternLabel = i18n.translate('visTypeTimeseries.indexPatternSelect.label', { - defaultMessage: 'Index pattern', + defaultMessage: 'Data view', }); export const IndexPatternSelect = ({ @@ -103,17 +109,14 @@ export const IndexPatternSelect = ({ diff --git a/src/plugins/vis_types/timeseries/public/application/components/lib/index_pattern_select/switch_mode_popover.tsx b/src/plugins/vis_types/timeseries/public/application/components/lib/index_pattern_select/switch_mode_popover.tsx index 43ef091da251d..5e66b50eac467 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/lib/index_pattern_select/switch_mode_popover.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/lib/index_pattern_select/switch_mode_popover.tsx @@ -79,7 +79,7 @@ export const SwitchModePopover = ({ onModeChange, useKibanaIndices }: PopoverPro aria-label={i18n.translate( 'visTypeTimeseries.indexPatternSelect.switchModePopover.areaLabel', { - defaultMessage: 'Configure index pattern selection mode', + defaultMessage: 'Configure data view selection mode', } )} onClick={onButtonClick} @@ -97,14 +97,13 @@ export const SwitchModePopover = ({ onModeChange, useKibanaIndices }: PopoverPro > {i18n.translate('visTypeTimeseries.indexPatternSelect.switchModePopover.title', { - defaultMessage: 'Index pattern selection mode', + defaultMessage: 'Data view mode', })} { { } iconType="cheer" @@ -42,13 +42,13 @@ export const UseIndexPatternModeCallout = () => { >

@@ -59,7 +59,7 @@ export const UseIndexPatternModeCallout = () => { diff --git a/src/plugins/vis_types/timeseries/public/application/components/vis_types/timeseries/config.js b/src/plugins/vis_types/timeseries/public/application/components/vis_types/timeseries/config.js index 4257c35a6d4c2..208f9af9bb250 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/vis_types/timeseries/config.js +++ b/src/plugins/vis_types/timeseries/public/application/components/vis_types/timeseries/config.js @@ -538,8 +538,8 @@ export const TimeseriesConfig = injectI18n(function (props) { { type: 'datatable', rows: [{ 'col-0-1': 0 }], columns: [{ id: 'col-0-1', name: 'Count' }], - }; + } as unknown as Datatable; const visConfig = { type: 'pie', addTooltip: true, diff --git a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/controls_references.ts b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/controls_references.ts index 7a0bb4584e83a..31713d8ad7d5e 100644 --- a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/controls_references.ts +++ b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/controls_references.ts @@ -8,7 +8,7 @@ import { SavedObjectReference } from '../../../../../core/types'; import { VisParams } from '../../../common'; -import { INDEX_PATTERN_SAVED_OBJECT_TYPE } from '../../../../data/public'; +import { INDEX_PATTERN_SAVED_OBJECT_TYPE } from '../../../../data/common'; const isControlsVis = (visType: string) => visType === 'input_control_vis'; diff --git a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/timeseries_references.ts b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/timeseries_references.ts index 98970a0127c71..a3917699fcab3 100644 --- a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/timeseries_references.ts +++ b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/timeseries_references.ts @@ -8,7 +8,7 @@ import { SavedObjectReference } from '../../../../../core/types'; import { VisParams } from '../../../common'; -import { INDEX_PATTERN_SAVED_OBJECT_TYPE } from '../../../../data/public'; +import { INDEX_PATTERN_SAVED_OBJECT_TYPE } from '../../../../data/common'; /** @internal **/ const REF_NAME_POSTFIX = '_ref_name'; diff --git a/test/api_integration/apis/index_patterns/deprecations/scripted_fields.ts b/test/api_integration/apis/index_patterns/deprecations/scripted_fields.ts index 168c2b005d80d..c32b9b4241206 100644 --- a/test/api_integration/apis/index_patterns/deprecations/scripted_fields.ts +++ b/test/api_integration/apis/index_patterns/deprecations/scripted_fields.ts @@ -30,7 +30,9 @@ export default function ({ getService }: FtrProviderContext) { const { body } = await supertest.get('/api/deprecations/'); const { deprecations } = body as DeprecationsGetResponse; - const dataPluginDeprecations = deprecations.filter(({ domainId }) => domainId === 'data'); + const dataPluginDeprecations = deprecations.filter( + ({ domainId }) => domainId === 'dataViews' + ); expect(dataPluginDeprecations.length).to.be(0); }); @@ -59,7 +61,9 @@ export default function ({ getService }: FtrProviderContext) { const { body } = await supertest.get('/api/deprecations/'); const { deprecations } = body as DeprecationsGetResponse; - const dataPluginDeprecations = deprecations.filter(({ domainId }) => domainId === 'data'); + const dataPluginDeprecations = deprecations.filter( + ({ domainId }) => domainId === 'dataViews' + ); expect(dataPluginDeprecations.length).to.be(1); expect(dataPluginDeprecations[0].message).to.contain(title); diff --git a/test/api_integration/apis/index_patterns/es_errors/errors.js b/test/api_integration/apis/index_patterns/es_errors/errors.js index ac656e487323d..d5ca92c371617 100644 --- a/test/api_integration/apis/index_patterns/es_errors/errors.js +++ b/test/api_integration/apis/index_patterns/es_errors/errors.js @@ -15,7 +15,7 @@ import { createNoMatchingIndicesError, isNoMatchingIndicesError, convertEsError, -} from '../../../../../src/plugins/data/server/data_views/fetcher/lib/errors'; +} from '../../../../../src/plugins/data_views/server/fetcher/lib/errors'; import { getIndexNotFoundError, getDocNotFoundError } from './lib'; diff --git a/test/functional/apps/discover/_discover_histogram.ts b/test/functional/apps/discover/_discover_histogram.ts index de12cde84edc5..36abcd81d53a0 100644 --- a/test/functional/apps/discover/_discover_histogram.ts +++ b/test/functional/apps/discover/_discover_histogram.ts @@ -52,21 +52,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { } it('should visualize monthly data with different day intervals', async () => { - const fromTime = 'Nov 01, 2017 @ 00:00:00.000'; + const fromTime = 'Nov 1, 2017 @ 00:00:00.000'; const toTime = 'Mar 21, 2018 @ 00:00:00.000'; await prepareTest(fromTime, toTime, 'Month'); const chartCanvasExist = await elasticChart.canvasExists(); expect(chartCanvasExist).to.be(true); }); it('should visualize weekly data with within DST changes', async () => { - const fromTime = 'Mar 01, 2018 @ 00:00:00.000'; - const toTime = 'May 01, 2018 @ 00:00:00.000'; + const fromTime = 'Mar 1, 2018 @ 00:00:00.000'; + const toTime = 'May 1, 2018 @ 00:00:00.000'; await prepareTest(fromTime, toTime, 'Week'); const chartCanvasExist = await elasticChart.canvasExists(); expect(chartCanvasExist).to.be(true); }); it('should visualize monthly data with different years scaled to 30 days', async () => { - const fromTime = 'Jan 01, 2010 @ 00:00:00.000'; + const fromTime = 'Jan 1, 2010 @ 00:00:00.000'; const toTime = 'Mar 21, 2019 @ 00:00:00.000'; await prepareTest(fromTime, toTime, 'Day'); const chartCanvasExist = await elasticChart.canvasExists(); @@ -75,7 +75,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(chartIntervalIconTip).to.be(true); }); it('should allow hide/show histogram, persisted in url state', async () => { - const fromTime = 'Jan 01, 2010 @ 00:00:00.000'; + const fromTime = 'Jan 1, 2010 @ 00:00:00.000'; const toTime = 'Mar 21, 2019 @ 00:00:00.000'; await prepareTest(fromTime, toTime); let canvasExists = await elasticChart.canvasExists(); @@ -95,7 +95,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(canvasExists).to.be(true); }); it('should allow hiding the histogram, persisted in saved search', async () => { - const fromTime = 'Jan 01, 2010 @ 00:00:00.000'; + const fromTime = 'Jan 1, 2010 @ 00:00:00.000'; const toTime = 'Mar 21, 2019 @ 00:00:00.000'; const savedSearch = 'persisted hidden histogram'; await prepareTest(fromTime, toTime); diff --git a/test/functional/page_objects/time_picker.ts b/test/functional/page_objects/time_picker.ts index 00133d720d884..6cc4fda513ea6 100644 --- a/test/functional/page_objects/time_picker.ts +++ b/test/functional/page_objects/time_picker.ts @@ -116,23 +116,38 @@ export class TimePickerPageObject extends FtrService { public async setAbsoluteRange(fromTime: string, toTime: string) { this.log.debug(`Setting absolute range to ${fromTime} to ${toTime}`); await this.showStartEndTimes(); + let panel!: WebElementWrapper; // set to time - await this.testSubjects.click('superDatePickerendDatePopoverButton'); - let panel = await this.getTimePickerPanel(); - await this.testSubjects.click('superDatePickerAbsoluteTab'); - await this.testSubjects.click('superDatePickerAbsoluteDateInput'); - await this.inputValue('superDatePickerAbsoluteDateInput', toTime); - await this.browser.pressKeys(this.browser.keys.ESCAPE); // close popover because sometimes browser can't find start input + await this.retry.waitFor(`endDate is set to ${toTime}`, async () => { + await this.testSubjects.click('superDatePickerendDatePopoverButton'); + panel = await this.getTimePickerPanel(); + await this.testSubjects.click('superDatePickerAbsoluteTab'); + await this.testSubjects.click('superDatePickerAbsoluteDateInput'); + await this.inputValue('superDatePickerAbsoluteDateInput', toTime); + await this.browser.pressKeys(this.browser.keys.ESCAPE); // close popover because sometimes browser can't find start input + const actualToTime = await this.testSubjects.getVisibleText( + 'superDatePickerendDatePopoverButton' + ); + this.log.debug(`Validating 'endDate' - expected: '${toTime}, actual: ${actualToTime}'`); + return toTime === actualToTime; + }); // set from time - await this.testSubjects.click('superDatePickerstartDatePopoverButton'); - await this.waitPanelIsGone(panel); - panel = await this.getTimePickerPanel(); - await this.testSubjects.click('superDatePickerAbsoluteTab'); - await this.testSubjects.click('superDatePickerAbsoluteDateInput'); - await this.inputValue('superDatePickerAbsoluteDateInput', fromTime); - await this.browser.pressKeys(this.browser.keys.ESCAPE); + await this.retry.waitFor(`endDate is set to ${fromTime}`, async () => { + await this.testSubjects.click('superDatePickerstartDatePopoverButton'); + await this.waitPanelIsGone(panel); + panel = await this.getTimePickerPanel(); + await this.testSubjects.click('superDatePickerAbsoluteTab'); + await this.testSubjects.click('superDatePickerAbsoluteDateInput'); + await this.inputValue('superDatePickerAbsoluteDateInput', fromTime); + await this.browser.pressKeys(this.browser.keys.ESCAPE); + const actualFromTime = await this.testSubjects.getVisibleText( + 'superDatePickerstartDatePopoverButton' + ); + this.log.debug(`Validating 'startDate' - expected: '${fromTime}, actual: ${actualFromTime}'`); + return fromTime === actualFromTime; + }); await this.retry.waitFor('Timepicker popover to close', async () => { return !(await this.testSubjects.exists('superDatePickerAbsoluteDateInput')); diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts index 729286b594089..3f7cdecf4affd 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts @@ -1696,6 +1696,223 @@ describe('successful migrations', () => { }, }); }); + + test('security solution is migrated to saved object references if it has a "ruleAlertId"', () => { + const migration7160 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const alert = getMockData({ + alertTypeId: 'siem.notifications', + params: { + ruleAlertId: '123', + }, + }); + + expect(migration7160(alert, migrationContext)).toEqual({ + ...alert, + attributes: { + ...alert.attributes, + legacyId: alert.id, + }, + references: [ + { + name: 'param:alert_0', + id: '123', + type: 'alert', + }, + ], + }); + }); + + test('security solution does not migrate anything if its type is not siem.notifications', () => { + const migration7160 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const alert = getMockData({ + alertTypeId: 'other-type', + params: { + ruleAlertId: '123', + }, + }); + + expect(migration7160(alert, migrationContext)).toEqual({ + ...alert, + attributes: { + ...alert.attributes, + legacyId: alert.id, + }, + }); + }); + test('security solution does not change anything if "ruleAlertId" is missing', () => { + const migration7160 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const alert = getMockData({ + alertTypeId: 'siem.notifications', + params: {}, + }); + + expect(migration7160(alert, migrationContext)).toEqual({ + ...alert, + attributes: { + ...alert.attributes, + legacyId: alert.id, + }, + }); + }); + + test('security solution will keep existing references if we do not have a "ruleAlertId" but we do already have references', () => { + const migration7160 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const alert = { + ...getMockData({ + alertTypeId: 'siem.notifications', + params: {}, + }), + references: [ + { + name: 'param:alert_0', + id: '123', + type: 'alert', + }, + ], + }; + + expect(migration7160(alert, migrationContext)).toEqual({ + ...alert, + attributes: { + ...alert.attributes, + legacyId: alert.id, + }, + references: [ + { + name: 'param:alert_0', + id: '123', + type: 'alert', + }, + ], + }); + }); + + test('security solution will keep any foreign references if they exist but still migrate other "ruleAlertId" references', () => { + const migration7160 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const alert = { + ...getMockData({ + alertTypeId: 'siem.notifications', + params: { + ruleAlertId: '123', + }, + }), + references: [ + { + name: 'foreign-name', + id: '999', + type: 'foreign-name', + }, + ], + }; + + expect(migration7160(alert, migrationContext)).toEqual({ + ...alert, + attributes: { + ...alert.attributes, + legacyId: alert.id, + }, + references: [ + { + name: 'foreign-name', + id: '999', + type: 'foreign-name', + }, + { + name: 'param:alert_0', + id: '123', + type: 'alert', + }, + ], + }); + }); + + test('security solution is idempotent and if re-run on the same migrated data will keep the same items "ruleAlertId" references', () => { + const migration7160 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const alert = { + ...getMockData({ + alertTypeId: 'siem.notifications', + params: { + ruleAlertId: '123', + }, + }), + references: [ + { + name: 'param:alert_0', + id: '123', + type: 'alert', + }, + ], + }; + + expect(migration7160(alert, migrationContext)).toEqual({ + ...alert, + attributes: { + ...alert.attributes, + legacyId: alert.id, + }, + references: [ + { + name: 'param:alert_0', + id: '123', + type: 'alert', + }, + ], + }); + }); + + test('security solution will not migrate "ruleAlertId" if it is invalid data', () => { + const migration7160 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const alert = { + ...getMockData({ + alertTypeId: 'siem.notifications', + params: { + ruleAlertId: 55, // This is invalid if it is a number and not a string + }, + }), + }; + + expect(migration7160(alert, migrationContext)).toEqual({ + ...alert, + attributes: { + ...alert.attributes, + legacyId: alert.id, + }, + }); + }); + + test('security solution will not migrate "ruleAlertId" if it is invalid data but will keep existing references', () => { + const migration7160 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const alert = { + ...getMockData({ + alertTypeId: 'siem.notifications', + params: { + ruleAlertId: 456, // This is invalid if it is a number and not a string + }, + }), + references: [ + { + name: 'param:alert_0', + id: '123', + type: 'alert', + }, + ], + }; + + expect(migration7160(alert, migrationContext)).toEqual({ + ...alert, + attributes: { + ...alert.attributes, + legacyId: alert.id, + }, + references: [ + { + name: 'param:alert_0', + id: '123', + type: 'alert', + }, + ], + }); + }); }); describe('8.0.0', () => { diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.ts index bd795b9efc61f..9dcca54285279 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations.ts @@ -54,6 +54,17 @@ export const isAnyActionSupportIncidents = (doc: SavedObjectUnsanitizedDoc): boolean => doc.attributes.alertTypeId === 'siem.signals'; +/** + * Returns true if the alert type is that of "siem.notifications" which is a legacy notification system that was deprecated in 7.16.0 + * in favor of using the newer alerting notifications system. + * @param doc The saved object alert type document + * @returns true if this is a legacy "siem.notifications" rule, otherwise false + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + */ +export const isSecuritySolutionLegacyNotification = ( + doc: SavedObjectUnsanitizedDoc +): boolean => doc.attributes.alertTypeId === 'siem.notifications'; + export function getMigrations( encryptedSavedObjects: EncryptedSavedObjectsPluginSetup, isPreconfigured: (connectorId: string) => boolean @@ -103,7 +114,11 @@ export function getMigrations( const migrateRules716 = createEsoMigration( encryptedSavedObjects, (doc): doc is SavedObjectUnsanitizedDoc => true, - pipeMigrations(setLegacyId, getRemovePreconfiguredConnectorsFromReferencesFn(isPreconfigured)) + pipeMigrations( + setLegacyId, + getRemovePreconfiguredConnectorsFromReferencesFn(isPreconfigured), + addRuleIdsToLegacyNotificationReferences + ) ); const migrationRules800 = createEsoMigration( @@ -574,6 +589,49 @@ function removeMalformedExceptionsList( } } +/** + * This migrates rule_id's within the legacy siem.notification to saved object references on an upgrade. + * We only migrate if we find these conditions: + * - ruleAlertId is a string and not null, undefined, or malformed data. + * - The existing references do not already have a ruleAlertId found within it. + * Some of these issues could crop up during either user manual errors of modifying things, earlier migration + * issues, etc... so we are safer to check them as possibilities + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + * @param doc The document that might have "ruleAlertId" to migrate into the references + * @returns The document migrated with saved object references + */ +function addRuleIdsToLegacyNotificationReferences( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + const { + attributes: { + params: { ruleAlertId }, + }, + references, + } = doc; + if (!isSecuritySolutionLegacyNotification(doc) || !isString(ruleAlertId)) { + // early return if we are not a string or if we are not a security solution notification saved object. + return doc; + } else { + const existingReferences = references ?? []; + const existingReferenceFound = existingReferences.find((reference) => { + return reference.id === ruleAlertId && reference.type === 'alert'; + }); + if (existingReferenceFound) { + // skip this if the references already exists for some uncommon reason so we do not add an additional one. + return doc; + } else { + const savedObjectReference: SavedObjectReference = { + id: ruleAlertId, + name: 'param:alert_0', + type: 'alert', + }; + const newReferences = [...existingReferences, savedObjectReference]; + return { ...doc, references: newReferences }; + } + } +} + function setLegacyId( doc: SavedObjectUnsanitizedDoc ): SavedObjectUnsanitizedDoc { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/as.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/as.ts index 4c37d9acc4868..63c3f1dcabcaf 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/as.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/as.ts @@ -5,7 +5,12 @@ * 2.0. */ -import { Datatable, ExpressionFunctionDefinition, getType } from '../../../types'; +import { + Datatable, + DatatableColumnType, + ExpressionFunctionDefinition, + getType, +} from '../../../types'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { @@ -30,14 +35,14 @@ export function asFn(): ExpressionFunctionDefinition<'as', Input, Arguments, Dat default: 'value', }, }, - fn: (input, args) => { + fn: (input, args): Datatable => { return { type: 'datatable', columns: [ { id: args.name, name: args.name, - meta: { type: getType(input) }, + meta: { type: getType(input) as DatatableColumnType }, }, ], rows: [ diff --git a/x-pack/plugins/canvas/public/components/function_reference_generator/generate_function_reference.ts b/x-pack/plugins/canvas/public/components/function_reference_generator/generate_function_reference.ts index 075e65bc24dab..289704ae79539 100644 --- a/x-pack/plugins/canvas/public/components/function_reference_generator/generate_function_reference.ts +++ b/x-pack/plugins/canvas/public/components/function_reference_generator/generate_function_reference.ts @@ -156,7 +156,7 @@ ${examplesBlock} *Returns:* ${output ? wrapInBackTicks(output) : 'Depends on your input and arguments'}\n\n`; }; -const getArgsTable = (args: { [key: string]: ExpressionFunctionParameter }) => { +const getArgsTable = (args: { [key: string]: ExpressionFunctionParameter }) => { if (!args || Object.keys(args).length === 0) { return 'None'; } diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx index 45e8944c7c667..e2793512e23df 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx @@ -23,7 +23,7 @@ import { roundToDecimalPlace, kibanaFieldFormat } from '../utils'; import { ExpandedRowFieldHeader } from '../stats_table/components/expanded_row_field_header'; import { FieldVisStats } from '../../../../../common/types'; import { ExpandedRowPanel } from '../stats_table/components/field_data_expanded_row/expanded_row_panel'; -import { IndexPatternField } from '../../../../../../../../src/plugins/data/common/data_views/fields'; +import { IndexPatternField } from '../../../../../../../../src/plugins/data_views/common'; interface Props { stats: FieldVisStats | undefined; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/utils/saved_search_utils.test.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/utils/saved_search_utils.test.ts index 4fb50aecc1062..3dd81015393b4 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/utils/saved_search_utils.test.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/utils/saved_search_utils.test.ts @@ -13,7 +13,7 @@ import { import type { SavedSearchSavedObject } from '../../../../common'; import type { SavedSearch } from '../../../../../../../src/plugins/discover/public'; import type { Filter, FilterStateStore } from '@kbn/es-query'; -import { stubbedSavedObjectIndexPattern } from '../../../../../../../src/plugins/data/common/data_views/data_view.stub'; +import { stubbedSavedObjectIndexPattern } from '../../../../../../../src/plugins/data_views/common/data_view.stub'; import { IndexPattern } from '../../../../../../../src/plugins/data/common'; import { fieldFormatsMock } from '../../../../../../../src/plugins/field_formats/common/mocks'; import { uiSettingsServiceMock } from 'src/core/public/mocks'; diff --git a/x-pack/plugins/fleet/common/constants/agent_policy.ts b/x-pack/plugins/fleet/common/constants/agent_policy.ts index d0bde39e9f2d7..95078d8ead84f 100644 --- a/x-pack/plugins/fleet/common/constants/agent_policy.ts +++ b/x-pack/plugins/fleet/common/constants/agent_policy.ts @@ -11,3 +11,17 @@ export const agentPolicyStatuses = { Active: 'active', Inactive: 'inactive', } as const; + +export const AGENT_POLICY_DEFAULT_MONITORING_DATASETS = [ + 'elastic_agent', + 'elastic_agent.elastic_agent', + 'elastic_agent.apm_server', + 'elastic_agent.filebeat', + 'elastic_agent.fleet_server', + 'elastic_agent.metricbeat', + 'elastic_agent.osquerybeat', + 'elastic_agent.packetbeat', + 'elastic_agent.endpoint_security', + 'elastic_agent.auditbeat', + 'elastic_agent.heartbeat', +]; diff --git a/x-pack/plugins/fleet/server/constants/index.ts b/x-pack/plugins/fleet/server/constants/index.ts index 0dcd5e7f47800..2ce457242c6b5 100644 --- a/x-pack/plugins/fleet/server/constants/index.ts +++ b/x-pack/plugins/fleet/server/constants/index.ts @@ -50,6 +50,7 @@ export { DEFAULT_OUTPUT, DEFAULT_PACKAGES, PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES, + AGENT_POLICY_DEFAULT_MONITORING_DATASETS, // Fleet Server index FLEET_SERVER_SERVERS_INDEX, ENROLLMENT_API_KEYS_INDEX, diff --git a/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/full_agent_policy.test.ts.snap b/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/full_agent_policy.test.ts.snap index 970bccbafa634..36044d35703ee 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/full_agent_policy.test.ts.snap +++ b/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/full_agent_policy.test.ts.snap @@ -51,25 +51,14 @@ Object { "cluster": Array [ "monitor", ], + }, + "_elastic_agent_monitoring": Object { "indices": Array [ Object { "names": Array [ - "metrics-elastic_agent-default", - "metrics-elastic_agent.elastic_agent-default", - "metrics-elastic_agent.apm_server-default", - "metrics-elastic_agent.filebeat-default", - "metrics-elastic_agent.fleet_server-default", - "metrics-elastic_agent.metricbeat-default", - "metrics-elastic_agent.osquerybeat-default", - "metrics-elastic_agent.packetbeat-default", - "metrics-elastic_agent.endpoint_security-default", - "metrics-elastic_agent.auditbeat-default", - "metrics-elastic_agent.heartbeat-default", - ], - "privileges": Array [ - "auto_configure", - "create_doc", + "metrics-default", ], + "privileges": Array [], }, ], }, @@ -148,25 +137,14 @@ Object { "cluster": Array [ "monitor", ], + }, + "_elastic_agent_monitoring": Object { "indices": Array [ Object { "names": Array [ - "metrics-elastic_agent-default", - "metrics-elastic_agent.elastic_agent-default", - "metrics-elastic_agent.apm_server-default", - "metrics-elastic_agent.filebeat-default", - "metrics-elastic_agent.fleet_server-default", - "metrics-elastic_agent.metricbeat-default", - "metrics-elastic_agent.osquerybeat-default", - "metrics-elastic_agent.packetbeat-default", - "metrics-elastic_agent.endpoint_security-default", - "metrics-elastic_agent.auditbeat-default", - "metrics-elastic_agent.heartbeat-default", - ], - "privileges": Array [ - "auto_configure", - "create_doc", + "metrics-default", ], + "privileges": Array [], }, ], }, @@ -245,25 +223,14 @@ Object { "cluster": Array [ "monitor", ], + }, + "_elastic_agent_monitoring": Object { "indices": Array [ Object { "names": Array [ - "metrics-elastic_agent-default", - "metrics-elastic_agent.elastic_agent-default", - "metrics-elastic_agent.apm_server-default", - "metrics-elastic_agent.filebeat-default", - "metrics-elastic_agent.fleet_server-default", - "metrics-elastic_agent.metricbeat-default", - "metrics-elastic_agent.osquerybeat-default", - "metrics-elastic_agent.packetbeat-default", - "metrics-elastic_agent.endpoint_security-default", - "metrics-elastic_agent.auditbeat-default", - "metrics-elastic_agent.heartbeat-default", - ], - "privileges": Array [ - "auto_configure", - "create_doc", + "metrics-default", ], + "privileges": Array [], }, ], }, diff --git a/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/monitoring_permissions.test.ts.snap b/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/monitoring_permissions.test.ts.snap new file mode 100644 index 0000000000000..a54d4beb6c041 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/monitoring_permissions.test.ts.snap @@ -0,0 +1,195 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getMonitoringPermissions With elastic agent package installed should return default logs and metrics permissions if both are enabled 1`] = ` +Object { + "_elastic_agent_monitoring": Object { + "indices": Array [ + Object { + "names": Array [ + "logs-elastic_agent.metricbeat-testnamespace123", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + Object { + "names": Array [ + "metrics-elastic_agent.metricbeat-testnamespace123", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + Object { + "names": Array [ + "logs-elastic_agent.filebeat-testnamespace123", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + Object { + "names": Array [ + "metrics-elastic_agent.filebeat-testnamespace123", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + ], + }, +} +`; + +exports[`getMonitoringPermissions With elastic agent package installed should return default logs permissions if only logs are enabled 1`] = ` +Object { + "_elastic_agent_monitoring": Object { + "indices": Array [ + Object { + "names": Array [ + "logs-elastic_agent.metricbeat-testnamespace123", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + Object { + "names": Array [ + "logs-elastic_agent.filebeat-testnamespace123", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + ], + }, +} +`; + +exports[`getMonitoringPermissions With elastic agent package installed should return default metrics permissions if only metrics are enabled 1`] = ` +Object { + "_elastic_agent_monitoring": Object { + "indices": Array [ + Object { + "names": Array [ + "metrics-elastic_agent.metricbeat-testnamespace123", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + Object { + "names": Array [ + "metrics-elastic_agent.filebeat-testnamespace123", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + ], + }, +} +`; + +exports[`getMonitoringPermissions Without elastic agent package installed should return default logs and metrics permissions if both are enabled 1`] = ` +Object { + "_elastic_agent_monitoring": Object { + "indices": Array [ + Object { + "names": Array [ + "logs-elastic_agent-testnamespace123", + "logs-elastic_agent.elastic_agent-testnamespace123", + "logs-elastic_agent.apm_server-testnamespace123", + "logs-elastic_agent.filebeat-testnamespace123", + "logs-elastic_agent.fleet_server-testnamespace123", + "logs-elastic_agent.metricbeat-testnamespace123", + "logs-elastic_agent.osquerybeat-testnamespace123", + "logs-elastic_agent.packetbeat-testnamespace123", + "logs-elastic_agent.endpoint_security-testnamespace123", + "logs-elastic_agent.auditbeat-testnamespace123", + "logs-elastic_agent.heartbeat-testnamespace123", + "metrics-elastic_agent-testnamespace123", + "metrics-elastic_agent.elastic_agent-testnamespace123", + "metrics-elastic_agent.apm_server-testnamespace123", + "metrics-elastic_agent.filebeat-testnamespace123", + "metrics-elastic_agent.fleet_server-testnamespace123", + "metrics-elastic_agent.metricbeat-testnamespace123", + "metrics-elastic_agent.osquerybeat-testnamespace123", + "metrics-elastic_agent.packetbeat-testnamespace123", + "metrics-elastic_agent.endpoint_security-testnamespace123", + "metrics-elastic_agent.auditbeat-testnamespace123", + "metrics-elastic_agent.heartbeat-testnamespace123", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + ], + }, +} +`; + +exports[`getMonitoringPermissions Without elastic agent package installed should return default logs permissions if only logs are enabled 1`] = ` +Object { + "_elastic_agent_monitoring": Object { + "indices": Array [ + Object { + "names": Array [ + "logs-elastic_agent-testnamespace123", + "logs-elastic_agent.elastic_agent-testnamespace123", + "logs-elastic_agent.apm_server-testnamespace123", + "logs-elastic_agent.filebeat-testnamespace123", + "logs-elastic_agent.fleet_server-testnamespace123", + "logs-elastic_agent.metricbeat-testnamespace123", + "logs-elastic_agent.osquerybeat-testnamespace123", + "logs-elastic_agent.packetbeat-testnamespace123", + "logs-elastic_agent.endpoint_security-testnamespace123", + "logs-elastic_agent.auditbeat-testnamespace123", + "logs-elastic_agent.heartbeat-testnamespace123", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + ], + }, +} +`; + +exports[`getMonitoringPermissions Without elastic agent package installed should return default metrics permissions if only metrics are enabled 1`] = ` +Object { + "_elastic_agent_monitoring": Object { + "indices": Array [ + Object { + "names": Array [ + "metrics-elastic_agent-testnamespace123", + "metrics-elastic_agent.elastic_agent-testnamespace123", + "metrics-elastic_agent.apm_server-testnamespace123", + "metrics-elastic_agent.filebeat-testnamespace123", + "metrics-elastic_agent.fleet_server-testnamespace123", + "metrics-elastic_agent.metricbeat-testnamespace123", + "metrics-elastic_agent.osquerybeat-testnamespace123", + "metrics-elastic_agent.packetbeat-testnamespace123", + "metrics-elastic_agent.endpoint_security-testnamespace123", + "metrics-elastic_agent.auditbeat-testnamespace123", + "metrics-elastic_agent.heartbeat-testnamespace123", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + ], + }, +} +`; diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts index 8df1234982ee6..9a9b200d14130 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts @@ -13,7 +13,11 @@ import { agentPolicyService } from '../agent_policy'; import { agentPolicyUpdateEventHandler } from '../agent_policy_update'; import { getFullAgentPolicy } from './full_agent_policy'; +import { getMonitoringPermissions } from './monitoring_permissions'; +const mockedGetElasticAgentMonitoringPermissions = getMonitoringPermissions as jest.Mock< + ReturnType +>; const mockedAgentPolicyService = agentPolicyService as jest.Mocked; function mockAgentPolicy(data: Partial) { @@ -87,6 +91,8 @@ jest.mock('../agent_policy_update'); jest.mock('../agents'); jest.mock('../package_policy'); +jest.mock('./monitoring_permissions'); + function getAgentPolicyUpdateMock() { return agentPolicyUpdateEventHandler as unknown as jest.Mock< typeof agentPolicyUpdateEventHandler @@ -97,6 +103,29 @@ describe('getFullAgentPolicy', () => { beforeEach(() => { getAgentPolicyUpdateMock().mockClear(); mockedAgentPolicyService.get.mockReset(); + mockedGetElasticAgentMonitoringPermissions.mockReset(); + mockedGetElasticAgentMonitoringPermissions.mockImplementation( + async (soClient, { logs, metrics }, namespace) => { + const names: string[] = []; + if (logs) { + names.push(`logs-${namespace}`); + } + if (metrics) { + names.push(`metrics-${namespace}`); + } + + return { + _elastic_agent_monitoring: { + indices: [ + { + names, + privileges: [], + }, + ], + }, + }; + } + ); }); it('should return a policy without monitoring if monitoring is not enabled', async () => { @@ -200,6 +229,24 @@ describe('getFullAgentPolicy', () => { }); }); + it('should get the permissions for monitoring', async () => { + mockAgentPolicy({ + namespace: 'testnamespace', + revision: 1, + monitoring_enabled: ['metrics'], + }); + await getFullAgentPolicy(savedObjectsClientMock.create(), 'agent-policy'); + + expect(mockedGetElasticAgentMonitoringPermissions).toHaveBeenCalledWith( + expect.anything(), + { + logs: false, + metrics: true, + }, + 'testnamespace' + ); + }); + it('should support a different monitoring output', async () => { mockAgentPolicy({ namespace: 'default', diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts index 4e8b3a2c1952e..561c463b998d4 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts @@ -24,21 +24,9 @@ import { import { storedPackagePoliciesToAgentInputs, dataTypes, outputType } from '../../../common'; import type { FullAgentPolicyOutputPermissions } from '../../../common'; import { getSettings } from '../settings'; -import { PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES, DEFAULT_OUTPUT } from '../../constants'; - -const MONITORING_DATASETS = [ - 'elastic_agent', - 'elastic_agent.elastic_agent', - 'elastic_agent.apm_server', - 'elastic_agent.filebeat', - 'elastic_agent.fleet_server', - 'elastic_agent.metricbeat', - 'elastic_agent.osquerybeat', - 'elastic_agent.packetbeat', - 'elastic_agent.endpoint_security', - 'elastic_agent.auditbeat', - 'elastic_agent.heartbeat', -]; +import { DEFAULT_OUTPUT } from '../../constants'; + +import { getMonitoringPermissions } from './monitoring_permissions'; export async function getFullAgentPolicy( soClient: SavedObjectsClientContract, @@ -125,41 +113,17 @@ export async function getFullAgentPolicy( cluster: DEFAULT_PERMISSIONS.cluster, }; - // TODO: fetch this from the elastic agent package https://github.com/elastic/kibana/issues/107738 - const monitoringNamespace = fullAgentPolicy.agent?.monitoring.namespace; - const monitoringPermissions: FullAgentPolicyOutputPermissions = - monitoringOutputId === dataOutputId - ? dataPermissions - : { - _elastic_agent_checks: { - cluster: DEFAULT_PERMISSIONS.cluster, - }, - }; - if ( - fullAgentPolicy.agent?.monitoring.enabled && - monitoringNamespace && - monitoringOutput && - monitoringOutput.type === outputType.Elasticsearch - ) { - let names: string[] = []; - if (fullAgentPolicy.agent.monitoring.logs) { - names = names.concat( - MONITORING_DATASETS.map((dataset) => `logs-${dataset}-${monitoringNamespace}`) - ); - } - if (fullAgentPolicy.agent.monitoring.metrics) { - names = names.concat( - MONITORING_DATASETS.map((dataset) => `metrics-${dataset}-${monitoringNamespace}`) - ); - } - - monitoringPermissions._elastic_agent_checks.indices = [ - { - names, - privileges: PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES, - }, - ]; - } + const monitoringPermissions = await getMonitoringPermissions( + soClient, + { + logs: agentPolicy.monitoring_enabled?.includes(dataTypes.Logs) ?? false, + metrics: agentPolicy.monitoring_enabled?.includes(dataTypes.Metrics) ?? false, + }, + agentPolicy.namespace + ); + monitoringPermissions._elastic_agent_checks = { + cluster: DEFAULT_PERMISSIONS.cluster, + }; // Only add permissions if output.type is "elasticsearch" fullAgentPolicy.output_permissions = Object.keys(fullAgentPolicy.outputs).reduce< @@ -167,10 +131,16 @@ export async function getFullAgentPolicy( >((outputPermissions, outputId) => { const output = fullAgentPolicy.outputs[outputId]; if (output && output.type === outputType.Elasticsearch) { - outputPermissions[outputId] = - outputId === getOutputIdForAgentPolicy(dataOutput) - ? dataPermissions - : monitoringPermissions; + const permissions: FullAgentPolicyOutputPermissions = {}; + if (outputId === getOutputIdForAgentPolicy(monitoringOutput)) { + Object.assign(permissions, monitoringPermissions); + } + + if (outputId === getOutputIdForAgentPolicy(dataOutput)) { + Object.assign(permissions, dataPermissions); + } + + outputPermissions[outputId] = permissions; } return outputPermissions; }, {}); diff --git a/x-pack/plugins/fleet/server/services/agent_policies/monitoring_permissions.test.ts b/x-pack/plugins/fleet/server/services/agent_policies/monitoring_permissions.test.ts new file mode 100644 index 0000000000000..3d928bed0f661 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/agent_policies/monitoring_permissions.test.ts @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { savedObjectsClientMock } from 'src/core/server/mocks'; + +import type { Installation, PackageInfo } from '../../types'; +import { getPackageInfo, getInstallation } from '../epm/packages'; + +import { getMonitoringPermissions } from './monitoring_permissions'; + +jest.mock('../epm/packages'); + +const mockedGetInstallation = getInstallation as jest.Mock>; +const mockedGetPackageInfo = getPackageInfo as jest.Mock>; + +describe('getMonitoringPermissions', () => { + describe('Without elastic agent package installed', () => { + it('should return default logs and metrics permissions if both are enabled', async () => { + const permissions = await getMonitoringPermissions( + savedObjectsClientMock.create(), + { logs: true, metrics: true }, + 'testnamespace123' + ); + expect(permissions).toMatchSnapshot(); + }); + it('should return default logs permissions if only logs are enabled', async () => { + const permissions = await getMonitoringPermissions( + savedObjectsClientMock.create(), + { logs: true, metrics: false }, + 'testnamespace123' + ); + expect(permissions).toMatchSnapshot(); + }); + it('should return default metrics permissions if only metrics are enabled', async () => { + const permissions = await getMonitoringPermissions( + savedObjectsClientMock.create(), + { logs: false, metrics: true }, + 'testnamespace123' + ); + expect(permissions).toMatchSnapshot(); + }); + }); + + describe('With elastic agent package installed', () => { + beforeEach(() => { + // Mock a simplified elastic agent package with only 4 datastreams logs and metrics for filebeat and metricbeat + mockedGetInstallation.mockResolvedValue({ + name: 'elastic_agent', + version: '1.0.0', + } as Installation); + mockedGetPackageInfo.mockResolvedValue({ + data_streams: [ + { + type: 'logs', + dataset: 'elastic_agent.metricbeat', + }, + { + type: 'metrics', + dataset: 'elastic_agent.metricbeat', + }, + { + type: 'logs', + dataset: 'elastic_agent.filebeat', + }, + { + type: 'metrics', + dataset: 'elastic_agent.filebeat', + }, + ], + } as PackageInfo); + }); + it('should return default logs and metrics permissions if both are enabled', async () => { + const permissions = await getMonitoringPermissions( + savedObjectsClientMock.create(), + { logs: true, metrics: true }, + 'testnamespace123' + ); + expect(permissions).toMatchSnapshot(); + }); + it('should return default logs permissions if only logs are enabled', async () => { + const permissions = await getMonitoringPermissions( + savedObjectsClientMock.create(), + { logs: true, metrics: false }, + 'testnamespace123' + ); + expect(permissions).toMatchSnapshot(); + }); + it('should return default metrics permissions if only metrics are enabled', async () => { + const permissions = await getMonitoringPermissions( + savedObjectsClientMock.create(), + { logs: false, metrics: true }, + 'testnamespace123' + ); + expect(permissions).toMatchSnapshot(); + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/agent_policies/monitoring_permissions.ts b/x-pack/plugins/fleet/server/services/agent_policies/monitoring_permissions.ts new file mode 100644 index 0000000000000..3533d829e1342 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/agent_policies/monitoring_permissions.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SavedObjectsClientContract } from 'kibana/server'; + +import { getPackageInfo, getInstallation } from '../epm/packages'; +import { getDataStreamPrivileges } from '../package_policies_to_agent_permissions'; +import { + PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES, + AGENT_POLICY_DEFAULT_MONITORING_DATASETS, +} from '../../constants'; +import type { FullAgentPolicyOutputPermissions } from '../../../common'; +import { FLEET_ELASTIC_AGENT_PACKAGE } from '../../../common'; +import { dataTypes } from '../../../common'; + +function buildDefault(enabled: { logs: boolean; metrics: boolean }, namespace: string) { + let names: string[] = []; + if (enabled.logs) { + names = names.concat( + AGENT_POLICY_DEFAULT_MONITORING_DATASETS.map((dataset) => `logs-${dataset}-${namespace}`) + ); + } + if (enabled.metrics) { + names = names.concat( + AGENT_POLICY_DEFAULT_MONITORING_DATASETS.map((dataset) => `metrics-${dataset}-${namespace}`) + ); + } + + return { + _elastic_agent_monitoring: { + indices: [ + { + names, + privileges: PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES, + }, + ], + }, + }; +} + +export async function getMonitoringPermissions( + soClient: SavedObjectsClientContract, + enabled: { logs: boolean; metrics: boolean }, + namespace: string +): Promise { + const installation = await getInstallation({ + savedObjectsClient: soClient, + pkgName: FLEET_ELASTIC_AGENT_PACKAGE, + }); + + if (!installation) { + return buildDefault(enabled, namespace); + } + + const pkg = await getPackageInfo({ + savedObjectsClient: soClient, + pkgName: installation.name, + pkgVersion: installation.version, + }); + + if (!pkg.data_streams || pkg.data_streams.length === 0) { + return buildDefault(enabled, namespace); + } + + return { + _elastic_agent_monitoring: { + indices: pkg.data_streams + .map((ds) => { + if (ds.type === dataTypes.Logs && !enabled.logs) { + return; + } + if (ds.type === dataTypes.Metrics && !enabled.metrics) { + return; + } + return getDataStreamPrivileges(ds, namespace); + }) + .filter( + ( + i + ): i is { + names: string[]; + privileges: string[]; + } => typeof i !== 'undefined' + ), + }, + }; +} diff --git a/x-pack/plugins/lens/common/expressions/format_column/format_column.test.ts b/x-pack/plugins/lens/common/expressions/format_column/format_column.test.ts index 28f9b08eff1cb..4d53f96c71fd8 100644 --- a/x-pack/plugins/lens/common/expressions/format_column/format_column.test.ts +++ b/x-pack/plugins/lens/common/expressions/format_column/format_column.test.ts @@ -7,11 +7,10 @@ import { Datatable, DatatableColumn } from 'src/plugins/expressions/public'; import { functionWrapper } from 'src/plugins/expressions/common/expression_functions/specs/tests/utils'; -import { FormatColumnArgs, formatColumn } from './index'; +import { formatColumn } from './index'; describe('format_column', () => { - const fn: (input: Datatable, args: FormatColumnArgs) => Promise = - functionWrapper(formatColumn); + const fn = functionWrapper(formatColumn); let datatable: Datatable; diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts index 2a4941995054b..d174fb831c2dc 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts @@ -13,6 +13,7 @@ import { Operation } from '../types'; import { createMockDatasource, createMockFramePublicAPI } from '../mocks'; import { layerTypes } from '../../common'; import { fieldFormatsServiceMock } from '../../../../../src/plugins/field_formats/public/mocks'; +import { defaultThresholdColor } from './color_assignment'; describe('#toExpression', () => { const xyVisualization = getXyVisualization({ @@ -319,4 +320,42 @@ describe('#toExpression', () => { ) as Ast; expect(expression.chain[0].arguments.valueLabels[0] as Ast).toEqual('inside'); }); + + it('should compute the correct series color fallback based on the layer type', () => { + const expression = xyVisualization.toExpression( + { + legend: { position: Position.Bottom, isVisible: true }, + valueLabels: 'inside', + preferredSeriesType: 'bar', + layers: [ + { + layerId: 'first', + layerType: layerTypes.DATA, + seriesType: 'area', + splitAccessor: 'd', + xAccessor: 'a', + accessors: ['b', 'c'], + yConfig: [{ forAccessor: 'a' }], + }, + { + layerId: 'threshold', + layerType: layerTypes.THRESHOLD, + seriesType: 'area', + splitAccessor: 'd', + xAccessor: 'a', + accessors: ['b', 'c'], + yConfig: [{ forAccessor: 'a' }], + }, + ], + }, + { ...frame.datasourceLayers, threshold: mockDatasource.publicAPIMock } + ) as Ast; + + function getYConfigColorForLayer(ast: Ast, index: number) { + return ((ast.chain[0].arguments.layers[index] as Ast).chain[0].arguments.yConfig[0] as Ast) + .chain[0].arguments.color; + } + expect(getYConfigColorForLayer(expression, 0)).toEqual([]); + expect(getYConfigColorForLayer(expression, 1)).toEqual([defaultThresholdColor]); + }); }); diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts index 2fce7c6a612ae..bb65b69a8d121 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts @@ -335,7 +335,12 @@ export const buildExpression = ( arguments: { forAccessor: [yConfig.forAccessor], axisMode: yConfig.axisMode ? [yConfig.axisMode] : [], - color: [yConfig.color || defaultThresholdColor], + color: + layer.layerType === layerTypes.THRESHOLD + ? [yConfig.color || defaultThresholdColor] + : yConfig.color + ? [yConfig.color] + : [], lineStyle: [yConfig.lineStyle || 'solid'], lineWidth: [yConfig.lineWidth || 1], fill: [yConfig.fill || 'none'], diff --git a/x-pack/plugins/security/common/types.ts b/x-pack/plugins/security/common/types.ts index 690aced63b187..e6354841cc9e0 100644 --- a/x-pack/plugins/security/common/types.ts +++ b/x-pack/plugins/security/common/types.ts @@ -12,3 +12,10 @@ export interface SessionInfo { canBeExtended: boolean; provider: AuthenticationProvider; } + +export enum LogoutReason { + 'SESSION_EXPIRED' = 'SESSION_EXPIRED', + 'AUTHENTICATION_ERROR' = 'AUTHENTICATION_ERROR', + 'LOGGED_OUT' = 'LOGGED_OUT', + 'UNAUTHENTICATED' = 'UNAUTHENTICATED', +} diff --git a/x-pack/plugins/security/public/authentication/login/components/index.ts b/x-pack/plugins/security/public/authentication/login/components/index.ts index 63928e4e82e37..a2c12a3c0055a 100644 --- a/x-pack/plugins/security/public/authentication/login/components/index.ts +++ b/x-pack/plugins/security/public/authentication/login/components/index.ts @@ -5,5 +5,6 @@ * 2.0. */ +export type { LoginFormProps } from './login_form'; export { LoginForm, LoginFormMessageType } from './login_form'; export { DisabledLoginForm } from './disabled_login_form'; diff --git a/x-pack/plugins/security/public/authentication/login/components/login_form/index.ts b/x-pack/plugins/security/public/authentication/login/components/login_form/index.ts index d12ea30c784cb..f1b469f669c0e 100644 --- a/x-pack/plugins/security/public/authentication/login/components/login_form/index.ts +++ b/x-pack/plugins/security/public/authentication/login/components/login_form/index.ts @@ -5,4 +5,5 @@ * 2.0. */ +export type { LoginFormProps } from './login_form'; export { LoginForm, MessageType as LoginFormMessageType } from './login_form'; diff --git a/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.tsx b/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.tsx index f60e525e0949d..b8808415fc60b 100644 --- a/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.tsx +++ b/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.tsx @@ -36,7 +36,7 @@ import type { HttpStart, IHttpFetchError, NotificationsStart } from 'src/core/pu import type { LoginSelector, LoginSelectorProvider } from '../../../../../common/login_state'; import { LoginValidator } from './validate_login'; -interface Props { +export interface LoginFormProps { http: HttpStart; notifications: NotificationsStart; selector: LoginSelector; @@ -78,7 +78,7 @@ export enum PageMode { LoginHelp, } -export class LoginForm extends Component { +export class LoginForm extends Component { private readonly validator: LoginValidator; /** @@ -88,7 +88,7 @@ export class LoginForm extends Component { */ private readonly suggestedProvider?: LoginSelectorProvider; - constructor(props: Props) { + constructor(props: LoginFormProps) { super(props); this.validator = new LoginValidator({ shouldValidate: false }); @@ -513,7 +513,7 @@ export class LoginForm extends Component { ); window.location.href = location; - } catch (err) { + } catch (err: any) { this.props.notifications.toasts.addError( err?.body?.message ? new Error(err?.body?.message) : err, { diff --git a/x-pack/plugins/security/public/authentication/login/login_page.tsx b/x-pack/plugins/security/public/authentication/login/login_page.tsx index 40438ac1c78f3..e22c38b956e8d 100644 --- a/x-pack/plugins/security/public/authentication/login/login_page.tsx +++ b/x-pack/plugins/security/public/authentication/login/login_page.tsx @@ -12,7 +12,6 @@ import classNames from 'classnames'; import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import { BehaviorSubject } from 'rxjs'; -import { parse } from 'url'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -23,6 +22,8 @@ import { LOGOUT_REASON_QUERY_STRING_PARAMETER, } from '../../../common/constants'; import type { LoginState } from '../../../common/login_state'; +import type { LogoutReason } from '../../../common/types'; +import type { LoginFormProps } from './components'; import { DisabledLoginForm, LoginForm, LoginFormMessageType } from './components'; interface Props { @@ -36,36 +37,33 @@ interface State { loginState: LoginState | null; } -const messageMap = new Map([ - [ - 'SESSION_EXPIRED', - { - type: LoginFormMessageType.Info, - content: i18n.translate('xpack.security.login.sessionExpiredDescription', { - defaultMessage: 'Your session has timed out. Please log in again.', - }), - }, - ], - [ - 'LOGGED_OUT', - { - type: LoginFormMessageType.Info, - content: i18n.translate('xpack.security.login.loggedOutDescription', { - defaultMessage: 'You have logged out of Elastic.', - }), - }, - ], - [ - 'UNAUTHENTICATED', - { - type: LoginFormMessageType.Danger, - content: i18n.translate('xpack.security.unauthenticated.errorDescription', { - defaultMessage: - "We hit an authentication error. Please check your credentials and try again. If you still can't log in, contact your system administrator.", - }), - }, - ], -]); +const loginFormMessages: Record> = { + SESSION_EXPIRED: { + type: LoginFormMessageType.Info, + content: i18n.translate('xpack.security.login.sessionExpiredDescription', { + defaultMessage: 'Your session has timed out. Please log in again.', + }), + }, + AUTHENTICATION_ERROR: { + type: LoginFormMessageType.Info, + content: i18n.translate('xpack.security.login.authenticationErrorDescription', { + defaultMessage: 'An unexpected authentication error occurred. Please log in again.', + }), + }, + LOGGED_OUT: { + type: LoginFormMessageType.Info, + content: i18n.translate('xpack.security.login.loggedOutDescription', { + defaultMessage: 'You have logged out of Elastic.', + }), + }, + UNAUTHENTICATED: { + type: LoginFormMessageType.Danger, + content: i18n.translate('xpack.security.unauthenticated.errorDescription', { + defaultMessage: + "We hit an authentication error. Please check your credentials and try again. If you still can't log in, contact your system administrator.", + }), + }, +}; export class LoginPage extends Component { state = { loginState: null } as State; @@ -77,7 +75,7 @@ export class LoginPage extends Component { try { this.setState({ loginState: await this.props.http.get('/internal/security/login_state') }); } catch (err) { - this.props.fatalErrors.add(err); + this.props.fatalErrors.add(err as Error); } loadingCount$.next(0); @@ -235,17 +233,19 @@ export class LoginPage extends Component { ); } - const query = parse(window.location.href, true).query; + const { searchParams } = new URL(window.location.href); + return ( ); }; diff --git a/x-pack/plugins/security/public/session/session_expired.mock.ts b/x-pack/plugins/security/public/session/session_expired.mock.ts index f3a0e2b88f7eb..aa9134556cab7 100644 --- a/x-pack/plugins/security/public/session/session_expired.mock.ts +++ b/x-pack/plugins/security/public/session/session_expired.mock.ts @@ -5,10 +5,12 @@ * 2.0. */ -import type { ISessionExpired } from './session_expired'; +import type { PublicMethodsOf } from '@kbn/utility-types'; + +import type { SessionExpired } from './session_expired'; export function createSessionExpiredMock() { return { logout: jest.fn(), - } as jest.Mocked; + } as jest.Mocked>; } diff --git a/x-pack/plugins/security/public/session/session_expired.test.ts b/x-pack/plugins/security/public/session/session_expired.test.ts index 40059722cee87..12fec1665ff00 100644 --- a/x-pack/plugins/security/public/session/session_expired.test.ts +++ b/x-pack/plugins/security/public/session/session_expired.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { LogoutReason } from '../../common/types'; import { SessionExpired } from './session_expired'; describe('#logout', () => { @@ -41,7 +42,7 @@ describe('#logout', () => { it(`redirects user to the logout URL with 'msg' and 'next' parameters`, async () => { const sessionExpired = new SessionExpired(LOGOUT_URL, TENANT); - sessionExpired.logout(); + sessionExpired.logout(LogoutReason.SESSION_EXPIRED); const next = `&next=${encodeURIComponent(CURRENT_URL)}`; await expect(window.location.assign).toHaveBeenCalledWith( @@ -49,12 +50,22 @@ describe('#logout', () => { ); }); + it(`redirects user to the logout URL with custom reason 'msg'`, async () => { + const sessionExpired = new SessionExpired(LOGOUT_URL, TENANT); + sessionExpired.logout(LogoutReason.AUTHENTICATION_ERROR); + + const next = `&next=${encodeURIComponent(CURRENT_URL)}`; + await expect(window.location.assign).toHaveBeenCalledWith( + `${LOGOUT_URL}?msg=AUTHENTICATION_ERROR${next}` + ); + }); + it(`adds 'provider' parameter when sessionStorage contains the provider name for this tenant`, async () => { const providerName = 'basic'; mockGetItem.mockReturnValueOnce(providerName); const sessionExpired = new SessionExpired(LOGOUT_URL, TENANT); - sessionExpired.logout(); + sessionExpired.logout(LogoutReason.SESSION_EXPIRED); expect(mockGetItem).toHaveBeenCalledTimes(1); expect(mockGetItem).toHaveBeenCalledWith(`${TENANT}/session_provider`); diff --git a/x-pack/plugins/security/public/session/session_expired.ts b/x-pack/plugins/security/public/session/session_expired.ts index 0bfbde1f31b36..ad1d4658817b4 100644 --- a/x-pack/plugins/security/public/session/session_expired.ts +++ b/x-pack/plugins/security/public/session/session_expired.ts @@ -10,10 +10,7 @@ import { LOGOUT_REASON_QUERY_STRING_PARAMETER, NEXT_URL_QUERY_STRING_PARAMETER, } from '../../common/constants'; - -export interface ISessionExpired { - logout(): void; -} +import type { LogoutReason } from '../../common/types'; const getNextParameter = () => { const { location } = window; @@ -32,11 +29,11 @@ const getProviderParameter = (tenant: string) => { export class SessionExpired { constructor(private logoutUrl: string, private tenant: string) {} - logout() { + logout(reason: LogoutReason) { const next = getNextParameter(); const provider = getProviderParameter(this.tenant); window.location.assign( - `${this.logoutUrl}?${LOGOUT_REASON_QUERY_STRING_PARAMETER}=SESSION_EXPIRED${next}${provider}` + `${this.logoutUrl}?${LOGOUT_REASON_QUERY_STRING_PARAMETER}=${reason}${next}${provider}` ); } } diff --git a/x-pack/plugins/security/public/session/session_timeout.ts b/x-pack/plugins/security/public/session/session_timeout.ts index 7a23f1bb25ad2..8b83f34f642fd 100644 --- a/x-pack/plugins/security/public/session/session_timeout.ts +++ b/x-pack/plugins/security/public/session/session_timeout.ts @@ -24,9 +24,10 @@ import { SESSION_GRACE_PERIOD_MS, SESSION_ROUTE, } from '../../common/constants'; +import { LogoutReason } from '../../common/types'; import type { SessionInfo } from '../../common/types'; import { createSessionExpirationToast } from './session_expiration_toast'; -import type { ISessionExpired } from './session_expired'; +import type { SessionExpired } from './session_expired'; export interface SessionState extends Pick { lastExtensionTime: number; @@ -58,7 +59,7 @@ export class SessionTimeout { constructor( private notifications: NotificationsSetup, - private sessionExpired: ISessionExpired, + private sessionExpired: Pick, private http: HttpSetup, private tenant: string ) {} @@ -168,7 +169,10 @@ export class SessionTimeout { const fetchSessionInMs = showWarningInMs - SESSION_CHECK_MS; // Schedule logout when session is about to expire - this.stopLogoutTimer = startTimer(() => this.sessionExpired.logout(), logoutInMs); + this.stopLogoutTimer = startTimer( + () => this.sessionExpired.logout(LogoutReason.SESSION_EXPIRED), + logoutInMs + ); // Hide warning if session has been extended if (showWarningInMs > 0) { diff --git a/x-pack/plugins/security/public/session/unauthorized_response_http_interceptor.test.ts b/x-pack/plugins/security/public/session/unauthorized_response_http_interceptor.test.ts index 35bdb911b6ed1..6d955bb5ad89e 100644 --- a/x-pack/plugins/security/public/session/unauthorized_response_http_interceptor.test.ts +++ b/x-pack/plugins/security/public/session/unauthorized_response_http_interceptor.test.ts @@ -56,6 +56,7 @@ it(`logs out 401 responses`, async () => { await drainPromiseQueue(); expect(fetchResolved).toBe(false); expect(fetchRejected).toBe(false); + expect(sessionExpired.logout).toHaveBeenCalledWith('AUTHENTICATION_ERROR'); }); it(`ignores anonymous paths`, async () => { diff --git a/x-pack/plugins/security/public/session/unauthorized_response_http_interceptor.ts b/x-pack/plugins/security/public/session/unauthorized_response_http_interceptor.ts index 43945c8f43c0f..92c5c4485bcad 100644 --- a/x-pack/plugins/security/public/session/unauthorized_response_http_interceptor.ts +++ b/x-pack/plugins/security/public/session/unauthorized_response_http_interceptor.ts @@ -12,6 +12,7 @@ import type { IHttpInterceptController, } from 'src/core/public'; +import { LogoutReason } from '../../common/types'; import type { SessionExpired } from './session_expired'; export class UnauthorizedResponseHttpInterceptor implements HttpInterceptor { @@ -39,7 +40,7 @@ export class UnauthorizedResponseHttpInterceptor implements HttpInterceptor { } if (response.status === 401) { - this.sessionExpired.logout(); + this.sessionExpired.logout(LogoutReason.AUTHENTICATION_ERROR); controller.halt(); } } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_rules_notification_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_rules_notification_alert_type.ts index 07f571bc7be1b..fa05b1fb5b07a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_rules_notification_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_rules_notification_alert_type.ts @@ -6,7 +6,6 @@ */ import { Logger } from 'src/core/server'; -import { schema } from '@kbn/config-schema'; import { parseScheduleDates } from '@kbn/securitysolution-io-ts-utils'; import { DEFAULT_RULE_NOTIFICATION_QUERY_SIZE, @@ -15,12 +14,19 @@ import { } from '../../../../common/constants'; // eslint-disable-next-line no-restricted-imports -import { LegacyNotificationAlertTypeDefinition } from './legacy_types'; +import { + LegacyNotificationAlertTypeDefinition, + legacyRulesNotificationParams, +} from './legacy_types'; import { AlertAttributes } from '../signals/types'; import { siemRuleActionGroups } from '../signals/siem_rule_action_groups'; import { scheduleNotificationActions } from './schedule_notification_actions'; import { getNotificationResultsLink } from './utils'; import { getSignals } from './get_signals'; +// eslint-disable-next-line no-restricted-imports +import { legacyExtractReferences } from './legacy_saved_object_references/legacy_extract_references'; +// eslint-disable-next-line no-restricted-imports +import { legacyInjectReferences } from './legacy_saved_object_references/legacy_inject_references'; /** * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function @@ -36,9 +42,12 @@ export const legacyRulesNotificationAlertType = ({ defaultActionGroupId: 'default', producer: SERVER_APP_ID, validate: { - params: schema.object({ - ruleAlertId: schema.string(), - }), + params: legacyRulesNotificationParams, + }, + useSavedObjectReferences: { + extractReferences: (params) => legacyExtractReferences({ logger, params }), + injectReferences: (params, savedObjectReferences) => + legacyInjectReferences({ logger, params, savedObjectReferences }), }, minimumLicenseRequired: 'basic', isExportable: false, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/README.md new file mode 100644 index 0000000000000..da9ccd30cfdac --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/README.md @@ -0,0 +1,217 @@ +This is where you add code when you have rules which contain saved object references. Saved object references are for +when you have "joins" in the saved objects between one saved object and another one. This can be a 1 to M (1 to many) +relationship for example where you have a rule which contains the "id" of another saved object. + +NOTE: This is the "legacy saved object references" and should only be for the "legacy_rules_notification_alert_type". +The legacy notification system is being phased out and deprecated in favor of using the newer alerting notification system. +It would be considered wrong to see additional code being added here at this point. However, maintenance should be expected +until we have all users moved away from the legacy system. + + +## How to create a legacy notification + +* Create a rule and activate it normally within security_solution +* Do not add actions to the rule at this point as we are exercising the older legacy system. However, you want at least one action configured such as a slack notification. +* Within dev tools do a query for all your actions and grab one of the `_id` of them without their prefix: + +```json +# See all your actions +GET .kibana/_search +{ + "query": { + "term": { + "type": "action" + } + } +} +``` + +Mine was `"_id" : "action:879e8ff0-1be1-11ec-a722-83da1c22a481"`, so I will be copying the ID of `879e8ff0-1be1-11ec-a722-83da1c22a481` + +Go to the file `detection_engine/scripts/legacy_notifications/one_action.json` and add this id to the file. Something like this: + +```json +{ + "name": "Legacy notification with one action", + "interval": "1m", <--- You can use whatever you want. Real values are "1h", "1d", "1w". I use "1m" for testing purposes. + "actions": [ + { + "id": "879e8ff0-1be1-11ec-a722-83da1c22a481", <--- My action id + "group": "default", + "params": { + "message": "Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts" + }, + "actionTypeId": ".slack" <--- I am a slack action id type. + } + ] +} +``` + +Query for an alert you want to add manually add back a legacy notification to it. Such as: + +```json +# See all your siem.signals alert types and choose one +GET .kibana/_search +{ + "query": { + "term": { + "alert.alertTypeId": "siem.signals" + } + } +} +``` + +Grab the `_id` without the alert prefix. For mine this was `933ca720-1be1-11ec-a722-83da1c22a481` + +Within the directory of detection_engine/scripts execute the script: + +```json +./post_legacy_notification.sh 933ca720-1be1-11ec-a722-83da1c22a481 +{ + "ok": "acknowledged" +} +``` + +which is going to do a few things. See the file `detection_engine/routes/rules/legacy_create_legacy_notification.ts` for the definition of the route and what it does in full, but we should notice that we have now: + +Created a legacy side car action object of type `siem-detection-engine-rule-actions` you can see in dev tools: + +```json +# See the actions "side car" which are part of the legacy notification system. +GET .kibana/_search +{ + "query": { + "term": { + "type": { + "value": "siem-detection-engine-rule-actions" + } + } + } +} +``` + +But more importantly what the saved object references are which should be this: + +```json +# Get the alert type of "siem-notifications" which is part of the legacy system. +GET .kibana/_search +{ + "query": { + "term": { + "alert.alertTypeId": "siem.notifications" + } + } +} +``` + +I omit parts but leave the important parts pre-migration and post-migration of the Saved Object. + +```json +"data..omitted": "data..omitted", +"params" : { + "ruleAlertId" : "933ca720-1be1-11ec-a722-83da1c22a481" <-- Pre-migration we had this Saved Object ID which is not part of references array below +}, +"actions" : [ + { + "group" : "default", + "params" : { + "message" : "Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts" + }, + "actionTypeId" : ".slack", + "actionRef" : "action_0" <-- Pre-migration this is correct as this work is already done within the alerting plugin + }, + "references" : [ + { + "id" : "879e8ff0-1be1-11ec-a722-83da1c22a481", + "name" : "action_0", <-- Pre-migration this is correct as this work is already done within the alerting plugin + "type" : "action" + } + ] +], +"data..omitted": "data..omitted", +``` + +Post migration this structure should look like this after Kibana has started and finished the migration. + +```json +"data..omitted": "data..omitted", +"params" : { + "ruleAlertId" : "933ca720-1be1-11ec-a722-83da1c22a481" <-- Post-migration this is not used but rather the serialized version references is used instead. +}, +"actions" : [ + { + "group" : "default", + "params" : { + "message" : "Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts" + }, + "actionTypeId" : ".slack", + "actionRef" : "action_0" + }, + "references" : [ + { + "id" : "879e8ff0-1be1-11ec-a722-83da1c22a481", + "name" : "action_0", + "type" : "action" + }, + { + "id" : "933ca720-1be1-11ec-a722-83da1c22a481", <-- Our id here is preferred and used during serialization. + "name" : "param:alert_0", <-- We add the name of our reference which is param:alert_0 similar to action_0 but with "param" + "type" : "alert" <-- We add the type which is type of rule to the references + } + ] +], +"data..omitted": "data..omitted", +``` + +Only if for some reason a migration has failed due to a bug would we fallback and try to use `"ruleAlertId" : "933ca720-1be1-11ec-a722-83da1c22a481"`, as it was last stored within SavedObjects. Otherwise all access will come from the +references array's version. If the migration fails or the fallback to the last known saved object id `"ruleAlertId" : "933ca720-1be1-11ec-a722-83da1c22a481"` does happen, then the code emits several error messages to the +user which should further encourage the user to help migrate the legacy notification system to the newer notification system. + +## Useful queries + +This gives you back the legacy notifications. + +```json +# Get the alert type of "siem-notifications" which is part of the legacy system. +GET .kibana/_search +{ + "query": { + "term": { + "alert.alertTypeId": "siem.notifications" + } + } +} +``` + +If you need to ad-hoc test what happens when the migration runs you can get the id of an alert and downgrade it, then +restart Kibana. The `ctx._source.references.remove(1)` removes the last element of the references array which is assumed +to have a rule. But it might not, so ensure you check your data structure and adjust accordingly. +```json +POST .kibana/_update/alert:933ca720-1be1-11ec-a722-83da1c22a481 +{ + "script" : { + "source": """ + ctx._source.migrationVersion.alert = "7.15.0"; + ctx._source.references.remove(1); + """, + "lang": "painless" + } +} +``` + +If you just want to remove your "param:alert_0" and it is the second array element to test the errors within the console +then you would use +```json +POST .kibana/_update/alert:933ca720-1be1-11ec-a722-83da1c22a481 +{ + "script" : { + "source": """ + ctx._source.references.remove(1); + """, + "lang": "painless" + } +} +``` + +## End to end tests +See `test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts` for tests around migrations diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_extract_references.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_extract_references.test.ts new file mode 100644 index 0000000000000..231451947a1dd --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_extract_references.test.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { loggingSystemMock } from 'src/core/server/mocks'; +// eslint-disable-next-line no-restricted-imports +import { LegacyRulesNotificationParams } from '../legacy_types'; +// eslint-disable-next-line no-restricted-imports +import { legacyExtractReferences } from './legacy_extract_references'; + +describe('legacy_extract_references', () => { + type FuncReturn = ReturnType; + let logger = loggingSystemMock.create().get('security_solution'); + + beforeEach(() => { + logger = loggingSystemMock.create().get('security_solution'); + }); + + test('It returns the references extracted as saved object references', () => { + const params: LegacyRulesNotificationParams = { + ruleAlertId: '123', + }; + expect( + legacyExtractReferences({ + logger, + params, + }) + ).toEqual({ + params, + references: [ + { + id: '123', + name: 'alert_0', + type: 'alert', + }, + ], + }); + }); + + test('It returns the empty references array if the ruleAlertId is missing for any particular unusual reason', () => { + const params = {}; + expect( + legacyExtractReferences({ + logger, + params: params as LegacyRulesNotificationParams, + }) + ).toEqual({ + params: params as LegacyRulesNotificationParams, + references: [], + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_extract_references.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_extract_references.ts new file mode 100644 index 0000000000000..1461b78ba73a6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_extract_references.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Logger } from 'src/core/server'; +import { RuleParamsAndRefs } from '../../../../../../alerting/server'; +// eslint-disable-next-line no-restricted-imports +import { LegacyRulesNotificationParams } from '../legacy_types'; +// eslint-disable-next-line no-restricted-imports +import { legacyExtractRuleId } from './legacy_extract_rule_id'; + +/** + * Extracts references and returns the saved object references. + * NOTE: You should not have to add any new ones here at all, but this keeps consistency with the other + * version(s) used for security_solution rules. + * + * How to add a new extracted references here (This should be rare or non-existent): + * --- + * Add a new file for extraction named: extract_.ts, example: extract_foo.ts + * Add a function into that file named: extract, example: extractFoo(logger, params.foo) + * Add a new line below and concat together the new extract with existing ones like so: + * + * const legacyRuleIdReferences = legacyExtractRuleId(logger, params.ruleAlertId); + * const fooReferences = extractFoo(logger, params.foo); + * const returnReferences = [...legacyRuleIdReferences, ...fooReferences]; + * + * Optionally you can remove any parameters you do not want to store within the Saved Object here: + * const paramsWithoutSavedObjectReferences = { removeParam, ...otherParams }; + * + * If you do remove params, then update the types in: security_solution/server/lib/detection_engine/notifications/legacy_rules_notification_alert_type.ts + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + * @param logger Kibana injected logger + * @param params The params of the base rule(s). + * @returns The rule parameters and the saved object references to store. + */ +export const legacyExtractReferences = ({ + logger, + params, +}: { + logger: Logger; + params: LegacyRulesNotificationParams; +}): RuleParamsAndRefs => { + const legacyRuleIdReferences = legacyExtractRuleId({ + logger, + ruleAlertId: params.ruleAlertId, + }); + const returnReferences = [...legacyRuleIdReferences]; + + // Modify params if you want to remove any elements separately here. For legacy ruleAlertId, we do not remove the id and instead + // keep it to both fail safe guard against manually removed saved object references or if there are migration issues and the saved object + // references are removed. Also keeping it we can detect and log out a warning if the reference between it and the saved_object reference + // array have changed between each other indicating the saved_object array is being mutated outside of this functionality + const paramsWithoutSavedObjectReferences = { ...params }; + + return { + references: returnReferences, + params: paramsWithoutSavedObjectReferences, + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_extract_rule_id.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_extract_rule_id.test.ts new file mode 100644 index 0000000000000..476a72461e8a0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_extract_rule_id.test.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { loggingSystemMock } from 'src/core/server/mocks'; +// eslint-disable-next-line no-restricted-imports +import { LegacyRulesNotificationParams } from '../legacy_types'; +// eslint-disable-next-line no-restricted-imports +import { legacyExtractRuleId } from './legacy_extract_rule_id'; + +describe('legacy_extract_rule_id', () => { + type FuncReturn = ReturnType; + let logger = loggingSystemMock.create().get('security_solution'); + + beforeEach(() => { + logger = loggingSystemMock.create().get('security_solution'); + }); + + test('it returns an empty array given a "undefined" ruleAlertId.', () => { + expect( + legacyExtractRuleId({ + logger, + ruleAlertId: undefined as unknown as LegacyRulesNotificationParams['ruleAlertId'], + }) + ).toEqual([]); + }); + + test('logs expect error message if given a "undefined" ruleAlertId.', () => { + expect( + legacyExtractRuleId({ + logger, + ruleAlertId: null as unknown as LegacyRulesNotificationParams['ruleAlertId'], + }) + ).toEqual([]); + + expect(logger.error).toBeCalledWith( + 'Security Solution notification (Legacy) system "ruleAlertId" is null or undefined when it never should be. ,This indicates potentially that saved object migrations did not run correctly. Returning empty reference' + ); + }); + + test('it returns the "ruleAlertId" transformed into a saved object references array.', () => { + expect( + legacyExtractRuleId({ + logger, + ruleAlertId: '123', + }) + ).toEqual([ + { + id: '123', + name: 'alert_0', + type: 'alert', + }, + ]); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_extract_rule_id.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_extract_rule_id.ts new file mode 100644 index 0000000000000..bc43fd59e68ee --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_extract_rule_id.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Logger, SavedObjectReference } from 'src/core/server'; +// eslint-disable-next-line no-restricted-imports +import { LegacyRulesNotificationParams } from '../legacy_types'; + +/** + * This extracts the "ruleAlertId" "id" and returns it as a saved object reference. + * NOTE: Due to rolling upgrades with migrations and a few bugs with migrations, I do an additional check for if "ruleAlertId" exists or not. Once + * those bugs are fixed, we can remove the "if (ruleAlertId == null) {" check, but for the time being it is there to keep things running even + * if ruleAlertId has not been migrated. + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + * @param logger The kibana injected logger + * @param ruleAlertId The rule alert id to get the id from and return it as a saved object reference. + * @returns The saved object references from the rule alert id + */ +export const legacyExtractRuleId = ({ + logger, + ruleAlertId, +}: { + logger: Logger; + ruleAlertId: LegacyRulesNotificationParams['ruleAlertId']; +}): SavedObjectReference[] => { + if (ruleAlertId == null) { + logger.error( + [ + 'Security Solution notification (Legacy) system "ruleAlertId" is null or undefined when it never should be. ', + 'This indicates potentially that saved object migrations did not run correctly. Returning empty reference', + ].join() + ); + return []; + } else { + return [ + { + id: ruleAlertId, + name: 'alert_0', + type: 'alert', + }, + ]; + } +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_inject_references.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_inject_references.test.ts new file mode 100644 index 0000000000000..ae34479e73534 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_inject_references.test.ts @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { loggingSystemMock } from 'src/core/server/mocks'; +import { SavedObjectReference } from 'src/core/server'; + +// eslint-disable-next-line no-restricted-imports +import { LegacyRulesNotificationParams } from '../legacy_types'; +// eslint-disable-next-line no-restricted-imports +import { legacyInjectReferences } from './legacy_inject_references'; + +describe('legacy_inject_references', () => { + type FuncReturn = ReturnType; + let logger = loggingSystemMock.create().get('security_solution'); + const mockSavedObjectReferences = (): SavedObjectReference[] => [ + { + id: '123', + name: 'alert_0', + type: 'alert', + }, + ]; + + beforeEach(() => { + logger = loggingSystemMock.create().get('security_solution'); + }); + + test('returns parameters from a saved object if found', () => { + const params: LegacyRulesNotificationParams = { + ruleAlertId: '123', + }; + + expect( + legacyInjectReferences({ + logger, + params, + savedObjectReferences: mockSavedObjectReferences(), + }) + ).toEqual(params); + }); + + test('returns parameters from the saved object if found with a different saved object reference id', () => { + const params: LegacyRulesNotificationParams = { + ruleAlertId: '123', + }; + + expect( + legacyInjectReferences({ + logger, + params, + savedObjectReferences: [{ ...mockSavedObjectReferences()[0], id: '456' }], + }) + ).toEqual({ + ruleAlertId: '456', + }); + }); + + test('It returns params with an added ruleAlertId if the ruleAlertId is missing due to migration bugs', () => { + const params = {} as LegacyRulesNotificationParams; + + expect( + legacyInjectReferences({ + logger, + params, + savedObjectReferences: [{ ...mockSavedObjectReferences()[0], id: '456' }], + }) + ).toEqual({ + ruleAlertId: '456', + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_inject_references.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_inject_references.ts new file mode 100644 index 0000000000000..5a7118d64ba3a --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_inject_references.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Logger, SavedObjectReference } from 'src/core/server'; +// eslint-disable-next-line no-restricted-imports +import { LegacyRulesNotificationParams } from '../legacy_types'; +// eslint-disable-next-line no-restricted-imports +import { legacyInjectRuleIdReferences } from './legacy_inject_rule_id_references'; + +/** + * Injects references and returns the saved object references. + * How to add a new injected references here (NOTE: We do not expect to add more here but we leave this as the same pattern we have in other reference sections): + * --- + * Add a new file for injection named: legacy_inject_.ts, example: legacy_inject_foo.ts + * Add a new function into that file named: legacy_inject, example: legacyInjectFooReferences(logger, params.foo) + * Add a new line below and spread the new parameter together like so: + * + * const foo = legacyInjectFooReferences(logger, params.foo, savedObjectReferences); + * const ruleParamsWithSavedObjectReferences: RuleParams = { + * ...params, + * foo, + * ruleAlertId, + * }; + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + * @param logger Kibana injected logger + * @param params The params of the base rule(s). + * @param savedObjectReferences The saved object references to merge with the rule params + * @returns The rule parameters with the saved object references. + */ +export const legacyInjectReferences = ({ + logger, + params, + savedObjectReferences, +}: { + logger: Logger; + params: LegacyRulesNotificationParams; + savedObjectReferences: SavedObjectReference[]; +}): LegacyRulesNotificationParams => { + const ruleAlertId = legacyInjectRuleIdReferences({ + logger, + ruleAlertId: params.ruleAlertId, + savedObjectReferences, + }); + const ruleParamsWithSavedObjectReferences: LegacyRulesNotificationParams = { + ...params, + ruleAlertId, + }; + return ruleParamsWithSavedObjectReferences; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_inject_rule_id_references.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_inject_rule_id_references.test.ts new file mode 100644 index 0000000000000..2f63a184875f1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_inject_rule_id_references.test.ts @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { loggingSystemMock } from 'src/core/server/mocks'; +import { SavedObjectReference } from 'src/core/server'; + +// eslint-disable-next-line no-restricted-imports +import { legacyInjectRuleIdReferences } from './legacy_inject_rule_id_references'; +// eslint-disable-next-line no-restricted-imports +import { LegacyRulesNotificationParams } from '../legacy_types'; + +describe('legacy_inject_rule_id_references', () => { + type FuncReturn = ReturnType; + let logger = loggingSystemMock.create().get('security_solution'); + const mockSavedObjectReferences = (): SavedObjectReference[] => [ + { + id: '123', + name: 'alert_0', + type: 'alert', + }, + ]; + + beforeEach(() => { + logger = loggingSystemMock.create().get('security_solution'); + }); + + test('returns parameters from the saved object if found', () => { + expect( + legacyInjectRuleIdReferences({ + logger, + ruleAlertId: '123', + savedObjectReferences: mockSavedObjectReferences(), + }) + ).toEqual('123'); + }); + + test('returns parameters from the saved object if "ruleAlertId" is undefined', () => { + expect( + legacyInjectRuleIdReferences({ + logger, + ruleAlertId: undefined as unknown as LegacyRulesNotificationParams['ruleAlertId'], + savedObjectReferences: mockSavedObjectReferences(), + }) + ).toEqual('123'); + }); + + test('prefers to use saved object references if the two are different from each other', () => { + expect( + legacyInjectRuleIdReferences({ + logger, + ruleAlertId: '456', + savedObjectReferences: mockSavedObjectReferences(), + }) + ).toEqual('123'); + }); + + test('returns sent in "ruleAlertId" if the saved object references is empty', () => { + expect( + legacyInjectRuleIdReferences({ + logger, + ruleAlertId: '456', + savedObjectReferences: [], + }) + ).toEqual('456'); + }); + + test('does not log an error if it returns parameters from the saved object when found', () => { + legacyInjectRuleIdReferences({ + logger, + ruleAlertId: '123', + savedObjectReferences: mockSavedObjectReferences(), + }); + expect(logger.error).not.toHaveBeenCalled(); + }); + + test('logs an error if found with a different saved object reference id', () => { + legacyInjectRuleIdReferences({ + logger, + ruleAlertId: '456', + savedObjectReferences: mockSavedObjectReferences(), + }); + expect(logger.error).toBeCalledWith( + 'The id of the "saved object reference id": 123 is not the same as the "saved object id": 456. Preferring and using the "saved object reference id" instead of the "saved object id"' + ); + }); + + test('logs an error if the saved object references is empty', () => { + legacyInjectRuleIdReferences({ + logger, + ruleAlertId: '123', + savedObjectReferences: [], + }); + expect(logger.error).toBeCalledWith( + 'The saved object reference was not found for the "ruleAlertId" when we were expecting to find it. Kibana migrations might not have run correctly or someone might have removed the saved object references manually. Returning the last known good "ruleAlertId" which might not work. "ruleAlertId" with its id being returned is: 123' + ); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_inject_rule_id_references.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_inject_rule_id_references.ts new file mode 100644 index 0000000000000..5cb32c6563157 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_inject_rule_id_references.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Logger, SavedObjectReference } from 'src/core/server'; +// eslint-disable-next-line no-restricted-imports +import { LegacyRulesNotificationParams } from '../legacy_types'; + +/** + * This injects any legacy "id"'s from saved object reference and returns the "ruleAlertId" using the saved object reference. If for + * some reason it is missing on saved object reference, we log an error about it and then take the last known good value from the "ruleId" + * + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + * @param logger The kibana injected logger + * @param ruleAlertId The alert id to merge the saved object reference from. + * @param savedObjectReferences The saved object references which should contain a "ruleAlertId" + * @returns The "ruleAlertId" with the saved object reference replacing any value in the saved object's id. + */ +export const legacyInjectRuleIdReferences = ({ + logger, + ruleAlertId, + savedObjectReferences, +}: { + logger: Logger; + ruleAlertId: LegacyRulesNotificationParams['ruleAlertId']; + savedObjectReferences: SavedObjectReference[]; +}): LegacyRulesNotificationParams['ruleAlertId'] => { + const referenceFound = savedObjectReferences.find((reference) => { + return reference.name === 'alert_0'; + }); + if (referenceFound) { + if (referenceFound.id !== ruleAlertId) { + // This condition should not be reached but we log an error if we encounter it to help if we migrations + // did not run correctly or we create a regression in the future. + logger.error( + [ + 'The id of the "saved object reference id": ', + referenceFound.id, + ' is not the same as the "saved object id": ', + ruleAlertId, + '. Preferring and using the "saved object reference id" instead of the "saved object id"', + ].join('') + ); + } + return referenceFound.id; + } else { + logger.error( + [ + 'The saved object reference was not found for the "ruleAlertId" when we were expecting to find it. ', + 'Kibana migrations might not have run correctly or someone might have removed the saved object references manually. ', + 'Returning the last known good "ruleAlertId" which might not work. "ruleAlertId" with its id being returned is: ', + ruleAlertId, + ].join('') + ); + return ruleAlertId; + } +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_types.ts index 2a52f14379845..28fa62f28ed2e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_types.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { schema, TypeOf } from '@kbn/config-schema'; + import { RulesClient, PartialAlert, @@ -102,8 +104,8 @@ export type LegacyNotificationExecutorOptions = AlertExecutorOptions< export const legacyIsNotificationAlertExecutor = ( obj: LegacyNotificationAlertTypeDefinition ): obj is AlertType< - AlertTypeParams, - AlertTypeParams, + LegacyRuleNotificationAlertTypeParams, + LegacyRuleNotificationAlertTypeParams, AlertTypeState, AlertInstanceState, AlertInstanceContext @@ -116,8 +118,8 @@ export const legacyIsNotificationAlertExecutor = ( */ export type LegacyNotificationAlertTypeDefinition = Omit< AlertType< - AlertTypeParams, - AlertTypeParams, + LegacyRuleNotificationAlertTypeParams, + LegacyRuleNotificationAlertTypeParams, AlertTypeState, AlertInstanceState, AlertInstanceContext, @@ -131,3 +133,19 @@ export type LegacyNotificationAlertTypeDefinition = Omit< state, }: LegacyNotificationExecutorOptions) => Promise; }; + +/** + * This is the notification type used within legacy_rules_notification_alert_type for the alert params. + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + * @see legacy_rules_notification_alert_type + */ +export const legacyRulesNotificationParams = schema.object({ + ruleAlertId: schema.string(), +}); + +/** + * This legacy rules notification type used within legacy_rules_notification_alert_type for the alert params. + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + * @see legacy_rules_notification_alert_type + */ +export type LegacyRulesNotificationParams = TypeOf; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/legacy_notifications/one_action.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/legacy_notifications/one_action.json index b1500ac6fa6b7..1966dcf5ff53c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/legacy_notifications/one_action.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/legacy_notifications/one_action.json @@ -3,7 +3,7 @@ "interval": "1m", "actions": [ { - "id": "879e8ff0-1be1-11ec-a722-83da1c22a481", + "id": "42534430-2092-11ec-99a6-05d79563c01a", "group": "default", "params": { "message": "Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/README.md index 893797afa44d7..c76a69db084ca 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/README.md +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/README.md @@ -21,6 +21,26 @@ GET .kibana/_search } ``` +If you want to manually test the downgrade of an alert then you can use this script. +```json +# Set saved object array references as empty arrays and set our migration version to be 7.14.0 +POST .kibana/_update/alert:38482620-ef1b-11eb-ad71-7de7959be71c +{ + "script" : { + "source": """ + ctx._source.migrationVersion.alert = "7.14.0"; + ctx._source.references = [] + """, + "lang": "painless" + } +} +``` + +Reload the alert in the security_solution and notice you get these errors until you restart Kibana to cause a migration moving forward. Although you get errors, +everything should still operate normally as we try to work even if migrations did not run correctly for any unforeseen reasons. + +For testing idempotentence, just re-run the same script above for a downgrade after you restarted Kibana. + ## Structure on disk Run a query in dev tools and you should see this code that adds the following savedObject references to any newly saved rule: @@ -141,4 +161,4 @@ Good examples and utilities can be found in the folder of `utils` such as: You can follow those patterns but if it doesn't fit your use case it's fine to just create a new file and wire up your new saved object references ## End to end tests -At this moment there are none. \ No newline at end of file +See `test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts` for tests around migrations diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 5da17d8a746a0..b1bcc0b67eecb 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1653,12 +1653,12 @@ "data.functions.esaggs.help": "AggConfig 集約を実行します", "data.functions.esaggs.inspector.dataRequest.description": "このリクエストはElasticsearchにクエリし、ビジュアライゼーション用のデータを取得します。", "data.functions.esaggs.inspector.dataRequest.title": "データ", - "data.functions.indexPatternLoad.help": "インデックスパターンを読み込みます", - "data.functions.indexPatternLoad.id.help": "読み込むインデックスパターンID", - "data.indexPatterns.ensureDefaultIndexPattern.bannerLabel": "Kibanaでデータの可視化と閲覧を行うには、Elasticsearchからデータを取得するためのインデックスパターンの作成が必要です。", - "data.indexPatterns.fetchFieldErrorTitle": "インデックスパターンのフィールド取得中にエラーが発生 {title}(ID:{id})", - "data.indexPatterns.indexPatternLoad.error.kibanaRequest": "サーバーでこの検索を実行するには、KibanaRequest が必要です。式実行パラメーターに要求オブジェクトを渡してください。", - "data.indexPatterns.unableWriteLabel": "インデックスパターンを書き込めません。このインデックスパターンへの最新の変更を取得するには、ページを更新してください。", + "dataViews.indexPatternLoad.help": "インデックスパターンを読み込みます", + "dataViews.functions.indexPatternLoad.id.help": "読み込むインデックスパターンID", + "dataViews.ensureDefaultIndexPattern.bannerLabel": "Kibanaでデータの可視化と閲覧を行うには、Elasticsearchからデータを取得するためのインデックスパターンの作成が必要です。", + "dataViews.fetchFieldErrorTitle": "インデックスパターンのフィールド取得中にエラーが発生 {title}(ID:{id})", + "dataViews.indexPatternLoad.error.kibanaRequest": "サーバーでこの検索を実行するには、KibanaRequest が必要です。式実行パラメーターに要求オブジェクトを渡してください。", + "dataViews.unableWriteLabel": "インデックスパターンを書き込めません。このインデックスパターンへの最新の変更を取得するには、ページを更新してください。", "data.inspector.table..dataDescriptionTooltip": "ビジュアライゼーションの元のデータを表示", "data.inspector.table.dataTitle": "データ", "data.inspector.table.downloadCSVToggleButtonLabel": "CSV をダウンロード", @@ -5198,12 +5198,6 @@ "visTypeTimeseries.indexPattern.timeRange.lastValue": "最終値", "visTypeTimeseries.indexPattern.timeRange.selectTimeRange": "選択してください", "visTypeTimeseries.indexPattern.сoarse": "粗い", - "visTypeTimeseries.indexPatternSelect.createIndexPatternText": "インデックスパターンを作成", - "visTypeTimeseries.indexPatternSelect.defaultIndexPatternText": "デフォルトのインデックスパターンが使用されています。", - "visTypeTimeseries.indexPatternSelect.label": "インデックスパターン", - "visTypeTimeseries.indexPatternSelect.queryAllIndexesText": "すべてのインデックスにクエリを実行するには * を使用します", - "visTypeTimeseries.indexPatternSelect.switchModePopover.areaLabel": "インデックスパターン選択モードを構成", - "visTypeTimeseries.indexPatternSelect.switchModePopover.title": "インデックスパターン選択モード", "visTypeTimeseries.kbnVisTypes.metricsDescription": "時系列データの高度な分析を実行します。", "visTypeTimeseries.kbnVisTypes.metricsTitle": "TSVB", "visTypeTimeseries.lastValueModeIndicator.lastBucketDate": "バケット:{lastBucketDate}", @@ -5332,7 +5326,6 @@ "visTypeTimeseries.seriesConfig.ignoreGlobalFilterLabel": "グローバルフィルターを無視しますか?", "visTypeTimeseries.seriesConfig.missingSeriesComponentDescription": "パネルタイプ {panelType} の数列コンポーネントが欠けています", "visTypeTimeseries.seriesConfig.offsetSeriesTimeLabel": "数列の時間を(1m, 1h, 1w, 1d)でオフセット", - "visTypeTimeseries.seriesConfig.overrideIndexPatternLabel": "インデックスパターンを上書きしますか?", "visTypeTimeseries.seriesConfig.templateHelpText": "eg. {templateExample}", "visTypeTimeseries.seriesConfig.templateLabel": "テンプレート", "visTypeTimeseries.sort.dragToSortAriaLabel": "ドラッグして並べ替えます", @@ -5463,7 +5456,6 @@ "visTypeTimeseries.timeseries.optionsTab.styleLabel": "スタイル", "visTypeTimeseries.timeseries.optionsTab.tooltipMode": "ツールチップ", "visTypeTimeseries.timeseries.optionsTab.truncateLegendLabel": "凡例を切り捨てますか?", - "visTypeTimeseries.timeSeries.overrideIndexPatternLabel": "インデックスパターンを上書きしますか?", "visTypeTimeseries.timeSeries.percentLabel": "パーセント", "visTypeTimeseries.timeseries.positionOptions.leftLabel": "左", "visTypeTimeseries.timeseries.positionOptions.rightLabel": "右", @@ -5528,9 +5520,8 @@ "visTypeTimeseries.visEditorVisualization.changesHaveNotBeenAppliedMessage": "ビジュアライゼーションへの変更が適用されました。", "visTypeTimeseries.visEditorVisualization.changesSuccessfullyAppliedMessage": "最新の変更が適用されました。", "visTypeTimeseries.visEditorVisualization.changesWillBeAutomaticallyAppliedMessage": "変更が自動的に適用されます。", - "visTypeTimeseries.visEditorVisualization.indexPatternMode.dismissNoticeButtonText": "閉じる", - "visTypeTimeseries.visEditorVisualization.indexPatternMode.link": "確認してください。", - "visTypeTimeseries.visEditorVisualization.indexPatternMode.notificationTitle": "TSVBはインデックスパターンをサポートします", + "visTypeTimeseries.visEditorVisualization.dataViewMode.dismissNoticeButtonText": "閉じる", + "visTypeTimeseries.visEditorVisualization.dataViewMode.link": "確認してください。", "visTypeTimeseries.visPicker.gaugeLabel": "ゲージ", "visTypeTimeseries.visPicker.metricLabel": "メトリック", "visTypeTimeseries.visPicker.tableLabel": "表", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 6d5b94f49cb6f..d8bd9774551d8 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1669,12 +1669,12 @@ "data.functions.esaggs.help": "运行 AggConfig 聚合", "data.functions.esaggs.inspector.dataRequest.description": "此请求查询 Elasticsearch,以获取可视化的数据。", "data.functions.esaggs.inspector.dataRequest.title": "数据", - "data.functions.indexPatternLoad.help": "加载索引模式", - "data.functions.indexPatternLoad.id.help": "要加载的索引模式 id", - "data.indexPatterns.ensureDefaultIndexPattern.bannerLabel": "要在 Kibana 中可视化和浏览数据,必须创建索引模式,以从 Elasticsearch 中检索数据。", - "data.indexPatterns.fetchFieldErrorTitle": "提取索引模式 {title} (ID: {id}) 的字段时出错", - "data.indexPatterns.indexPatternLoad.error.kibanaRequest": "在服务器上执行此搜索时需要 Kibana 请求。请向表达式执行模式参数提供请求对象。", - "data.indexPatterns.unableWriteLabel": "无法写入索引模式!请刷新页面以获取此索引模式的最新更改。", + "dataViews.indexPatternLoad.help": "加载索引模式", + "dataViews.functions.indexPatternLoad.id.help": "要加载的索引模式 id", + "dataViews.ensureDefaultIndexPattern.bannerLabel": "要在 Kibana 中可视化和浏览数据,必须创建索引模式,以从 Elasticsearch 中检索数据。", + "dataViews.fetchFieldErrorTitle": "提取索引模式 {title} (ID: {id}) 的字段时出错", + "dataViews.indexPatternLoad.error.kibanaRequest": "在服务器上执行此搜索时需要 Kibana 请求。请向表达式执行模式参数提供请求对象。", + "dataViews.unableWriteLabel": "无法写入索引模式!请刷新页面以获取此索引模式的最新更改。", "data.inspector.table..dataDescriptionTooltip": "查看可视化后面的数据", "data.inspector.table.dataTitle": "数据", "data.inspector.table.downloadCSVToggleButtonLabel": "下载 CSV", @@ -5241,12 +5241,6 @@ "visTypeTimeseries.indexPattern.timeRange.lastValue": "最后值", "visTypeTimeseries.indexPattern.timeRange.selectTimeRange": "选择", "visTypeTimeseries.indexPattern.сoarse": "粗糙", - "visTypeTimeseries.indexPatternSelect.createIndexPatternText": "创建索引模式", - "visTypeTimeseries.indexPatternSelect.defaultIndexPatternText": "将使用默认索引模式。", - "visTypeTimeseries.indexPatternSelect.label": "索引模式", - "visTypeTimeseries.indexPatternSelect.queryAllIndexesText": "要查询所有索引,请使用 *", - "visTypeTimeseries.indexPatternSelect.switchModePopover.areaLabel": "配置索引模式选择模式", - "visTypeTimeseries.indexPatternSelect.switchModePopover.title": "索引模式选择模式", "visTypeTimeseries.kbnVisTypes.metricsDescription": "对时间序列数据执行高级分析。", "visTypeTimeseries.kbnVisTypes.metricsTitle": "TSVB", "visTypeTimeseries.lastValueModeIndicator.lastBucketDate": "存储桶:{lastBucketDate}", @@ -5376,7 +5370,6 @@ "visTypeTimeseries.seriesConfig.ignoreGlobalFilterLabel": "忽略全局筛选?", "visTypeTimeseries.seriesConfig.missingSeriesComponentDescription": "以下面板类型缺失序列组件:{panelType}", "visTypeTimeseries.seriesConfig.offsetSeriesTimeLabel": "将序列时间偏移(1m、1h、1w、1d)", - "visTypeTimeseries.seriesConfig.overrideIndexPatternLabel": "覆盖索引模式?", "visTypeTimeseries.seriesConfig.templateHelpText": "例如 {templateExample}", "visTypeTimeseries.seriesConfig.templateLabel": "模板", "visTypeTimeseries.sort.dragToSortAriaLabel": "拖动以排序", @@ -5507,7 +5500,6 @@ "visTypeTimeseries.timeseries.optionsTab.styleLabel": "样式", "visTypeTimeseries.timeseries.optionsTab.tooltipMode": "工具提示", "visTypeTimeseries.timeseries.optionsTab.truncateLegendLabel": "截断图例?", - "visTypeTimeseries.timeSeries.overrideIndexPatternLabel": "覆盖索引模式?", "visTypeTimeseries.timeSeries.percentLabel": "百分比", "visTypeTimeseries.timeseries.positionOptions.leftLabel": "左", "visTypeTimeseries.timeseries.positionOptions.rightLabel": "右", @@ -5572,9 +5564,8 @@ "visTypeTimeseries.visEditorVisualization.changesHaveNotBeenAppliedMessage": "尚未应用对此可视化的更改。", "visTypeTimeseries.visEditorVisualization.changesSuccessfullyAppliedMessage": "已应用最新更改。", "visTypeTimeseries.visEditorVisualization.changesWillBeAutomaticallyAppliedMessage": "将自动应用更改。", - "visTypeTimeseries.visEditorVisualization.indexPatternMode.dismissNoticeButtonText": "关闭", - "visTypeTimeseries.visEditorVisualization.indexPatternMode.link": "请查看。", - "visTypeTimeseries.visEditorVisualization.indexPatternMode.notificationTitle": "TSVB 现在支持索引模式", + "visTypeTimeseries.visEditorVisualization.dataViewMode.dismissNoticeButtonText": "关闭", + "visTypeTimeseries.visEditorVisualization.dataViewMode.link": "请查看。", "visTypeTimeseries.visPicker.gaugeLabel": "仪表盘", "visTypeTimeseries.visPicker.metricLabel": "指标", "visTypeTimeseries.visPicker.tableLabel": "表", diff --git a/x-pack/plugins/uptime/public/apps/uptime_page_template.tsx b/x-pack/plugins/uptime/public/apps/uptime_page_template.tsx index 829f587e248e7..033fdcb61b28b 100644 --- a/x-pack/plugins/uptime/public/apps/uptime_page_template.tsx +++ b/x-pack/plugins/uptime/public/apps/uptime_page_template.tsx @@ -38,12 +38,14 @@ export const UptimePageTemplateComponent: React.FC = ({ path, pageHeader, const noDataConfig = useNoDataConfig(); - const { loading, error } = useHasData(); + const { loading, error, data } = useHasData(); if (error) { return ; } + const showLoading = loading && path === OVERVIEW_ROUTE && !data; + return ( <>

@@ -51,9 +53,9 @@ export const UptimePageTemplateComponent: React.FC = ({ path, pageHeader, pageHeader={pageHeader} noDataConfig={path === OVERVIEW_ROUTE && !loading ? noDataConfig : undefined} > - {loading && path === OVERVIEW_ROUTE && } + {showLoading && }
{children} diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/use_has_data.tsx b/x-pack/plugins/uptime/public/components/overview/empty_state/use_has_data.tsx index 66c68834f285f..01ca9d9927e1b 100644 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/use_has_data.tsx +++ b/x-pack/plugins/uptime/public/components/overview/empty_state/use_has_data.tsx @@ -13,7 +13,7 @@ import { UptimeRefreshContext } from '../../../contexts'; import { getDynamicSettings } from '../../../state/actions/dynamic_settings'; export const useHasData = () => { - const { loading, error } = useSelector(indexStatusSelector); + const { loading, error, data } = useSelector(indexStatusSelector); const { lastRefresh } = useContext(UptimeRefreshContext); const { settings } = useSelector(selectDynamicSettings); @@ -29,6 +29,7 @@ export const useHasData = () => { }, [dispatch]); return { + data, error, loading, settings, diff --git a/x-pack/plugins/uptime/public/contexts/uptime_index_pattern_context.tsx b/x-pack/plugins/uptime/public/contexts/uptime_index_pattern_context.tsx index 580160bac4012..8171f7e19865f 100644 --- a/x-pack/plugins/uptime/public/contexts/uptime_index_pattern_context.tsx +++ b/x-pack/plugins/uptime/public/contexts/uptime_index_pattern_context.tsx @@ -9,7 +9,7 @@ import React, { createContext, useContext, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useFetcher } from '../../../observability/public'; import { DataPublicPluginStart, IndexPattern } from '../../../../../src/plugins/data/public'; -import { selectDynamicSettings } from '../state/selectors'; +import { indexStatusSelector, selectDynamicSettings } from '../state/selectors'; import { getDynamicSettings } from '../state/actions/dynamic_settings'; export const UptimeIndexPatternContext = createContext({} as IndexPattern); @@ -19,6 +19,8 @@ export const UptimeIndexPatternContextProvider: React.FC<{ data: DataPublicPlugi data: { indexPatterns }, }) => { const { settings } = useSelector(selectDynamicSettings); + const { data: indexStatus } = useSelector(indexStatusSelector); + const dispatch = useDispatch(); useEffect(() => { @@ -30,11 +32,11 @@ export const UptimeIndexPatternContextProvider: React.FC<{ data: DataPublicPlugi const heartbeatIndices = settings?.heartbeatIndices || ''; const { data } = useFetcher>(async () => { - if (heartbeatIndices) { + if (heartbeatIndices && indexStatus?.indexExists) { // this only creates an index pattern in memory, not as saved object return indexPatterns.create({ title: heartbeatIndices }); } - }, [heartbeatIndices]); + }, [heartbeatIndices, indexStatus?.indexExists]); return ; }; diff --git a/x-pack/plugins/uptime/public/hooks/use_composite_image.test.tsx b/x-pack/plugins/uptime/public/hooks/use_composite_image.test.tsx index 79e0cde1eaab8..9e2cb1e498b73 100644 --- a/x-pack/plugins/uptime/public/hooks/use_composite_image.test.tsx +++ b/x-pack/plugins/uptime/public/hooks/use_composite_image.test.tsx @@ -191,10 +191,13 @@ describe('use composite image', () => { expect(composeSpy.mock.calls[0][1]).toBe(canvasMock); expect(composeSpy.mock.calls[0][2]).toBe(blocks); - await waitFor(() => { - expect(onComposeImageSuccess).toHaveBeenCalledTimes(1); - expect(onComposeImageSuccess).toHaveBeenCalledWith('compose success'); - }); + await waitFor( + () => { + expect(onComposeImageSuccess).toHaveBeenCalledTimes(1); + expect(onComposeImageSuccess).toHaveBeenCalledWith('compose success'); + }, + { timeout: 10000 } + ); }); }); }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts index c98fe9c7d67f2..e3a062a08ffb9 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts @@ -257,5 +257,21 @@ export default function createGetTests({ getService }: FtrProviderContext) { }, ]); }); + + it('7.16.0 migrates security_solution (Legacy) siem.notifications with "ruleAlertId" to be saved object references', async () => { + // NOTE: We hae to use elastic search directly against the ".kibana" index because alerts do not expose the references which we want to test exists + const response = await es.get<{ references: [{}] }>({ + index: '.kibana', + id: 'alert:d7a8c6a1-9394-48df-a634-d5457c35d747', + }); + expect(response.statusCode).to.eql(200); + expect(response.body._source?.references).to.eql([ + { + name: 'param:alert_0', + id: '1a4ed6ae-3c89-44b2-999d-db554144504c', + type: 'alert', + }, + ]); + }); }); } diff --git a/x-pack/test/functional/apps/discover/feature_controls/discover_spaces.ts b/x-pack/test/functional/apps/discover/feature_controls/discover_spaces.ts index 0cf4ecefe8dbd..c245b45917497 100644 --- a/x-pack/test/functional/apps/discover/feature_controls/discover_spaces.ts +++ b/x-pack/test/functional/apps/discover/feature_controls/discover_spaces.ts @@ -28,8 +28,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.timePicker.setDefaultAbsoluteRange(); } - // FLAKY https://github.com/elastic/kibana/issues/113067 - describe.skip('spaces', () => { + describe('spaces', () => { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); }); diff --git a/x-pack/test/functional/apps/discover/saved_searches.ts b/x-pack/test/functional/apps/discover/saved_searches.ts index ec649935adec2..1d8de9fe9fb6d 100644 --- a/x-pack/test/functional/apps/discover/saved_searches.ts +++ b/x-pack/test/functional/apps/discover/saved_searches.ts @@ -18,8 +18,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const panelActionsTimeRange = getService('dashboardPanelTimeRange'); const ecommerceSOPath = 'x-pack/test/functional/fixtures/kbn_archiver/reporting/ecommerce.json'; - // FLAKY https://github.com/elastic/kibana/issues/104578 - describe.skip('Discover Saved Searches', () => { + describe('Discover Saved Searches', () => { before('initialize tests', async () => { await esArchiver.load('x-pack/test/functional/es_archives/reporting/ecommerce'); await kibanaServer.importExport.load(ecommerceSOPath); diff --git a/x-pack/test/functional/apps/lens/persistent_context.ts b/x-pack/test/functional/apps/lens/persistent_context.ts index 90a7b9fe83c12..e7b99ad804cd0 100644 --- a/x-pack/test/functional/apps/lens/persistent_context.ts +++ b/x-pack/test/functional/apps/lens/persistent_context.ts @@ -31,7 +31,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visualize.navigateToNewVisualization(); await PageObjects.visualize.clickVisType('lens'); await PageObjects.lens.goToTimeRange( - 'Sep 06, 2015 @ 06:31:44.000', + 'Sep 6, 2015 @ 06:31:44.000', 'Sep 18, 2025 @ 06:31:44.000' ); await filterBar.addFilter('ip', 'is', '97.220.3.248'); @@ -45,7 +45,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should remember time range and pinned filters from discover', async () => { await PageObjects.lens.goToTimeRange( - 'Sep 07, 2015 @ 06:31:44.000', + 'Sep 7, 2015 @ 06:31:44.000', 'Sep 19, 2025 @ 06:31:44.000' ); await filterBar.toggleFilterEnabled('ip'); diff --git a/x-pack/test/functional/es_archives/alerts/data.json b/x-pack/test/functional/es_archives/alerts/data.json index 1685d909eee81..880a81a8fc209 100644 --- a/x-pack/test/functional/es_archives/alerts/data.json +++ b/x-pack/test/functional/es_archives/alerts/data.json @@ -451,4 +451,44 @@ "updated_at": "2020-06-17T15:35:39.839Z" } } -} \ No newline at end of file +} + +{ + "type": "doc", + "value": { + "id": "alert:d7a8c6a1-9394-48df-a634-d5457c35d747", + "index": ".kibana_1", + "source": { + "alert" : { + "name" : "test upgrade of ruleAlertId", + "alertTypeId" : "siem.notifications", + "consumer" : "alertsFixture", + "params" : { + "ruleAlertId" : "1a4ed6ae-3c89-44b2-999d-db554144504c" + }, + "schedule" : { + "interval" : "1m" + }, + "enabled" : true, + "actions" : [ ], + "throttle" : null, + "apiKeyOwner" : null, + "apiKey" : null, + "createdBy" : "elastic", + "updatedBy" : "elastic", + "createdAt" : "2021-07-27T20:42:55.896Z", + "muteAll" : false, + "mutedInstanceIds" : [ ], + "scheduledTaskId" : null, + "tags": [] + }, + "type" : "alert", + "migrationVersion" : { + "alert" : "7.8.0" + }, + "updated_at" : "2021-08-13T23:00:11.985Z", + "references": [ + ] + } + } +}