diff --git a/.eslintrc.js b/.eslintrc.js index de96e44479448c..93f0cbb8078a1b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -798,6 +798,7 @@ module.exports = { { files: ['x-pack/plugins/fleet/**/*.{js,mjs,ts,tsx}'], rules: { + '@typescript-eslint/consistent-type-imports': 'error', 'import/order': [ 'warn', { diff --git a/api_docs/core_saved_objects.json b/api_docs/core_saved_objects.json index 0a191960388050..d862df7ef10bbc 100644 --- a/api_docs/core_saved_objects.json +++ b/api_docs/core_saved_objects.json @@ -4581,7 +4581,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/export/errors.ts", - "lineNumber": 34 + "lineNumber": 36 } } ], @@ -4589,7 +4589,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/export/errors.ts", - "lineNumber": 34 + "lineNumber": 36 } }, { @@ -4628,7 +4628,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/export/errors.ts", - "lineNumber": 43 + "lineNumber": 45 } }, { @@ -4641,7 +4641,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/export/errors.ts", - "lineNumber": 43 + "lineNumber": 45 } } ], @@ -4649,7 +4649,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/export/errors.ts", - "lineNumber": 43 + "lineNumber": 45 } }, { @@ -4681,7 +4681,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/export/errors.ts", - "lineNumber": 58 + "lineNumber": 60 } } ], @@ -4689,7 +4689,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/export/errors.ts", - "lineNumber": 58 + "lineNumber": 60 } } ], diff --git a/api_docs/data.json b/api_docs/data.json index 30d8a7190d4f77..42d6dbc6d226ff 100644 --- a/api_docs/data.json +++ b/api_docs/data.json @@ -27843,4 +27843,4 @@ } ] } -} \ No newline at end of file +} diff --git a/api_docs/data_search.json b/api_docs/data_search.json index 8c828ff7ce80ae..0214704a88517d 100644 --- a/api_docs/data_search.json +++ b/api_docs/data_search.json @@ -19470,4 +19470,4 @@ } ] } -} \ No newline at end of file +} diff --git a/api_docs/expressions.json b/api_docs/expressions.json index ff04fcd03f046e..ee496cc7c06a33 100644 --- a/api_docs/expressions.json +++ b/api_docs/expressions.json @@ -33883,4 +33883,4 @@ } ] } -} \ No newline at end of file +} diff --git a/api_docs/lens.json b/api_docs/lens.json index 1c7581a8a1db67..235f2021e98235 100644 --- a/api_docs/lens.json +++ b/api_docs/lens.json @@ -330,7 +330,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/indexpattern_datasource/types.ts", - "lineNumber": 72 + "lineNumber": 73 }, "signature": [ "Record boolean" @@ -542,7 +542,7 @@ ], "source": { "path": "x-pack/plugins/lens/public/plugin.ts", - "lineNumber": 79 + "lineNumber": 81 }, "initialIsOpen": false }, @@ -1553,7 +1553,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/indexpattern_datasource/types.ts", - "lineNumber": 75 + "lineNumber": 76 }, "signature": [ "{ columns: Record; columnOrder: string[]; incompleteColumns?: Record | undefined; }" diff --git a/docs/api/saved-objects/export.asciidoc b/docs/api/saved-objects/export.asciidoc index 1b28d8bcbea8a2..667a9c57ca802c 100644 --- a/docs/api/saved-objects/export.asciidoc +++ b/docs/api/saved-objects/export.asciidoc @@ -36,6 +36,9 @@ experimental[] Retrieve sets of saved objects that you want to import into {kib} TIP: You must include `type` or `objects` in the request body. +NOTE: The <> configuration setting +limits the number of saved objects which may be exported. + [[saved-objects-api-export-request-response-body]] ==== Response body diff --git a/docs/api/saved-objects/import.asciidoc b/docs/api/saved-objects/import.asciidoc index 4df2f07bfcf41e..437d142402b9f7 100644 --- a/docs/api/saved-objects/import.asciidoc +++ b/docs/api/saved-objects/import.asciidoc @@ -41,6 +41,11 @@ The request body must include the multipart/form-data type. `file`:: A file exported using the export API. ++ +NOTE: The <> configuration setting +limits the number of saved objects which may be included in this file. Similarly, the +<> setting limits the overall +size of the file that can be imported. [[saved-objects-api-import-response-body]] ==== Response body diff --git a/docs/apm/advanced-queries.asciidoc b/docs/apm/advanced-queries.asciidoc index 7b771eb662616e..8aac22c7424330 100644 --- a/docs/apm/advanced-queries.asciidoc +++ b/docs/apm/advanced-queries.asciidoc @@ -2,42 +2,63 @@ [[advanced-queries]] === Query your data -Querying your APM data is a powerful tool that can make finding bottlenecks in your code even easier. -Imagine you have a user that complains about a slow response time in a specific service. -With the query bar, you can easily filter the APM app to only display trace data for that user, -or, to only show transactions that are slower than a specified time threshold. +Querying your APM data is an essential tool that can make finding bottlenecks in your code even more straightforward. -[float] -==== Example APM app queries +Using the query bar, a powerful data query feature, you can pass advanced queries on your data +to filter on specific pieces of information you’re interested in. + +The query bar comes with a handy autocomplete that helps find the fields and even provides suggestions to the data they include. +You can select the query bar and hit the down arrow on your keyboard to begin scanning recommendations. -* Exclude response times slower than 2000 ms: `transaction.duration.us > 2000000` -* Filter by response status code: `context.response.status_code ≥ 400` -* Filter by single user ID: `context.user.id : 12` +[float] +[[apm-app-advanced-queries]] +=== Querying in the APM app -When querying in the APM app, you're merely searching and selecting data from fields in Elasticsearch documents. -Queries entered into the query bar are also added as parameters to the URL, -so it's easy to share a specific query or view with others. +When querying in the APM app, you’re merely searching and selecting data from fields in {es} documents. Queries entered +into the query bar are also added as parameters to the URL, so it’s easy to share a specific query or view with others. When you type, you can begin to see some of the transaction fields available for filtering: [role="screenshot"] image::apm/images/apm-query-bar.png[Example of the Kibana Query bar in APM app in Kibana] -TIP: Read the {kibana-ref}/kuery-query.html[Kibana Query Language Enhancements] documentation to learn more about the capabilities of the {kib} query language. +[TIP] +===== +To learn more about the {kib} query language capabilities, see the {kibana-ref}/kuery-query.html[Kibana Query Language Enhancements] documentation. +===== + +[float] +[[apm-app-queries]] +==== APM app queries + +APM queries can be handy for removing noise from your data in the <>, <>, +<>, <>, and <> views. + +For example, in the *Services* view, you can quickly view a list of all the instrumented services running on your production +environment: `service.environment : production`. Or filter the list by including the APM agent's name and the host it’s running on: +`service.environment : "production" and agent.name : "java" and host.name : "prod-server1"`. + +On the *Traces* view, you might want to view failed transaction results from any of your running containers: +`transaction.result :"FAILURE" and container.id : *`. + +On the *Transactions* view, you may want to list only the slower transactions than a specified time threshold: `transaction.duration.us > 2000000`. +Or filter the list by including the service version and the Kubernetes pod it's running on: +`transaction.duration.us > 2000000 and service.version : "7.12.0" and kubernetes.pod.name : "pod-5468b47f57-pqk2m"`. [float] [[discover-advanced-queries]] === Querying in Discover Alternatively, you can query your APM documents in {kibana-ref}/discover.html[*Discover*]. -Querying documents in *Discover* works the same way as querying in the APM app, +Querying documents in *Discover* works the same way as queries in the APM app, and *Discover* supports all of the example APM app queries shown on this page. [float] -==== Example Discover query +[[discover-queries]] +==== Discover queries One example where you may want to make use of *Discover*, -is for viewing _all_ transactions for an endpoint, instead of just a sample. +is to view _all_ transactions for an endpoint instead of just a sample. TIP: Starting in v7.6, you can view ten samples per bucket in the APM app, instead of just one. diff --git a/docs/apm/filters.asciidoc b/docs/apm/filters.asciidoc index 3fe9146658eefc..56602ab7c05c90 100644 --- a/docs/apm/filters.asciidoc +++ b/docs/apm/filters.asciidoc @@ -6,49 +6,34 @@ Filter data ++++ -APM provides two different ways you can filter your data within the APM App: - -* <> -* <> - -[[global-filters]] -==== Global filters - -Global filters are ways you can filter any and all data across the APM app. -They are available in the Services, Transactions, Errors, Metrics, and Traces views, -and any filter applied will persist as you move between pages. +Global filters are ways you can filter data across the APM app based on a specific +time range or environment. They are available in the Services, Transactions, Errors, +Metrics, and Traces views, and any filter applied will persist as you move between pages. [role="screenshot"] image::apm/images/global-filters.png[Global filters available in the APM app in Kibana] -[float] -===== Global time range - -The <> in {kib} restricts APM data to a specific time period. - -[float] -[[query-bar]] -===== Query bar +[NOTE] +===== +If you prefer to use advanced queries on your data to filter on specific pieces +of information, see <>. +===== -The query bar is a powerful data query feature. -Similar to the query bar in {kibana-ref}/discover.html[Discover], -it enables you to pass advanced queries on your data to filter on particular pieces of information that you're interested in. -It comes with a handy autocomplete that helps find the fields and even provides suggestions to the data they include. -You can select the query bar and hit the down arrow on your keyboard to begin seeing recommendations. +[[global-time-range]] +==== Global time range -See <> for more information and sample queries. +The <> in {kib} restricts APM data to a specific time period. -[float] [[environment-selector]] -===== Service environment filter +==== Service environment filter The environment selector is a global filter for `service.environment`. -It allows you to view only relevant data, and is especially useful for separating development from production environments. +It allows you to view only relevant data and is especially useful for separating development from production environments. By default, all environments are displayed. If there are no environment options, you'll see "not defined". Service environments are defined when configuring your APM agents. It's vital to be consistent when naming environments in your agents. -See the documentation for each agent you're using to learn how to configure service environments: +To learn how to configure service environments, see the specific agent documentation: * *Go:* {apm-go-ref}/configuration.html#config-environment[`ELASTIC_APM_ENVIRONMENT`] * *Java:* {apm-java-ref}/config-core.html#config-environment[`environment`] @@ -58,19 +43,3 @@ See the documentation for each agent you're using to learn how to configure serv * *Python:* {apm-py-ref}/configuration.html#config-environment[`environment`] * *Ruby:* {apm-ruby-ref}/configuration.html#config-environment[`environment`] * *Real User Monitoring:* {apm-rum-ref}/configuration.html#environment[`environment`] - -[[contextual-filters]] -==== Contextual filters - -Contextual filters are ways you can filter your specific APM data on each individual page. -The filters shown are relevant to your data, and will persist between pages, -but only where they are applicable -- they are typically most useful in their original context. -As an example, if you select a host on the Services overview, then select a transaction group, -the host filter will still be applied. - -These filters are very useful for quickly and easily removing noise from your data. -With just a click, you can filter your transactions by the transaction result, -host, container ID, Kubernetes pod, and more. - -[role="screenshot"] -image::apm/images/local-filter.png[Local filters available in the APM app in Kibana] \ No newline at end of file diff --git a/docs/apm/images/apm-errors-overview.png b/docs/apm/images/apm-errors-overview.png index 5b3b00a3b1ef28..425464a1ffd21d 100644 Binary files a/docs/apm/images/apm-errors-overview.png and b/docs/apm/images/apm-errors-overview.png differ diff --git a/docs/apm/images/apm-metrics.png b/docs/apm/images/apm-metrics.png index af083b5ba3c08a..c2d609c7c4cd5d 100644 Binary files a/docs/apm/images/apm-metrics.png and b/docs/apm/images/apm-metrics.png differ diff --git a/docs/apm/images/apm-query-bar.png b/docs/apm/images/apm-query-bar.png index 92398065c25454..a1fb129d3c2004 100644 Binary files a/docs/apm/images/apm-query-bar.png and b/docs/apm/images/apm-query-bar.png differ diff --git a/docs/apm/images/apm-services-overview.png b/docs/apm/images/apm-services-overview.png index 3a56d597abfb79..1c16ac5b572c3d 100644 Binary files a/docs/apm/images/apm-services-overview.png and b/docs/apm/images/apm-services-overview.png differ diff --git a/docs/apm/images/apm-traces.png b/docs/apm/images/apm-traces.png index ed15423b42c511..0e9062ee448b43 100644 Binary files a/docs/apm/images/apm-traces.png and b/docs/apm/images/apm-traces.png differ diff --git a/docs/apm/images/apm-transactions-overview.png b/docs/apm/images/apm-transactions-overview.png index 1b25668f0fd92a..be292c37e24e01 100644 Binary files a/docs/apm/images/apm-transactions-overview.png and b/docs/apm/images/apm-transactions-overview.png differ diff --git a/docs/apm/images/global-filters.png b/docs/apm/images/global-filters.png index 70ae50aea6057c..f93a5214c316b1 100644 Binary files a/docs/apm/images/global-filters.png and b/docs/apm/images/global-filters.png differ diff --git a/docs/apm/images/jvm-metrics-overview.png b/docs/apm/images/jvm-metrics-overview.png index 4b882574e2b9a6..c6f28f7bdf48f6 100644 Binary files a/docs/apm/images/jvm-metrics-overview.png and b/docs/apm/images/jvm-metrics-overview.png differ diff --git a/docs/developer/telemetry.asciidoc b/docs/developer/telemetry.asciidoc index 75f42a860624b8..45d2a140cf8b96 100644 --- a/docs/developer/telemetry.asciidoc +++ b/docs/developer/telemetry.asciidoc @@ -6,6 +6,7 @@ To help us provide a good developer experience, we track some straightforward me The operations we current report timing data for: * Total execution time of `yarn kbn bootstrap` +* Total execution time of `@kbn/optimizer` runs as well as the following metadata about the runs: The number of bundles created, the number of bundles which were cached, usage of `--watch`, `--dist`, `--workers` and `--no-cache` flags, and the count of themes being built. Along with the execution time of each execution, we ship the following information about your machine to the service: diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index dc0e60b5986a45..df5ce62cc07afc 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md @@ -100,6 +100,8 @@ readonly links: { }; readonly indexPatterns: { readonly introduction: string; + readonly fieldFormattersNumber: string; + readonly fieldFormattersString: string; }; readonly addData: string; readonly kibana: string; diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md index 0f362f302104b2..742b54e19216e0 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md @@ -9,6 +9,7 @@ ```typescript esFilters: { FilterLabel: (props: import("./ui/filter_bar/filter_editor/lib/filter_label").FilterLabelProps) => JSX.Element; + FilterItem: (props: import("./ui/filter_bar/filter_item").FilterItemProps) => JSX.Element; FILTERS: typeof FILTERS; FilterStateStore: typeof FilterStateStore; buildEmptyFilter: (isPinned: boolean, index?: string | undefined) => import("../common").Filter; diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.appendsessionstarttimetoname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.appendsessionstarttimetoname.md new file mode 100644 index 00000000000000..6b6b58d1838c93 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.appendsessionstarttimetoname.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSessionInfoProvider](./kibana-plugin-plugins-data-public.searchsessioninfoprovider.md) > [appendSessionStartTimeToName](./kibana-plugin-plugins-data-public.searchsessioninfoprovider.appendsessionstarttimetoname.md) + +## SearchSessionInfoProvider.appendSessionStartTimeToName property + +Append session start time to a session name, `true` by default + +Signature: + +```typescript +appendSessionStartTimeToName?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.md index 77125bc8deeadd..b6dfbd9fbb7cf8 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.md @@ -16,6 +16,7 @@ export interface SearchSessionInfoProviderboolean | Append session start time to a session name, true by default | | [getName](./kibana-plugin-plugins-data-public.searchsessioninfoprovider.getname.md) | () => Promise<string> | User-facing name of the session. e.g. will be displayed in saved Search Sessions management list | | [getUrlGeneratorData](./kibana-plugin-plugins-data-public.searchsessioninfoprovider.geturlgeneratordata.md) | () => Promise<{
urlGeneratorId: ID;
initialState: UrlGeneratorStateMapping[ID]['State'];
restoreState: UrlGeneratorStateMapping[ID]['State'];
}> | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsserviceprovider.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsserviceprovider.md index d408f00e33c9e8..698b4bc7f20434 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsserviceprovider.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsserviceprovider.md @@ -14,6 +14,6 @@ export declare class IndexPatternsServiceProvider implements PluginSignature: ```typescript -setup(core: CoreSetup, { expressions }: IndexPatternsServiceSetupDeps): void; +setup(core: CoreSetup, { logger, expressions }: IndexPatternsServiceSetupDeps): void; ``` ## Parameters @@ -15,7 +15,7 @@ setup(core: CoreSetup, { expressio | Parameter | Type | Description | | --- | --- | --- | | core | CoreSetup<DataPluginStartDependencies, DataPluginStart> | | -| { expressions } | IndexPatternsServiceSetupDeps | | +| { logger, expressions } | IndexPatternsServiceSetupDeps | | Returns: diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md index e0734bc017f4f5..16d9ce457603eb 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md @@ -109,6 +109,5 @@ | [KibanaContext](./kibana-plugin-plugins-data-server.kibanacontext.md) | | | [ParsedInterval](./kibana-plugin-plugins-data-server.parsedinterval.md) | | | [Query](./kibana-plugin-plugins-data-server.query.md) | | -| [SearchRequestHandlerContext](./kibana-plugin-plugins-data-server.searchrequesthandlercontext.md) | | | [TimeRange](./kibana-plugin-plugins-data-server.timerange.md) | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md index 9dc38f96df4be6..f479ffd52e9b89 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md @@ -12,7 +12,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }; @@ -31,7 +31,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchrequesthandlercontext.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchrequesthandlercontext.md deleted file mode 100644 index f031ddfbd09af7..00000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchrequesthandlercontext.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [SearchRequestHandlerContext](./kibana-plugin-plugins-data-server.searchrequesthandlercontext.md) - -## SearchRequestHandlerContext type - -Signature: - -```typescript -export declare type SearchRequestHandlerContext = IScopedSearchClient; -``` diff --git a/docs/management/ingest-pipelines/ingest-pipelines.asciidoc b/docs/management/ingest-pipelines/ingest-pipelines.asciidoc deleted file mode 100644 index d9745bfef524a8..00000000000000 --- a/docs/management/ingest-pipelines/ingest-pipelines.asciidoc +++ /dev/null @@ -1,170 +0,0 @@ -[role="xpack"] -[[ingest-node-pipelines]] -== Ingest Node Pipelines - -*Ingest Node Pipelines* enables you to create and manage {es} -pipelines that perform common transformations and -enrichments on your data. For example, you might remove a field, -rename an existing field, or set a new field. - -To begin, open the main menu, then click *Stack Management > Ingest Node Pipelines*. With *Ingest Node Pipelines*, you can: - -* View a list of your pipelines and drill down into details. -* Create a pipeline that defines a series of tasks, known as processors. -* Test a pipeline before feeding it with real data to ensure the pipeline works as expected. -* Delete a pipeline that is no longer needed. - -[role="screenshot"] -image:management/ingest-pipelines/images/ingest-pipeline-list.png["Ingest node pipeline list"] - -[float] -=== Required permissions - -The minimum required permissions to access *Ingest Node Pipelines* are -the `manage_pipeline` and `cluster:monitor/nodes/info` cluster privileges. - -To add privileges, open the main menu, then click *Stack Management > Roles*. - -[role="screenshot"] -image:management/ingest-pipelines/images/ingest-pipeline-privileges.png["Privileges required for Ingest Node Pipelines"] - -[float] -[[ingest-node-pipelines-manage]] -=== Manage pipelines - -From the list view, you can to drill down into the details of a pipeline. -To -edit, clone, or delete a pipeline, use the *Actions* menu. - -If you don’t have any pipelines, you can create one using the -*Create pipeline* form. You’ll define processors to transform documents -in a specific way. To handle exceptions, you can optionally define -failure processors to execute immediately after a failed processor. -Before creating the pipeline, you can verify it provides the expected output. - -[float] -[[ingest-node-pipelines-example]] -==== Example: Create a pipeline - -In this example, you’ll create a pipeline to handle server logs in the -Common Log Format. The log looks similar to this: - -[source,js] ----------------------------------- -212.87.37.154 - - [05/May/2020:16:21:15 +0000] \"GET /favicon.ico HTTP/1.1\" -200 3638 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) -AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36\" ----------------------------------- - -The log contains an IP address, timestamp, and user agent. You want to give -these three items their own field in {es} for fast search and visualization. -You also want to know where the request is coming from. - -. In *Ingest Node Pipelines*, click *Create a pipeline*. -. Provide a name and description for the pipeline. -. Add a grok processor to parse the log message: - -.. Click *Add a processor* and select the *Grok* processor type. -.. Set the field input to `message` and enter the following grok pattern: -+ -[source,js] ----------------------------------- -%{IPORHOST:clientip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] "%{WORD:verb} %{DATA:request} HTTP/%{NUMBER:httpversion}" %{NUMBER:response:int} (?:-|%{NUMBER:bytes:int}) %{QS:referrer} %{QS:agent} ----------------------------------- -+ -.. Click *Update* to save the processor. - -. Add processors to map the date, IP, and user agent fields. - -.. Map the appropriate field to each processor type: -+ --- -* **Date**: `timestamp` -* **GeoIP**: `clientip` -* **User agent**: `agent` - -For the **Date** processor, you also need to specify the date format you want to use: `dd/MMM/YYYY:HH:mm:ss Z`. --- -Your form should look similar to this: -+ -[role="screenshot"] -image:management/ingest-pipelines/images/ingest-pipeline-processor.png["Processors for Ingest Node Pipelines"] -+ -Alternatively, you can click the **Import processors** link and define the processors as JSON: -+ -[source,js] ----------------------------------- -{ - "processors": [ - { - "grok": { - "field": "message", - "patterns": ["%{IPORHOST:clientip} %{USER:ident} %{USER:auth} \\[%{HTTPDATE:timestamp}\\] \"%{WORD:verb} %{DATA:request} HTTP/%{NUMBER:httpversion}\" %{NUMBER:response:int} (?:-|%{NUMBER:bytes:int}) %{QS:referrer} %{QS:agent}"] - } - }, - { - "date": { - "field": "timestamp", - "formats": [ "dd/MMM/YYYY:HH:mm:ss Z" ] - } - }, - { - "geoip": { - "field": "clientip" - } - }, - { - "user_agent": { - "field": "agent" - } - } - ] -} ----------------------------------- -+ -The four {ref}/ingest-processors.html[processors] will run sequentially: -{ref}/grok-processor.html[grok], {ref}/date-processor.html[date], -{ref}/geoip-processor.html[geoip], and {ref}/user-agent-processor.html[user_agent]. You can reorder processors using the arrow icon next to each processor. - -. To test the pipeline to verify that it produces the expected results, click *Add documents*. - -. In the *Documents* tab, provide a sample document for testing: -+ -[source,js] ----------------------------------- -[ - { - "_source": { - "message": "212.87.37.154 - - [05/May/2020:16:21:15 +0000] \"GET /favicon.ico HTTP/1.1\" 200 3638 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36\"" - } - } -] ----------------------------------- - -. Click *Run the pipeline* and check if the pipeline worked as expected. -+ -You can also -view the verbose output and refresh the output from this view. - -. If everything looks correct, close the panel, and then click *Create pipeline*. -+ -At this point, you’re ready to use the Elasticsearch index API to load -the logs data. - -. In the Kibana Console, index a document with the pipeline -you created. -+ -[source,js] ----------------------------------- -PUT my-index/_doc/1?pipeline=access_logs -{ - "message": "212.87.37.154 - - [05/May/2020:16:21:15 +0000] \"GET /favicon.ico HTTP/1.1\" 200 3638 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36\"" -} ----------------------------------- - -. To verify, run: -+ -[source,js] ----------------------------------- -GET my-index/_doc/1 ----------------------------------- diff --git a/docs/management/managing-saved-objects.asciidoc b/docs/management/managing-saved-objects.asciidoc index 9e26abca115fcd..423865c69d90ce 100644 --- a/docs/management/managing-saved-objects.asciidoc +++ b/docs/management/managing-saved-objects.asciidoc @@ -52,6 +52,12 @@ navigate to the NDJSON file that represents the objects to import. By default, saved objects already in {kib} are overwritten. +NOTE: The <> configuration setting +limits the number of saved objects which may be included in this file. Similarly, the +<> setting limits the overall +size of the file that can be imported. + + [float] ==== Export @@ -63,6 +69,10 @@ You have two options for exporting saved objects. This action creates an NDJSON with all your saved objects. By default, the NDJSON includes child objects that are related to the saved objects. Exported dashboards include their associated index patterns. +NOTE: The <> configuration setting +limits the number of saved objects which may be exported. + + [float] [role="xpack"] [[managing-saved-objects-copy-to-space]] diff --git a/docs/management/numeral.asciidoc b/docs/management/numeral.asciidoc index 79ce9fdbd1a7ed..893873eb1075a2 100644 --- a/docs/management/numeral.asciidoc +++ b/docs/management/numeral.asciidoc @@ -10,8 +10,8 @@ Numeral formatting patterns are used in multiple places in {kib}, including: * <> * <> -* <> -* <> +* <> +* <> The simplest pattern format is `0`, and the default {kib} pattern is `0,0.[000]`. The numeral pattern syntax expresses: diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index fa3e13de651539..538f963c0bd002 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -179,11 +179,6 @@ This content has moved. See <>. This content has moved. See <>. -[role="exclude",id="lens"] -== Lens - -This content has moved. See <>. - [role="exclude",id="known-plugins"] == Known plugins @@ -257,7 +252,7 @@ This page has been moved. Refer to <> [[heatmap-chart]] === Heatmap Chart -This page has been moved. Refer to <>. +This page has been moved. Refer to <>. [float] [[interface-overview]] @@ -296,3 +291,14 @@ This content has moved. Refer to <>. [role="exclude",id="explore-dashboard-data"] This content has moved. Refer to <>. + +[role="exclude",id="ingest-node-pipelines"] +== Ingest Node Pipelines + +This content has moved. See {ref}/ingest.html[Ingest pipelines]. + + +[role="exclude",id="create-panels-with-timelion"] +== Timelion + +This content has moved. refer to <>. diff --git a/docs/settings/banners-settings.asciidoc b/docs/settings/banners-settings.asciidoc new file mode 100644 index 00000000000000..2a68cbe82f9f2b --- /dev/null +++ b/docs/settings/banners-settings.asciidoc @@ -0,0 +1,35 @@ +[role="xpack"] +[[banners-settings-kb]] +=== Banner settings in {kib} +++++ +Banners settings +++++ + +Banners are disabled by default. You need to manually configure them in order to use the feature. + +You can configure the `xpack.banners` settings in your `kibana.yml` file. + +[[general-banners-settings-kb]] +==== General banner settings + +[cols="2*<"] +|=== + +| `xpack.banners.placement` +| Set to `header` to enable the header banner. Defaults to `disabled`. + +| `xpack.banners.textContent` +| The text to display inside the banner, either plain text or Markdown. + +| `xpack.banners.textColor` +| The color for the banner text. Defaults to `#8A6A0A`. + +| `xpack.banners.backgroundColor` +| The color of the banner background. Defaults to `#FFF9E8`. + +|=== + +[NOTE] +==== +The `banners` plugin is a https://www.elastic.co/subscriptions[subscription feature] +==== \ No newline at end of file diff --git a/docs/settings/settings-xkb.asciidoc b/docs/settings/settings-xkb.asciidoc index 4a211976be8cfd..1bd38578750d7f 100644 --- a/docs/settings/settings-xkb.asciidoc +++ b/docs/settings/settings-xkb.asciidoc @@ -12,6 +12,7 @@ For more {kib} configuration settings, see <>. include::alert-action-settings.asciidoc[] include::apm-settings.asciidoc[] +include::banners-settings.asciidoc[] include::dev-settings.asciidoc[] include::graph-settings.asciidoc[] include::infrastructure-ui-settings.asciidoc[] diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 070bf03b39e4bb..9934f8508707c1 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -455,6 +455,20 @@ manner that is inconsistent with `/proc/self/cgroup`. | Override for cgroup cpuacct path when mounted in a manner that is inconsistent with `/proc/self/cgroup`. +|[[savedObjects-maxImportExportSize]] `savedObjects.maxImportExportSize:` + | The maximum count of saved objects that can be imported or exported. +This setting exists to prevent the {kib} server from runnning out of memory when handling +large numbers of saved objects. It is recommended to only raise this setting if you are +confident your server can hold this many objects in memory. +*Default: `10000`* + +|[[savedObjects-maxImportPayloadBytes]] `savedObjects.maxImportPayloadBytes:` + | The maximum byte size of a saved objects import that the {kib} server will accept. +This setting exists to prevent the {kib} server from runnning out of memory when handling +a large import payload. Note that this setting overrides the more general +<> for saved object imports only. +*Default: `26214400`* + |[[server-basePath]] `server.basePath:` | Enables you to specify a path to mount {kib} at if you are running behind a proxy. Use the <> setting to tell {kib} @@ -495,7 +509,7 @@ back end server. To allow remote users to connect, set the value to the IP addre | The number of milliseconds to wait for additional data before restarting the <> counter. *Default: `"120000"`* -| `server.maxPayloadBytes:` +|[[server-maxPayloadBytes]] `server.maxPayloadBytes:` | The maximum payload size in bytes for incoming server requests. *Default: `1048576`* @@ -671,6 +685,7 @@ Valid locales are: `en`, `zh-CN`, `ja-JP`. *Default: `en`* include::{kib-repo-dir}/settings/alert-action-settings.asciidoc[] include::{kib-repo-dir}/settings/apm-settings.asciidoc[] +include::{kib-repo-dir}/settings/banners-settings.asciidoc[] include::{kib-repo-dir}/settings/dev-settings.asciidoc[] include::{kib-repo-dir}/settings/graph-settings.asciidoc[] include::{kib-repo-dir}/settings/fleet-settings.asciidoc[] diff --git a/docs/user/canvas.asciidoc b/docs/user/canvas.asciidoc index e5ac44a4e54018..1face015f1f76b 100644 --- a/docs/user/canvas.asciidoc +++ b/docs/user/canvas.asciidoc @@ -126,7 +126,7 @@ image::images/canvas-element-select.gif[Canvas elements] * *{es} documents* — Access your data in {es} without using aggregations. To use, select an index and fields, and optionally enter a query using the <>. Use the *{es} documents* data source when you have low volume datasets, to view raw documents, or to plot exact, non-aggregated values on a chart. -* *Timelion* — Access your time series data using <> queries. To use *Timelion* queries, you can enter a query using the <>. +* *Timelion* — Access your time series data using <> queries. To use *Timelion* queries, you can enter a query using the <>. + Each element can display a different data source, and pages and workpads often contain multiple data sources. diff --git a/docs/user/dashboard/advanced-editors.asciidoc b/docs/user/dashboard/advanced-editors.asciidoc deleted file mode 100644 index e2e32c8373f5a7..00000000000000 --- a/docs/user/dashboard/advanced-editors.asciidoc +++ /dev/null @@ -1,65 +0,0 @@ -[[add-panels-with-advanced-editors]] -== Create panels with the advanced editors - -{kib} provides you with three advanced editors that you can use to manually create dashboard panels. - -[float] -[[tsvb-advanced-editor]] -=== TSVB - -*TSVB* is a time series data visualization editor that allows you to use the full power of the {es} aggregation framework. - -With *TSVB*, you can: - -* Combine an infinite number of <> to display your data. -* Annotate time series data with timestamped events from an {es} index. -* View the data in several types of visualizations, including charts, data tables, and markdown panels. -* Display multiple <> in each visualization. -* Customize the data with labels and colors. - -image::images/tsvb.png[TSVB UI] - -[float] -[[vega-advanced-editor]] -=== Custom visualizations - -*Vega* and *Vega-Lite* are visualization grammars that are integrated into {kib} for custom visualizations. - -* *Vega-Lite* — A high-level grammar for rapid analysis - -* *Vega* — A declarative language with support for interactivity - -*Vega* and *Vega-Lite* panels can display one or more data sources, including {es}, Elastic Map Service, -URL, or static data, and support <> that allow you to embed the panels on your dashboard and add interactive tools. - -Use *Vega* or *Vega-Lite* when you want to create visualizations with: - -* Aggregations that use `nested` or `parent/child` mapping -* Aggregations without an index pattern -* Queries that use custom time filters -* Complex calculations -* Extracted data from _source instead of aggregations -* Scatter charts, sankey charts, and custom maps -* An unsupported visual theme - -These grammars have some limitations: they do not support tables, and can't run queries conditionally. - -image::images/vega.png[Vega UI] - -For detailed *Vega* and *Vega-Lite* information and examples, refer to <>. - -[float] -[[timelion-advanced-editor]] -=== Timelion - -*Timelion* is driven by a simple expression language that you use to: - -* Retrieve time series data from one or more indices -* Perform math across two or more time series -* Visualize the results - -image:dashboard/images/timelion.png[Timelion] - -include::tsvb.asciidoc[] -include::vega.asciidoc[] -include::timelion.asciidoc[] \ No newline at end of file diff --git a/docs/user/dashboard/aggregation-based.asciidoc b/docs/user/dashboard/aggregation-based.asciidoc index b086214c87ed45..49c092f8baa4cd 100644 --- a/docs/user/dashboard/aggregation-based.asciidoc +++ b/docs/user/dashboard/aggregation-based.asciidoc @@ -1,32 +1,14 @@ [[add-aggregation-based-visualization-panels]] -== Create aggregation-based visualization panels +=== Aggregation-based Aggregation-based visualizations are the core {kib} panels, and are not optimized -for a specific use case. If you are new to {kib}, <> is recommended as a -simpler way to get started. - -The main features that these panel types have compared to other {kib} panel types are: - -* Support for heat map, tag cloud, gauge, and goal chart types -* Support for split charts at up to 3 levels of aggregation, more than *Lens* and *TSVB* -* Time series data is not required -* Ability to use a <> as an input -* Data table can be sorted, also supports summary row and percentage column features -* Can assign specific colors to series -* Ability for plugin authors to extend the features - -These panel types also have some limitations: - -* Not as simple as *Lens* -* Limited styling options -* No support for math -* No support for multiple indices +for a specific use case. [float] -[[types-of-visualization-panels]] -=== Types of aggregation-based panels +[[types-of-visualizations]] +==== Types of aggregation-based visualizations -{kib} supports the following types of aggregation-based panels. +{kib} supports the following types of aggregation-based visualizations. [cols="50, 50"] |=== @@ -106,13 +88,13 @@ create visual art for a specific topic. [float] [[create-aggregation-based-panel]] -=== Create an aggregation-based panel +==== Create an aggregation-based visualization panel -Choose the type of panel you want to create, then use the editor to configure the options. Each panel type supports different options. +Choose the type of visualization you want to create, then use the editor to configure the options. . From the dashboard, click *Create panel*, then click *Aggregation based* on the *New visualization* window. -.. Click the type of panel you want to create. +.. Click the type of visualization you want to create. .. Click the data source you want to visualize. @@ -132,13 +114,13 @@ image:images/aggregation-based-color-picker.png[Color picker] [float] [[try-it-aggregation-based-panel]] -=== Try it: Create an aggregation-based bar chart +==== Try it: Create an aggregation-based bar chart You collected data from your web server, and you want to visualize and analyze the data on a dashboard. To create a dashboard panel of the data, create a bar chart that displays the top five log traffic sources for every three hours. [float] -==== Add the data and create the dashboard +===== Add the data and create the dashboard Add the sample web logs data that you'll use to create the bar chart, then create the dashboard. @@ -151,7 +133,7 @@ Add the sample web logs data that you'll use to create the bar chart, then creat . On the *Dashboards* page, click *Create dashboard*. [float] -==== Open and set up the aggregation-based bar chart +===== Open and set up the aggregation-based bar chart Open the bar chart visualization builder and change the time range. @@ -165,7 +147,7 @@ Open the bar chart visualization builder and change the time range. [float] [[tutorial-configure-the-bar-chart]] -==== Create the bar chart +===== Create the bar chart To create the bar chart, add a <>, then add the terms sub-aggregation to display the top values. @@ -196,7 +178,7 @@ TIP: Aggregation-based panels support a maximum of three *Split series*. image:images/bar-chart-tutorial-2.png[Bar chart with sample logs data] [float] -==== Save the panel +===== Save the panel Save and add the visualization panel to the dashboard. diff --git a/docs/user/dashboard/create-panels-with-editors.asciidoc b/docs/user/dashboard/create-panels-with-editors.asciidoc new file mode 100644 index 00000000000000..8e047819fd1c6c --- /dev/null +++ b/docs/user/dashboard/create-panels-with-editors.asciidoc @@ -0,0 +1,131 @@ +[[create-panels-with-editors]] +== Create panels with editors + +{kib} provides several editors that you can use to create dashboard panels. + +[cols="2"] +|=== + +| <> +| Create visualizations with the drag and drop editor. *Lens* is recommended for most users. + +| <> +| Create visualizations with your geographical data. + +| <> +| Create visualizations with your time series data. + +| <> +| Create custom visualizations with the *Vega* and *Vega-Lite* grammars. + +| <> +| Build most visualization types using {es} <>. + +| <> +| Create visualizations with your time series data using a simple expression language. + +|=== + +[float] +[[lens-editor]] +=== Lens + +*Lens* is the drag and drop editor that creates visualizations of your data. + +With *Lens*, you can: + +* Use the automatically generated suggestions to change the visualization type. +* Create visualizations with multiple layers and indices. +* Change the aggregation and labels to customize the data. + +[role="screenshot"] +image:dashboard/images/lens_advanced_1_1.png[Lens] + +[float] +[[tsvb-editor]] +=== TSVB + +*TSVB* is a time series data visualization editor that allows you to use the full power of the {es} aggregation framework. + +With *TSVB*, you can: + +* Combine an infinite number of <> to display your data. +* Annotate time series data with timestamped events from an {es} index. +* View the data in several types of visualizations, including charts, data tables, and markdown panels. +* Display multiple <> in each visualization. +* Customize the data with labels and colors. + +[role="screenshot"] +image::images/tsvb.png[TSVB UI] + +[float] +[[custom-visualizations]] +=== Custom visualizations + +*Vega* and *Vega-Lite* are visualization grammars that are integrated into {kib} for custom visualizations. + +* *Vega-Lite* — A high-level grammar for rapid analysis. + +* *Vega* — A declarative language with support for interactivity. + +*Vega* and *Vega-Lite* panels can display one or more data sources, including {es}, Elastic Map Service, +URL, or static data, and support <> that allow you to embed the panels on your dashboard and add interactive tools. + +Use *Vega* or *Vega-Lite* when you want to create visualizations with: + +* Aggregations that use `nested` or `parent/child` mapping +* Aggregations without an index pattern +* Queries that use custom time filters +* Complex calculations +* Extracted data from _source instead of aggregations +* Scatter charts, sankey charts, and custom maps +* An unsupported visual theme + +These grammars have some limitations: they do not support tables, and can't run queries conditionally. + +[role="screenshot"] +image::images/vega.png[Vega UI] + +For detailed *Vega* and *Vega-Lite* information and examples, refer to <>. + +[float] +[[aggregation-based]] +=== Aggregation-based + +With aggregation-based visualizations, you can: + +* Create heat map, tag cloud, gauge, and goal visualizations +* Split charts up to three aggregation levels, which is more than *Lens* and *TSVB* +* Time series data is not required +* Use a <> as an input +* Sort data tables and use the summary row and percentage column features +* Assign colors to series +* Extend features with plugins + +[role="screenshot"] +image:dashboard/images/aggregation_based.png[Aggregation-based editor] + +Aggregation-based visualizations include the following limitations: + +* Limited styling options +* Math is unsupported +* Multiple indices is unsupported + +[float] +[[timelion-editor]] +=== Timelion + +*Timelion* is driven by a simple expression language that you use to: + +* Retrieve time series data from one or more indices +* Perform math across two or more time series +* Visualize the results + +[role="screenshot"] +image:dashboard/images/timelion.png[Timelion] + +include::lens.asciidoc[] +include::tsvb.asciidoc[] +include::vega.asciidoc[] +include::aggregation-based.asciidoc[] +include::timelion.asciidoc[] \ No newline at end of file diff --git a/docs/user/dashboard/dashboard.asciidoc b/docs/user/dashboard/dashboard.asciidoc index 1752f067801b49..89fa564b0ac710 100644 --- a/docs/user/dashboard/dashboard.asciidoc +++ b/docs/user/dashboard/dashboard.asciidoc @@ -12,25 +12,18 @@ then compare the panels side-by-side to identify the patterns and connections in [role="screenshot"] image:images/Dashboard_example.png[Example dashboard] -[[tsvb]] - Dashboards support many types of panels, and provide several editors that you can use to create panels. [cols="2"] |=== -| <> -| The drag and drop editor that creates visualizations of your data. *Lens* is recommended for most users. +| <> +| Use the *Lens*, *TSVB*, *Vega*, and *Timelion* editors to help you create visualizations of your data, or create aggregation-based visualizations using {es} <>. +*Lens* is recommended for most users. | <> | Create beautiful displays of your geographical data. -| <> -| Visualize time series data with *TSVB* or *Timelion*, or create a custom visualization using *Vega* or *Vega-Lite*. - -| <> -| Build most visualization types using {es} <>. - | <> | Add context to your panels with <>, or add dynamic filters with <>. @@ -152,6 +145,8 @@ Add a panel that displays the results from machine learning anomaly detection jo . On the *Add from library* flyout, click *Create new*, then select *ML Anomaly Swim Lane*. +[[tsvb]] + [float] [[arrange-panels]] [[moving-containers]] @@ -332,11 +327,16 @@ for *Lens* panels. [role="xpack"] To download *Lens* panel data in a CSV file: -Open the *Lens* panel menu, then select *More > Download as CSV*. +. Open the *Lens* panel menu. +. Select *More > Download as CSV*. ++ [role="screenshot"] image::images/download_csv_context_menu.png[Download as CSV from panel context menu] +Each layer produces a single CSV file with columns. +When you download multiple layers, the file names combine the visualization and layer index names. + To download all other panel data in a CSV file: . Open the panel menu, then select *Inspect*. @@ -385,11 +385,11 @@ log in using their {kib} credentials, via reverse proxy, or enable <>. It is important to export dashboards with all references needed. -- -include::lens.asciidoc[] +include::tutorial-create-a-dashboard-of-lens-panels.asciidoc[] -include::advanced-editors.asciidoc[] +include::lens-advanced.asciidoc[] -include::aggregation-based.asciidoc[] +include::create-panels-with-editors.asciidoc[] include::enhance-dashboards.asciidoc[] diff --git a/docs/user/dashboard/images/aggregation_based.png b/docs/user/dashboard/images/aggregation_based.png new file mode 100644 index 00000000000000..0e34dbf665811e Binary files /dev/null and b/docs/user/dashboard/images/aggregation_based.png differ diff --git a/docs/user/dashboard/images/lens.png b/docs/user/dashboard/images/lens.png new file mode 100644 index 00000000000000..a78af1414df737 Binary files /dev/null and b/docs/user/dashboard/images/lens.png differ diff --git a/docs/user/dashboard/images/lens_advanced_1_1.png b/docs/user/dashboard/images/lens_advanced_1_1.png new file mode 100644 index 00000000000000..9d67f5cc691ffb Binary files /dev/null and b/docs/user/dashboard/images/lens_advanced_1_1.png differ diff --git a/docs/user/dashboard/images/lens_advanced_1_1_2.png b/docs/user/dashboard/images/lens_advanced_1_1_2.png new file mode 100644 index 00000000000000..8b5fe130ce7b73 Binary files /dev/null and b/docs/user/dashboard/images/lens_advanced_1_1_2.png differ diff --git a/docs/user/dashboard/images/lens_advanced_1_2.png b/docs/user/dashboard/images/lens_advanced_1_2.png new file mode 100644 index 00000000000000..bc5d1f67be46c3 Binary files /dev/null and b/docs/user/dashboard/images/lens_advanced_1_2.png differ diff --git a/docs/user/dashboard/images/lens_advanced_2_1.png b/docs/user/dashboard/images/lens_advanced_2_1.png new file mode 100644 index 00000000000000..5090f0d3b2841f Binary files /dev/null and b/docs/user/dashboard/images/lens_advanced_2_1.png differ diff --git a/docs/user/dashboard/images/lens_advanced_2_1_1.png b/docs/user/dashboard/images/lens_advanced_2_1_1.png new file mode 100644 index 00000000000000..f4d9ca488782e6 Binary files /dev/null and b/docs/user/dashboard/images/lens_advanced_2_1_1.png differ diff --git a/docs/user/dashboard/images/lens_advanced_2_2.png b/docs/user/dashboard/images/lens_advanced_2_2.png new file mode 100644 index 00000000000000..820bc3bd4dfa9e Binary files /dev/null and b/docs/user/dashboard/images/lens_advanced_2_2.png differ diff --git a/docs/user/dashboard/images/lens_advanced_2_2_1.png b/docs/user/dashboard/images/lens_advanced_2_2_1.png new file mode 100644 index 00000000000000..3124dd1de0654a Binary files /dev/null and b/docs/user/dashboard/images/lens_advanced_2_2_1.png differ diff --git a/docs/user/dashboard/images/lens_advanced_3_1.gif b/docs/user/dashboard/images/lens_advanced_3_1.gif new file mode 100644 index 00000000000000..5fdf58eb2fc868 Binary files /dev/null and b/docs/user/dashboard/images/lens_advanced_3_1.gif differ diff --git a/docs/user/dashboard/images/lens_advanced_3_1_1.png b/docs/user/dashboard/images/lens_advanced_3_1_1.png new file mode 100644 index 00000000000000..4d52a23cc2cff0 Binary files /dev/null and b/docs/user/dashboard/images/lens_advanced_3_1_1.png differ diff --git a/docs/user/dashboard/images/lens_advanced_3_2.png b/docs/user/dashboard/images/lens_advanced_3_2.png new file mode 100644 index 00000000000000..20da2ed706dfd2 Binary files /dev/null and b/docs/user/dashboard/images/lens_advanced_3_2.png differ diff --git a/docs/user/dashboard/images/lens_advanced_3_3.png b/docs/user/dashboard/images/lens_advanced_3_3.png new file mode 100644 index 00000000000000..1d88bcd238ca30 Binary files /dev/null and b/docs/user/dashboard/images/lens_advanced_3_3.png differ diff --git a/docs/user/dashboard/images/lens_advanced_4_1.png b/docs/user/dashboard/images/lens_advanced_4_1.png new file mode 100644 index 00000000000000..43c8db213d482c Binary files /dev/null and b/docs/user/dashboard/images/lens_advanced_4_1.png differ diff --git a/docs/user/dashboard/images/lens_advanced_4_2.png b/docs/user/dashboard/images/lens_advanced_4_2.png new file mode 100644 index 00000000000000..4b3e98910e7b79 Binary files /dev/null and b/docs/user/dashboard/images/lens_advanced_4_2.png differ diff --git a/docs/user/dashboard/images/lens_advanced_result.png b/docs/user/dashboard/images/lens_advanced_result.png new file mode 100644 index 00000000000000..19963d87c8e1c5 Binary files /dev/null and b/docs/user/dashboard/images/lens_advanced_result.png differ diff --git a/docs/user/dashboard/images/lens_area_percentage.png b/docs/user/dashboard/images/lens_area_percentage.png new file mode 100644 index 00000000000000..e682dbb24c0563 Binary files /dev/null and b/docs/user/dashboard/images/lens_area_percentage.png differ diff --git a/docs/user/dashboard/images/lens_bucketed_aggregation_advanced_dropdown.png b/docs/user/dashboard/images/lens_bucketed_aggregation_advanced_dropdown.png new file mode 100644 index 00000000000000..f4616905ad3443 Binary files /dev/null and b/docs/user/dashboard/images/lens_bucketed_aggregation_advanced_dropdown.png differ diff --git a/docs/user/dashboard/images/lens_drag_drop.gif b/docs/user/dashboard/images/lens_drag_drop.gif deleted file mode 100644 index 22939467daa120..00000000000000 Binary files a/docs/user/dashboard/images/lens_drag_drop.gif and /dev/null differ diff --git a/docs/user/dashboard/images/lens_drag_drop_1.gif b/docs/user/dashboard/images/lens_drag_drop_1.gif new file mode 100644 index 00000000000000..e2fc30fc7caed0 Binary files /dev/null and b/docs/user/dashboard/images/lens_drag_drop_1.gif differ diff --git a/docs/user/dashboard/images/lens_drag_drop_1.png b/docs/user/dashboard/images/lens_drag_drop_1.png new file mode 100644 index 00000000000000..3462e26026d255 Binary files /dev/null and b/docs/user/dashboard/images/lens_drag_drop_1.png differ diff --git a/docs/user/dashboard/images/lens_drag_drop_2.png b/docs/user/dashboard/images/lens_drag_drop_2.png new file mode 100644 index 00000000000000..4c3c35ce58c9f8 Binary files /dev/null and b/docs/user/dashboard/images/lens_drag_drop_2.png differ diff --git a/docs/user/dashboard/images/lens_drag_drop_3.gif b/docs/user/dashboard/images/lens_drag_drop_3.gif new file mode 100644 index 00000000000000..39211300301b06 Binary files /dev/null and b/docs/user/dashboard/images/lens_drag_drop_3.gif differ diff --git a/docs/user/dashboard/images/lens_fields_indexpattern.png b/docs/user/dashboard/images/lens_fields_indexpattern.png new file mode 100644 index 00000000000000..87804fd06020d7 Binary files /dev/null and b/docs/user/dashboard/images/lens_fields_indexpattern.png differ diff --git a/docs/user/dashboard/images/lens_ip_mixed_sorting.png b/docs/user/dashboard/images/lens_ip_mixed_sorting.png new file mode 100644 index 00000000000000..b10d77b723583f Binary files /dev/null and b/docs/user/dashboard/images/lens_ip_mixed_sorting.png differ diff --git a/docs/user/dashboard/images/lens_ipv4_sorting.png b/docs/user/dashboard/images/lens_ipv4_sorting.png new file mode 100644 index 00000000000000..cec8c458d1f3c7 Binary files /dev/null and b/docs/user/dashboard/images/lens_ipv4_sorting.png differ diff --git a/docs/user/dashboard/images/lens_value_labels_partition_toggle.png b/docs/user/dashboard/images/lens_value_labels_partition_toggle.png new file mode 100644 index 00000000000000..82ee7b1b7eedb3 Binary files /dev/null and b/docs/user/dashboard/images/lens_value_labels_partition_toggle.png differ diff --git a/docs/user/dashboard/images/lens_value_labels_xychart_toggle.png b/docs/user/dashboard/images/lens_value_labels_xychart_toggle.png new file mode 100644 index 00000000000000..8cf5feb5712ac8 Binary files /dev/null and b/docs/user/dashboard/images/lens_value_labels_xychart_toggle.png differ diff --git a/docs/user/dashboard/lens-advanced.asciidoc b/docs/user/dashboard/lens-advanced.asciidoc new file mode 100644 index 00000000000000..6b090f6017f5d7 --- /dev/null +++ b/docs/user/dashboard/lens-advanced.asciidoc @@ -0,0 +1,259 @@ +[[create-a-dashboard-of-panels-with-ecommerce-data]] +== Tutorial: Create a dashboard of panels with ecommerce sales data + +You collected sales data from your store, and you want to visualize and analyze the data on a dashboard. +To create dashboard panels of the data, open the *Lens* visualization builder, then +create the visualization panels that best display the data. + +When you've completed the tutorial, you'll have a dashboard that provides you with a complete overview of your ecommerce sales data. + +[role="screenshot"] +image::images/lens_advanced_result.png[Dashboard view] + +[discrete] +[[add-the-data-and-create-the-dashboard-advanced]] +=== Add the data and create the dashboard + +To create visualizations of the data from your store, add the data set, then create the dashboard. + +. From the {kib} *Home* page, click *Try our sample data*. + +. From *Sample eCommerce orders*, click *Add data*. + +. Open the main menu, then click *Dashboard*. + +. On the *Dashboards* page, click *Create dashboard*. + +[float] +[[open-and-set-up-lens-advanced]] +=== Open and set up Lens + +Open the *Lens* editor, then make sure the correct fields appear. + +. From the dashboard, click *Create panel*. + +. On the *New visualization* window, click *Lens*. ++ +[role="screenshot"] +image::images/lens_end_to_end_1_1.png[New visualization popover] + +. Make sure the *kibana_sample_data_ecommerce_* index appears. + +[discrete] +[[view-the-number-of-transactions-per-day]] +=== View the number of transactions per hour + +To determine the number of orders made every hour, create a bar chart, then add the chart to the dashboard. + +. Set the <> to *Last 30 days*. + +. From the *Available fields* list, drag and drop *Records* to the visualization builder. ++ +[role="screenshot"] +image::images/lens_advanced_1_1.png[Added records to the workspace] + +. Change the *Vertical axis* title and display the number of orders per day. + +.. In the editor, click *Count of Records*. + +.. In the *Display name* field, enter `Number of orders`. + +.. Click *Add advanced options > Normalize by unit*. + +.. From the *Normalize by unit* dropdown, select *per hour*, then click *Close*. + +. To hide the *Horizontal axis* label, open the *Bottom Axis* menu, then deselect *Show*. ++ +[role="screenshot"] +image::images/lens_advanced_1_1_2.png[Bottom axis menu] ++ +You have a bar chart that shows you how many orders were made at your store every hour. ++ +[role="screenshot"] +image::images/lens_advanced_1_2.png[Orders per day] + +. Click *Save and return*. + +[discrete] +[[view-the-cumulative-number-of-products-sold-over-time]] +=== View the cumulative number of products sold on weekends + +To determine the number of orders made only on Saturday and Sunday, create an area chart, then add it to the dashboard. + +. Open *Lens*. + +. From the *Chart Type* dropdown, select *Area*. ++ +[role="screenshot"] +image::images/lens_advanced_2_1_1.png[Chart type menu with Area selected] + +. Configure the cumulative sum of the store orders. + +.. From the *Available fields* list, drag and drop *Records* to the visualization builder. + +.. From the editor, click *Count of Records*. + +.. From *Select a function*, click *Cumulative sum*. + +.. In the *Display name* field, enter `Cumulative orders during weekend days`, then click *Close*. + +. Filter the results to display the data for only Saturday and Sunday. + +.. From the editor, click the *Drop a field or click to add* field for *Break down by*. + +.. From *Select a function*, click *Filters*. + +.. Click *All records*. + +.. In the *KQL* field, enter `day_of_week : "Saturday" or day_of_week : "Sunday"`, then press Return. ++ +The <> displays all documents where `day_of_week` matches `Saturday` or `Sunday`. ++ +[role="screenshot"] +image::images/lens_advanced_2_1.png[Filter aggregation to filter weekend days] + +. To hide the legend, open the *Legend* menu, then click *Hide*. ++ +[role="screenshot"] +image::images/lens_advanced_2_2_1.png[Legend menu] ++ +You have an area chart that shows you how many orders your store received during the weekend. ++ +[role="screenshot"] +image::images/lens_advanced_2_2.png[Line chart with cumulative sum of orders made on the weekend] + +. Click *Save and return*. + +[discrete] +[[add-a-data-layer-advanced]] +=== Create multiple key percentiles of product prices + +To view the price distribution of products sold over time, create a percentile chart, then add it to the dashboard. + +. Open *Lens*. + +. From the *Chart Type* dropdown, select *Line*. + +. From the *Available fields* list, drag and drop the data fields to the *Drop a field or click to add* fields in the editor. + +* Drag and drop *products.price* to the *Vertical axis* field. + +* Drag and drop *order_date* to the *Horizontal axis* field. + +. Create the 95th percentile. + +.. In the editor, click *Median of products.price*. + +.. From *Select a function*, click *Percentile*. + +.. In the *Display name* field, enter `95th`, then click *Close*. + +. To create the 90th percentile, duplicate the `95th` percentile. + +.. Drag and drop *95th* to *Drop a field or click to add*. + +.. Click *95th [1]*, then enter `90` in the *Percentile* field. + +.. In the *Display name* field enter `90th`, then click *Close*. ++ +[role="screenshot"] +image::images/lens_advanced_3_1.gif[Easily duplicate the items with drag and drop] + +. Create the 50th percentile. + +.. Drag and drop *90th* to *Drop a field or click to add*. + +.. Click *90th [1]*, then enter `50` in the *Percentile* field. + +.. In the *Display name* field enter `50th`, then click *Close*. + +. Create the 10th percentile. + +.. Drag and drop *50th* to *Drop a field or click to add*. + +.. Click *50th [1]*, then enter `10` in the *Percentile* field. + +.. In the *Display name* field enter `10th`, then click *Close*. + +. To change the left axis label, open the *Left Axis* menu, then enter `Percentiles for product prices` in the *Axis name* field. ++ +[role="screenshot"] +image::images/lens_advanced_3_1_1.png[Left Axis menu] ++ +You have a line chart that shows you the price distribution of products sold over time. ++ +[role="screenshot"] +image::images/lens_advanced_3_3.png[Percentiles for product prices chart] + +. Click *Save and return*. + +[discrete] +[[add-the-response-code-filters-advanced]] +=== View the moving average of inventory prices + +To view and analyze the prices of shoes, accessories, and clothing in the store inventory, create a line chart. + +. Open *Lens*. + +. From the *Chart Type* dropdown, select *Line*. + +. From the *Available fields* list, drag and drop *products.price* to the visualization builder. + +. In the editor, click the *Drop a field or click to add* field for *Break down by*. + +. From *Select a function*, click *Filters*. + +. Add a filter for shoes. + +.. Click *All records*. + +.. In the *KQL* field, enter `category.keyword : *Shoes*`. + +.. In the *Label* field, enter `Shoes`, then press Return. + +. Add a filter for accessories. + +.. Click *Add a filter*. + +.. In the *KQL* field, enter `category.keyword : *Accessories*`. + +.. In the *Label* field, enter `Accessories`, then press Return. + +. Add a filter for clothing. + +.. Click *Add a filter*. + +.. In the *KQL* field, enter `category.keyword : *Clothing*`. + +.. In the *Label* field, enter `Clothing`, then press Return. + +. Click *Close* ++ +[role="screenshot"] +image::images/lens_advanced_4_1.png[Median prices chart for different categories] + +[discrete] +[[add-the-moving-average]] +==== Add the moving average + +To focus on the general trends rather than on the peaks in the data, add the moving average, then add the visualization to the dashboard. + +. In the editor, click the *Median of products.price*. + +. From *Select a function*, click *Moving average*. + +. In the *Window size* field, enter `7`, then click *Close*. ++ +[role="screenshot"] +image::images/lens_advanced_4_2.png[Moving average prices chart for different categories] + +. Click *Save and return*. + +[discrete] +=== Save the dashboard + +Now that you have a complete overview of your ecommerce sales data, save the dashboard. + +. In the toolbar, click *Save*. + +. On the *Save dashboard* window, enter `Ecommerce sales data`, then click *Save*. diff --git a/docs/user/dashboard/lens-end-to-end/images/non-edited/lens_end_to_end_1.png b/docs/user/dashboard/lens-end-to-end/images/non-edited/lens_end_to_end_1.png deleted file mode 100644 index 49b917753b7ef9..00000000000000 Binary files a/docs/user/dashboard/lens-end-to-end/images/non-edited/lens_end_to_end_1.png and /dev/null differ diff --git a/docs/user/dashboard/lens-end-to-end/images/non-edited/lens_end_to_end_2.png b/docs/user/dashboard/lens-end-to-end/images/non-edited/lens_end_to_end_2.png deleted file mode 100644 index 64330de64ca782..00000000000000 Binary files a/docs/user/dashboard/lens-end-to-end/images/non-edited/lens_end_to_end_2.png and /dev/null differ diff --git a/docs/user/dashboard/lens.asciidoc b/docs/user/dashboard/lens.asciidoc index ca651ed16189aa..58476bcae87dff 100644 --- a/docs/user/dashboard/lens.asciidoc +++ b/docs/user/dashboard/lens.asciidoc @@ -1,436 +1,222 @@ -[[create-a-dashboard-of-panels-with-web-server-data]] -== Tutorial: Create a dashboard of panels with web server data +[[lens]] +=== Lens -You collected data from your web server, and you want to visualize and analyze the data on a dashboard. To create dashboard panels of the data, open the *Lens* visualization builder, then -create the visualization panels that best display the data. +To create visualizations with *Lens*, you drag and drop data fields onto the visualization builder, +then *Lens* uses heuristics to apply each field. -[discrete] -[[add-the-data-and-create-the-dashboard]] -=== Add the data and create the dashboard +[role="screenshot"] +image:dashboard/images/lens.png[Lens] -To create visualizations of the data from the web server, add the data set, then create the dashboard. +[float] +[[lens-required-choices]] +==== Open and set up Lens -. From the {kib} *Home* page, click *Try our sample data*. +Open *Lens*, then explore the fields in your data. The list of fields are determined by the index pattern and time filter. -. From *Sample web logs*, click *Add data*. +. On the dashboard, click *Create panel*. -. Open the main menu, click *Dashboard*. +. On the *New visualization* window, click *Lens*. -. Click *Create dashboard*. +. <>. -[float] -[[open-and-set-up-lens]] -=== Open and set up Lens +. To view the fields in the a different index pattern, click the index pattern, then select a different index pattern from the dropdown. -With *Lens*, you identify the data fields you want to visualize, drag and drop the fields, then watch as -*Lens* uses heuristics to apply the fields and create a visualization for you. +. Scan through the list of fields to see what’s in your data. ++ +TIP: For sparse datasets, *Empty fields* can contain data, which you can use to create visualizations. -. From the dashboard, click *Create panel*. +. To filter the data fields, use the following options: -. On the *New visualization* window, click *Lens*. -+ -[role="screenshot"] -image::images/lens_end_to_end_1_1.png[New visualization popover] +* Enter the field name in the *Search field names* field. -. Make sure the *kibana_sample_data_logs* index appears. -+ -The list of fields are dependent on the <>, <>, and field filters. +* Click *Field by type*, then select the filter. To show all fields in the index pattern, deselect *Only show fields with data*. [float] [[view-the-data-summaries]] -=== View the data summaries +===== View the data summaries -For each field, *Lens* shows a summary depending on the type of data. Date fields show the time distribution, string fields show the top 10 values, -and numeric fields show a detailed summary with the top 10 values and a value distribution. +For each field, *Lens* displays a summary depending on the type of data. To analyze the fields, *Lens* uses a sample of 5,000 documents. +Each summary displays the percentage of sampled documents over all available documents. -To view the data summary for a field, click *i* next to the field. +To view a data summary, click *i* next to the field. [role="screenshot"] image::images/lens_data_info_documents.png[Data summary analyzed documents] -*Lens* uses a sample of 5,000 documents to perform the field analysis. The bottom line of the summary shows the percentage of sampled documents over all available documents. +Each data type display the following summaries: -When *Lens* presents the top 10 values distribution, it also shows the percentage of *Other* values. For array value fields, the percentage distribution considers each value in the array as separate. +* *Date* — Displays the time distribution. -[role="screenshot"] -image::images/lens_data_info.png[Data summary window with Other] - -NOTE: The sum of all the *Other* fields can equal more than 100% by a small amount. - -[discrete] -[[view-the-number-of-website-visitors]] -=== View the number of website visitors - -To determine how many users have visited your website within the last 90 days, create a metric visualization, then add it to the dashboard. +* *String* — Displays the top 10 values. -. Set the <> to *Last 90 days*. +* *Numeric* — Displays the top 10 values and a value distribution. When *Lens* displays the top 10 values distribution, the percentage of *Other* values is also displayed. +For array value fields, the percentage distribution considers each value in the array as separate. -. From the *Chart Type* dropdown, select *Metric*. -+ -[role="screenshot"] -image::images/lens_end_to_end_1_2_1.png[Chart Type dropdown with Metric selected] +TIP: *Other* can equal more than 100% by a small amount. -. From the *Available fields* list, drag and drop *clientip* to the visualization builder. -+ -[role="screenshot"] -image::images/lens_end_to_end_1_3.png[Changed type and dropped clientip field] - -. From the editor, click *Unique count of clientip*. - -.. In the *Display name* field, enter `Unique visitors`. - -.. Click *Close*. -+ -[role="screenshot"] -image::images/lens_end_to_end_1_4.png[Flyout config open] - -. *Save* the panel, enter `Unique visitors` in the *Title* field, then add the panel to the dashboard. - -[discrete] -[[view-the-distribution-of-visitors-by-operating-system]] -=== View the distribution of visitors by operating system - -To determine the operating systems you should continue to support, and the importance of mobile traffic from iOS devices, -create a donut chart that displays the top operating systems that your visitors used to access your website within the last 90 days. - -. Open *Lens*, then set the <> to *Last 90 days*. - -. From the *Chart Type* dropdown, select *Donut*. +[float] +[[create-the-visualization-panel]] +==== Create the visualization panel -. From the *Available fields* list, drag and drop the data fields to the *Drop a field or click to add* fields in the editor. +Drag and drop the fields on to the visualization builder, then -.. Drag and drop *clientip* to the *Size by* field. +. Drag and drop the fields to the visualization builder. -.. Drag and drop *machine.os.keyword* to the *Slice by* field. -+ -[role="screenshot"] -image::images/lens_end_to_end_2_1_1.png[Donut chart with clientip and machine.os.keyword fields] +. To change the visualization type, use the following options: -. Change the color palette. +* Click the *Suggestions*. -.. From the editor, click *Top values of machine.os.keyword*. +* From the *Chart Type* dropdown, click the visualization. -.. From the *Color palette* dropdown, select *Compatibility*. +. To configure the data, use the editor, then click *Close*. -.. Click *Close*. +. To configure the visualization, use the dropdown menus. + [role="screenshot"] -image::images/lens_end_to_end_2_1.png[Donut chart with open config panel] - -. *Save* the panel, enter `Visitors by OS` in the *Title* field, then add the panel to the dashboard. - -[discrete] -[[mixed-multiaxis]] -=== View the average of bytes transfer per day - -To prevent potential server failures, and optimize the cost of website maintenance, create an area chart that displays the average of bytes transfer. To compare -the data to the number of visitors to your website, add a line chart layer. +image::images/lens_value_labels_xychart_toggle.png[Lens Bar chart value labels menu] -. Open *Lens*. +[float] +[[drag-and-drop-keyboard-navigation]] +===== Create visualization panels with keyboard navigation -. From the *Available fields* list, drag and drop *bytes* to the visualization builder. +*Lens* has a fully accessible, continuously improved drag and drop system, which allows you to use a keyboard instead of a mouse. -. To zoom in on the data you want to view, click and drag your cursor across the bars. -+ [role="screenshot"] -image::images/lens_end_to_end_3_1_1.gif[Zoom in on the data] - -. Change the *timestamp* interval. - -.. From the editor, click *timestamp*. - -.. Select *Customize time interval*. +image::images/lens_drag_drop_1.gif[Presented Lens drag and drop] -.. Change the *Minimum interval* to `1 days`, then click *Close*. +. Set a focus on the chosen item. Most of the draggable elements have two focus states. The inner focus state opens a panel with detailed information or options. +The outer focus state allows you to drag an item. Tab through the page until you get the outer focus state on the chosen item: + [role="screenshot"] -image::images/lens_end_to_end_3_1.png[Customize time interval] +image::images/lens_drag_drop_2.png[Lens drag and drop focus state] -. From the *Chart Type* dropdown, select *Area*. +. Complete the following actions: -[discrete] -[[add-a-data-layer]] -==== Add the line chart layer +* To start dragging an item, press Space bar. -To compare the average of bytes transfer to the number of users that visit your website, add a line chart layer. +* To select where you want to drop the item, use the Left and Right arrows. -. From the editor, click *+*. -+ -[role="screenshot"] -image::images/lens_end_to_end_3_2.png[Add new layer button] +* To reorder the fields in the group, use Up and Down arrows. -. From the new layer editor, click the *Chart type* dropdown, then click the line chart. +* To duplicate an operation, use the Left and Right arrows to select the `Drop a field or click to add` in the same group. + [role="screenshot"] -image::images/lens_end_to_end_3_3.png[Change layer type] -+ -The chart type for the visualization changes to *Mixed XY*. - -. From the *Available fields* list, drag and drop the data fields to the *Drop a field or click to add* fields in the editor. - -.. Drag and drop *timestamp* to the *Horizontal axis* field. - -.. Drag and drop *clientip* to the *Vertical axis* field. - -. Change the *timestamp* interval. - -.. From the editor, click *timestamp* in the line chart layer. - -.. Select *Customize time interval*. - -.. Change the *Minimum interval* to `1 days`, then click *Close*. - -. Change the *Unique count of clientip* label and color. - -.. From the editor, click *Unique count of clientip*. +image::images/lens_drag_drop_3.gif[Using drag and drop to reorder] -.. In the *Display name* field, enter `Unique visitors` in the line chart layer. - -.. In the *Series color* field, enter *#CA8EAE*, then click *Close*. - -[discrete] -[[configure-the-multiaxis-chart]] -==== Configure the y-axes - -There is a significant difference between the *timestamp per day* and *Unique visitors* data, which makes the *Unique visitors* data difficult to read. To improve the readability, -display the *Unique visitors* data along a second y-axis, then change the formatting. When functions contain multiple formats, separate axes are created by default. - -. From the editor, click *Unique visitors* in the line chart layer. - -. For *Axis side*, click *Right*, then click *Close*. +. Press Space bar to confirm, or to cancel, press Esc. [float] -[[change-the-visualization-type]] -==== Change the visualization type - -. From the editor, click *Average of bytes* in the area chart layer. - -. From the *Value format* dropdown, select *Bytes (1024)*, then click *Close*. -+ -[role="screenshot"] -image::images/lens_end_to_end_3_4.png[Multiaxis chart] - -[discrete] -[[lens-legend-position]] -==== Change the legend position +[[lens-faq]] +==== Frequently asked questions -The visualization is done, but the legend uses a lot of space. Change the legend position to the top of the chart. +For the answers to common *Lens* questions, review the following. -. From the *Legend* dropdown, select the top position. -+ -[role="screenshot"] -image::images/lens_end_to_end_3_5.png[legend position] +[float] +[[kql-]] +===== When should I use the Filter function instead of KQL filters? -. *Save* the panel, enter `Average Bytes vs. Unique Visitors` in the *Title* field, then add the panel to the dashboard. +The easiest way to apply KQL filters is to use <>, but you can also use the *Filters* function in the following scenarios: -[discrete] -[[percentage-stacked-area]] -=== View the health of your website +* When you want to apply more than one KQL filter to the visualization. -To detect unusual traffic, bad website links, and server errors, create a percentage stacked area chart that displays the associated response codes. +* When you want to apply the KQL filter to a single layer, which allows you to visualize filtered and unfiltered data. -. Open *Lens*. +[float] +[[when-should-i-normalize-the-data-by-unit-or-use-a-custom-interval]] +===== When should I normalize the data by unit or use a custom interval? -. From the *Available fields* list, drag and drop the data fields to the *Drop a field or click to add* fields in the editor. +* *Normalize by unit* — Calculates the average for the specified interval. When you normalize the data by unit, the data appears less granular, but *Lens* is able to calculate the data faster. -.. Drag and drop *Records* to the *Vertical axis* field. +* *Customize time interval* — Creates a bucket for each specified interval. When you customize the time interval, you can use a large time range, but *Lens* calculates the data slower. -.. Drag and drop *@timestamp* to the *Horizontal axis* field. +To normalize the interval: -. From the *Chart Type* dropdown, select *Percentage bar*. +. In the editor, click a field. -. To remove the vertical axis label, click *Left axis*, then deselect *Show*. -+ -[role="screenshot"] -image::images/lens_end_to_end_4_3.png[Turn off axis name] +. Click *Add advanced options > Normalize by unit*. -[discrete] -[[add-the-response-code-filters]] -==== Add the response code filters +. From the *Normalize by unit* dropdown, select an option, then click *Close*. -For each response code that you want to display, create a filter. +To create a custom interval: -. From the editor, click the *Drop a field or click to add* field for *Break down by*. +. In the editor, click a field. -. From *Select a function*, click *Filters*. +. Select *Customize time interval*. -. Add the filter for the successful response codes. +. Change the *Minimum interval*, then click *Close*. -.. Click *All records*. +[float] +[[can-i-show-value-labels-for-my-chart]] +===== How do I display value labels? -.. In the *KQL* field, enter `response.keyword>=200 AND response.keyword<300`. +A subset of *Lens* visualizations support value labels. -.. In the *Label* field, enter `2XX`, then press Return. +* *Bar* and *Horizontal Bar* + [role="screenshot"] -image::images/lens_end_to_end_4_1.png[First filter in filters aggregation] - -. Add the filter for the redirect codes. - -.. Click *Add a filter*. - -.. In the *KQL* field, enter `response.keyword>=300 AND response.keyword<400`. - -.. In the *Label* field, enter `3XX`, then press Return. - -. Add the filter for the client error codes. - -.. Click *Add a filter*. - -.. In the *KQL* field, enter `response.keyword>=400 AND response.keyword<500`. - -.. In the *Label* field, enter `4XX`, then press Return. - -. Add the filter for the server error codes. - -.. Click *Add a filter*. - -.. In the *KQL* field, enter `response.keyword>=500 AND response.keyword<600`. - -.. In the *Label* field, enter `5XX`, then press Return. - -. To change the color palette, select *Status* from the *Color palette* dropdown, then click *Close*. - -. *Save* the panel, enter `Response Codes Over Time` in the *Title* field, then add the panel to the dashboard. - -[discrete] -[[histogram]] -=== View the traffic for your website by the hour - -To find the best time to shut down your website for maintenance, create a histogram that displays the traffic for your website by the hour. +image::images/lens_value_labels_xychart_toggle.png[Lens Bar chart value labels menu] -. Open *Lens*. - -. From the *Available fields* list, drag and drop *bytes* to *Vertical axis* in the editor, then configure the options. - -.. Click *Average of bytes*. - -.. From *Select a function*, click *Sum*. - -.. In the *Display name* field, enter `Transferred bytes`. - -.. From the *Value format* dropdown, select `Bytes (1024)`, then click *Close*. - -. From the *Available fields* list, drag and drop *hour_of_day* to *Horizontal axis* in the editor, then configure the options. - -.. Click *hour_of_day*. - -.. Click and slide the *Intervals granularity* slider until the horizontal axis displays hourly intervals. +* *Pie*, *Donut*, and *Treemap* + [role="screenshot"] -image::images/lens_end_to_end_5_2.png[Create custom ranges] +image::images/lens_value_labels_partition_toggle.png[Lens Pie chart value labels menu] -. *Save* the panel, enter `Hourly Traffic Distribution` in the *Title* field, then add the panel to the dashboard. - -[discrete] -[[custom-ranges]] -=== View the percent of small versus large transferred files - -To determine if your users transfer more small files versus large files, create a pie chart that displays the percentage of each size. - -. Open *Lens*. - -. From the *Available fields* list, drag and drop *bytes* to *Vertical axis* in the editor, then configure the options. - -.. Click *Average of bytes*. - -.. From *Select a function*, click *Sum*, then click *Close*. - -. From the *Available fields* list, drag and drop *bytes* to *Break down by* in the editor, then specify the file size ranges. - -.. Click *bytes*. +[float] +[[what-is-the-other-category]] +===== What data is categorized as Other? -.. Click *Create custom ranges*, enter the following, then press Return: +The *Other* category contains all of the documents that do not match the specified criteria or filters. +Use *Other* when you want to compare a value, or multiple values, to a whole. +By default, *Group other values as "Other"* is enabled when you select the *Top values* function. -* *Ranges* — `0` -> `10240` +To disable *Group other values as "Other"*: -* *Label* — `Below 10KB` +. In the editor, click *Advanced*. -.. Click *Add range*, enter the following, then press Return: +. Deselect *Group other values as "Other"*. -* *Ranges* — `10240` -> `+∞` +[float] +[[how-can-i-include-documents-without-the-field-in-the-operation]] +===== How can I include documents without the field? -* *Label* — `Above 10KB` -+ -[role="screenshot"] -image::images/lens_end_to_end_6_1.png[Custom ranges configuration] +By default, *Lens* retrieves only the documents from the specified field. +For bucket aggregations, such as *Top values*, you can choose to include documents that do not contain the specified field, +which is helpful when you want to compare to the whole documentation set. -.. From the *Value format* dropdown, select *Bytes (1024)*, then click *Close*. +. In the editor, click *Advanced*. -. From the *Chart Type* dropdown, select *Pie*. +. Select *Include documents without this field*. + [role="screenshot"] -image::images/lens_end_to_end_6_2.png[Files size distribution] - -. *Save* the panel, enter `File size distribution` in the *Title* field, then add the panel to the dashboard. - -[discrete] -[[treemap]] -=== View the top sources of website traffic - -To determine how users find out about your website and where your users are located, create a treemap that displays the percentage of users that -enter your website from specific social media websites, and the top countries where users are located. - -. Open *Lens*. - -. From the *Chart Type* dropdown, select *Treemap*. - -. From the *Available fields* list, drag and drop *Records* to the *Size by* field in the editor. - -. From the editor, click the *Drop a field or click to add* field for *Group by*, then create a filter for each website traffic source. - -.. From *Select a function*, click *Filters*. - -.. Click *All records*, enter the following, then press Return: - -* *KQL* — `referer : *facebook.com*` - -* *Label* — `Facebook` - -.. Click *Add a filter*, enter the following, then press Return: - -* *KQL* — `referer : *twitter.com*` +image::images/lens_bucketed_aggregation_advanced_dropdown.png[Lens Advanced options for bucketed aggregations] -* *Label* — `Twitter` - -.. Click *Add a filter*, enter the following, then press Return: - -* *KQL* — `NOT referer : *twitter* OR NOT referer: *facebook.com*` - -* *Label* — `Other` - -.. Click *Close*. - -[discrete] -[[add-the-countries]] -==== Add the geographic data - -To determine the top countries where users are located, add the geographic data. +[float] +[[is-it-possible-to-select-color-for-specific-bar-or-point]] +===== How do I change the color for a single data point? -Compare the top sources of website traffic data to the top three countries. +*Lens* provides you with color pallettes that you can apply to the entire visualization, but you are unable to change the color for a single data point, such as a bar or line. -. From the *Available fields* list, drag and drop *geo.src* to the visualization builder. +[float] +[[can-i-sort-by-multiple-columns]] +===== How do I sort by multiple columns? -. To change the *Group by* order, click and drag *Top values of geo.src* so that it appears first in the editor. -+ -[role="screenshot"] -image::images/lens_end_to_end_7_2.png[Treemap vis] +Multiple column sorting is unsupported in *Lens*, but is supported in *Discover*. For information on how to sort multiple columns in *Discover*, +refer to <>. -. To view only the Facebook and Twitter data, remove the *Other* category. +[float] +[[is-it-possible-to-sort-dimensions-in-a-chart]] +===== How do I sort the dimensions in a chart? -.. From the editor, click *Top values of geo.src*. +Sorting dimensions in visualizations is unsupported in *Lens*. -.. From the *Advanced* dropdown, deselect *Group other values as "Other"*, then click *Close*. -+ -[role="screenshot"] -image::images/lens_end_to_end_7_3.png[Group other values as Other] +[float] +[[is-it-possible-to-use-saved-serches-in-lens]] +===== How do I visualize saved searches? -. *Save* the panel, enter `Traffic Source For Top 3 Countries` in the *Title* field, then add the panel to the dashboard. +Visualizing saved searches in unsupported in *Lens*. [float] -== What's next? +[[is-it-possible-to-decrease-or-increase-the-number-of-suggestions]] +===== How do I change the number of suggestions? -Your dashboard is complete and provides you with an overall picture of the data from your web server. - -[role="screenshot"] -image::images/lens_end_to_end_dashboard.png[Final dashboard vis] \ No newline at end of file +Configuring the *Suggestions* that *Lens* automatically populates is unsupported. diff --git a/docs/user/dashboard/timelion.asciidoc b/docs/user/dashboard/timelion.asciidoc index f785f147d04e0c..941f78168ecf71 100644 --- a/docs/user/dashboard/timelion.asciidoc +++ b/docs/user/dashboard/timelion.asciidoc @@ -1,4 +1,4 @@ -[[create-panels-with-timelion]] +[[timelion]] === Timelion Instead of using a visual editor to create charts, you define a graph by chaining functions together, using the *Timelion*-specific syntax. diff --git a/docs/user/dashboard/tsvb.asciidoc b/docs/user/dashboard/tsvb.asciidoc index 0fa0b00ad040fc..f94048a836a1f0 100644 --- a/docs/user/dashboard/tsvb.asciidoc +++ b/docs/user/dashboard/tsvb.asciidoc @@ -1,4 +1,4 @@ -[[TSVB]] +[[tsvb]] === TSVB *TSVB* enables you to visualize the data from multiple data series, supports <> or <>. +Performing math across data series is unsupported in *TSVB*. To calculate the difference between two data series, use <> or <>. [float] ===== How do I compare the current versus previous month? @@ -153,7 +153,7 @@ The ability to calculate a month over month change is not fully supported in *TS time filter is set to 3 months or more _and_ the *Interval* is `1m`. Use the *Derivative* to get the absolute monthly change. To convert to a percent, add the *Math* function with the `params.current / (params.current - params.derivative)` formula, then select *Percent* from the *Data Formatter* dropdown. -For other types of month over month calculations, use <> or <>. +For other types of month over month calculations, use <> or <>. [float] ===== How do I calculate the duration between the start and end of an event? diff --git a/docs/user/dashboard/lens-end-to-end/lens-end-to-end.asciidoc b/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc similarity index 61% rename from docs/user/dashboard/lens-end-to-end/lens-end-to-end.asciidoc rename to docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc index f111f602de8493..22483b28018488 100644 --- a/docs/user/dashboard/lens-end-to-end/lens-end-to-end.asciidoc +++ b/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc @@ -1,43 +1,81 @@ -[float] -[[lens-end-to-end]] -== Tutorial: Analyze website data on a dashboard +[[create-a-dashboard-of-panels-with-web-server-data]] +== Tutorial: Create a dashboard of panels with web server data -Most dashboards use similar types of analysis to answer key questions. In this tutorial you will base your analysis off of {kib} sample data which resembles logs from the Apache web server. +You collected data from your web server, and you want to visualize and analyze the data on a dashboard. To create dashboard panels of the data, open the *Lens* visualization builder, then +create the visualization panels that best display the data. -When you're finished creating the dashboard, you'll be able to answer the following questions: +When you've completed the tutorial, you'll have a dashboard that provides you with a complete overview of your web server data. -* How many users have visited your website? -* What is the distribution of visitors by operating system? -* What is the average of bytes transfer per day? -* What is the health of your website? -* What is the traffic for your website by the hour? -* What is the percentage of small compared to large transferred files? -* What are the top social media sources of website traffic, and from what countries? +[role="screenshot"] +image::images/lens_end_to_end_dashboard.png[Final dashboard vis] [discrete] -[[add-the-sample-web-logs-data]] +[[add-the-data-and-create-the-dashboard]] === Add the data and create the dashboard -To create visualizations of the website traffic data, add the data set, then create the dashboard. +To create visualizations of the data from the web server, add the data set, then create the dashboard. -. From the *Home* page, click *Try our sample data*. +. From the {kib} *Home* page, click *Try our sample data*. . From *Sample web logs*, click *Add data*. . Open the main menu, click *Dashboard*. -. On the *Dashboards* page, click *Create dashboard*. +. Click *Create dashboard*. + +[float] +[[open-and-set-up-lens]] +=== Open and set up Lens + +With *Lens*, you identify the data fields you want to visualize, drag and drop the fields, then watch as +*Lens* uses heuristics to apply the fields and create a visualization for you. + +. From the dashboard, click *Create panel*. + +. On the *New visualization* window, click *Lens*. ++ +[role="screenshot"] +image::images/lens_end_to_end_1_1.png[New visualization popover] + +. Make sure the *kibana_sample_data_logs* index appears. + +[discrete] +[[view-the-number-of-website-visitors]] +=== View the number of website visitors + +To determine how many users have visited your website within the last 90 days, create a metric visualization, then add it to the dashboard. + +. Set the <> to *Last 90 days*. + +. From the *Chart Type* dropdown, select *Metric*. ++ +[role="screenshot"] +image::images/lens_end_to_end_1_2_1.png[Chart Type dropdown with Metric selected] + +. From the *Available fields* list, drag and drop *clientip* to the visualization builder. ++ +[role="screenshot"] +image::images/lens_end_to_end_1_3.png[Changed type and dropped clientip field] + +. In the editor, click *Unique count of clientip*. + +.. In the *Display name* field, enter `Unique visitors`. + +.. Click *Close*. ++ +[role="screenshot"] +image::images/lens_end_to_end_1_4.png[Flyout config open] + +. Click *Save and return*. [discrete] -[[donut-vis]] +[[view-the-distribution-of-visitors-by-operating-system]] === View the distribution of visitors by operating system To determine the operating systems you should continue to support, and the importance of mobile traffic from iOS devices, -create a donut chart that displays the top operating systems that your visitors use to access your webiste. - -. From the dashboard, click *Create panel*, then click *Lens* on the *New visualization* window. +create a donut chart that displays the top operating systems that your visitors used to access your website within the last 90 days. -. Make sure the *kibana_sample_data_logs* index appears, and the <> is set to *Last 90 days*. +. Open *Lens*, then set the <> to *Last 90 days*. . From the *Chart Type* dropdown, select *Donut*. @@ -52,35 +90,25 @@ image::images/lens_end_to_end_2_1_1.png[Donut chart with clientip and machine.os . Change the color palette. -.. From the editor, click *Top values of machine.os.keyword*. +.. In the editor, click *Top values of machine.os.keyword*. -.. From the *Color palette* dropdown, select *Compability*. +.. From the *Color palette* dropdown, select *Compatibility*. .. Click *Close*. + [role="screenshot"] image::images/lens_end_to_end_2_1.png[Donut chart with open config panel] -. Save the visualization, then add it to the dashboard. - -.. From the toolbar, click *Save*. - -.. In the *Title* field, enter `Visitors by OS`. - -.. Select *Add to dashboard after saving*. - -.. Click *Save and return*. +. Click *Save and return*. [discrete] [[mixed-multiaxis]] === View the average of bytes transfer per day -To prevent potential server failures and optimize the cost of website maintenance, create an area chart that displays the average of bytes transfer, -then add a line chart layer to compare the data to the number of visitors to your website. - -. From the dashboard, click *Create panel*, then click *Lens* on the *New visualization* window. +To prevent potential server failures, and optimize the cost of website maintenance, create an area chart that displays the average of bytes transfer. To compare +the data to the number of visitors to your website, add a line chart layer. -. Make sure the *kibana_sample_data_logs* index appears. +. Open *Lens*. . From the *Available fields* list, drag and drop *bytes* to the visualization builder. @@ -91,17 +119,15 @@ image::images/lens_end_to_end_3_1_1.gif[Zoom in on the data] . Change the *timestamp* interval. -.. From the editor, click *timestamp*. +.. In the editor, click *timestamp*. .. Select *Customize time interval*. -.. Change the *Minimum interval* to `1 days`. +.. Change the *Minimum interval* to `1 days`, then click *Close*. + [role="screenshot"] image::images/lens_end_to_end_3_1.png[Customize time interval] -.. Click *Close*. - . From the *Chart Type* dropdown, select *Area*. [discrete] @@ -110,7 +136,7 @@ image::images/lens_end_to_end_3_1.png[Customize time interval] To compare the average of bytes transfer to the number of users that visit your website, add a line chart layer. -. From the editor, click *+*. +. In the editor, click *+*. + [role="screenshot"] image::images/lens_end_to_end_3_2.png[Add new layer button] @@ -130,23 +156,19 @@ The chart type for the visualization changes to *Mixed XY*. . Change the *timestamp* interval. -.. From the editor, click *timestamp* in the line chart layer. +.. In the editor, click *timestamp* in the line chart layer. .. Select *Customize time interval*. -.. Change the *Minimum interval* to `1 days`. - -.. Click *Close*. +.. Change the *Minimum interval* to `1 days`, then click *Close*. . Change the *Unique count of clientip* label and color. -.. From the editor, click *Unique count of clientip*. +.. In the editor, click *Unique count of clientip*. .. In the *Display name* field, enter `Unique visitors` in the line chart layer. -.. In the *Series color* field, enter *#CA8EAE*. - -.. Click *Close*. +.. In the *Series color* field, enter *#CA8EAE*, then click *Close*. [discrete] [[configure-the-multiaxis-chart]] @@ -155,41 +177,33 @@ The chart type for the visualization changes to *Mixed XY*. There is a significant difference between the *timestamp per day* and *Unique visitors* data, which makes the *Unique visitors* data difficult to read. To improve the readability, display the *Unique visitors* data along a second y-axis, then change the formatting. When functions contain multiple formats, separate axes are created by default. -. From the editor, click *Unique visitors* in the line chart layer. +. In the editor, click *Unique visitors* in the line chart layer. -.. For *Axis side*, click *Right*. +. For *Axis side*, click *Right*, then click *Close*. -.. Click *Close*. +[float] +[[change-the-visualization-type]] +==== Change the visualization type -. From the editor, click *Average of bytes* in the area chart layer. +. In the editor, click *Average of bytes* in the area chart layer. -.. From the *Value format* dropdown, select *Bytes (1024)*. +. From the *Value format* dropdown, select *Bytes (1024)*, then click *Close*. + [role="screenshot"] image::images/lens_end_to_end_3_4.png[Multiaxis chart] -.. Click *Close*. - [discrete] [[lens-legend-position]] -==== Change the legend position and save the visualization +==== Change the legend position -The visualization is done, but the legend uses a lot of space. Change the legend position to the top of the chart, then save the visualization and add it to the dashboard. +The visualization is done, but the legend uses a lot of space. Change the legend position to the top of the chart. . From the *Legend* dropdown, select the top position. + [role="screenshot"] image::images/lens_end_to_end_3_5.png[legend position] -. Save the visualization, then add it to the dashboard. - -.. From the toolbar, click *Save*. - -.. In the *Title* field, enter `Average Bytes vs. Unique Visitors`. - -.. Select *Add to dashboard after saving*. - -.. Click *Save and return*. +. Click *Save and return*. [discrete] [[percentage-stacked-area]] @@ -197,9 +211,7 @@ image::images/lens_end_to_end_3_5.png[legend position] To detect unusual traffic, bad website links, and server errors, create a percentage stacked area chart that displays the associated response codes. -. From the dashboard, click *Create panel*, then click *Lens* on the *New visualization* window. - -. Make sure the *kibana_sample_data_logs* index appears. +. Open *Lens*. . From the *Available fields* list, drag and drop the data fields to the *Drop a field or click to add* fields in the editor. @@ -220,7 +232,7 @@ image::images/lens_end_to_end_4_3.png[Turn off axis name] For each response code that you want to display, create a filter. -. From the editor, click the *Drop a field or click to add* field for *Break down by*. +. In the editor, click the *Drop a field or click to add* field for *Break down by*. . From *Select a function*, click *Filters*. @@ -230,13 +242,11 @@ For each response code that you want to display, create a filter. .. In the *KQL* field, enter `response.keyword>=200 AND response.keyword<300`. -.. In the *Label* field, enter `2XX`. +.. In the *Label* field, enter `2XX`, then press Return. + [role="screenshot"] image::images/lens_end_to_end_4_1.png[First filter in filters aggregation] -.. Press Return. - . Add the filter for the redirect codes. .. Click *Add a filter*. @@ -261,19 +271,9 @@ image::images/lens_end_to_end_4_1.png[First filter in filters aggregation] .. In the *Label* field, enter `5XX`, then press Return. -. To change the color pallette, select *Status* from the *Color palette* dropdown. - -.. Click *Close*. - -. Save the visualization, then add it to the dashboard. - -.. From the toolbar, click *Save*. - -.. In the *Title* field, enter `Response Codes Over Time`. +. To change the color palette, select *Status* from the *Color palette* dropdown, then click *Close*. -.. Select *Add to dashboard after saving*. - -.. Click *Save and return*. +. Click *Save and return*. [discrete] [[histogram]] @@ -281,9 +281,7 @@ image::images/lens_end_to_end_4_1.png[First filter in filters aggregation] To find the best time to shut down your website for maintenance, create a histogram that displays the traffic for your website by the hour. -. From the dashboard, click *Create panel*, then click *Lens* on the *New visualization* window. - -. Make sure the *kibana_sample_data_logs* index appears. +. Open *Lens*. . From the *Available fields* list, drag and drop *bytes* to *Vertical axis* in the editor, then configure the options. @@ -293,9 +291,7 @@ To find the best time to shut down your website for maintenance, create a histog .. In the *Display name* field, enter `Transferred bytes`. -.. From the *Value format* dropdown, select `Bytes (1024)`. - -.. Click *Close*. +.. From the *Value format* dropdown, select `Bytes (1024)`, then click *Close*. . From the *Available fields* list, drag and drop *hour_of_day* to *Horizontal axis* in the editor, then configure the options. @@ -306,15 +302,7 @@ To find the best time to shut down your website for maintenance, create a histog [role="screenshot"] image::images/lens_end_to_end_5_2.png[Create custom ranges] -. Save the visualization, then add it to the dashboard. - -.. From the toolbar, click *Save*. - -.. In the *Title* field, enter `Hourly Traffic Distribution`. - -.. Select *Add to dashboard after saving*. - -.. Click *Save and return*. +. Click *Save and return*. [discrete] [[custom-ranges]] @@ -322,17 +310,13 @@ image::images/lens_end_to_end_5_2.png[Create custom ranges] To determine if your users transfer more small files versus large files, create a pie chart that displays the percentage of each size. -. From the dashboard, click *Create panel*, then click *Lens* on the *New visualization* window. - -. Make sure the *kibana_sample_data_logs* index appears. +. Open *Lens*. . From the *Available fields* list, drag and drop *bytes* to *Vertical axis* in the editor, then configure the options. .. Click *Average of bytes*. -.. From *Select a function*, click *Sum*. - -.. Click *Close*. +.. From *Select a function*, click *Sum*, then click *Close*. . From the *Available fields* list, drag and drop *bytes* to *Break down by* in the editor, then specify the file size ranges. @@ -353,24 +337,14 @@ To determine if your users transfer more small files versus large files, create [role="screenshot"] image::images/lens_end_to_end_6_1.png[Custom ranges configuration] -.. From the *Value format* dropdown, select *Bytes (1024)*. - -.. Click *Close*. +.. From the *Value format* dropdown, select *Bytes (1024)*, then click *Close*. . From the *Chart Type* dropdown, select *Pie*. + [role="screenshot"] image::images/lens_end_to_end_6_2.png[Files size distribution] -. Save the visualization, then add it to the dashboard. - -.. From the toolbar, click *Save*. - -.. In the *Title* field, enter `File size distribution`. - -.. Select *Add to dashboard after saving*. - -.. Click *Save and return*. +. Click *Save and return*. [discrete] [[treemap]] @@ -379,15 +353,13 @@ image::images/lens_end_to_end_6_2.png[Files size distribution] To determine how users find out about your website and where your users are located, create a treemap that displays the percentage of users that enter your website from specific social media websites, and the top countries where users are located. -. From the dashboard, click *Create panel*, then click *Lens* on the *New visualization* window. - -. Make sure the *kibana_sample_data_logs* index appears. +. Open *Lens*. . From the *Chart Type* dropdown, select *Treemap*. . From the *Available fields* list, drag and drop *Records* to the *Size by* field in the editor. -. From the editor, click the *Drop a field or click to add* field for *Group by*, then create a filter for each website traffic source. +. In the editor, click the *Drop a field or click to add* field for *Group by*, then create a filter for each website traffic source. .. From *Select a function*, click *Filters*. @@ -415,7 +387,7 @@ enter your website from specific social media websites, and the top countries wh [[add-the-countries]] ==== Add the geographic data -To determine the top countries where users are located, add the geographic data, then save and add the visualization to the dashboard. +To determine the top countries where users are located, add the geographic data. Compare the top sources of website traffic data to the top three countries. @@ -428,26 +400,20 @@ image::images/lens_end_to_end_7_2.png[Treemap vis] . To view only the Facebook and Twitter data, remove the *Other* category. -.. From the editor, click *Top values of geo.src*. +.. In the editor, click *Top values of geo.src*. -.. From the *Advanced* dropdown, deselect *Group other values as "Other"*. +.. From the *Advanced* dropdown, deselect *Group other values as "Other"*, then click *Close*. + [role="screenshot"] image::images/lens_end_to_end_7_3.png[Group other values as Other] -.. Click *Close*. - -. Save the visualization, then add it to the dashboard. +. Click *Save and return*. -.. From the toolbar, click *Save*. - -.. In the *Title* field, enter `Traffic Source For Top 3 Countries`. - -.. Select *Add to dashboard after saving*. +[discrete] +=== Save the dashboard -.. Click *Save and return*. +Now that you have a complete overview of your web server data, save the dashboard. -That's it! You've created a dashboard that provides you with a complete picture of your website data. +. In the toolbar, click *Save*. -[role="screenshot"] -image::images/lens_end_to_end_dashboard.png[Final dashboard vis] +. On the *Save dashboard* window, enter `Web server data`, then click *Save*. diff --git a/docs/user/management.asciidoc b/docs/user/management.asciidoc index 6bdb0571f28d19..b0381b8f3e2308 100644 --- a/docs/user/management.asciidoc +++ b/docs/user/management.asciidoc @@ -17,10 +17,9 @@ Consult your administrator if you do not have the appropriate access. [cols="50, 50"] |=== -| <> -| Create and manage {es} -pipelines that enable you to perform common transformations and -enrichments on your data. +| {ref}/ingest.html[Ingest Node Pipelines] +| Create and manage ingest pipelines that let you perform common transformations +and enrichments on your data. | {logstash-ref}/logstash-centralized-pipeline-management.html[Logstash Pipelines] | Create, edit, and delete your Logstash pipeline configurations. @@ -187,8 +186,6 @@ include::{kib-repo-dir}/management/alerting/connector-management.asciidoc[] include::{kib-repo-dir}/management/managing-beats.asciidoc[] -include::{kib-repo-dir}/management/ingest-pipelines/ingest-pipelines.asciidoc[] - include::{kib-repo-dir}/management/managing-fields.asciidoc[] include::{kib-repo-dir}/management/managing-licenses.asciidoc[] diff --git a/packages/kbn-config/src/env.ts b/packages/kbn-config/src/env.ts index 8526cfc7691ef7..b6ff5e3b5aab22 100644 --- a/packages/kbn-config/src/env.ts +++ b/packages/kbn-config/src/env.ts @@ -22,8 +22,9 @@ export interface EnvOptions { export interface CliArgs { dev: boolean; envName?: string; - quiet: boolean; - silent: boolean; + /** @deprecated */ + quiet?: boolean; + silent?: boolean; watch: boolean; basePath: boolean; oss: boolean; diff --git a/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts b/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts index ab90f4424d9fae..57cf05d8f0f73c 100644 --- a/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts +++ b/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts @@ -64,21 +64,6 @@ export async function migrateKibanaIndex({ client: Client; kbnClient: KbnClient; }) { - // we allow dynamic mappings on the index, as some interceptors are accessing documents before - // the migration is actually performed. The migrator will put the value back to `strict` after migration. - await client.indices.putMapping( - { - index: '.kibana', - body: { - dynamic: true, - }, - }, - { - ignore: [404], - headers: ES_CLIENT_HEADERS, - } - ); - await kbnClient.savedObjects.migrate(); } diff --git a/packages/kbn-legacy-logging/src/schema.ts b/packages/kbn-legacy-logging/src/schema.ts index 2c5271157fb721..76d7381ee87284 100644 --- a/packages/kbn-legacy-logging/src/schema.ts +++ b/packages/kbn-legacy-logging/src/schema.ts @@ -11,7 +11,12 @@ import Joi from 'joi'; const HANDLED_IN_KIBANA_PLATFORM = Joi.any().description( 'This key is handled in the new platform ONLY' ); - +/** + * @deprecated + * + * Legacy logging has been deprecated and will be removed in 8.0. + * Set up logging from the platform logging instead + */ export interface LegacyLoggingConfig { silent: boolean; quiet: boolean; @@ -38,13 +43,11 @@ export const legacyLoggingConfigSchema = Joi.object() root: HANDLED_IN_KIBANA_PLATFORM, silent: Joi.boolean().default(false), - quiet: Joi.boolean().when('silent', { is: true, then: Joi.boolean().default(true).valid(true), otherwise: Joi.boolean().default(false), }), - verbose: Joi.boolean().when('quiet', { is: true, then: Joi.valid(false).default(false), diff --git a/packages/kbn-optimizer/src/cli.ts b/packages/kbn-optimizer/src/cli.ts index 8f82f34646e607..6e3106dbc2af79 100644 --- a/packages/kbn-optimizer/src/cli.ts +++ b/packages/kbn-optimizer/src/cli.ts @@ -18,6 +18,7 @@ import { logOptimizerState } from './log_optimizer_state'; import { OptimizerConfig } from './optimizer'; import { runOptimizer } from './run_optimizer'; import { validateLimitsForAllBundles, updateBundleLimits } from './limits'; +import { reportOptimizerTimings } from './report_optimizer_timings'; function getLimitsPath(flags: Flags, defaultPath: string) { if (flags.limits) { @@ -144,7 +145,9 @@ export function runKbnOptimizerCli(options: { defaultLimitsPath: string }) { const update$ = runOptimizer(config); - await lastValueFrom(update$.pipe(logOptimizerState(log, config))); + await lastValueFrom( + update$.pipe(logOptimizerState(log, config), reportOptimizerTimings(log, config)) + ); if (updateLimits) { updateBundleLimits({ diff --git a/packages/kbn-optimizer/src/index.ts b/packages/kbn-optimizer/src/index.ts index 8d6e89008bc681..a5838a8a0fac8e 100644 --- a/packages/kbn-optimizer/src/index.ts +++ b/packages/kbn-optimizer/src/index.ts @@ -12,3 +12,4 @@ export * from './log_optimizer_state'; export * from './node'; export * from './limits'; export * from './cli'; +export * from './report_optimizer_timings'; diff --git a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap index 9e9e8960da21bb..b36487fa6e668f 100644 --- a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap +++ b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap @@ -101,16 +101,16 @@ OptimizerConfig { } `; -exports[`prepares assets for distribution: bar bundle 1`] = `"(function(modules){var installedModules={};function __webpack_require__(moduleId){if(installedModules[moduleId]){return installedModules[moduleId].exports}var module=installedModules[moduleId]={i:moduleId,l:false,exports:{}};modules[moduleId].call(module.exports,module,module.exports,__webpack_require__);module.l=true;return module.exports}__webpack_require__.m=modules;__webpack_require__.c=installedModules;__webpack_require__.d=function(exports,name,getter){if(!__webpack_require__.o(exports,name)){Object.defineProperty(exports,name,{enumerable:true,get:getter})}};__webpack_require__.r=function(exports){if(typeof Symbol!==\\"undefined\\"&&Symbol.toStringTag){Object.defineProperty(exports,Symbol.toStringTag,{value:\\"Module\\"})}Object.defineProperty(exports,\\"__esModule\\",{value:true})};__webpack_require__.t=function(value,mode){if(mode&1)value=__webpack_require__(value);if(mode&8)return value;if(mode&4&&typeof value===\\"object\\"&&value&&value.__esModule)return value;var ns=Object.create(null);__webpack_require__.r(ns);Object.defineProperty(ns,\\"default\\",{enumerable:true,value:value});if(mode&2&&typeof value!=\\"string\\")for(var key in value)__webpack_require__.d(ns,key,function(key){return value[key]}.bind(null,key));return ns};__webpack_require__.n=function(module){var getter=module&&module.__esModule?function getDefault(){return module[\\"default\\"]}:function getModuleExports(){return module};__webpack_require__.d(getter,\\"a\\",getter);return getter};__webpack_require__.o=function(object,property){return Object.prototype.hasOwnProperty.call(object,property)};__webpack_require__.p=\\"\\";return __webpack_require__(__webpack_require__.s=3)})([function(module,exports,__webpack_require__){\\"use strict\\";var isOldIE=function isOldIE(){var memo;return function memorize(){if(typeof memo===\\"undefined\\"){memo=Boolean(window&&document&&document.all&&!window.atob)}return memo}}();var getTarget=function getTarget(){var memo={};return function memorize(target){if(typeof memo[target]===\\"undefined\\"){var styleTarget=document.querySelector(target);if(window.HTMLIFrameElement&&styleTarget instanceof window.HTMLIFrameElement){try{styleTarget=styleTarget.contentDocument.head}catch(e){styleTarget=null}}memo[target]=styleTarget}return memo[target]}}();var stylesInDom=[];function getIndexByIdentifier(identifier){var result=-1;for(var i=0;i { + let sent = false; + + const cachedBundles = new Set(); + const notCachedBundles = new Set(); + + return update$.pipe( + concatMap(async (update) => { + // if we've already sent timing data then move on + if (sent) { + return update; + } + + if (update.event?.type === 'bundle cached') { + cachedBundles.add(update.event.bundle.id); + } + if (update.event?.type === 'bundle not cached') { + notCachedBundles.add(update.event.bundle.id); + } + + // wait for the optimizer to complete, either with a success or failure + if (update.state.phase !== 'issue' && update.state.phase !== 'success') { + return update; + } + + sent = true; + const reporter = CiStatsReporter.fromEnv(log); + const time = Date.now() - update.state.startTime; + + await reporter.timings({ + timings: [ + { + group: '@kbn/optimizer', + id: 'overall time', + ms: time, + meta: { + optimizerBundleCount: config.bundles.length, + optimizerBundleCacheCount: cachedBundles.size, + optimizerBundleCachePct: Math.floor( + (cachedBundles.size / config.bundles.length) * 100 + ), + optimizerWatch: config.watch, + optimizerProduction: config.dist, + optimizerProfileWebpack: config.profileWebpack, + optimizerBundleThemeTagsCount: config.themeTags.length, + optimizerCache: config.cache, + optimizerMaxWorkerCount: config.maxWorkerCount, + }, + }, + ], + }); + + return update; + }) + ); + }); +} diff --git a/packages/kbn-optimizer/src/worker/webpack.config.ts b/packages/kbn-optimizer/src/worker/webpack.config.ts index 5532b3a610bbc3..1542fde9054a49 100644 --- a/packages/kbn-optimizer/src/worker/webpack.config.ts +++ b/packages/kbn-optimizer/src/worker/webpack.config.ts @@ -266,7 +266,7 @@ export function getWebpackConfig(bundle: Bundle, bundleRefs: BundleRefs, worker: extractComments: false, parallel: false, terserOptions: { - compress: false, + compress: true, mangle: false, }, }), diff --git a/packages/kbn-telemetry-tools/src/tools/__fixture__/all_extracted_collectors.ts b/packages/kbn-telemetry-tools/src/tools/__fixture__/all_extracted_collectors.ts index 37bdd327f945b5..1f74a2a02eb1ed 100644 --- a/packages/kbn-telemetry-tools/src/tools/__fixture__/all_extracted_collectors.ts +++ b/packages/kbn-telemetry-tools/src/tools/__fixture__/all_extracted_collectors.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import type { ParsedUsageCollection } from '../ts_parser'; import { parsedExternallyDefinedCollector } from './parsed_externally_defined_collector'; import { parsedImportedSchemaCollector } from './parsed_imported_schema'; import { parsedImportedUsageInterface } from './parsed_imported_usage_interface'; @@ -14,15 +15,18 @@ import { parsedNestedCollector } from './parsed_nested_collector'; import { parsedSchemaDefinedWithSpreadsCollector } from './parsed_schema_defined_with_spreads_collector'; import { parsedWorkingCollector } from './parsed_working_collector'; import { parsedCollectorWithDescription } from './parsed_working_collector_with_description'; -import { ParsedUsageCollection } from '../ts_parser'; +import { parsedStatsCollector } from './parsed_stats_collector'; +import { parsedImportedInterfaceFromExport } from './parsed_imported_interface_from_export'; export const allExtractedCollectors: ParsedUsageCollection[] = [ ...parsedExternallyDefinedCollector, + ...parsedImportedInterfaceFromExport, ...parsedImportedSchemaCollector, ...parsedImportedUsageInterface, parsedIndexedInterfaceWithNoMatchingSchema, parsedNestedCollector, parsedSchemaDefinedWithSpreadsCollector, + ...parsedStatsCollector, parsedCollectorWithDescription, parsedWorkingCollector, ]; diff --git a/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_imported_interface_from_export.ts b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_imported_interface_from_export.ts new file mode 100644 index 00000000000000..42f958d1e33c50 --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_imported_interface_from_export.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { SyntaxKind } from 'typescript'; +import { ParsedUsageCollection } from '../ts_parser'; + +export const parsedImportedInterfaceFromExport: ParsedUsageCollection[] = [ + [ + 'src/fixtures/telemetry_collectors/imported_interface_from_export/index.ts', + { + collectorName: 'importing_from_export_collector', + schema: { + value: { + some_field: { + type: 'keyword', + }, + }, + }, + fetch: { + typeName: 'Usage', + typeDescriptor: { + some_field: { + kind: SyntaxKind.StringKeyword, + type: 'StringKeyword', + }, + }, + }, + }, + ], +]; diff --git a/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_stats_collector.ts b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_stats_collector.ts new file mode 100644 index 00000000000000..828372bf0b7d9c --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_stats_collector.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { SyntaxKind } from 'typescript'; +import { ParsedUsageCollection } from '../ts_parser'; + +export const parsedStatsCollector: ParsedUsageCollection[] = [ + [ + 'src/fixtures/telemetry_collectors/stats_collector.ts', + { + collectorName: 'my_stats_collector_with_schema', + schema: { + value: { + some_field: { + type: 'keyword', + }, + }, + }, + fetch: { + typeName: 'Usage', + typeDescriptor: { + some_field: { + kind: SyntaxKind.StringKeyword, + type: 'StringKeyword', + }, + }, + }, + }, + ], +]; diff --git a/packages/kbn-telemetry-tools/src/tools/extract_collectors.test.ts b/packages/kbn-telemetry-tools/src/tools/extract_collectors.test.ts index 5106ac7855fc6c..5eee06a5182eef 100644 --- a/packages/kbn-telemetry-tools/src/tools/extract_collectors.test.ts +++ b/packages/kbn-telemetry-tools/src/tools/extract_collectors.test.ts @@ -24,7 +24,7 @@ describe('extractCollectors', () => { const programPaths = await getProgramPaths(configs[0]); const results = [...extractCollectors(programPaths, tsConfig)]; - expect(results).toHaveLength(9); + expect(results).toHaveLength(11); expect(results).toStrictEqual(allExtractedCollectors); }); }); diff --git a/packages/kbn-telemetry-tools/src/tools/serializer.ts b/packages/kbn-telemetry-tools/src/tools/serializer.ts index b3111af5eec94a..9bde3cb8393641 100644 --- a/packages/kbn-telemetry-tools/src/tools/serializer.ts +++ b/packages/kbn-telemetry-tools/src/tools/serializer.ts @@ -202,7 +202,7 @@ export function getDescriptor(node: ts.Node, program: ts.Program): Descriptor | return getDescriptor(node.typeName, program); } - if (ts.isImportSpecifier(node)) { + if (ts.isImportSpecifier(node) || ts.isExportSpecifier(node)) { const source = node.getSourceFile(); const importedModuleName = getModuleSpecifier(node); diff --git a/packages/kbn-telemetry-tools/src/tools/ts_parser.test.ts b/packages/kbn-telemetry-tools/src/tools/ts_parser.test.ts index 761645b9887dad..4a58e3fc1101b7 100644 --- a/packages/kbn-telemetry-tools/src/tools/ts_parser.test.ts +++ b/packages/kbn-telemetry-tools/src/tools/ts_parser.test.ts @@ -15,6 +15,8 @@ import { parsedExternallyDefinedCollector } from './__fixture__/parsed_externall import { parsedImportedUsageInterface } from './__fixture__/parsed_imported_usage_interface'; import { parsedImportedSchemaCollector } from './__fixture__/parsed_imported_schema'; import { parsedSchemaDefinedWithSpreadsCollector } from './__fixture__/parsed_schema_defined_with_spreads_collector'; +import { parsedStatsCollector } from './__fixture__/parsed_stats_collector'; +import { parsedImportedInterfaceFromExport } from './__fixture__/parsed_imported_interface_from_export'; export function loadFixtureProgram(fixtureName: string) { const fixturePath = path.resolve( @@ -89,6 +91,18 @@ describe('parseUsageCollection', () => { expect(result).toEqual(parsedImportedUsageInterface); }); + it('parses stats collectors, discarding those without schemas', () => { + const { program, sourceFile } = loadFixtureProgram('stats_collector.ts'); + const result = [...parseUsageCollection(sourceFile, program)]; + expect(result).toEqual(parsedStatsCollector); + }); + + it('follows `export { Usage } from "./path"` expressions', () => { + const { program, sourceFile } = loadFixtureProgram('imported_interface_from_export/index.ts'); + const result = [...parseUsageCollection(sourceFile, program)]; + expect(result).toEqual(parsedImportedInterfaceFromExport); + }); + it('skips files that do not define a collector', () => { const { program, sourceFile } = loadFixtureProgram('file_with_no_collector.ts'); const result = [...parseUsageCollection(sourceFile, program)]; diff --git a/packages/kbn-telemetry-tools/src/tools/ts_parser.ts b/packages/kbn-telemetry-tools/src/tools/ts_parser.ts index 1e2bb0a0dbed09..9431e7e0536841 100644 --- a/packages/kbn-telemetry-tools/src/tools/ts_parser.ts +++ b/packages/kbn-telemetry-tools/src/tools/ts_parser.ts @@ -41,6 +41,24 @@ export function isMakeUsageCollectorFunction( return false; } +export function isMakeStatsCollectorFunctionWithSchema( + node: ts.Node, + sourceFile: ts.SourceFile +): node is ts.CallExpression { + if (ts.isCallExpression(node)) { + const isMakeStatsCollector = /makeStatsCollector$/.test(node.expression.getText(sourceFile)); + if (isMakeStatsCollector) { + const collectorConfig = getCollectionConfigNode(node, sourceFile); + const schemaProperty = getProperty(collectorConfig, 'schema'); + if (schemaProperty) { + return true; + } + } + } + + return false; +} + export interface CollectorDetails { collectorName: string; fetch: { typeName: string; typeDescriptor: Descriptor }; @@ -140,6 +158,7 @@ function extractCollectorDetails( throw Error(`usageCollector.schema must be be an object.`); } + // TODO: Try to infer the output type from fetch instead of being explicit const collectorNodeType = collectorNode.typeArguments; if (!collectorNodeType || collectorNodeType?.length === 0) { throw Error(`makeUsageCollector requires a Usage type makeUsageCollector({ ... }).`); @@ -172,7 +191,19 @@ export function sourceHasUsageCollector(sourceFile: ts.SourceFile) { } return false; - return true; +} + +export function sourceHasStatsCollector(sourceFile: ts.SourceFile) { + if (sourceFile.isDeclarationFile === true || (sourceFile as any).identifierCount === 0) { + return false; + } + + const identifiers = (sourceFile as any).identifiers; + if (identifiers.get('makeStatsCollector')) { + return true; + } + + return false; } export type ParsedUsageCollection = [string, CollectorDetails]; @@ -182,9 +213,12 @@ export function* parseUsageCollection( program: ts.Program ): Generator { const relativePath = path.relative(process.cwd(), sourceFile.fileName); - if (sourceHasUsageCollector(sourceFile)) { + if (sourceHasUsageCollector(sourceFile) || sourceHasStatsCollector(sourceFile)) { for (const node of traverseNodes(sourceFile)) { - if (isMakeUsageCollectorFunction(node, sourceFile)) { + if ( + isMakeUsageCollectorFunction(node, sourceFile) || + isMakeStatsCollectorFunctionWithSchema(node, sourceFile) + ) { try { const collectorDetails = extractCollectorDetails(node, program, sourceFile); yield [relativePath, collectorDetails]; diff --git a/packages/kbn-telemetry-tools/src/tools/utils.ts b/packages/kbn-telemetry-tools/src/tools/utils.ts index 52362668c2f535..c9526fe7d04038 100644 --- a/packages/kbn-telemetry-tools/src/tools/utils.ts +++ b/packages/kbn-telemetry-tools/src/tools/utils.ts @@ -65,7 +65,9 @@ export function getIdentifierDeclarationFromSource(node: ts.Node, source: ts.Sou } const identifierName = node.getText(); - const identifierDefinition: ts.Node = (source as any).locals.get(identifierName); + const identifierDefinition: ts.Node = + (source as any).locals.get(identifierName) || + (source as any).symbol.exports.get(identifierName); if (!identifierDefinition) { throw new Error(`Unable to find identifier in source ${identifierName}`); } diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js index 891f2b0fff7974..13c16691bf12a2 100644 --- a/src/cli/serve/serve.js +++ b/src/cli/serve/serve.js @@ -51,7 +51,6 @@ function applyConfigOverrides(rawConfig, opts, extraCliOptions) { const get = _.partial(_.get, rawConfig); const has = _.partial(_.has, rawConfig); const merge = _.partial(_.merge, rawConfig); - if (opts.oss) { delete rawConfig.xpack; } @@ -112,10 +111,18 @@ function applyConfigOverrides(rawConfig, opts, extraCliOptions) { if (opts.elasticsearch) set('elasticsearch.hosts', opts.elasticsearch.split(',')); if (opts.port) set('server.port', opts.port); if (opts.host) set('server.host', opts.host); - if (opts.quiet) set('logging.quiet', true); - if (opts.silent) set('logging.silent', true); - if (opts.verbose) set('logging.verbose', true); - if (opts.logFile) set('logging.dest', opts.logFile); + if (opts.silent) { + set('logging.silent', true); + set('logging.root.level', 'off'); + } + if (opts.verbose) { + if (has('logging.root.appenders')) { + set('logging.root.level', 'all'); + } else { + // Only set logging.verbose to true for legacy logging when KP logging isn't configured. + set('logging.verbose', true); + } + } set('plugins.scanDirs', _.compact([].concat(get('plugins.scanDirs'), opts.pluginDir))); set('plugins.paths', _.compact([].concat(get('plugins.paths'), opts.pluginPath))); @@ -140,11 +147,14 @@ export default function (program) { [getConfigPath()] ) .option('-p, --port ', 'The port to bind to', parseInt) - .option('-q, --quiet', 'Prevent all logging except errors') + .option('-q, --quiet', 'Deprecated, set logging level in your configuration') .option('-Q, --silent', 'Prevent all logging') .option('--verbose', 'Turns on verbose logging') .option('-H, --host ', 'The host to bind to') - .option('-l, --log-file ', 'The file to log to') + .option( + '-l, --log-file ', + 'Deprecated, set logging file destination in your configuration' + ) .option( '--plugin-dir ', 'A path to scan for plugins, this can be specified multiple ' + @@ -204,6 +214,7 @@ export default function (program) { cliArgs: { dev: !!opts.dev, envName: unknownOptions.env ? unknownOptions.env.name : undefined, + // no longer supported quiet: !!opts.quiet, silent: !!opts.silent, watch: !!opts.watch, diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 049fd79aa2ac85..dfe3583dd60abc 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -122,6 +122,7 @@ export class DocLinksService { }, indexPatterns: { introduction: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/index-patterns.html`, + fieldFormattersNumber: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/numeral.html`, fieldFormattersString: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/field-formatters-string.html`, }, addData: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/connect-to-elasticsearch.html`, @@ -392,6 +393,8 @@ export interface DocLinksStart { }; readonly indexPatterns: { readonly introduction: string; + readonly fieldFormattersNumber: string; + readonly fieldFormattersString: string; }; readonly addData: string; readonly kibana: string; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index bf25fcaa75acca..396bf16cbdc6f2 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -571,6 +571,8 @@ export interface DocLinksStart { }; readonly indexPatterns: { readonly introduction: string; + readonly fieldFormattersNumber: string; + readonly fieldFormattersString: string; }; readonly addData: string; readonly kibana: string; diff --git a/src/core/server/config/deprecation/core_deprecations.test.ts b/src/core/server/config/deprecation/core_deprecations.test.ts index 5bb2ebf702aa74..c63ebb6c9c5514 100644 --- a/src/core/server/config/deprecation/core_deprecations.test.ts +++ b/src/core/server/config/deprecation/core_deprecations.test.ts @@ -264,17 +264,10 @@ describe('core deprecations', () => { }); expect(messages).toMatchInlineSnapshot(` Array [ - "\\"logging.events.ops\\" has been deprecated and will be removed in 8.0. To access ops data moving forward, please enable debug logs for the \\"metrics.ops\\" context in your logging configuration. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.md", + "\\"logging.events.ops\\" has been deprecated and will be removed in 8.0. To access ops data moving forward, please enable debug logs for the \\"metrics.ops\\" context in your logging configuration. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx", ] `); }); - - it('does not warn when other events are configured', () => { - const { messages } = applyCoreDeprecations({ - logging: { events: { log: '*' } }, - }); - expect(messages).toEqual([]); - }); }); describe('logging.events.request and logging.events.response', () => { @@ -284,7 +277,7 @@ describe('core deprecations', () => { }); expect(messages).toMatchInlineSnapshot(` Array [ - "\\"logging.events.request\\" and \\"logging.events.response\\" have been deprecated and will be removed in 8.0. To access request and/or response data moving forward, please enable debug logs for the \\"http.server.response\\" context in your logging configuration. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.md", + "\\"logging.events.request\\" and \\"logging.events.response\\" have been deprecated and will be removed in 8.0. To access request and/or response data moving forward, please enable debug logs for the \\"http.server.response\\" context in your logging configuration. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx", ] `); }); @@ -295,7 +288,7 @@ describe('core deprecations', () => { }); expect(messages).toMatchInlineSnapshot(` Array [ - "\\"logging.events.request\\" and \\"logging.events.response\\" have been deprecated and will be removed in 8.0. To access request and/or response data moving forward, please enable debug logs for the \\"http.server.response\\" context in your logging configuration. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.md", + "\\"logging.events.request\\" and \\"logging.events.response\\" have been deprecated and will be removed in 8.0. To access request and/or response data moving forward, please enable debug logs for the \\"http.server.response\\" context in your logging configuration. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx", ] `); }); @@ -306,36 +299,187 @@ describe('core deprecations', () => { }); expect(messages).toMatchInlineSnapshot(` Array [ - "\\"logging.events.request\\" and \\"logging.events.response\\" have been deprecated and will be removed in 8.0. To access request and/or response data moving forward, please enable debug logs for the \\"http.server.response\\" context in your logging configuration. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.md", + "\\"logging.events.request\\" and \\"logging.events.response\\" have been deprecated and will be removed in 8.0. To access request and/or response data moving forward, please enable debug logs for the \\"http.server.response\\" context in your logging configuration. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx", ] `); }); + }); - it('does not warn when other events are configured', () => { + describe('logging.timezone', () => { + it('warns when ops events are used', () => { const { messages } = applyCoreDeprecations({ - logging: { events: { log: '*' } }, + logging: { timezone: 'GMT' }, }); - expect(messages).toEqual([]); + expect(messages).toMatchInlineSnapshot(` + Array [ + "\\"logging.timezone\\" has been deprecated and will be removed in 8.0. To set the timezone moving forward, please add a timezone date modifier to the log pattern in your logging configuration. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx", + ] + `); }); }); - describe('logging.timezone', () => { - it('warns when ops events are used', () => { + describe('logging.dest', () => { + it('warns when dest is used', () => { const { messages } = applyCoreDeprecations({ - logging: { timezone: 'GMT' }, + logging: { dest: 'stdout' }, + }); + expect(messages).toMatchInlineSnapshot(` + Array [ + "\\"logging.dest\\" has been deprecated and will be removed in 8.0. To set the destination moving forward, you can use the \\"console\\" appender in your logging configuration or define a custom one. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx.", + ] + `); + }); + it('warns when dest path is given', () => { + const { messages } = applyCoreDeprecations({ + logging: { dest: '/log-log.txt' }, + }); + expect(messages).toMatchInlineSnapshot(` + Array [ + "\\"logging.dest\\" has been deprecated and will be removed in 8.0. To set the destination moving forward, you can use the \\"console\\" appender in your logging configuration or define a custom one. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx.", + ] + `); + }); + }); + + describe('logging.quiet, logging.silent and logging.verbose', () => { + it('warns when quiet is used', () => { + const { messages } = applyCoreDeprecations({ + logging: { quiet: true }, + }); + expect(messages).toMatchInlineSnapshot(` + Array [ + "\\"logging.quiet\\" has been deprecated and will be removed in 8.0. Moving forward, you can use \\"logging.root.level:error\\" in your logging configuration. ", + ] + `); + }); + it('warns when silent is used', () => { + const { messages } = applyCoreDeprecations({ + logging: { silent: true }, + }); + expect(messages).toMatchInlineSnapshot(` + Array [ + "\\"logging.silent\\" has been deprecated and will be removed in 8.0. Moving forward, you can use \\"logging.root.level:off\\" in your logging configuration. ", + ] + `); + }); + it('warns when verbose is used', () => { + const { messages } = applyCoreDeprecations({ + logging: { verbose: true }, + }); + expect(messages).toMatchInlineSnapshot(` + Array [ + "\\"logging.verbose\\" has been deprecated and will be removed in 8.0. Moving forward, you can use \\"logging.root.level:all\\" in your logging configuration. ", + ] + `); + }); + }); + + describe('logging.json', () => { + it('warns when json is used', () => { + const { messages } = applyCoreDeprecations({ + logging: { json: true }, + }); + expect(messages).toMatchInlineSnapshot(` + Array [ + "\\"logging.json\\" has been deprecated and will be removed in 8.0. To specify log message format moving forward, you can configure the \\"appender.layout\\" property for every custom appender in your logging configuration. There is currently no default layout for custom appenders and each one must be declared explicitly. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx.", + ] + `); + }); + }); + + describe('logging.rotate.enabled, logging.rotate.usePolling, logging.rotate.pollingInterval, logging.rotate.everyBytes and logging.rotate.keepFiles', () => { + it('warns when logging.rotate configurations are used', () => { + const { messages } = applyCoreDeprecations({ + logging: { rotate: { enabled: true } }, + }); + expect(messages).toMatchInlineSnapshot(` + Array [ + "\\"logging.rotate\\" and sub-options have been deprecated and will be removed in 8.0. Moving forward, you can enable log rotation using the \\"rolling-file\\" appender for a logger in your logging configuration. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#rolling-file-appender", + ] + `); + }); + + it('warns when logging.rotate polling configurations are used', () => { + const { messages } = applyCoreDeprecations({ + logging: { rotate: { enabled: true, usePolling: true, pollingInterval: 5000 } }, + }); + expect(messages).toMatchInlineSnapshot(` + Array [ + "\\"logging.rotate\\" and sub-options have been deprecated and will be removed in 8.0. Moving forward, you can enable log rotation using the \\"rolling-file\\" appender for a logger in your logging configuration. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#rolling-file-appender", + ] + `); + }); + + it('warns when logging.rotate.everyBytes configurations are used', () => { + const { messages } = applyCoreDeprecations({ + logging: { rotate: { enabled: true, everyBytes: 1048576 } }, }); expect(messages).toMatchInlineSnapshot(` Array [ - "\\"logging.timezone\\" has been deprecated and will be removed in 8.0. To set the timezone moving forward, please add a timezone date modifier to the log pattern in your logging configuration. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.md", + "\\"logging.rotate\\" and sub-options have been deprecated and will be removed in 8.0. Moving forward, you can enable log rotation using the \\"rolling-file\\" appender for a logger in your logging configuration. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#rolling-file-appender", ] `); }); - it('does not warn when other events are configured', () => { + it('warns when logging.rotate.keepFiles is used', () => { const { messages } = applyCoreDeprecations({ - logging: { events: { log: '*' } }, + logging: { rotate: { enabled: true, keepFiles: 1024 } }, }); - expect(messages).toEqual([]); + expect(messages).toMatchInlineSnapshot(` + Array [ + "\\"logging.rotate\\" and sub-options have been deprecated and will be removed in 8.0. Moving forward, you can enable log rotation using the \\"rolling-file\\" appender for a logger in your logging configuration. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#rolling-file-appender", + ] + `); + }); + }); + + describe('logging.events.log', () => { + it('warns when events.log is used', () => { + const { messages } = applyCoreDeprecations({ + logging: { events: { log: ['info'] } }, + }); + expect(messages).toMatchInlineSnapshot(` + Array [ + "\\"logging.events.log\\" has been deprecated and will be removed in 8.0. Moving forward, log levels can be customized on a per-logger basis using the new logging configuration. ", + ] + `); + }); + }); + + describe('logging.events.error', () => { + it('warns when events.error is used', () => { + const { messages } = applyCoreDeprecations({ + logging: { events: { error: ['some error'] } }, + }); + expect(messages).toMatchInlineSnapshot(` + Array [ + "\\"logging.events.error\\" has been deprecated and will be removed in 8.0. Moving forward, you can use \\"logging.root.level: error\\" in your logging configuration. ", + ] + `); + }); + }); + + describe('logging.filter', () => { + it('warns when filter.cookie is used', () => { + const { messages } = applyCoreDeprecations({ + logging: { filter: { cookie: 'none' } }, + }); + expect(messages).toMatchInlineSnapshot(` + Array [ + "\\"logging.filter\\" has been deprecated and will be removed in 8.0. ", + ] + `); + }); + + it('warns when filter.authorization is used', () => { + const { messages } = applyCoreDeprecations({ + logging: { filter: { authorization: 'none' } }, + }); + expect(messages).toMatchInlineSnapshot(` + Array [ + "\\"logging.filter\\" has been deprecated and will be removed in 8.0. ", + ] + `); }); }); }); diff --git a/src/core/server/config/deprecation/core_deprecations.ts b/src/core/server/config/deprecation/core_deprecations.ts index 966dbaf282c460..461bcf5e6b1d5e 100644 --- a/src/core/server/config/deprecation/core_deprecations.ts +++ b/src/core/server/config/deprecation/core_deprecations.ts @@ -119,7 +119,7 @@ const opsLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, log) '"logging.events.ops" has been deprecated and will be removed ' + 'in 8.0. To access ops data moving forward, please enable debug logs for the ' + '"metrics.ops" context in your logging configuration. For more details, see ' + - 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.md' + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx' ); } return settings; @@ -131,22 +131,123 @@ const requestLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, l '"logging.events.request" and "logging.events.response" have been deprecated and will be removed ' + 'in 8.0. To access request and/or response data moving forward, please enable debug logs for the ' + '"http.server.response" context in your logging configuration. For more details, see ' + - 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.md' + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx' ); } return settings; }; + const timezoneLoggingDeprecation: ConfigDeprecation = (settings, fromPath, log) => { if (has(settings, 'logging.timezone')) { log( '"logging.timezone" has been deprecated and will be removed ' + 'in 8.0. To set the timezone moving forward, please add a timezone date modifier to the log pattern ' + 'in your logging configuration. For more details, see ' + - 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.md' + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx' + ); + } + return settings; +}; + +const destLoggingDeprecation: ConfigDeprecation = (settings, fromPath, log) => { + if (has(settings, 'logging.dest')) { + log( + '"logging.dest" has been deprecated and will be removed ' + + 'in 8.0. To set the destination moving forward, you can use the "console" appender ' + + 'in your logging configuration or define a custom one. For more details, see ' + + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx.' + ); + } + return settings; +}; + +const quietLoggingDeprecation: ConfigDeprecation = (settings, fromPath, log) => { + if (has(settings, 'logging.quiet')) { + log( + '"logging.quiet" has been deprecated and will be removed ' + + 'in 8.0. Moving forward, you can use "logging.root.level:error" in your logging configuration. ' + ); + } + return settings; +}; + +const silentLoggingDeprecation: ConfigDeprecation = (settings, fromPath, log) => { + if (has(settings, 'logging.silent')) { + log( + '"logging.silent" has been deprecated and will be removed ' + + 'in 8.0. Moving forward, you can use "logging.root.level:off" in your logging configuration. ' + ); + } + return settings; +}; + +const verboseLoggingDeprecation: ConfigDeprecation = (settings, fromPath, log) => { + if (has(settings, 'logging.verbose')) { + log( + '"logging.verbose" has been deprecated and will be removed ' + + 'in 8.0. Moving forward, you can use "logging.root.level:all" in your logging configuration. ' ); } return settings; }; + +const jsonLoggingDeprecation: ConfigDeprecation = (settings, fromPath, log) => { + // We silence the deprecation warning when running in development mode because + // the dev CLI code in src/dev/cli_dev_mode/using_server_process.ts manually + // specifies `--logging.json=false`. Since it's executed in a child process, the + // ` legacyLoggingConfigSchema` returns `true` for the TTY check on `process.stdout.isTTY` + if (has(settings, 'logging.json') && settings.env !== 'development') { + log( + '"logging.json" has been deprecated and will be removed ' + + 'in 8.0. To specify log message format moving forward, ' + + 'you can configure the "appender.layout" property for every custom appender in your logging configuration. ' + + 'There is currently no default layout for custom appenders and each one must be declared explicitly. ' + + 'For more details, see ' + + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx.' + ); + } + return settings; +}; + +const logRotateDeprecation: ConfigDeprecation = (settings, fromPath, log) => { + if (has(settings, 'logging.rotate')) { + log( + '"logging.rotate" and sub-options have been deprecated and will be removed in 8.0. ' + + 'Moving forward, you can enable log rotation using the "rolling-file" appender for a logger ' + + 'in your logging configuration. For more details, see ' + + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#rolling-file-appender' + ); + } + return settings; +}; + +const logEventsLogDeprecation: ConfigDeprecation = (settings, fromPath, log) => { + if (has(settings, 'logging.events.log')) { + log( + '"logging.events.log" has been deprecated and will be removed ' + + 'in 8.0. Moving forward, log levels can be customized on a per-logger basis using the new logging configuration. ' + ); + } + return settings; +}; + +const logEventsErrorDeprecation: ConfigDeprecation = (settings, fromPath, log) => { + if (has(settings, 'logging.events.error')) { + log( + '"logging.events.error" has been deprecated and will be removed ' + + 'in 8.0. Moving forward, you can use "logging.root.level: error" in your logging configuration. ' + ); + } + return settings; +}; + +const logFilterDeprecation: ConfigDeprecation = (settings, fromPath, log) => { + if (has(settings, 'logging.filter')) { + log('"logging.filter" has been deprecated and will be removed ' + 'in 8.0. '); + } + return settings; +}; + export const coreDeprecationProvider: ConfigDeprecationProvider = ({ unusedFromRoot, renameFromRoot, @@ -196,4 +297,13 @@ export const coreDeprecationProvider: ConfigDeprecationProvider = ({ opsLoggingEventDeprecation, requestLoggingEventDeprecation, timezoneLoggingDeprecation, + destLoggingDeprecation, + quietLoggingDeprecation, + silentLoggingDeprecation, + verboseLoggingDeprecation, + jsonLoggingDeprecation, + logRotateDeprecation, + logEventsLogDeprecation, + logEventsErrorDeprecation, + logFilterDeprecation, ]; diff --git a/src/core/server/config/integration_tests/config_deprecation.test.ts b/src/core/server/config/integration_tests/config_deprecation.test.ts index 4cb0ec02d85f73..5b672774c515a9 100644 --- a/src/core/server/config/integration_tests/config_deprecation.test.ts +++ b/src/core/server/config/integration_tests/config_deprecation.test.ts @@ -23,13 +23,17 @@ describe('configuration deprecations', () => { } }); - it('should not log deprecation warnings for default configuration', async () => { + it('should not log deprecation warnings for default configuration that is not one of `logging.verbose`, `logging.quiet` or `logging.silent`', async () => { root = kbnTestServer.createRoot(); await root.setup(); const logs = loggingSystemMock.collect(mockLoggingSystem); - expect(logs.warn.flat()).toMatchInlineSnapshot(`Array []`); + expect(logs.warn.flat()).toMatchInlineSnapshot(` + Array [ + "\\"logging.silent\\" has been deprecated and will be removed in 8.0. Moving forward, you can use \\"logging.root.level:off\\" in your logging configuration. ", + ] + `); }); it('should log deprecation warnings for core deprecations', async () => { @@ -47,6 +51,7 @@ describe('configuration deprecations', () => { Array [ "optimize.lazy is deprecated and is no longer used", "optimize.lazyPort is deprecated and is no longer used", + "\\"logging.silent\\" has been deprecated and will be removed in 8.0. Moving forward, you can use \\"logging.root.level:off\\" in your logging configuration. ", ] `); }); diff --git a/src/core/server/core_usage_data/core_usage_data_service.mock.ts b/src/core/server/core_usage_data/core_usage_data_service.mock.ts index 21a599e45da013..8ed627cebec7e4 100644 --- a/src/core/server/core_usage_data/core_usage_data_service.mock.ts +++ b/src/core/server/core_usage_data/core_usage_data_service.mock.ts @@ -106,7 +106,7 @@ const createStartContractMock = () => { }, savedObjects: { customIndex: false, - maxImportExportSizeBytes: 10000, + maxImportExportSize: 10000, maxImportPayloadBytes: 26214400, }, }, diff --git a/src/core/server/core_usage_data/core_usage_data_service.test.ts b/src/core/server/core_usage_data/core_usage_data_service.test.ts index ddd041b0f544ee..673190d22e4218 100644 --- a/src/core/server/core_usage_data/core_usage_data_service.test.ts +++ b/src/core/server/core_usage_data/core_usage_data_service.test.ts @@ -239,7 +239,7 @@ describe('CoreUsageDataService', () => { }, "savedObjects": Object { "customIndex": false, - "maxImportExportSizeBytes": 10000, + "maxImportExportSize": 10000, "maxImportPayloadBytes": 26214400, }, }, diff --git a/src/core/server/core_usage_data/core_usage_data_service.ts b/src/core/server/core_usage_data/core_usage_data_service.ts index e57d8d90a02dcc..b9d8c9fc7e39fb 100644 --- a/src/core/server/core_usage_data/core_usage_data_service.ts +++ b/src/core/server/core_usage_data/core_usage_data_service.ts @@ -235,7 +235,7 @@ export class CoreUsageDataService implements CoreService { request, types: ['index-pattern', 'search'], }) - ).rejects.toThrowErrorMatchingInlineSnapshot(`"Can't export more than 1 objects"`); + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Can't export more than 1 objects. If your server has enough memory, this limit can be increased by adjusting the \\"savedObjects.maxImportExportSize\\" setting."` + ); expect(savedObjectsClient.closePointInTime).toHaveBeenCalledTimes(1); }); @@ -1112,7 +1114,7 @@ describe('getSortedObjectsForExport()', () => { ], }; await expect(exporter.exportByObjects(exportOpts)).rejects.toThrowErrorMatchingInlineSnapshot( - `"Can't export more than 1 objects"` + `"Can't export more than 1 objects. If your server has enough memory, this limit can be increased by adjusting the \\"savedObjects.maxImportExportSize\\" setting."` ); }); diff --git a/src/core/server/saved_objects/saved_objects_config.ts b/src/core/server/saved_objects/saved_objects_config.ts index 6fefd397de2459..1806bb6e0c8954 100644 --- a/src/core/server/saved_objects/saved_objects_config.ts +++ b/src/core/server/saved_objects/saved_objects_config.ts @@ -28,7 +28,7 @@ export const savedObjectsConfig = { path: 'savedObjects', schema: schema.object({ maxImportPayloadBytes: schema.byteSize({ defaultValue: 26214400 }), - maxImportExportSize: schema.byteSize({ defaultValue: 10000 }), + maxImportExportSize: schema.number({ defaultValue: 10000 }), }), }; @@ -43,7 +43,7 @@ export class SavedObjectConfig { rawMigrationConfig: SavedObjectsMigrationConfigType ) { this.maxImportPayloadBytes = rawConfig.maxImportPayloadBytes.getValueInBytes(); - this.maxImportExportSize = rawConfig.maxImportExportSize.getValueInBytes(); + this.maxImportExportSize = rawConfig.maxImportExportSize; this.migration = rawMigrationConfig; } } diff --git a/src/core/server/saved_objects/saved_objects_service.test.ts b/src/core/server/saved_objects/saved_objects_service.test.ts index 1d1e6d39fe90ea..d589809e38f01d 100644 --- a/src/core/server/saved_objects/saved_objects_service.test.ts +++ b/src/core/server/saved_objects/saved_objects_service.test.ts @@ -42,7 +42,7 @@ describe('SavedObjectsService', () => { } return new BehaviorSubject({ maxImportPayloadBytes: new ByteSizeValue(0), - maxImportExportSize: new ByteSizeValue(0), + maxImportExportSize: 10000, }); }); return mockCoreContext.create({ configService, env }); diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index a1a774e8721c86..580315973ce8fb 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -452,7 +452,7 @@ export interface CoreConfigUsageData { savedObjects: { customIndex: boolean; maxImportPayloadBytes: number; - maxImportExportSizeBytes: number; + maxImportExportSize: number; }; } diff --git a/src/core/test_helpers/kbn_server.ts b/src/core/test_helpers/kbn_server.ts index 14f614643ac9f1..5e274712ad3a78 100644 --- a/src/core/test_helpers/kbn_server.ts +++ b/src/core/test_helpers/kbn_server.ts @@ -60,7 +60,6 @@ export function createRootWithSettings( configs: [], cliArgs: { dev: false, - quiet: false, silent: false, watch: false, basePath: false, diff --git a/src/dev/build/tasks/build_kibana_platform_plugins.ts b/src/dev/build/tasks/build_kibana_platform_plugins.ts index 973d71043f028b..edff77d458f0f8 100644 --- a/src/dev/build/tasks/build_kibana_platform_plugins.ts +++ b/src/dev/build/tasks/build_kibana_platform_plugins.ts @@ -11,7 +11,12 @@ import Path from 'path'; import { REPO_ROOT } from '@kbn/utils'; import { lastValueFrom } from '@kbn/std'; import { CiStatsMetric } from '@kbn/dev-utils'; -import { runOptimizer, OptimizerConfig, logOptimizerState } from '@kbn/optimizer'; +import { + runOptimizer, + OptimizerConfig, + logOptimizerState, + reportOptimizerTimings, +} from '@kbn/optimizer'; import { Task, deleteAll, write, read } from '../lib'; @@ -30,7 +35,9 @@ export const BuildKibanaPlatformPlugins: Task = { limitsPath: Path.resolve(REPO_ROOT, 'packages/kbn-optimizer/limits.yml'), }); - await lastValueFrom(runOptimizer(config).pipe(logOptimizerState(log, config))); + await lastValueFrom( + runOptimizer(config).pipe(logOptimizerState(log, config), reportOptimizerTimings(log, config)) + ); const combinedMetrics: CiStatsMetric[] = []; const metricFilePaths: string[] = []; diff --git a/src/fixtures/telemetry_collectors/imported_interface_from_export/index.ts b/src/fixtures/telemetry_collectors/imported_interface_from_export/index.ts new file mode 100644 index 00000000000000..095ee9e8f6091f --- /dev/null +++ b/src/fixtures/telemetry_collectors/imported_interface_from_export/index.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { CollectorSet } from '../../../plugins/usage_collection/server/collector'; +import { loggerMock } from '../../../core/server/logging/logger.mock'; +import type { Usage } from './types'; + +const { makeUsageCollector } = new CollectorSet({ + logger: loggerMock.create(), + maximumWaitTimeForAllCollectorsInS: 0, +}); + +export const myCollector = makeUsageCollector({ + type: 'importing_from_export_collector', + isReady: () => true, + fetch() { + return { + some_field: 'abc', + }; + }, + schema: { + some_field: { + type: 'keyword', + }, + }, +}); diff --git a/src/fixtures/telemetry_collectors/imported_interface_from_export/types.ts b/src/fixtures/telemetry_collectors/imported_interface_from_export/types.ts new file mode 100644 index 00000000000000..c8dd38f4144062 --- /dev/null +++ b/src/fixtures/telemetry_collectors/imported_interface_from_export/types.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 type { Usage } from './usage_type'; diff --git a/src/fixtures/telemetry_collectors/imported_interface_from_export/usage_type.ts b/src/fixtures/telemetry_collectors/imported_interface_from_export/usage_type.ts new file mode 100644 index 00000000000000..765b8901a83e1b --- /dev/null +++ b/src/fixtures/telemetry_collectors/imported_interface_from_export/usage_type.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 interface Usage { + some_field: string; +} diff --git a/src/fixtures/telemetry_collectors/stats_collector.ts b/src/fixtures/telemetry_collectors/stats_collector.ts new file mode 100644 index 00000000000000..55d447751d4b67 --- /dev/null +++ b/src/fixtures/telemetry_collectors/stats_collector.ts @@ -0,0 +1,49 @@ +/* + * 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 { CollectorSet } from '../../plugins/usage_collection/server/collector'; +import { loggerMock } from '../../core/server/logging/logger.mock'; + +const { makeStatsCollector } = new CollectorSet({ + logger: loggerMock.create(), + maximumWaitTimeForAllCollectorsInS: 0, +}); + +interface Usage { + some_field: string; +} + +/** + * Stats Collectors are allowed with schema and without them. + * We should collect them when the schema is defined. + */ + +export const myCollectorWithSchema = makeStatsCollector({ + type: 'my_stats_collector_with_schema', + isReady: () => true, + fetch() { + return { + some_field: 'abc', + }; + }, + schema: { + some_field: { + type: 'keyword', + }, + }, +}); + +export const myCollectorWithoutSchema = makeStatsCollector({ + type: 'my_stats_collector_without_schema', + isReady: () => true, + fetch() { + return { + some_field: 'abc', + }; + }, +}); diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts index 57ecaeab209ed9..3fe0f5899668f9 100644 --- a/src/legacy/server/kbn_server.d.ts +++ b/src/legacy/server/kbn_server.d.ts @@ -33,7 +33,6 @@ declare module 'hapi' { interface Server { config: () => KibanaConfig; - logWithMetadata: (tags: string[], message: string, meta: Record) => void; newPlatform: KbnServer['newPlatform']; } } diff --git a/src/legacy/server/kbn_server.js b/src/legacy/server/kbn_server.js index d2eebb7b0cd235..4bc76b6a7706fc 100644 --- a/src/legacy/server/kbn_server.js +++ b/src/legacy/server/kbn_server.js @@ -94,20 +94,13 @@ export default class KbnServer { async listen() { await this.ready(); - const { server, config } = this; + const { server } = this; if (process.env.isDevCliChild) { // help parent process know when we are ready process.send(['SERVER_LISTENING']); } - server.log( - ['listening', 'info'], - `Server running at ${server.info.uri}${ - config.get('server.rewriteBasePath') ? config.get('server.basePath') : '' - }` - ); - return server; } @@ -133,13 +126,6 @@ export default class KbnServer { const loggingConfig = config.get('logging'); const opsConfig = config.get('ops'); - const subset = { - ops: opsConfig, - logging: loggingConfig, - }; - const plain = JSON.stringify(subset, null, 2); - this.server.log(['info', 'config'], 'New logging configuration:\n' + plain); - reconfigureLogging(this.server, loggingConfig, opsConfig.interval); } } diff --git a/src/legacy/server/logging/index.js b/src/legacy/server/logging/index.js index 0a3d7e3e0a5a91..1b2ae59f4aa002 100644 --- a/src/legacy/server/logging/index.js +++ b/src/legacy/server/logging/index.js @@ -6,13 +6,9 @@ * Side Public License, v 1. */ -import { setupLogging, setupLoggingRotate, attachMetaData } from '@kbn/legacy-logging'; +import { setupLogging, setupLoggingRotate } from '@kbn/legacy-logging'; export async function loggingMixin(kbnServer, server, config) { - server.decorate('server', 'logWithMetadata', (tags, message, metadata = {}) => { - server.log(tags, attachMetaData(message, metadata)); - }); - const loggingConfig = config.get('logging'); const opsInterval = config.get('ops.interval'); diff --git a/src/plugins/dashboard/public/application/dashboard_app_functions.ts b/src/plugins/dashboard/public/application/dashboard_app_functions.ts index be1eea0e17a330..6d51422d4bd23e 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_functions.ts +++ b/src/plugins/dashboard/public/application/dashboard_app_functions.ts @@ -213,8 +213,8 @@ export const getOutputSubscription = ({ }), distinctUntilChanged((a, b) => deepEqual( - a.map((ip) => ip.id), - b.map((ip) => ip.id) + a.map((ip) => ip && ip.id), + b.map((ip) => ip && ip.id) ) ), // using switchMap for previous task cancellation diff --git a/src/plugins/data/common/es_query/es_query/filter_matches_index.test.ts b/src/plugins/data/common/es_query/es_query/filter_matches_index.test.ts index a8c9b9144707db..ad4d7ff8d78e27 100644 --- a/src/plugins/data/common/es_query/es_query/filter_matches_index.test.ts +++ b/src/plugins/data/common/es_query/es_query/filter_matches_index.test.ts @@ -31,6 +31,20 @@ describe('filterMatchesIndex', () => { expect(filterMatchesIndex(filter, indexPattern)).toBe(true); }); + it('should return true if custom filter for the same index is passed', () => { + const filter = { meta: { index: 'foo', key: 'bar', type: 'custom' } } as Filter; + const indexPattern = { id: 'foo', fields: [{ name: 'bara' }] } as IIndexPattern; + + expect(filterMatchesIndex(filter, indexPattern)).toBe(true); + }); + + it('should return false if custom filter for a different index is passed', () => { + const filter = { meta: { index: 'foo', key: 'bar', type: 'custom' } } as Filter; + const indexPattern = { id: 'food', fields: [{ name: 'bara' }] } as IIndexPattern; + + expect(filterMatchesIndex(filter, indexPattern)).toBe(false); + }); + it('should return false if the filter key does not match a field name', () => { const filter = { meta: { index: 'foo', key: 'baz' } } as Filter; const indexPattern = { id: 'foo', fields: [{ name: 'bar' }] } as IIndexPattern; diff --git a/src/plugins/data/common/es_query/es_query/filter_matches_index.ts b/src/plugins/data/common/es_query/es_query/filter_matches_index.ts index 9fd8567b76e2be..478263d5ce6014 100644 --- a/src/plugins/data/common/es_query/es_query/filter_matches_index.ts +++ b/src/plugins/data/common/es_query/es_query/filter_matches_index.ts @@ -18,5 +18,12 @@ export function filterMatchesIndex(filter: Filter, indexPattern?: IIndexPattern if (!filter.meta?.key || !indexPattern) { return true; } + + // Fixes https://github.com/elastic/kibana/issues/89878 + // Custom filters may refer multiple fields. Validate the index id only. + if (filter.meta?.type === 'custom') { + return filter.meta.index === indexPattern.id; + } + return indexPattern.fields.some((field: IFieldType) => field.name === filter.meta.key); } diff --git a/src/plugins/data/common/search/expressions/index.ts b/src/plugins/data/common/search/expressions/index.ts index 8ac5ffec850f69..b38dce247261c5 100644 --- a/src/plugins/data/common/search/expressions/index.ts +++ b/src/plugins/data/common/search/expressions/index.ts @@ -8,6 +8,11 @@ export * from './kibana'; export * from './kibana_context'; +export * from './kql'; +export * from './lucene'; +export * from './query_to_ast'; +export * from './timerange_to_ast'; export * from './kibana_context_type'; export * from './esaggs'; export * from './utils'; +export * from './timerange'; diff --git a/src/plugins/data/common/search/expressions/kibana_context.ts b/src/plugins/data/common/search/expressions/kibana_context.ts index 982db7505a3cf8..5c2e2f418e69c9 100644 --- a/src/plugins/data/common/search/expressions/kibana_context.ts +++ b/src/plugins/data/common/search/expressions/kibana_context.ts @@ -12,11 +12,13 @@ import { ExpressionFunctionDefinition, ExecutionContext } from 'src/plugins/expr import { Adapters } from 'src/plugins/inspector/common'; import { Query, uniqFilters } from '../../query'; import { ExecutionContextSearch, KibanaContext } from './kibana_context_type'; +import { KibanaQueryOutput } from './kibana_context_type'; +import { KibanaTimerangeOutput } from './timerange'; interface Arguments { - q?: string | null; + q?: KibanaQueryOutput | null; filters?: string | null; - timeRange?: string | null; + timeRange?: KibanaTimerangeOutput | null; savedSearchId?: string | null; } @@ -46,7 +48,7 @@ export const kibanaContextFunction: ExpressionFunctionKibanaContext = { }), args: { q: { - types: ['string', 'null'], + types: ['kibana_query', 'null'], aliases: ['query', '_'], default: null, help: i18n.translate('data.search.functions.kibana_context.q.help', { @@ -61,7 +63,7 @@ export const kibanaContextFunction: ExpressionFunctionKibanaContext = { }), }, timeRange: { - types: ['string', 'null'], + types: ['timerange', 'null'], default: null, help: i18n.translate('data.search.functions.kibana_context.timeRange.help', { defaultMessage: 'Specify Kibana time range filter', @@ -77,8 +79,8 @@ export const kibanaContextFunction: ExpressionFunctionKibanaContext = { }, async fn(input, args, { getSavedObject }) { - const timeRange = getParsedValue(args.timeRange, input?.timeRange); - let queries = mergeQueries(input?.query, getParsedValue(args?.q, [])); + const timeRange = args.timeRange || input?.timeRange; + let queries = mergeQueries(input?.query, args?.q || []); let filters = [...(input?.filters || []), ...getParsedValue(args?.filters, [])]; if (args.savedSearchId) { diff --git a/src/plugins/data/common/search/expressions/kibana_context_type.ts b/src/plugins/data/common/search/expressions/kibana_context_type.ts index 40adbc65317ad1..090f09f7004ca6 100644 --- a/src/plugins/data/common/search/expressions/kibana_context_type.ts +++ b/src/plugins/data/common/search/expressions/kibana_context_type.ts @@ -22,6 +22,8 @@ export type ExpressionValueSearchContext = ExpressionValueBoxed< ExecutionContextSearch >; +export type KibanaQueryOutput = ExpressionValueBoxed<'kibana_query', Query>; + // TODO: These two are exported for legacy reasons - remove them eventually. export type KIBANA_CONTEXT_NAME = 'kibana_context'; export type KibanaContext = ExpressionValueSearchContext; diff --git a/src/plugins/data/common/search/expressions/kql.test.ts b/src/plugins/data/common/search/expressions/kql.test.ts new file mode 100644 index 00000000000000..dcf3906e6c2f52 --- /dev/null +++ b/src/plugins/data/common/search/expressions/kql.test.ts @@ -0,0 +1,43 @@ +/* + * 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 { ExecutionContext } from 'src/plugins/expressions/common'; +import { ExpressionValueSearchContext } from './kibana_context_type'; +import { functionWrapper } from './utils'; +import { kqlFunction } from './kql'; + +describe('interpreter/functions#kql', () => { + const fn = functionWrapper(kqlFunction); + let input: Partial; + let context: ExecutionContext; + + beforeEach(() => { + input = { timeRange: { from: '0', to: '1' } }; + context = { + getSearchContext: () => ({}), + getSearchSessionId: () => undefined, + types: {}, + variables: {}, + abortSignal: {} as any, + inspectorAdapters: {} as any, + }; + }); + + it('returns an object with the correct structure', () => { + const actual = fn(input, { q: 'test' }, context); + expect(actual).toMatchInlineSnapshot( + ` + Object { + "language": "kuery", + "query": "test", + "type": "kibana_query", + } + ` + ); + }); +}); diff --git a/src/plugins/data/common/search/expressions/kql.ts b/src/plugins/data/common/search/expressions/kql.ts new file mode 100644 index 00000000000000..5dd830f92f8349 --- /dev/null +++ b/src/plugins/data/common/search/expressions/kql.ts @@ -0,0 +1,49 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; +import { KibanaQueryOutput } from './kibana_context_type'; + +interface Arguments { + q: string; +} + +export type ExpressionFunctionKql = ExpressionFunctionDefinition< + 'kql', + null, + Arguments, + KibanaQueryOutput +>; + +export const kqlFunction: ExpressionFunctionKql = { + name: 'kql', + type: 'kibana_query', + inputTypes: ['null'], + help: i18n.translate('data.search.functions.kql.help', { + defaultMessage: 'Create kibana kql query', + }), + args: { + q: { + types: ['string'], + required: true, + aliases: ['query', '_'], + help: i18n.translate('data.search.functions.kql.q.help', { + defaultMessage: 'Specify Kibana KQL free form text query', + }), + }, + }, + + fn(input, args) { + return { + type: 'kibana_query', + language: 'kuery', + query: args.q, + }; + }, +}; diff --git a/src/plugins/data/common/search/expressions/lucene.test.ts b/src/plugins/data/common/search/expressions/lucene.test.ts new file mode 100644 index 00000000000000..d0b26aad98ed88 --- /dev/null +++ b/src/plugins/data/common/search/expressions/lucene.test.ts @@ -0,0 +1,43 @@ +/* + * 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 { ExecutionContext } from 'src/plugins/expressions/common'; +import { ExpressionValueSearchContext } from './kibana_context_type'; +import { functionWrapper } from './utils'; +import { luceneFunction } from './lucene'; + +describe('interpreter/functions#lucene', () => { + const fn = functionWrapper(luceneFunction); + let input: Partial; + let context: ExecutionContext; + + beforeEach(() => { + input = { timeRange: { from: '0', to: '1' } }; + context = { + getSearchContext: () => ({}), + getSearchSessionId: () => undefined, + types: {}, + variables: {}, + abortSignal: {} as any, + inspectorAdapters: {} as any, + }; + }); + + it('returns an object with the correct structure', () => { + const actual = fn(input, { q: '{ "test": 1 }' }, context); + expect(actual).toMatchInlineSnapshot(` + Object { + "language": "lucene", + "query": Object { + "test": 1, + }, + "type": "kibana_query", + } + `); + }); +}); diff --git a/src/plugins/data/common/search/expressions/lucene.ts b/src/plugins/data/common/search/expressions/lucene.ts new file mode 100644 index 00000000000000..a00ff7ed5f447d --- /dev/null +++ b/src/plugins/data/common/search/expressions/lucene.ts @@ -0,0 +1,49 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; +import { KibanaQueryOutput } from './kibana_context_type'; + +interface Arguments { + q: string; +} + +export type ExpressionFunctionLucene = ExpressionFunctionDefinition< + 'lucene', + null, + Arguments, + KibanaQueryOutput +>; + +export const luceneFunction: ExpressionFunctionLucene = { + name: 'lucene', + type: 'kibana_query', + inputTypes: ['null'], + help: i18n.translate('data.search.functions.lucene.help', { + defaultMessage: 'Create kibana lucene query', + }), + args: { + q: { + types: ['string'], + required: true, + aliases: ['query', '_'], + help: i18n.translate('data.search.functions.lucene.q.help', { + defaultMessage: 'Specify Lucene free form text query', + }), + }, + }, + + fn(input, args) { + return { + type: 'kibana_query', + language: 'lucene', + query: JSON.parse(args.q), + }; + }, +}; diff --git a/src/plugins/data/common/search/expressions/query_to_ast.test.ts b/src/plugins/data/common/search/expressions/query_to_ast.test.ts new file mode 100644 index 00000000000000..4b9c97e99e7c76 --- /dev/null +++ b/src/plugins/data/common/search/expressions/query_to_ast.test.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { queryToAst } from './query_to_ast'; + +describe('queryToAst', () => { + it('returns an object with the correct structure for lucene queies', () => { + const actual = queryToAst({ language: 'lucene', query: { country: 'US' } }); + expect(actual).toHaveProperty('functions'); + expect(actual.functions[0]).toHaveProperty('name', 'lucene'); + expect(actual.functions[0]).toHaveProperty('arguments', { + q: ['{"country":"US"}'], + }); + }); + + it('returns an object with the correct structure for kql queies', () => { + const actual = queryToAst({ language: 'kuery', query: 'country:US' }); + expect(actual).toHaveProperty('functions'); + expect(actual.functions[0]).toHaveProperty('name', 'kql'); + expect(actual.functions[0]).toHaveProperty('arguments', { + q: ['country:US'], + }); + }); +}); diff --git a/src/plugins/data/common/search/expressions/query_to_ast.ts b/src/plugins/data/common/search/expressions/query_to_ast.ts new file mode 100644 index 00000000000000..a9a6583f566c88 --- /dev/null +++ b/src/plugins/data/common/search/expressions/query_to_ast.ts @@ -0,0 +1,23 @@ +/* + * 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 { buildExpression, buildExpressionFunction } from '../../../../expressions/common'; +import { Query } from '../../query'; +import { ExpressionFunctionKql } from './kql'; +import { ExpressionFunctionLucene } from './lucene'; + +export const queryToAst = (query: Query) => { + if (query.language === 'kuery') { + return buildExpression([ + buildExpressionFunction('kql', { q: query.query as string }), + ]); + } + return buildExpression([ + buildExpressionFunction('lucene', { q: JSON.stringify(query.query) }), + ]); +}; diff --git a/src/plugins/data/common/search/expressions/timerange.test.ts b/src/plugins/data/common/search/expressions/timerange.test.ts new file mode 100644 index 00000000000000..ae461b482e182c --- /dev/null +++ b/src/plugins/data/common/search/expressions/timerange.test.ts @@ -0,0 +1,44 @@ +/* + * 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 { ExecutionContext } from 'src/plugins/expressions/common'; +import { ExpressionValueSearchContext } from './kibana_context_type'; +import { functionWrapper } from './utils'; +import { kibanaTimerangeFunction } from './timerange'; + +describe('interpreter/functions#timerange', () => { + const fn = functionWrapper(kibanaTimerangeFunction); + let input: Partial; + let context: ExecutionContext; + + beforeEach(() => { + input = { timeRange: { from: '0', to: '1' } }; + context = { + getSearchContext: () => ({}), + getSearchSessionId: () => undefined, + types: {}, + variables: {}, + abortSignal: {} as any, + inspectorAdapters: {} as any, + }; + }); + + it('returns an object with the correct structure', () => { + const actual = fn(input, { from: 'now', to: 'now-7d', mode: 'absolute' }, context); + expect(actual).toMatchInlineSnapshot( + ` + Object { + "from": "now", + "mode": "absolute", + "to": "now-7d", + "type": "timerange", + } + ` + ); + }); +}); diff --git a/src/plugins/data/common/search/expressions/timerange.ts b/src/plugins/data/common/search/expressions/timerange.ts new file mode 100644 index 00000000000000..ed09bab629519f --- /dev/null +++ b/src/plugins/data/common/search/expressions/timerange.ts @@ -0,0 +1,61 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { ExpressionFunctionDefinition, ExpressionValueBoxed } from 'src/plugins/expressions/common'; +import { TimeRange } from '../../query'; + +export type KibanaTimerangeOutput = ExpressionValueBoxed<'timerange', TimeRange>; + +export type ExpressionFunctionKibanaTimerange = ExpressionFunctionDefinition< + 'timerange', + null, + TimeRange, + KibanaTimerangeOutput +>; + +export const kibanaTimerangeFunction: ExpressionFunctionKibanaTimerange = { + name: 'timerange', + type: 'timerange', + inputTypes: ['null'], + help: i18n.translate('data.search.functions.timerange.help', { + defaultMessage: 'Create kibana timerange', + }), + args: { + from: { + types: ['string'], + required: true, + help: i18n.translate('data.search.functions.timerange.from.help', { + defaultMessage: 'Specify the start date', + }), + }, + to: { + types: ['string'], + required: true, + help: i18n.translate('data.search.functions.timerange.to.help', { + defaultMessage: 'Specify the end date', + }), + }, + mode: { + types: ['string'], + options: ['absolute', 'relative'], + help: i18n.translate('data.search.functions.timerange.mode.help', { + defaultMessage: 'Specify the mode (absolute or relative)', + }), + }, + }, + + fn(input, args) { + return { + type: 'timerange', + from: args.from, + to: args.to, + mode: args.mode, + }; + }, +}; diff --git a/src/plugins/data/common/search/expressions/timerange_to_ast.test.ts b/src/plugins/data/common/search/expressions/timerange_to_ast.test.ts new file mode 100644 index 00000000000000..12ba1e012bb659 --- /dev/null +++ b/src/plugins/data/common/search/expressions/timerange_to_ast.test.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { timerangeToAst } from './timerange_to_ast'; + +describe('timerangeToAst', () => { + it('returns an object with the correct structure', () => { + const actual = timerangeToAst({ from: 'now', to: 'now-7d', mode: 'absolute' }); + expect(actual).toHaveProperty('name', 'timerange'); + expect(actual).toHaveProperty('arguments', { + from: ['now'], + mode: ['absolute'], + to: ['now-7d'], + }); + }); +}); diff --git a/src/plugins/data/common/search/expressions/timerange_to_ast.ts b/src/plugins/data/common/search/expressions/timerange_to_ast.ts new file mode 100644 index 00000000000000..ad66c12e68c83a --- /dev/null +++ b/src/plugins/data/common/search/expressions/timerange_to_ast.ts @@ -0,0 +1,19 @@ +/* + * 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 { buildExpressionFunction } from '../../../../expressions/common'; +import { TimeRange } from '../../query'; +import { ExpressionFunctionKibanaTimerange } from './timerange'; + +export const timerangeToAst = (timerange: TimeRange) => { + return buildExpressionFunction('timerange', { + from: timerange.from, + to: timerange.to, + mode: timerange.mode, + }); +}; diff --git a/src/plugins/data/common/search/index.ts b/src/plugins/data/common/search/index.ts index 474ff3b1b97807..1b74cec2fc8472 100644 --- a/src/plugins/data/common/search/index.ts +++ b/src/plugins/data/common/search/index.ts @@ -13,3 +13,4 @@ export * from './search_source'; export * from './tabify'; export * from './types'; export * from './utils'; +export * from './session'; diff --git a/src/plugins/data/common/search/session/index.ts b/src/plugins/data/common/search/session/index.ts new file mode 100644 index 00000000000000..042786719dbbae --- /dev/null +++ b/src/plugins/data/common/search/session/index.ts @@ -0,0 +1,12 @@ +/* + * 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 './status'; +export * from './types'; + +export const SEARCH_SESSIONS_TABLE_ID = 'searchSessionsMgmtUiTable'; diff --git a/src/plugins/data/common/search/session/status.ts b/src/plugins/data/common/search/session/status.ts new file mode 100644 index 00000000000000..790adbe7ba0001 --- /dev/null +++ b/src/plugins/data/common/search/session/status.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 enum SearchSessionStatus { + IN_PROGRESS = 'in_progress', + ERROR = 'error', + COMPLETE = 'complete', + CANCELLED = 'cancelled', + EXPIRED = 'expired', +} diff --git a/src/plugins/data/common/search/session/types.ts b/src/plugins/data/common/search/session/types.ts new file mode 100644 index 00000000000000..f63d2dfec142cc --- /dev/null +++ b/src/plugins/data/common/search/session/types.ts @@ -0,0 +1,94 @@ +/* + * 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 { SearchSessionStatus } from './status'; + +export const SEARCH_SESSION_TYPE = 'search-session'; +export interface SearchSessionSavedObjectAttributes { + sessionId: string; + /** + * User-facing session name to be displayed in session management + */ + name?: string; + /** + * App that created the session. e.g 'discover' + */ + appId?: string; + /** + * Creation time of the session + */ + created: string; + /** + * Last touch time of the session + */ + touched: string; + /** + * Expiration time of the session. Expiration itself is managed by Elasticsearch. + */ + expires: string; + /** + * status + */ + status: SearchSessionStatus; + /** + * urlGeneratorId + */ + urlGeneratorId?: string; + /** + * The application state that was used to create the session. + * Should be used, for example, to re-load an expired search session. + */ + initialState?: Record; + /** + * Application state that should be used to restore the session. + * For example, relative dates are conveted to absolute ones. + */ + restoreState?: Record; + /** + * Mapping of search request hashes to their corresponsing info (async search id, etc.) + */ + idMapping: Record; + + /** + * This value is true if the session was actively stored by the user. If it is false, the session may be purged by the system. + */ + persisted: boolean; + /** + * The realm type/name & username uniquely identifies the user who created this search session + */ + realmType?: string; + realmName?: string; + username?: string; +} + +export interface SearchSessionRequestInfo { + /** + * ID of the async search request + */ + id: string; + /** + * Search strategy used to submit the search request + */ + strategy: string; + /** + * status + */ + status: string; + /** + * An optional error. Set if status is set to error. + */ + error?: string; +} + +export interface SearchSessionFindOptions { + page?: number; + perPage?: number; + sortField?: string; + sortOrder?: string; + filter?: string; +} diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 1ab8e29ff2fd14..1838ca43e8c231 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -40,6 +40,7 @@ import { } from '../common'; import { FilterLabel } from './ui'; +import { FilterItem } from './ui/filter_bar'; import { generateFilters, @@ -54,6 +55,7 @@ import { // Filter helpers namespace: export const esFilters = { FilterLabel, + FilterItem, FILTERS, FilterStateStore, @@ -92,7 +94,7 @@ export const esFilters = { extractTimeRange, }; -export { +export type { RangeFilter, RangeFilterMeta, RangeFilterParams, diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 5405f07986c8ce..d719d5f1651b69 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -90,6 +90,7 @@ import { SavedObjectReference } from 'src/core/types'; import { SavedObjectsClientContract } from 'src/core/public'; import { SavedObjectsFindOptions } from 'kibana/public'; import { SavedObjectsFindResponse } from 'kibana/server'; +import { SavedObjectsUpdateResponse } from 'kibana/server'; import { SchemaTypeError } from '@kbn/config-schema'; import { Search } from '@elastic/elasticsearch/api/requestParams'; import { SearchResponse } from 'elasticsearch'; @@ -726,6 +727,7 @@ export type EsdslExpressionFunctionDefinition = ExpressionFunctionDefinition_2 JSX.Element; + FilterItem: (props: import("./ui/filter_bar/filter_item").FilterItemProps) => JSX.Element; FILTERS: typeof FILTERS; FilterStateStore: typeof FilterStateStore; buildEmptyFilter: (isPinned: boolean, index?: string | undefined) => import("../common").Filter; @@ -2385,6 +2387,7 @@ export type SearchRequest = Record; // // @public export interface SearchSessionInfoProvider { + appendSessionStartTimeToName?: boolean; getName: () => Promise; // (undocumented) getUrlGeneratorData: () => Promise<{ @@ -2656,55 +2659,55 @@ export const UI_SETTINGS: { // src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:169:7 - (ae-forgotten-export) The symbol "RuntimeField" needs to be exported by the entry point index.d.ts // src/plugins/data/common/search/aggs/types.ts:139:51 - (ae-forgotten-export) The symbol "AggTypesRegistryStart" needs to be exported by the entry point index.d.ts // src/plugins/data/public/field_formats/field_formats_service.ts:56:3 - (ae-forgotten-export) The symbol "FormatFactory" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:55:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:55:23 - (ae-forgotten-export) The symbol "getDisplayValueFromFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:55:23 - (ae-forgotten-export) The symbol "generateFilters" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:55:23 - (ae-forgotten-export) The symbol "changeTimeFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:55:23 - (ae-forgotten-export) The symbol "convertRangeFilterToTimeRangeString" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:55:23 - (ae-forgotten-export) The symbol "extractTimeFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:55:23 - (ae-forgotten-export) The symbol "extractTimeRange" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:127:21 - (ae-forgotten-export) The symbol "buildEsQuery" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:127:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:127:21 - (ae-forgotten-export) The symbol "luceneStringToDsl" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:127:21 - (ae-forgotten-export) The symbol "decorateQuery" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:168:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:168:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:168:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:168:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:168:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:168:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:168:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:168:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:168:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:168:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:168:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:168:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:168:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:168:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:168:26 - (ae-forgotten-export) The symbol "HistogramFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:211:23 - (ae-forgotten-export) The symbol "datatableToCSV" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:401:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:401:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:401:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:401:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:403:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:404:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:413:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:414:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:415:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:416:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:420:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:421:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:424:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:425:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:428:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:56:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:56:23 - (ae-forgotten-export) The symbol "getDisplayValueFromFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:56:23 - (ae-forgotten-export) The symbol "generateFilters" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:56:23 - (ae-forgotten-export) The symbol "changeTimeFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:56:23 - (ae-forgotten-export) The symbol "convertRangeFilterToTimeRangeString" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:56:23 - (ae-forgotten-export) The symbol "extractTimeFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:56:23 - (ae-forgotten-export) The symbol "extractTimeRange" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:129:21 - (ae-forgotten-export) The symbol "buildEsQuery" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:129:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:129:21 - (ae-forgotten-export) The symbol "luceneStringToDsl" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:129:21 - (ae-forgotten-export) The symbol "decorateQuery" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:170:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:170:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:170:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:170:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:170:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:170:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:170:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:170:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:170:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:170:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:170:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:170:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:170:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:170:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:170:26 - (ae-forgotten-export) The symbol "HistogramFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:213:23 - (ae-forgotten-export) The symbol "datatableToCSV" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:403:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:403:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:403:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:403:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:405:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:406:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:415:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:416:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:417:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:418:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:422:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:423:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:426:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:427:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:430:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:34:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/search/session/session_service.ts:42:5 - (ae-forgotten-export) The symbol "UrlGeneratorStateMapping" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/search/session/session_service.ts:55:5 - (ae-forgotten-export) The symbol "UrlGeneratorStateMapping" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index 6522cae3e044f4..8eb73ba62244f3 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -25,6 +25,9 @@ import { ISearchGeneric, SearchSourceDependencies, SearchSourceService, + kibanaTimerangeFunction, + luceneFunction, + kqlFunction, } from '../../common/search'; import { getCallMsearch } from './legacy'; import { AggsService, AggsStartDependencies } from './aggs'; @@ -102,6 +105,9 @@ export class SearchService implements Plugin { ); expressions.registerFunction(kibana); expressions.registerFunction(kibanaContextFunction); + expressions.registerFunction(luceneFunction); + expressions.registerFunction(kqlFunction); + expressions.registerFunction(kibanaTimerangeFunction); expressions.registerType(kibanaContext); expressions.registerFunction(esdsl); diff --git a/src/plugins/data/public/search/session/lib/session_name_formatter.ts b/src/plugins/data/public/search/session/lib/session_name_formatter.ts new file mode 100644 index 00000000000000..6ec33a2184415a --- /dev/null +++ b/src/plugins/data/public/search/session/lib/session_name_formatter.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 moment from 'moment'; + +export function formatSessionName( + sessionName: string, + opts: { sessionStartTime?: Date; appendStartTime?: boolean } +): string { + if (opts.sessionStartTime && opts.appendStartTime) { + sessionName = appendDate(sessionName, opts.sessionStartTime); + } + + return sessionName; +} + +function appendDate(sessionName: string, sessionStartTime: Date): string { + return `${sessionName} - ${moment(sessionStartTime).format(`L @ LT`)}`; +} diff --git a/src/plugins/data/public/search/session/mocks.ts b/src/plugins/data/public/search/session/mocks.ts index c615be641078b4..8ee44cb2ca4efb 100644 --- a/src/plugins/data/public/search/session/mocks.ts +++ b/src/plugins/data/public/search/session/mocks.ts @@ -19,6 +19,7 @@ export function getSessionsClientMock(): jest.Mocked { update: jest.fn(), extend: jest.fn(), delete: jest.fn(), + rename: jest.fn(), }; } @@ -30,6 +31,8 @@ export function getSessionServiceMock(): jest.Mocked { getSessionId: jest.fn(), getSession$: jest.fn(() => new BehaviorSubject(undefined).asObservable()), state$: new BehaviorSubject(SearchSessionState.None).asObservable(), + searchSessionName$: new BehaviorSubject(undefined).asObservable(), + renameCurrentSession: jest.fn(), trackSearch: jest.fn((searchDescriptor) => () => {}), destroy: jest.fn(), cancel: jest.fn(), diff --git a/src/plugins/data/public/search/session/search_session_state.test.ts b/src/plugins/data/public/search/session/search_session_state.test.ts index 7559a6e0fdc670..d702d5c71a24b6 100644 --- a/src/plugins/data/public/search/session/search_session_state.test.ts +++ b/src/plugins/data/public/search/session/search_session_state.test.ts @@ -7,6 +7,26 @@ */ import { createSessionStateContainer, SearchSessionState } from './search_session_state'; +import { SearchSessionSavedObject } from './sessions_client'; +import { SearchSessionStatus } from '../../../common'; + +const mockSavedObject: SearchSessionSavedObject = { + id: 'd7170a35-7e2c-48d6-8dec-9a056721b489', + type: 'search-session', + attributes: { + name: 'my_name', + appId: 'my_app_id', + urlGeneratorId: 'my_url_generator_id', + idMapping: {}, + sessionId: 'session_id', + touched: new Date().toISOString(), + created: new Date().toISOString(), + expires: new Date().toISOString(), + status: SearchSessionStatus.COMPLETE, + persisted: true, + }, + references: [], +}; describe('Session state container', () => { const appName = 'appName'; @@ -66,13 +86,13 @@ describe('Session state container', () => { }); test('store -> completed', () => { - expect(() => state.transitions.store()).toThrowError(); + expect(() => state.transitions.store(mockSavedObject)).toThrowError(); state.transitions.start({ appName }); const search = {}; state.transitions.trackSearch(search); expect(state.selectors.getState()).toBe(SearchSessionState.Loading); - state.transitions.store(); + state.transitions.store(mockSavedObject); expect(state.selectors.getState()).toBe(SearchSessionState.BackgroundLoading); state.transitions.unTrackSearch(search); expect(state.selectors.getState()).toBe(SearchSessionState.BackgroundCompleted); @@ -84,7 +104,7 @@ describe('Session state container', () => { const search = {}; state.transitions.trackSearch(search); expect(state.selectors.getState()).toBe(SearchSessionState.Loading); - state.transitions.store(); + state.transitions.store(mockSavedObject); expect(state.selectors.getState()).toBe(SearchSessionState.BackgroundLoading); state.transitions.cancel(); expect(state.selectors.getState()).toBe(SearchSessionState.Canceled); @@ -106,7 +126,7 @@ describe('Session state container', () => { state.transitions.unTrackSearch(search); expect(state.selectors.getState()).toBe(SearchSessionState.Restored); - expect(() => state.transitions.store()).toThrowError(); + expect(() => state.transitions.store(mockSavedObject)).toThrowError(); expect(state.selectors.getState()).toBe(SearchSessionState.Restored); expect(() => state.transitions.cancel()).toThrowError(); expect(state.selectors.getState()).toBe(SearchSessionState.Restored); diff --git a/src/plugins/data/public/search/session/search_session_state.ts b/src/plugins/data/public/search/session/search_session_state.ts index cd2561d52f00e3..e58e1062091bff 100644 --- a/src/plugins/data/public/search/session/search_session_state.ts +++ b/src/plugins/data/public/search/session/search_session_state.ts @@ -10,6 +10,7 @@ import uuid from 'uuid'; import { Observable } from 'rxjs'; import { distinctUntilChanged, map, shareReplay } from 'rxjs/operators'; import { createStateContainer, StateContainer } from '../../../../kibana_utils/public'; +import { SearchSessionSavedObject } from './sessions_client'; /** * Possible state that current session can be in @@ -78,6 +79,11 @@ export interface SessionStateInternal { */ isStored: boolean; + /** + * Saved object of a current search session + */ + searchSessionSavedObject?: SearchSessionSavedObject; + /** * Is this session a restored session (have these requests already been made, and we're just * looking to re-use the previous search IDs)? @@ -125,10 +131,13 @@ export interface SessionPureTransitions< start: (state: S) => ({ appName }: { appName: string }) => S; restore: (state: S) => (sessionId: string) => S; clear: (state: S) => () => S; - store: (state: S) => () => S; + store: (state: S) => (searchSessionSavedObject: SearchSessionSavedObject) => S; trackSearch: (state: S) => (search: SearchDescriptor) => S; unTrackSearch: (state: S) => (search: SearchDescriptor) => S; cancel: (state: S) => () => S; + setSearchSessionSavedObject: ( + state: S + ) => (searchSessionSavedObject: SearchSessionSavedObject) => S; } export const sessionPureTransitions: SessionPureTransitions = { @@ -145,13 +154,14 @@ export const sessionPureTransitions: SessionPureTransitions = { isStored: true, }), clear: (state) => () => createSessionDefaultState(), - store: (state) => () => { + store: (state) => (searchSessionSavedObject: SearchSessionSavedObject) => { if (!state.sessionId) throw new Error("Can't store session. Missing sessionId"); if (state.isStored || state.isRestore) throw new Error('Can\'t store because current session is already stored"'); return { ...state, isStored: true, + searchSessionSavedObject, }; }, trackSearch: (state) => (search) => { @@ -176,6 +186,21 @@ export const sessionPureTransitions: SessionPureTransitions = { pendingSearches: [], isCanceled: true, isStored: false, + searchSessionSavedObject: undefined, + }; + }, + setSearchSessionSavedObject: (state) => (searchSessionSavedObject: SearchSessionSavedObject) => { + if (!state.sessionId) + throw new Error( + "Can't add search session saved object session into the state. Missing sessionId" + ); + if (state.sessionId !== searchSessionSavedObject.attributes.sessionId) + throw new Error( + "Can't add search session saved object session into the state. SessionIds don't match." + ); + return { + ...state, + searchSessionSavedObject, }; }, }; @@ -222,6 +247,8 @@ export const createSessionStateContainer = ( stateContainer: SessionStateContainer; sessionState$: Observable; sessionStartTime$: Observable; + searchSessionSavedObject$: Observable; + searchSessionName$: Observable; } => { const stateContainer = createStateContainer( createSessionDefaultState(), @@ -242,9 +269,21 @@ export const createSessionStateContainer = ( shareReplay(1) ); + const searchSessionSavedObject$ = stateContainer.state$.pipe( + map(() => stateContainer.get().searchSessionSavedObject), + distinctUntilChanged(), + shareReplay(1) + ); + + const searchSessionName$ = searchSessionSavedObject$.pipe( + map((savedObject) => savedObject?.attributes?.name) + ); + return { stateContainer, sessionState$, sessionStartTime$, + searchSessionSavedObject$, + searchSessionName$, }; }; diff --git a/src/plugins/data/public/search/session/session_service.test.ts b/src/plugins/data/public/search/session/session_service.test.ts index 32fdd9b6a52b10..13a1a1bd388ba6 100644 --- a/src/plugins/data/public/search/session/session_service.test.ts +++ b/src/plugins/data/public/search/session/session_service.test.ts @@ -15,6 +15,27 @@ import { SearchSessionState } from './search_session_state'; import { createNowProviderMock } from '../../now_provider/mocks'; import { NowProviderInternalContract } from '../../now_provider'; import { SEARCH_SESSIONS_MANAGEMENT_ID } from './constants'; +import { SearchSessionSavedObject, ISessionsClient } from './sessions_client'; +import { SearchSessionStatus } from '../../../common'; +import { CoreStart } from 'kibana/public'; + +const mockSavedObject: SearchSessionSavedObject = { + id: 'd7170a35-7e2c-48d6-8dec-9a056721b489', + type: 'search-session', + attributes: { + name: 'my_name', + appId: 'my_app_id', + urlGeneratorId: 'my_url_generator_id', + idMapping: {}, + sessionId: 'session_id', + touched: new Date().toISOString(), + created: new Date().toISOString(), + expires: new Date().toISOString(), + status: SearchSessionStatus.COMPLETE, + persisted: true, + }, + references: [], +}; describe('Session service', () => { let sessionService: ISessionService; @@ -22,18 +43,28 @@ describe('Session service', () => { let nowProvider: jest.Mocked; let userHasAccessToSearchSessions = true; let currentAppId$: BehaviorSubject; + let toastService: jest.Mocked; + let sessionsClient: jest.Mocked; beforeEach(() => { const initializerContext = coreMock.createPluginInitializerContext(); const startService = coreMock.createSetup().getStartServices; + const startServicesMock = coreMock.createStart(); + toastService = startServicesMock.notifications.toasts; nowProvider = createNowProviderMock(); currentAppId$ = new BehaviorSubject('app'); + sessionsClient = getSessionsClientMock(); + sessionsClient.get.mockImplementation(async (id) => ({ + ...mockSavedObject, + id, + attributes: { ...mockSavedObject.attributes, sessionId: id }, + })); sessionService = new SessionService( initializerContext, () => startService().then(([coreStart, ...rest]) => [ { - ...coreStart, + ...startServicesMock, application: { ...coreStart.application, currentAppId$, @@ -49,7 +80,7 @@ describe('Session service', () => { }, ...rest, ]), - getSessionsClientMock(), + sessionsClient, nowProvider, { freezeState: false } // needed to use mocks inside state container ); @@ -269,4 +300,26 @@ describe('Session service', () => { ); }); }); + + test("rename() doesn't throw in case rename failed but shows a toast instead", async () => { + const renameError = new Error('Haha'); + sessionsClient.rename.mockRejectedValue(renameError); + sessionService.enableStorage({ + getName: async () => 'Name', + getUrlGeneratorData: async () => ({ + urlGeneratorId: 'id', + initialState: {}, + restoreState: {}, + }), + }); + sessionService.start(); + await sessionService.save(); + await expect(sessionService.renameCurrentSession('New name')).resolves.toBeUndefined(); + expect(toastService.addError).toHaveBeenCalledWith( + renameError, + expect.objectContaining({ + title: expect.stringContaining('Failed to edit name of the search session'), + }) + ); + }); }); diff --git a/src/plugins/data/public/search/session/session_service.ts b/src/plugins/data/public/search/session/session_service.ts index 430fc8913c5fd2..785b9357fc8953 100644 --- a/src/plugins/data/public/search/session/session_service.ts +++ b/src/plugins/data/public/search/session/session_service.ts @@ -9,7 +9,12 @@ import { PublicContract } from '@kbn/utility-types'; import { distinctUntilChanged, map, startWith } from 'rxjs/operators'; import { Observable, Subscription } from 'rxjs'; -import { PluginInitializerContext, StartServicesAccessor } from 'kibana/public'; +import { + PluginInitializerContext, + StartServicesAccessor, + ToastsStart as ToastService, +} from 'kibana/public'; +import { i18n } from '@kbn/i18n'; import { UrlGeneratorId, UrlGeneratorStateMapping } from '../../../../share/public/'; import { ConfigSchema } from '../../../config'; import { @@ -21,6 +26,7 @@ import { ISessionsClient } from './sessions_client'; import { ISearchOptions } from '../../../common'; import { NowProviderInternalContract } from '../../now_provider'; import { SEARCH_SESSIONS_MANAGEMENT_ID } from './constants'; +import { formatSessionName } from './lib/session_name_formatter'; export type ISessionService = PublicContract; @@ -37,6 +43,13 @@ export interface SearchSessionInfoProvider Promise; + + /** + * Append session start time to a session name, + * `true` by default + */ + appendSessionStartTimeToName?: boolean; + getUrlGeneratorData: () => Promise<{ urlGeneratorId: ID; initialState: UrlGeneratorStateMapping[ID]['State']; @@ -65,12 +78,15 @@ export class SessionService { public readonly state$: Observable; private readonly state: SessionStateContainer; + public readonly searchSessionName$: Observable; private searchSessionInfoProvider?: SearchSessionInfoProvider; private searchSessionIndicatorUiConfig?: Partial; private subscription = new Subscription(); private currentApp?: string; private hasAccessToSearchSessions: boolean = false; + private toastService?: ToastService; + constructor( initializerContext: PluginInitializerContext, getStartServices: StartServicesAccessor, @@ -82,11 +98,13 @@ export class SessionService { stateContainer, sessionState$, sessionStartTime$, + searchSessionName$, } = createSessionStateContainer({ freeze: freezeState, }); this.state$ = sessionState$; this.state = stateContainer; + this.searchSessionName$ = searchSessionName$; this.subscription.add( sessionStartTime$.subscribe((startTime) => { @@ -100,6 +118,8 @@ export class SessionService { this.hasAccessToSearchSessions = coreStart.application.capabilities.management?.kibana?.[SEARCH_SESSIONS_MANAGEMENT_ID]; + this.toastService = coreStart.notifications.toasts; + this.subscription.add( coreStart.application.currentAppId$.subscribe((newAppName) => { this.currentApp = newAppName; @@ -198,6 +218,7 @@ export class SessionService { */ public restore(sessionId: string) { this.state.transitions.restore(sessionId); + this.refreshSearchSessionSavedObject(); } /** @@ -252,8 +273,13 @@ export class SessionService { currentSessionInfoProvider.getUrlGeneratorData(), ]); - await this.sessionsClient.create({ - name, + const formattedName = formatSessionName(name, { + sessionStartTime: this.state.get().startTime, + appendStartTime: currentSessionInfoProvider.appendSessionStartTimeToName, + }); + + const searchSessionSavedObject = await this.sessionsClient.create({ + name: formattedName, appId: currentSessionApp, restoreState: (restoreState as unknown) as Record, initialState: (initialState as unknown) as Record, @@ -263,7 +289,33 @@ export class SessionService { // if we are still interested in this result if (this.getSessionId() === sessionId) { - this.state.transitions.store(); + this.state.transitions.store(searchSessionSavedObject); + } + } + + /** + * Change user-facing name of a current session + * Doesn't throw in case of API error but presents a notification toast instead + * @param newName - new session name + */ + public async renameCurrentSession(newName: string) { + const sessionId = this.getSessionId(); + if (sessionId && this.state.get().isStored) { + let renamed = false; + try { + await this.sessionsClient.rename(sessionId, newName); + renamed = true; + } catch (e) { + this.toastService?.addError(e, { + title: i18n.translate('data.searchSessions.sessionService.sessionEditNameError', { + defaultMessage: 'Failed to edit name of the search session', + }), + }); + } + + if (renamed && sessionId === this.getSessionId()) { + await this.refreshSearchSessionSavedObject(); + } } } @@ -315,7 +367,10 @@ export class SessionService { searchSessionInfoProvider: SearchSessionInfoProvider, searchSessionIndicatorUiConfig?: SearchSessionIndicatorUiConfig ) { - this.searchSessionInfoProvider = searchSessionInfoProvider; + this.searchSessionInfoProvider = { + appendSessionStartTimeToName: true, + ...searchSessionInfoProvider, + }; this.searchSessionIndicatorUiConfig = searchSessionIndicatorUiConfig; } @@ -333,4 +388,23 @@ export class SessionService { ...this.searchSessionIndicatorUiConfig, }; } + + private async refreshSearchSessionSavedObject() { + const sessionId = this.getSessionId(); + if (sessionId && this.state.get().isStored) { + try { + const savedObject = await this.sessionsClient.get(sessionId); + if (this.getSessionId() === sessionId) { + // still interested in this result + this.state.transitions.setSearchSessionSavedObject(savedObject); + } + } catch (e) { + this.toastService?.addError(e, { + title: i18n.translate('data.searchSessions.sessionService.sessionObjectFetchError', { + defaultMessage: 'Failed to fetch search session info', + }), + }); + } + } + } } diff --git a/src/plugins/data/public/search/session/sessions_client.ts b/src/plugins/data/public/search/session/sessions_client.ts index 1742db9d033bdd..0b6f1b79f0c63c 100644 --- a/src/plugins/data/public/search/session/sessions_client.ts +++ b/src/plugins/data/public/search/session/sessions_client.ts @@ -8,8 +8,13 @@ import { PublicContract } from '@kbn/utility-types'; import { HttpSetup, SavedObjectsFindOptions } from 'kibana/public'; -import type { SavedObject, SavedObjectsFindResponse } from 'kibana/server'; - +import type { + SavedObject, + SavedObjectsFindResponse, + SavedObjectsUpdateResponse, +} from 'kibana/server'; +import type { SearchSessionSavedObjectAttributes } from '../../../common'; +export type SearchSessionSavedObject = SavedObject; export type ISessionsClient = PublicContract; export interface SessionsClientDeps { http: HttpSetup; @@ -25,7 +30,7 @@ export class SessionsClient { this.http = deps.http; } - public get(sessionId: string): Promise { + public get(sessionId: string): Promise { return this.http.get(`/internal/session/${encodeURIComponent(sessionId)}`); } @@ -43,7 +48,7 @@ export class SessionsClient { restoreState: Record; urlGeneratorId: string; sessionId: string; - }): Promise { + }): Promise { return this.http.post(`/internal/session`, { body: JSON.stringify({ name, @@ -62,13 +67,26 @@ export class SessionsClient { }); } - public update(sessionId: string, attributes: unknown): Promise { + public update( + sessionId: string, + attributes: unknown + ): Promise> { return this.http!.put(`/internal/session/${encodeURIComponent(sessionId)}`, { body: JSON.stringify(attributes), }); } - public extend(sessionId: string, expires: string): Promise { + public rename( + sessionId: string, + newName: string + ): Promise>> { + return this.update(sessionId, { name: newName }); + } + + public extend( + sessionId: string, + expires: string + ): Promise> { return this.http!.post(`/internal/session/${encodeURIComponent(sessionId)}/_extend`, { body: JSON.stringify({ expires }), }); diff --git a/src/plugins/data/public/ui/filter_bar/filter_item.tsx b/src/plugins/data/public/ui/filter_bar/filter_item.tsx index 05ae4e74535f47..5ad88e6fdf5bef 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_item.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_item.tsx @@ -25,7 +25,9 @@ import { } from '../../../common'; import { getIndexPatterns } from '../../services'; -interface Props { +type PanelOptions = 'pinFilter' | 'editFilter' | 'negateFilter' | 'disableFilter' | 'deleteFilter'; + +export interface FilterItemProps { id: string; filter: Filter; indexPatterns: IIndexPattern[]; @@ -34,6 +36,7 @@ interface Props { onRemove: () => void; intl: InjectedIntl; uiSettings: IUiSettingsClient; + hiddenPanelOptions?: PanelOptions[]; } interface LabelOptions { @@ -53,10 +56,10 @@ export type FilterLabelStatus = export const FILTER_EDITOR_WIDTH = 800; -export function FilterItem(props: Props) { +export function FilterItem(props: FilterItemProps) { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [indexPatternExists, setIndexPatternExists] = useState(undefined); - const { id, filter, indexPatterns } = props; + const { id, filter, indexPatterns, hiddenPanelOptions } = props; useEffect(() => { const index = props.filter.meta.index; @@ -154,83 +157,90 @@ export function FilterItem(props: Props) { function getPanels() { const { negate, disabled } = filter.meta; - return [ + let mainPanelItems = [ { - id: 0, - items: [ - { - name: isFilterPinned(filter) - ? props.intl.formatMessage({ - id: 'data.filter.filterBar.unpinFilterButtonLabel', - defaultMessage: 'Unpin', - }) - : props.intl.formatMessage({ - id: 'data.filter.filterBar.pinFilterButtonLabel', - defaultMessage: 'Pin across all apps', - }), - icon: 'pin', - onClick: () => { - setIsPopoverOpen(false); - onTogglePinned(); - }, - 'data-test-subj': 'pinFilter', - }, - { - name: props.intl.formatMessage({ - id: 'data.filter.filterBar.editFilterButtonLabel', - defaultMessage: 'Edit filter', + name: isFilterPinned(filter) + ? props.intl.formatMessage({ + id: 'data.filter.filterBar.unpinFilterButtonLabel', + defaultMessage: 'Unpin', + }) + : props.intl.formatMessage({ + id: 'data.filter.filterBar.pinFilterButtonLabel', + defaultMessage: 'Pin across all apps', }), - icon: 'pencil', - panel: 1, - 'data-test-subj': 'editFilter', - }, - { - name: negate - ? props.intl.formatMessage({ - id: 'data.filter.filterBar.includeFilterButtonLabel', - defaultMessage: 'Include results', - }) - : props.intl.formatMessage({ - id: 'data.filter.filterBar.excludeFilterButtonLabel', - defaultMessage: 'Exclude results', - }), - icon: negate ? 'plusInCircle' : 'minusInCircle', - onClick: () => { - setIsPopoverOpen(false); - onToggleNegated(); - }, - 'data-test-subj': 'negateFilter', - }, - { - name: disabled - ? props.intl.formatMessage({ - id: 'data.filter.filterBar.enableFilterButtonLabel', - defaultMessage: 'Re-enable', - }) - : props.intl.formatMessage({ - id: 'data.filter.filterBar.disableFilterButtonLabel', - defaultMessage: 'Temporarily disable', - }), - icon: `${disabled ? 'eye' : 'eyeClosed'}`, - onClick: () => { - setIsPopoverOpen(false); - onToggleDisabled(); - }, - 'data-test-subj': 'disableFilter', - }, - { - name: props.intl.formatMessage({ - id: 'data.filter.filterBar.deleteFilterButtonLabel', - defaultMessage: 'Delete', + icon: 'pin', + onClick: () => { + setIsPopoverOpen(false); + onTogglePinned(); + }, + 'data-test-subj': 'pinFilter', + }, + { + name: props.intl.formatMessage({ + id: 'data.filter.filterBar.editFilterButtonLabel', + defaultMessage: 'Edit filter', + }), + icon: 'pencil', + panel: 1, + 'data-test-subj': 'editFilter', + }, + { + name: negate + ? props.intl.formatMessage({ + id: 'data.filter.filterBar.includeFilterButtonLabel', + defaultMessage: 'Include results', + }) + : props.intl.formatMessage({ + id: 'data.filter.filterBar.excludeFilterButtonLabel', + defaultMessage: 'Exclude results', + }), + icon: negate ? 'plusInCircle' : 'minusInCircle', + onClick: () => { + setIsPopoverOpen(false); + onToggleNegated(); + }, + 'data-test-subj': 'negateFilter', + }, + { + name: disabled + ? props.intl.formatMessage({ + id: 'data.filter.filterBar.enableFilterButtonLabel', + defaultMessage: 'Re-enable', + }) + : props.intl.formatMessage({ + id: 'data.filter.filterBar.disableFilterButtonLabel', + defaultMessage: 'Temporarily disable', }), - icon: 'trash', - onClick: () => { - setIsPopoverOpen(false); - props.onRemove(); - }, - 'data-test-subj': 'deleteFilter', - }, - ], + icon: `${disabled ? 'eye' : 'eyeClosed'}`, + onClick: () => { + setIsPopoverOpen(false); + onToggleDisabled(); + }, + 'data-test-subj': 'disableFilter', + }, + { + name: props.intl.formatMessage({ + id: 'data.filter.filterBar.deleteFilterButtonLabel', + defaultMessage: 'Delete', + }), + icon: 'trash', + onClick: () => { + setIsPopoverOpen(false); + props.onRemove(); + }, + 'data-test-subj': 'deleteFilter', + }, + ]; + + if (hiddenPanelOptions && hiddenPanelOptions.length > 0) { + mainPanelItems = mainPanelItems.filter( + (pItem) => !hiddenPanelOptions.includes(pItem['data-test-subj'] as PanelOptions) + ); + } + return [ + { + id: 0, + items: mainPanelItems, }, { id: 1, @@ -363,3 +373,6 @@ export function FilterItem(props: Props) { ); } + +// eslint-disable-next-line import/no-default-export +export default FilterItem; diff --git a/src/plugins/data/public/ui/filter_bar/filter_options.tsx b/src/plugins/data/public/ui/filter_bar/filter_options.tsx index a4a6eb3b50a319..4065c3b8fe0df4 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_options.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_options.tsx @@ -27,6 +27,8 @@ interface State { } class FilterOptionsUI extends Component { + private buttonRef = React.createRef(); + public state: State = { isPopoverOpen: false, }; @@ -39,6 +41,7 @@ class FilterOptionsUI extends Component { public closePopover = () => { this.setState({ isPopoverOpen: false }); + this.buttonRef.current?.focus(); }; public render() { @@ -151,6 +154,7 @@ class FilterOptionsUI extends Component { defaultMessage: 'Change all filters', })} data-test-subj="showFilterActions" + buttonRef={this.buttonRef} /> } anchorPosition="rightUp" diff --git a/src/plugins/data/public/ui/filter_bar/index.tsx b/src/plugins/data/public/ui/filter_bar/index.tsx index f8c2f46424c387..9fb352c64aa5d8 100644 --- a/src/plugins/data/public/ui/filter_bar/index.tsx +++ b/src/plugins/data/public/ui/filter_bar/index.tsx @@ -17,3 +17,12 @@ export const FilterLabel = (props: FilterLabelProps) => ( ); + +import type { FilterItemProps } from './filter_item'; + +const LazyFilterItem = React.lazy(() => import('./filter_item')); +export const FilterItem = (props: FilterItemProps) => ( + }> + + +); diff --git a/src/plugins/data/public/ui/query_string_input/_query_bar.scss b/src/plugins/data/public/ui/query_string_input/_query_bar.scss index 6f088fe641c51f..c758ace75bd609 100644 --- a/src/plugins/data/public/ui/query_string_input/_query_bar.scss +++ b/src/plugins/data/public/ui/query_string_input/_query_bar.scss @@ -26,18 +26,18 @@ padding-top: $euiSizeS + 3px; box-shadow: 0 0 0 1px $euiFormBorderColor; - &:not(:focus):not(:invalid) { + &:not(.kbnQueryBar__textarea--autoHeight):not(:invalid) { @include euiYScrollWithShadows; } - &:not(:focus) { + &:not(.kbnQueryBar__textarea--autoHeight) { white-space: nowrap; overflow-y: hidden; overflow-x: hidden; } // When focused, let it scroll - &:focus { + &.kbnQueryBar__textarea--autoHeight { overflow-x: auto; overflow-y: auto; white-space: normal; diff --git a/src/plugins/data/public/ui/query_string_input/language_switcher.tsx b/src/plugins/data/public/ui/query_string_input/language_switcher.tsx index 053ca6f78e910c..65e84612bc5088 100644 --- a/src/plugins/data/public/ui/query_string_input/language_switcher.tsx +++ b/src/plugins/data/public/ui/query_string_input/language_switcher.tsx @@ -86,6 +86,8 @@ export function QueryLanguageSwitcher({ isOpen={isPopoverOpen} closePopover={() => setIsPopoverOpen(false)} repositionOnScroll + ownFocus={true} + initialFocus={'[role="switch"]'} > ({ clear: jest.fn(), }); -function wrapQueryStringInputInContext(testProps: any, storage?: any) { - const defaultOptions = { - screenTitle: 'Another Screen', - intl: null as any, - }; +const QueryStringInput = withKibana(QueryStringInputUI); +function wrapQueryStringInputInContext(testProps: any, storage?: any) { const services = { ...startMock, data: dataPluginMock.createStartContract(), @@ -75,6 +73,11 @@ function wrapQueryStringInputInContext(testProps: any, storage?: any) { storage: storage || createMockStorage(), }; + const defaultOptions = { + screenTitle: 'Another Screen', + intl: null as any, + }; + return ( @@ -84,15 +87,12 @@ function wrapQueryStringInputInContext(testProps: any, storage?: any) { ); } -// FAILING: https://github.com/elastic/kibana/issues/85715 -// FAILING: https://github.com/elastic/kibana/issues/89603 -// FAILING: https://github.com/elastic/kibana/issues/89641 -describe.skip('QueryStringInput', () => { +describe('QueryStringInput', () => { beforeEach(() => { jest.clearAllMocks(); }); - it.skip('Should render the given query', async () => { + it('Should render the given query', async () => { const { getByText } = render( wrapQueryStringInputInContext({ query: kqlQuery, @@ -228,7 +228,7 @@ describe.skip('QueryStringInput', () => { expect(mockCallback).toHaveBeenCalledWith(); }); - it('Should fire onChangeQueryInputFocus callback on input blur', () => { + it('Should fire onChangeQueryInputFocus after a delay', () => { const mockCallback = jest.fn(); const component = mount( @@ -243,10 +243,93 @@ describe.skip('QueryStringInput', () => { const inputWrapper = component.find(EuiTextArea).find('textarea'); inputWrapper.simulate('blur'); + jest.advanceTimersByTime(10); + + expect(mockCallback).toHaveBeenCalledTimes(0); + + jest.advanceTimersByTime(100); + expect(mockCallback).toHaveBeenCalledTimes(1); expect(mockCallback).toHaveBeenCalledWith(false); }); + it('Should not fire onChangeQueryInputFocus if input is focused back', () => { + const mockCallback = jest.fn(); + + const component = mount( + wrapQueryStringInputInContext({ + query: kqlQuery, + onChangeQueryInputFocus: mockCallback, + indexPatterns: [stubIndexPatternWithFields], + disableAutoFocus: true, + }) + ); + + const inputWrapper = component.find(EuiTextArea).find('textarea'); + inputWrapper.simulate('blur'); + + jest.advanceTimersByTime(5); + expect(mockCallback).toHaveBeenCalledTimes(0); + + inputWrapper.simulate('focus'); + + expect(mockCallback).toHaveBeenCalledTimes(1); + expect(mockCallback).toHaveBeenCalledWith(true); + + jest.advanceTimersByTime(100); + expect(mockCallback).toHaveBeenCalledTimes(1); + }); + + it('Should call onSubmit after a delay when submitOnBlur is on and blurs input', () => { + const mockCallback = jest.fn(); + + const component = mount( + wrapQueryStringInputInContext({ + query: kqlQuery, + onSubmit: mockCallback, + indexPatterns: [stubIndexPatternWithFields], + disableAutoFocus: true, + submitOnBlur: true, + }) + ); + + const inputWrapper = component.find(EuiTextArea).find('textarea'); + inputWrapper.simulate('blur'); + + jest.advanceTimersByTime(10); + + expect(mockCallback).toHaveBeenCalledTimes(0); + + jest.advanceTimersByTime(100); + + expect(mockCallback).toHaveBeenCalledTimes(1); + expect(mockCallback).toHaveBeenCalledWith(kqlQuery); + }); + + it("Shouldn't call onSubmit on blur by default", () => { + const mockCallback = jest.fn(); + + const component = mount( + wrapQueryStringInputInContext({ + query: kqlQuery, + onSubmit: mockCallback, + indexPatterns: [stubIndexPatternWithFields], + disableAutoFocus: true, + }) + ); + + const inputWrapper = component.find(EuiTextArea).find('textarea'); + inputWrapper.simulate('blur'); + + jest.advanceTimersByTime(10); + + expect(mockCallback).toHaveBeenCalledTimes(0); + + jest.advanceTimersByTime(100); + + expect(mockCallback).toHaveBeenCalledTimes(0); + }); + it('Should use PersistedLog for recent search suggestions', async () => { const component = mount( wrapQueryStringInputInContext({ diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx index 65f7e4f3964cd9..5e34c401c76154 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx @@ -123,6 +123,12 @@ export default class QueryStringInputUI extends Component { private componentIsUnmounting = false; private queryBarInputDivRefInstance: RefObject = createRef(); + /** + * If any element within the container is currently focused + * @private + */ + private isFocusWithin = false; + private getQueryString = () => { return toUser(this.props.query.query); }; @@ -492,30 +498,37 @@ export default class QueryStringInputUI extends Component { private onOutsideClick = () => { if (this.state.isSuggestionsVisible) { this.setState({ isSuggestionsVisible: false, index: null }); - } - this.handleBlurHeight(); - if (this.props.onChangeQueryInputFocus) { - this.props.onChangeQueryInputFocus(false); + this.scheduleOnInputBlur(); } }; + private blurTimeoutHandle: number | undefined; + /** + * Notify parent about input's blur after a delay only + * if the focus didn't get back inside the input container + * and if suggestions were closed + * https://github.com/elastic/kibana/issues/92040 + */ + private scheduleOnInputBlur = () => { + clearTimeout(this.blurTimeoutHandle); + this.blurTimeoutHandle = window.setTimeout(() => { + if (!this.isFocusWithin && !this.state.isSuggestionsVisible && !this.componentIsUnmounting) { + this.handleBlurHeight(); + if (this.props.onChangeQueryInputFocus) { + this.props.onChangeQueryInputFocus(false); + } + + if (this.props.submitOnBlur) { + this.onSubmit(this.props.query); + } + } + }, 50); + }; + private onInputBlur = () => { - this.handleBlurHeight(); - if (this.props.onChangeQueryInputFocus) { - this.props.onChangeQueryInputFocus(false); - } if (isFunction(this.props.onBlur)) { this.props.onBlur(); } - if (this.props.submitOnBlur) { - // Input blur triggers when the user selects something from autocomplete, so wait a bit to ensure that - // the entire QueryStringInput component has actually blurred (e.g. from user clicking or tabbing away) - setTimeout(() => { - if (document.activeElement !== this.inputRef) { - this.onSubmit(this.props.query); - } - }, 200); - } }; private onClickSuggestion = (suggestion: QuerySuggestion, index: number) => { @@ -604,6 +617,7 @@ export default class QueryStringInputUI extends Component { handleAutoHeight = () => { if (this.inputRef !== null && document.activeElement === this.inputRef) { + this.inputRef.classList.add('kbnQueryBar__textarea--autoHeight'); this.inputRef.style.setProperty('height', `${this.inputRef.scrollHeight}px`, 'important'); } this.handleListUpdate(); @@ -612,6 +626,7 @@ export default class QueryStringInputUI extends Component { handleRemoveHeight = () => { if (this.inputRef !== null) { this.inputRef.style.removeProperty('height'); + this.inputRef.classList.remove('kbnQueryBar__textarea--autoHeight'); } }; @@ -648,7 +663,16 @@ export default class QueryStringInputUI extends Component { ); return ( -
+
{ + this.isFocusWithin = true; + }} + onBlur={(e) => { + this.isFocusWithin = false; + this.scheduleOnInputBlur(); + }} + > {this.props.prepend}
): void { - const router = http.createRouter(); + const router = http.createRouter(); registerValueSuggestionsRoute(router, config$); } diff --git a/src/plugins/data/server/autocomplete/value_suggestions_route.ts b/src/plugins/data/server/autocomplete/value_suggestions_route.ts index 489a23eb83897c..8e6d3afa18ed5f 100644 --- a/src/plugins/data/server/autocomplete/value_suggestions_route.ts +++ b/src/plugins/data/server/autocomplete/value_suggestions_route.ts @@ -12,12 +12,12 @@ import { IRouter, SharedGlobalConfig } from 'kibana/server'; import { Observable } from 'rxjs'; import { first } from 'rxjs/operators'; -import { IFieldType, Filter } from '../index'; -import { findIndexPatternById, getFieldByName } from '../index_patterns'; +import { IFieldType, Filter, ES_SEARCH_STRATEGY, IEsSearchRequest } from '../index'; import { getRequestAbortedSignal } from '../lib'; +import { DataRequestHandlerContext } from '../types'; export function registerValueSuggestionsRoute( - router: IRouter, + router: IRouter, config$: Observable ) { router.post( @@ -44,24 +44,40 @@ export function registerValueSuggestionsRoute( const config = await config$.pipe(first()).toPromise(); const { field: fieldName, query, filters } = request.body; const { index } = request.params; - const { client } = context.core.elasticsearch.legacy; const signal = getRequestAbortedSignal(request.events.aborted$); + if (!context.indexPatterns) { + return response.badRequest(); + } + const autocompleteSearchOptions = { timeout: `${config.kibana.autocompleteTimeout.asMilliseconds()}ms`, terminate_after: config.kibana.autocompleteTerminateAfter.asMilliseconds(), }; - const indexPattern = await findIndexPatternById(context.core.savedObjects.client, index); - - const field = indexPattern && getFieldByName(fieldName, indexPattern); + const indexPatterns = await context.indexPatterns.find(index, 1); + if (!indexPatterns || indexPatterns.length === 0) { + return response.notFound(); + } + const field = indexPatterns[0].getFieldByName(fieldName); const body = await getBody(autocompleteSearchOptions, field || fieldName, query, filters); - const result = await client.callAsCurrentUser('search', { index, body }, { signal }); + const searchRequest: IEsSearchRequest = { + params: { + index, + body, + }, + }; + const { rawResponse } = await context.search + .search(searchRequest, { + strategy: ES_SEARCH_STRATEGY, + abortSignal: signal, + }) + .toPromise(); const buckets: any[] = - get(result, 'aggregations.suggestions.buckets') || - get(result, 'aggregations.nestedSuggestions.suggestions.buckets'); + get(rawResponse, 'aggregations.suggestions.buckets') || + get(rawResponse, 'aggregations.nestedSuggestions.suggestions.buckets'); return response.ok({ body: map(buckets || [], 'key') }); } diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index cbf09ef57d96ae..c153c0efa88921 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -236,10 +236,10 @@ export { SearchUsage, SearchSessionService, ISearchSessionService, - SearchRequestHandlerContext, - DataRequestHandlerContext, } from './search'; +export { DataRequestHandlerContext } from './types'; + // Search namespace export const search = { aggs: { diff --git a/src/plugins/data/server/index_patterns/index.ts b/src/plugins/data/server/index_patterns/index.ts index 7226d6f015cf88..85610cd85a3cef 100644 --- a/src/plugins/data/server/index_patterns/index.ts +++ b/src/plugins/data/server/index_patterns/index.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +import { IndexPatternsService } from '../../common/index_patterns'; + export * from './utils'; export { IndexPatternsFetcher, @@ -15,3 +17,5 @@ export { getCapabilitiesForRollupIndices, } from './fetcher'; export { IndexPatternsServiceProvider, IndexPatternsServiceStart } from './index_patterns_service'; + +export type IndexPatternsHandlerContext = IndexPatternsService; diff --git a/src/plugins/data/server/index_patterns/index_patterns_service.ts b/src/plugins/data/server/index_patterns/index_patterns_service.ts index 9e0e8d19df5e70..4d93e9c0e06068 100644 --- a/src/plugins/data/server/index_patterns/index_patterns_service.ts +++ b/src/plugins/data/server/index_patterns/index_patterns_service.ts @@ -25,6 +25,7 @@ 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 { DataRequestHandlerContext } from '../types'; export interface IndexPatternsServiceStart { indexPatternsServiceFactory: ( @@ -35,6 +36,7 @@ export interface IndexPatternsServiceStart { export interface IndexPatternsServiceSetupDeps { expressions: ExpressionsServerSetup; + logger: Logger; } export interface IndexPatternsServiceStartDeps { @@ -45,11 +47,27 @@ export interface IndexPatternsServiceStartDeps { export class IndexPatternsServiceProvider implements Plugin { public setup( core: CoreSetup, - { expressions }: IndexPatternsServiceSetupDeps + { logger, expressions }: IndexPatternsServiceSetupDeps ) { core.savedObjects.registerType(indexPatternSavedObjectType); core.capabilities.registerProvider(capabilitiesProvider); + core.http.registerRouteHandlerContext( + 'indexPatterns', + async (context, request) => { + const [coreStart, , dataStart] = await core.getStartServices(); + try { + return await dataStart.indexPatterns.indexPatternsServiceFactory( + coreStart.savedObjects.getScopedClient(request), + coreStart.elasticsearch.client.asScoped(request).asCurrentUser + ); + } catch (e) { + logger.error(e); + return undefined; + } + } + ); + registerRoutes(core.http, core.getStartServices); expressions.registerFunction(getIndexPatternLoad({ getStartServices: core.getStartServices })); diff --git a/src/plugins/data/server/mocks.ts b/src/plugins/data/server/mocks.ts index 786dd30dbabd0b..c82db7a141403f 100644 --- a/src/plugins/data/server/mocks.ts +++ b/src/plugins/data/server/mocks.ts @@ -13,7 +13,7 @@ import { } from './search/mocks'; import { createFieldFormatsSetupMock, createFieldFormatsStartMock } from './field_formats/mocks'; import { createIndexPatternsStartMock } from './index_patterns/mocks'; -import { DataRequestHandlerContext } from './search'; +import { DataRequestHandlerContext } from './types'; function createSetupContract() { return { diff --git a/src/plugins/data/server/plugin.ts b/src/plugins/data/server/plugin.ts index a7a7663d6981c4..3408c39cbb8e2d 100644 --- a/src/plugins/data/server/plugin.ts +++ b/src/plugins/data/server/plugin.ts @@ -82,7 +82,10 @@ export class DataServerPlugin this.queryService.setup(core); this.autocompleteService.setup(core); this.kqlTelemetryService.setup(core, { usageCollection }); - this.indexPatterns.setup(core, { expressions }); + this.indexPatterns.setup(core, { + expressions, + logger: this.logger.get('indexPatterns'), + }); core.uiSettings.register(getUiSettings()); diff --git a/src/plugins/data/server/search/routes/msearch.ts b/src/plugins/data/server/search/routes/msearch.ts index b578805d8c2dfd..b5f06c4b343e78 100644 --- a/src/plugins/data/server/search/routes/msearch.ts +++ b/src/plugins/data/server/search/routes/msearch.ts @@ -12,7 +12,7 @@ import { SearchRouteDependencies } from '../search_service'; import { getCallMsearch } from './call_msearch'; import { reportServerError } from '../../../../kibana_utils/server'; -import type { DataPluginRouter } from '../types'; +import type { DataPluginRouter } from '../../types'; /** * The msearch route takes in an array of searches, each consisting of header * and body json, and reformts them into a single request for the _msearch API. diff --git a/src/plugins/data/server/search/routes/search.ts b/src/plugins/data/server/search/routes/search.ts index 1680a9c4a72371..6690e2b81f3e4a 100644 --- a/src/plugins/data/server/search/routes/search.ts +++ b/src/plugins/data/server/search/routes/search.ts @@ -10,7 +10,7 @@ import { first } from 'rxjs/operators'; import { schema } from '@kbn/config-schema'; import { getRequestAbortedSignal } from '../../lib'; import { reportServerError } from '../../../../kibana_utils/server'; -import type { DataPluginRouter } from '../types'; +import type { DataPluginRouter } from '../../types'; export function registerSearchRoute(router: DataPluginRouter): void { router.post( diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 6ece8ff9454687..ab9fc84d511879 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -29,7 +29,6 @@ import type { ISearchStrategy, SearchEnhancements, SearchStrategyDependencies, - DataRequestHandlerContext, } from './types'; import { AggsService } from './aggs'; @@ -52,6 +51,9 @@ import { kibana, kibanaContext, kibanaContextFunction, + kibanaTimerangeFunction, + kqlFunction, + luceneFunction, SearchSourceDependencies, searchSourceRequiredUiSettings, SearchSourceService, @@ -66,6 +68,7 @@ import { ConfigSchema } from '../../config'; import { ISearchSessionService, SearchSessionService } from './session'; import { KbnServerError } from '../../../kibana_utils/server'; import { registerBsearchRoute } from './routes/bsearch'; +import { DataRequestHandlerContext } from '../types'; type StrategyMap = Record>; @@ -142,6 +145,9 @@ export class SearchService implements Plugin { expressions.registerFunction(getEsaggs({ getStartServices: core.getStartServices })); expressions.registerFunction(kibana); + expressions.registerFunction(luceneFunction); + expressions.registerFunction(kqlFunction); + expressions.registerFunction(kibanaTimerangeFunction); expressions.registerFunction(kibanaContextFunction); expressions.registerType(kibanaContext); diff --git a/src/plugins/data/server/search/types.ts b/src/plugins/data/server/search/types.ts index e8548257c01679..d7aadcc348c87d 100644 --- a/src/plugins/data/server/search/types.ts +++ b/src/plugins/data/server/search/types.ts @@ -8,12 +8,10 @@ import { Observable } from 'rxjs'; import type { - IRouter, IScopedClusterClient, IUiSettingsClient, SavedObjectsClientContract, KibanaRequest, - RequestHandlerContext, } from 'src/core/server'; import { ISearchOptions, @@ -116,12 +114,3 @@ export interface ISearchStart< } export type SearchRequestHandlerContext = IScopedSearchClient; - -/** - * @internal - */ -export interface DataRequestHandlerContext extends RequestHandlerContext { - search: SearchRequestHandlerContext; -} - -export type DataPluginRouter = IRouter; diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 5a05d4a0445bd7..34001dc4d421e9 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -312,6 +312,12 @@ export const config: PluginConfigDescriptor; // @internal (undocumented) export interface DataRequestHandlerContext extends RequestHandlerContext { + // Warning: (ae-forgotten-export) The symbol "IndexPatternsHandlerContext" needs to be exported by the entry point index.d.ts + // + // (undocumented) + indexPatterns?: IndexPatternsHandlerContext; + // Warning: (ae-forgotten-export) The symbol "SearchRequestHandlerContext" needs to be exported by the entry point index.d.ts + // // (undocumented) search: SearchRequestHandlerContext; } @@ -960,7 +966,7 @@ export class IndexPatternsServiceProvider implements Plugin_3, { expressions }: IndexPatternsServiceSetupDeps): void; + setup(core: CoreSetup_2, { logger, expressions }: IndexPatternsServiceSetupDeps): void; // Warning: (ae-forgotten-export) The symbol "IndexPatternsServiceStartDeps" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -1325,11 +1331,6 @@ export const search: { tabifyGetColumns: typeof tabifyGetColumns; }; -// Warning: (ae-missing-release-tag) "SearchRequestHandlerContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export type SearchRequestHandlerContext = IScopedSearchClient; - // @internal export class SearchSessionService implements ISearchSessionService { constructor(); @@ -1521,7 +1522,7 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // src/plugins/data/server/index.ts:270:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:271:1 - (ae-forgotten-export) The symbol "calcAutoIntervalLessThan" needs to be exported by the entry point index.d.ts // src/plugins/data/server/plugin.ts:79:74 - (ae-forgotten-export) The symbol "DataEnhancements" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/search/types.ts:114:5 - (ae-forgotten-export) The symbol "ISearchStartSearchSource" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/search/types.ts:112:5 - (ae-forgotten-export) The symbol "ISearchStartSearchSource" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/data/server/types.ts b/src/plugins/data/server/types.ts new file mode 100644 index 00000000000000..ea0fa49058d374 --- /dev/null +++ b/src/plugins/data/server/types.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 type { IRouter, RequestHandlerContext } from 'src/core/server'; + +import { SearchRequestHandlerContext } from './search'; +import { IndexPatternsHandlerContext } from './index_patterns'; + +/** + * @internal + */ +export interface DataRequestHandlerContext extends RequestHandlerContext { + search: SearchRequestHandlerContext; + indexPatterns?: IndexPatternsHandlerContext; +} + +export type DataPluginRouter = IRouter; diff --git a/src/plugins/data/server/ui_settings.ts b/src/plugins/data/server/ui_settings.ts index b1d5b870826966..23589c22b33710 100644 --- a/src/plugins/data/server/ui_settings.ts +++ b/src/plugins/data/server/ui_settings.ts @@ -417,7 +417,7 @@ export function getUiSettings(): Record> { 'data.advancedSettings.format.numberFormat.numeralFormatLinkText', values: { numeralFormatLink: - '' + + '' + i18n.translate('data.advancedSettings.format.numberFormat.numeralFormatLinkText', { defaultMessage: 'numeral format', }) + @@ -439,7 +439,7 @@ export function getUiSettings(): Record> { 'data.advancedSettings.format.percentFormat.numeralFormatLinkText', values: { numeralFormatLink: - '' + + '' + i18n.translate('data.advancedSettings.format.percentFormat.numeralFormatLinkText', { defaultMessage: 'numeral format', }) + @@ -461,7 +461,7 @@ export function getUiSettings(): Record> { 'data.advancedSettings.format.bytesFormat.numeralFormatLinkText', values: { numeralFormatLink: - '' + + '' + i18n.translate('data.advancedSettings.format.bytesFormat.numeralFormatLinkText', { defaultMessage: 'numeral format', }) + @@ -483,7 +483,7 @@ export function getUiSettings(): Record> { 'data.advancedSettings.format.currencyFormat.numeralFormatLinkText', values: { numeralFormatLink: - '' + + '' + i18n.translate('data.advancedSettings.format.currencyFormat.numeralFormatLinkText', { defaultMessage: 'numeral format', }) + @@ -509,7 +509,7 @@ export function getUiSettings(): Record> { 'data.advancedSettings.format.formattingLocaleText', values: { numeralLanguageLink: - '' + + '' + i18n.translate( 'data.advancedSettings.format.formattingLocale.numeralLanguageLinkText', { diff --git a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts index 8ab2445b6143cc..9b69dacd8fdb5e 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts @@ -23,7 +23,7 @@ export const getSavedObjects = (): SavedObject[] => [ defaultMessage: '[eCommerce] Sales by Category', }), visState: - '{"title":"[eCommerce] Sales by Category","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Sum of total_quantity"}}],"seriesParams":[{"show":"true","type":"area","mode":"stacked","data":{"label":"Sum of total_quantity","id":"1"},"drawLinesBetweenPoints":true,"showCircles":true,"interpolate":"linear","valueAxis":"ValueAxis-1"}],"addTooltip":true,"addLegend":true,"legendPosition":"top","times":[],"addTimeMarker":false,"detailedTooltip":true},"aggs":[{"id":"1","enabled":true,"type":"sum","schema":"metric","params":{"field":"total_quantity"}},{"id":"2","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"order_date","interval":"auto","drop_partials":false,"min_doc_count":1,"extended_bounds":{}}},{"id":"3","enabled":true,"type":"terms","schema":"group","params":{"field":"category.keyword","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing"}}]}', + '{"title":"[eCommerce] Sales by Category","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Sum of total_quantity"}}],"seriesParams":[{"show":"true","type":"area","mode":"stacked","data":{"label":"Sum of total_quantity","id":"1"},"drawLinesBetweenPoints":true,"showCircles":true,"interpolate":"linear","valueAxis":"ValueAxis-1"}],"addTooltip":true,"addLegend":true,"legendPosition":"top","times":[],"addTimeMarker":false,"detailedTooltip":true,"palette":{"type":"palette","name":"default"}},"aggs":[{"id":"1","enabled":true,"type":"sum","schema":"metric","params":{"field":"total_quantity"}},{"id":"2","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"order_date","interval":"auto","drop_partials":false,"min_doc_count":1,"extended_bounds":{}}},{"id":"3","enabled":true,"type":"terms","schema":"group","params":{"field":"category.keyword","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing"}}]}', uiStateJSON: '{}', description: '', version: 1, diff --git a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts index 727ccc0bc95096..b316835029d7c5 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts @@ -45,7 +45,7 @@ export const getSavedObjects = (): SavedObject[] => [ defaultMessage: '[Flights] Flight Count and Average Ticket Price', }), visState: - '{"title":"[Flights] Flight Count and Average Ticket Price","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Average Ticket Price"}},{"id":"ValueAxis-2","name":"RightAxis-1","type":"value","position":"right","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Flight Count"}}],"seriesParams":[{"show":true,"mode":"stacked","type":"area","drawLinesBetweenPoints":true,"showCircles":false,"interpolate":"linear","lineWidth":2,"data":{"id":"5","label":"Flight Count"},"valueAxis":"ValueAxis-2"},{"show":true,"mode":"stacked","type":"line","drawLinesBetweenPoints":false,"showCircles":true,"interpolate":"linear","data":{"id":"4","label":"Average Ticket Price"},"valueAxis":"ValueAxis-1","lineWidth":2}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"radiusRatio":13,"detailedTooltip":true},"aggs":[{"id":"3","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"timestamp","interval":"auto","min_doc_count":1,"extended_bounds":{}}},{"id":"5","enabled":true,"type":"count","schema":"metric","params":{"customLabel":"Flight Count"}},{"id":"4","enabled":true,"type":"avg","schema":"metric","params":{"field":"AvgTicketPrice","customLabel":"Average Ticket Price"}},{"id":"2","enabled":true,"type":"avg","schema":"radius","params":{"field":"AvgTicketPrice"}}]}', + '{"title":"[Flights] Flight Count and Average Ticket Price","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Average Ticket Price"}},{"id":"ValueAxis-2","name":"RightAxis-1","type":"value","position":"right","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Flight Count"}}],"seriesParams":[{"show":true,"mode":"stacked","type":"area","drawLinesBetweenPoints":true,"showCircles":false,"interpolate":"linear","lineWidth":2,"data":{"id":"5","label":"Flight Count"},"valueAxis":"ValueAxis-2"},{"show":true,"mode":"stacked","type":"line","drawLinesBetweenPoints":false,"showCircles":true,"interpolate":"linear","data":{"id":"4","label":"Average Ticket Price"},"valueAxis":"ValueAxis-1","lineWidth":2}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"radiusRatio":13,"detailedTooltip":true,"palette":{"type":"palette","name":"default"}},"aggs":[{"id":"3","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"timestamp","interval":"auto","min_doc_count":1,"extended_bounds":{}}},{"id":"5","enabled":true,"type":"count","schema":"metric","params":{"customLabel":"Flight Count"}},{"id":"4","enabled":true,"type":"avg","schema":"metric","params":{"field":"AvgTicketPrice","customLabel":"Average Ticket Price"}},{"id":"2","enabled":true,"type":"avg","schema":"radius","params":{"field":"AvgTicketPrice"}}]}', uiStateJSON: '{"vis":{"legendOpen":true,"colors":{"Average Ticket Price":"#629E51","Flight Count":"#AEA2E0"}}}', description: '', @@ -122,7 +122,7 @@ export const getSavedObjects = (): SavedObject[] => [ defaultMessage: '[Flights] Delay Type', }), visState: - '{"title":"[Flights] Delay Type","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"drawLinesBetweenPoints":true,"showCircles":true,"interpolate":"cardinal","valueAxis":"ValueAxis-1"}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"detailedTooltip":true},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{}},{"id":"2","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"timestamp","interval":"auto","min_doc_count":1,"extended_bounds":{}}},{"id":"3","enabled":true,"type":"terms","schema":"group","params":{"field":"FlightDelayType","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing"}}]}', + '{"title":"[Flights] Delay Type","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"drawLinesBetweenPoints":true,"showCircles":true,"interpolate":"cardinal","valueAxis":"ValueAxis-1"}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"detailedTooltip":true,"palette":{"type":"palette","name":"default"}},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{}},{"id":"2","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"timestamp","interval":"auto","min_doc_count":1,"extended_bounds":{}}},{"id":"3","enabled":true,"type":"terms","schema":"group","params":{"field":"FlightDelayType","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing"}}]}', uiStateJSON: '{}', description: '', version: 1, @@ -165,7 +165,7 @@ export const getSavedObjects = (): SavedObject[] => [ defaultMessage: '[Flights] Delay Buckets', }), visState: - '{"title":"[Flights] Delay Buckets","type":"histogram","params":{"type":"histogram","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"valueAxis":"ValueAxis-1","drawLinesBetweenPoints":true,"showCircles":true}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"detailedTooltip":true},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{}},{"id":"2","enabled":true,"type":"histogram","schema":"segment","params":{"field":"FlightDelayMin","interval":30,"extended_bounds":{},"customLabel":"Flight Delay Minutes"}}]}', + '{"title":"[Flights] Delay Buckets","type":"histogram","params":{"type":"histogram","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"valueAxis":"ValueAxis-1","drawLinesBetweenPoints":true,"showCircles":true}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"detailedTooltip":true,"palette":{"type":"palette","name":"default"}},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{}},{"id":"2","enabled":true,"type":"histogram","schema":"segment","params":{"field":"FlightDelayMin","interval":30,"extended_bounds":{},"customLabel":"Flight Delay Minutes"}}]}', uiStateJSON: '{"vis":{"legendOpen":false}}', description: '', version: 1, @@ -187,7 +187,7 @@ export const getSavedObjects = (): SavedObject[] => [ defaultMessage: '[Flights] Flight Delays', }), visState: - '{"title":"[Flights] Flight Delays","type":"histogram","params":{"type":"histogram","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"left","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"BottomAxis-1","type":"value","position":"bottom","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"valueAxis":"ValueAxis-1","drawLinesBetweenPoints":true,"showCircles":true}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"detailedTooltip":true},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{"customLabel":""}},{"id":"2","enabled":true,"type":"terms","schema":"segment","params":{"field":"FlightDelay","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing","customLabel":"Flight Delays"}}]}', + '{"title":"[Flights] Flight Delays","type":"histogram","params":{"type":"histogram","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"left","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"BottomAxis-1","type":"value","position":"bottom","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"valueAxis":"ValueAxis-1","drawLinesBetweenPoints":true,"showCircles":true}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"detailedTooltip":true,"palette":{"type":"palette","name":"default"}},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{"customLabel":""}},{"id":"2","enabled":true,"type":"terms","schema":"segment","params":{"field":"FlightDelay","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing","customLabel":"Flight Delays"}}]}', uiStateJSON: '{}', description: '', version: 1, @@ -209,7 +209,7 @@ export const getSavedObjects = (): SavedObject[] => [ defaultMessage: '[Flights] Flight Cancellations', }), visState: - '{"title":"[Flights] Flight Cancellations","type":"histogram","params":{"type":"histogram","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"left","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"BottomAxis-1","type":"value","position":"bottom","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"valueAxis":"ValueAxis-1","drawLinesBetweenPoints":true,"showCircles":true}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"detailedTooltip":true},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{"customLabel":""}},{"id":"2","enabled":true,"type":"terms","schema":"segment","params":{"field":"Cancelled","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing","customLabel":"Flight Cancellations"}}]}', + '{"title":"[Flights] Flight Cancellations","type":"histogram","params":{"type":"histogram","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"left","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"BottomAxis-1","type":"value","position":"bottom","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"valueAxis":"ValueAxis-1","drawLinesBetweenPoints":true,"showCircles":true}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"detailedTooltip":true,"palette":{"type":"palette","name":"default"}},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{"customLabel":""}},{"id":"2","enabled":true,"type":"terms","schema":"segment","params":{"field":"Cancelled","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing","customLabel":"Flight Cancellations"}}]}', uiStateJSON: '{}', description: '', version: 1, diff --git a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts index fb19545ece3a7b..0396cb58d36928 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts @@ -22,7 +22,7 @@ export const getSavedObjects = (): SavedObject[] => [ defaultMessage: '[Logs] Unique Visitors vs. Average Bytes', }), visState: - '{"title":"[Logs] Unique Visitors vs. Average Bytes","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Avg. Bytes"}},{"id":"ValueAxis-2","name":"RightAxis-1","type":"value","position":"right","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Unique Visitors"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Avg. Bytes","id":"1"},"drawLinesBetweenPoints":true,"showCircles":true,"interpolate":"linear","valueAxis":"ValueAxis-1"},{"show":true,"mode":"stacked","type":"line","drawLinesBetweenPoints":false,"showCircles":true,"interpolate":"linear","data":{"id":"2","label":"Unique Visitors"},"valueAxis":"ValueAxis-2"}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"radiusRatio":17,"detailedTooltip":true},"aggs":[{"id":"1","enabled":true,"type":"avg","schema":"metric","params":{"field":"bytes","customLabel":"Avg. Bytes"}},{"id":"2","enabled":true,"type":"cardinality","schema":"metric","params":{"field":"clientip","customLabel":"Unique Visitors"}},{"id":"3","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"timestamp","interval":"auto","min_doc_count":1,"extended_bounds":{}}},{"id":"4","enabled":true,"type":"count","schema":"radius","params":{}}]}', + '{"title":"[Logs] Unique Visitors vs. Average Bytes","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Avg. Bytes"}},{"id":"ValueAxis-2","name":"RightAxis-1","type":"value","position":"right","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Unique Visitors"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Avg. Bytes","id":"1"},"drawLinesBetweenPoints":true,"showCircles":true,"interpolate":"linear","valueAxis":"ValueAxis-1"},{"show":true,"mode":"stacked","type":"line","drawLinesBetweenPoints":false,"showCircles":true,"interpolate":"linear","data":{"id":"2","label":"Unique Visitors"},"valueAxis":"ValueAxis-2"}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"radiusRatio":17,"detailedTooltip":true,"palette":{"type":"palette","name":"default"}},"aggs":[{"id":"1","enabled":true,"type":"avg","schema":"metric","params":{"field":"bytes","customLabel":"Avg. Bytes"}},{"id":"2","enabled":true,"type":"cardinality","schema":"metric","params":{"field":"clientip","customLabel":"Unique Visitors"}},{"id":"3","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"timestamp","interval":"auto","min_doc_count":1,"extended_bounds":{}}},{"id":"4","enabled":true,"type":"count","schema":"radius","params":{}}]}', uiStateJSON: '{"vis":{"colors":{"Avg. Bytes":"#70DBED","Unique Visitors":"#0A437C"}}}', description: '', version: 1, diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/script_field.tsx b/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/script_field.tsx index d15445f3e10ae6..29945e15874b78 100644 --- a/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/script_field.tsx +++ b/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/script_field.tsx @@ -91,7 +91,10 @@ export const ScriptField = React.memo(({ existingConcreteFields, links, syntaxEr const painlessSyntaxErrors = PainlessLang.getSyntaxErrors(); // It is possible for there to be more than one editor in a view, // so we need to get the syntax errors based on the editor (aka model) ID - const editorHasSyntaxErrors = editorId && painlessSyntaxErrors[editorId].length > 0; + const editorHasSyntaxErrors = + editorId && + painlessSyntaxErrors[editorId] && + painlessSyntaxErrors[editorId].length > 0; if (editorHasSyntaxErrors) { return resolve({ diff --git a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/bytes/__snapshots__/bytes.test.tsx.snap b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/bytes/__snapshots__/bytes.test.tsx.snap index 0f35267e1fb387..2ab8037639f85f 100644 --- a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/bytes/__snapshots__/bytes.test.tsx.snap +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/bytes/__snapshots__/bytes.test.tsx.snap @@ -11,7 +11,7 @@ exports[`BytesFormatEditor should render normally 1`] = ` helpText={ ; const fieldType = 'number'; const format = { @@ -25,7 +29,20 @@ const formatParams = { const onChange = jest.fn(); const onError = jest.fn(); +const KibanaReactContext = createKibanaReactContext( + coreMock.createStart({ basePath: 'my-base-path' }) +); + describe('BytesFormatEditor', () => { + beforeAll(() => { + // Enzyme does not support the new Context API in shallow rendering. + // @see https://github.com/enzymejs/enzyme/issues/2189 + (BytesFormatEditor as React.ComponentType).contextTypes = { + services: () => null, + }; + delete (BytesFormatEditor as Partial).contextType; + }); + it('should have a formatId', () => { expect(BytesFormatEditor.formatId).toEqual('bytes'); }); @@ -38,7 +55,8 @@ describe('BytesFormatEditor', () => { formatParams={formatParams} onChange={onChange} onError={onError} - /> + />, + { context: KibanaReactContext.value } ); expect(component).toMatchSnapshot(); }); diff --git a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/number/__snapshots__/number.test.tsx.snap b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/number/__snapshots__/number.test.tsx.snap index 3cac3850548351..4d42e3848d3cdc 100644 --- a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/number/__snapshots__/number.test.tsx.snap +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/number/__snapshots__/number.test.tsx.snap @@ -11,7 +11,7 @@ exports[`NumberFormatEditor should render normally 1`] = ` helpText={ ; + const fieldType = 'number'; const format = { getConverterFor: jest.fn().mockImplementation(() => (input: number) => input * 2), @@ -25,7 +29,20 @@ const formatParams = { const onChange = jest.fn(); const onError = jest.fn(); +const KibanaReactContext = createKibanaReactContext( + coreMock.createStart({ basePath: 'my-base-path' }) +); + describe('NumberFormatEditor', () => { + beforeAll(() => { + // Enzyme does not support the new Context API in shallow rendering. + // @see https://github.com/enzymejs/enzyme/issues/2189 + (NumberFormatEditor as React.ComponentType).contextTypes = { + services: () => null, + }; + delete (NumberFormatEditor as Partial).contextType; + }); + it('should have a formatId', () => { expect(NumberFormatEditor.formatId).toEqual('number'); }); @@ -38,7 +55,8 @@ describe('NumberFormatEditor', () => { formatParams={formatParams} onChange={onChange} onError={onError} - /> + />, + { context: KibanaReactContext.value } ); expect(component).toMatchSnapshot(); }); diff --git a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/number/number.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/number/number.tsx index 2aeb90373bfaba..f55c0c5f06c12a 100644 --- a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/number/number.tsx +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/number/number.tsx @@ -15,12 +15,17 @@ import { DefaultFormatEditor, defaultState } from '../default'; import { FormatEditorSamples } from '../../samples'; +import { context as contextType } from '../../../../../../kibana_react/public'; + export interface NumberFormatEditorParams { pattern: string; } export class NumberFormatEditor extends DefaultFormatEditor { + static contextType = contextType; static formatId = 'number'; + + context!: React.ContextType; state = { ...defaultState, sampleInputs: [10000, 12.345678, -1, -999, 0.52], @@ -43,7 +48,10 @@ export class NumberFormatEditor extends DefaultFormatEditor - + ; + const fieldType = 'number'; const format = { getConverterFor: jest.fn().mockImplementation(() => (input: number) => input * 2), @@ -25,7 +29,20 @@ const formatParams = { const onChange = jest.fn(); const onError = jest.fn(); +const KibanaReactContext = createKibanaReactContext( + coreMock.createStart({ basePath: 'my-base-path' }) +); + describe('PercentFormatEditor', () => { + beforeAll(() => { + // Enzyme does not support the new Context API in shallow rendering. + // @see https://github.com/enzymejs/enzyme/issues/2189 + (PercentFormatEditor as React.ComponentType).contextTypes = { + services: () => null, + }; + delete (PercentFormatEditor as Partial).contextType; + }); + it('should have a formatId', () => { expect(PercentFormatEditor.formatId).toEqual('percent'); }); @@ -38,7 +55,8 @@ describe('PercentFormatEditor', () => { formatParams={formatParams} onChange={onChange} onError={onError} - /> + />, + { context: KibanaReactContext.value } ); expect(component).toMatchSnapshot(); }); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/__snapshots__/table.test.tsx.snap b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/__snapshots__/table.test.tsx.snap index ed1183ac50532c..bd6af083675ea4 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/__snapshots__/table.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/__snapshots__/table.test.tsx.snap @@ -1,5 +1,29 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Table render name 1`] = ` + + customer + +`; + +exports[`Table render name 2`] = ` + + customer + +   + + This field exists on the index pattern only. + + } + title="Runtime field" + type="indexSettings" + /> + + +`; + exports[`Table should render conflicting type 1`] = ` conflict @@ -142,6 +166,15 @@ exports[`Table should render normally 1`] = ` "name": "conflictingField", "type": "text, long", }, + Object { + "displayName": "customer", + "excluded": false, + "info": Array [], + "isMapped": false, + "kbnType": "text", + "name": "customer", + "type": "keyword", + }, ] } pagination={ diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.test.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.test.tsx index 9c154ce1b0e7ba..76ffe61234e346 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.test.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.test.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { IIndexPattern } from 'src/plugins/data/public'; import { IndexedFieldItem } from '../../types'; -import { Table } from './table'; +import { Table, renderFieldName } from './table'; const indexPattern = { timeFieldName: 'timestamp', @@ -48,6 +48,15 @@ const items: IndexedFieldItem[] = [ format: '', isMapped: true, }, + { + name: 'customer', + displayName: 'customer', + type: 'keyword', + kbnType: 'text', + info: [], + excluded: false, + isMapped: false, + }, ]; const renderTable = ( @@ -103,4 +112,28 @@ describe('Table', () => { renderTable({ editField }).prop('columns')[6].actions[0].onClick(); expect(editField).toBeCalled(); }); + + test('render name', () => { + const mappedField = { + name: 'customer', + info: [], + excluded: false, + kbnType: 'string', + type: 'keyword', + isMapped: true, + }; + + expect(renderFieldName(mappedField)).toMatchSnapshot(); + + const runtimeField = { + name: 'customer', + info: [], + excluded: false, + kbnType: 'string', + type: 'keyword', + isMapped: false, + }; + + expect(renderFieldName(runtimeField)).toMatchSnapshot(); + }); }); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx index 4e9a2bb6451125..2b8a18cc67c64b 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx @@ -157,6 +157,16 @@ const labelDescription = i18n.translate( { defaultMessage: 'A custom label for the field.' } ); +const runtimeIconTipTitle = i18n.translate( + 'indexPatternManagement.editIndexPattern.fields.table.runtimeIconTipTitle', + { defaultMessage: 'Runtime field' } +); + +const runtimeIconTipText = i18n.translate( + 'indexPatternManagement.editIndexPattern.fields.table.runtimeIconTipText', + { defaultMessage: 'This field exists on the index pattern only.' } +); + interface IndexedFieldProps { indexPattern: IIndexPattern; items: IndexedFieldItem[]; @@ -164,54 +174,60 @@ interface IndexedFieldProps { deleteField: (fieldName: string) => void; } +export const renderFieldName = (field: IndexedFieldItem, timeFieldName?: string) => ( + + {field.name} + {field.info && field.info.length ? ( + +   + ( +
{info}
+ ))} + /> +
+ ) : null} + {timeFieldName === field.name ? ( + +   + + + ) : null} + {!field.isMapped ? ( + +   + {runtimeIconTipText}} + /> +
+ ) : null} + {field.customLabel && field.customLabel !== field.name ? ( +
+ + + {field.customLabel} + + +
+ ) : null} +
+); + export class Table extends PureComponent { renderBooleanTemplate(value: string, arialLabel: string) { return value ? : ; } - renderFieldName(name: string, field: IndexedFieldItem) { - const { indexPattern } = this.props; - - return ( - - {field.name} - {field.info && field.info.length ? ( - -   - ( -
{info}
- ))} - /> -
- ) : null} - {indexPattern.timeFieldName === name ? ( - -   - - - ) : null} - {field.customLabel && field.customLabel !== field.name ? ( -
- - - {field.customLabel} - - -
- ) : null} -
- ); - } - renderFieldType(type: string, isConflict: boolean) { return ( @@ -234,7 +250,7 @@ export class Table extends PureComponent { } render() { - const { items, editField, deleteField } = this.props; + const { items, editField, deleteField, indexPattern } = this.props; const pagination = { initialPageSize: 10, @@ -248,7 +264,7 @@ export class Table extends PureComponent { dataType: 'string', sortable: true, render: (value: string, field: IndexedFieldItem) => { - return this.renderFieldName(value, field); + return renderFieldName(field, indexPattern.timeFieldName); }, width: '38%', 'data-test-subj': 'indexedFieldName', diff --git a/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts index 70ac2302dc3b4b..5192e2300e3df0 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts @@ -269,11 +269,10 @@ export function getCoreUsageCollector( 'Maximum size of the payload in bytes of saved objects that can be imported.', }, }, - maxImportExportSizeBytes: { + maxImportExportSize: { type: 'long', _meta: { - description: - 'Maximum size in bytes of saved object that can be imported or exported.', + description: 'Maximum count of saved objects that can be imported or exported.', }, }, }, diff --git a/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard.tsx b/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard.tsx index 33c037c2e84b30..57d05262319f2c 100644 --- a/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard.tsx +++ b/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard.tsx @@ -30,7 +30,7 @@ export interface SaveModalDashboardProps { documentInfo: SaveModalDocumentInfo; objectType: string; onClose: () => void; - onSave: (props: OnSaveProps & { dashboardId: string | null }) => void; + onSave: (props: OnSaveProps & { dashboardId: string | null; addToLibrary: boolean }) => void; tagOptions?: React.ReactNode | ((state: SaveModalState) => React.ReactNode); } @@ -48,6 +48,9 @@ export function SavedObjectSaveModalDashboard(props: SaveModalDashboardProps) { const [dashboardOption, setDashboardOption] = useState<'new' | 'existing' | null>( documentId || disableDashboardOptions ? null : 'existing' ); + const [isAddToLibrarySelected, setAddToLibrary] = useState( + !initialCopyOnSave || disableDashboardOptions + ); const [selectedDashboard, setSelectedDashboard] = useState<{ id: string; name: string } | null>( null ); @@ -62,12 +65,13 @@ export function SavedObjectSaveModalDashboard(props: SaveModalDashboardProps) { onChange={(option) => { setDashboardOption(option); }} - {...{ copyOnSave, documentId, dashboardOption }} + {...{ copyOnSave, documentId, dashboardOption, setAddToLibrary, isAddToLibrarySelected }} /> ) : null; const onCopyOnSaveChange = (newCopyOnSave: boolean) => { + setAddToLibrary(true); setDashboardOption(null); setCopyOnSave(newCopyOnSave); }; @@ -85,7 +89,7 @@ export function SavedObjectSaveModalDashboard(props: SaveModalDashboardProps) { } } - props.onSave({ ...onSaveProps, dashboardId }); + props.onSave({ ...onSaveProps, dashboardId, addToLibrary: isAddToLibrarySelected }); }; const saveLibraryLabel = @@ -113,7 +117,7 @@ export function SavedObjectSaveModalDashboard(props: SaveModalDashboardProps) { onSave={onModalSave} title={documentInfo.title} showCopyOnSave={documentId ? true : false} - options={dashboardOption === null ? tagOptions : undefined} // Show tags when not adding to dashboard + options={isAddToLibrarySelected ? tagOptions : undefined} // Show tags when not adding to dashboard description={documentInfo.description} showDescription={true} {...{ diff --git a/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard_selector.stories.tsx b/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard_selector.stories.tsx index 6b41058f9c6c5c..dd6fd975f8e07f 100644 --- a/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard_selector.stories.tsx +++ b/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard_selector.stories.tsx @@ -44,6 +44,7 @@ export function Example({ hasDocumentId: boolean; } & StorybookParams) { const [dashboardOption, setDashboardOption] = useState<'new' | 'existing' | null>('existing'); + const [isAddToLibrarySelected, setAddToLibrary] = useState(false); return ( ); } diff --git a/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard_selector.tsx b/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard_selector.tsx index c2b5eac4dbb836..1ae54040571a24 100644 --- a/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard_selector.tsx +++ b/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard_selector.tsx @@ -19,6 +19,7 @@ import { EuiIconTip, EuiPanel, EuiSpacer, + EuiCheckbox, } from '@elastic/eui'; import { DashboardPicker, DashboardPickerProps } from './dashboard_picker'; @@ -29,24 +30,105 @@ export interface SaveModalDashboardSelectorProps { copyOnSave: boolean; documentId?: string; onSelectDashboard: DashboardPickerProps['onChange']; - + setAddToLibrary: (selected: boolean) => void; + isAddToLibrarySelected: boolean; dashboardOption: 'new' | 'existing' | null; onChange: (dashboardOption: 'new' | 'existing' | null) => void; } export function SaveModalDashboardSelector(props: SaveModalDashboardSelectorProps) { - const { documentId, onSelectDashboard, dashboardOption, onChange, copyOnSave } = props; + const { + documentId, + onSelectDashboard, + setAddToLibrary, + isAddToLibrarySelected, + dashboardOption, + onChange, + copyOnSave, + } = props; const isDisabled = !copyOnSave && !!documentId; return ( <> + } + hasChildLabel={false} + > + <> + +
+ <> + onChange('existing')} + disabled={isDisabled} + /> +
+ +
+ + + <> + onChange('new')} + disabled={isDisabled} + /> + + + { + setAddToLibrary(true); + onChange(null); + }} + disabled={isDisabled} + /> +
+
+ - - + setAddToLibrary(event.target.checked)} /> @@ -55,67 +137,13 @@ export function SaveModalDashboardSelector(props: SaveModalDashboardSelectorProp content={ } /> - } - hasChildLabel={false} - > - -
- <> - onChange('existing')} - disabled={isDisabled} - /> -
- -
- - - <> - onChange('new')} - disabled={isDisabled} - /> - - - onChange(null)} - disabled={isDisabled} - /> -
-
+
); diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 0dcae571497071..348936596e21f4 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -6675,10 +6675,10 @@ "description": "Maximum size of the payload in bytes of saved objects that can be imported." } }, - "maxImportExportSizeBytes": { + "maxImportExportSize": { "type": "long", "_meta": { - "description": "Maximum size in bytes of saved object that can be imported or exported." + "description": "Maximum count of saved objects that can be imported or exported." } } } diff --git a/src/plugins/usage_collection/README.mdx b/src/plugins/usage_collection/README.mdx index 9516ff656f4605..e6759b7dc6c7cc 100644 --- a/src/plugins/usage_collection/README.mdx +++ b/src/plugins/usage_collection/README.mdx @@ -166,7 +166,10 @@ In many cases, plugins need to report the custom usage of a feature. In this cas type: 'MY_USAGE_TYPE', schema: { my_objects: { - total: 'long', + total: { + type: 'long', + _meta: { description: 'The total number of objects in the cluster created in the last 24h' }, + }, }, }, isReady: () => isCollectorFetchReady, // Method to return `true`/`false` or Promise(`true`/`false`) to confirm if the collector is ready for the `fetch` method to be called. @@ -211,7 +214,7 @@ schema: { my_greeting: { type: 'keyword', _meta: { - description: 'The greeting keyword', + description: 'The greeting shown to the user. It reports only when overwritten by the user.', } } } @@ -254,21 +257,27 @@ export const myCollector = makeUsageCollector({ schema: { my_greeting: { type: 'keyword', + _meta: { description: 'The greeting shown to the user. It reports only when overwritten by the user.' } }, some_obj: { total: { type: 'long', + _meta: { description: 'The total count of some_obj since the creation of the cluster' } }, }, some_array: { type: 'array', - items: { type: 'keyword' } + items: { + type: 'keyword', + _meta: { description: 'Category assigned to ...' } + } }, some_array_of_obj: { type: 'array', items: { total: { type: 'long', + _meta: { description: 'The daily total number of items.' } }, }, }, diff --git a/src/plugins/visualizations/public/embeddable/to_ast.ts b/src/plugins/visualizations/public/embeddable/to_ast.ts index 5436b78c1b71f1..7ccff9394943af 100644 --- a/src/plugins/visualizations/public/embeddable/to_ast.ts +++ b/src/plugins/visualizations/public/embeddable/to_ast.ts @@ -10,6 +10,7 @@ import { ExpressionFunctionKibana, ExpressionFunctionKibanaContext } from '../.. import { buildExpression, buildExpressionFunction } from '../../../expressions/public'; import { VisToExpressionAst } from '../types'; +import { queryToAst } from '../../../data/common'; /** * Creates an ast expression for a visualization based on kibana context (query, filters, timerange) @@ -25,7 +26,7 @@ export const toExpressionAst: VisToExpressionAst = async (vis, params) => { const kibana = buildExpressionFunction('kibana', {}); const kibanaContext = buildExpressionFunction('kibana_context', { - q: query && JSON.stringify(query), + q: query && queryToAst(query), filters: filters && JSON.stringify(filters), savedSearchId, }); diff --git a/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts b/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts index 5c74de3e2ef9a8..6f1fba26b39b30 100644 --- a/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts +++ b/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts @@ -1672,7 +1672,12 @@ describe('migration visualization', () => { doc as Parameters[0], savedObjectMigrationContext ); - const getTestDoc = (type = 'area', categoryAxes?: object[], valueAxes?: object[]) => ({ + const getTestDoc = ( + type = 'area', + categoryAxes?: object[], + valueAxes?: object[], + hasPalette = false + ) => ({ attributes: { title: 'My Vis', description: 'This is my super cool vis.', @@ -1691,6 +1696,12 @@ describe('migration visualization', () => { labels: {}, }, ], + ...(hasPalette && { + palette: { + type: 'palette', + name: 'default', + }, + }), }, }), }, @@ -1709,13 +1720,20 @@ describe('migration visualization', () => { expect(isVislibVis).toEqual(true); }); - it('should decorate existing docs with the kibana legacy palette', () => { + it('should decorate existing docs without a predefined palette with the kibana legacy palette', () => { const migratedTestDoc = migrate(getTestDoc()); const { palette } = JSON.parse(migratedTestDoc.attributes.visState).params; expect(palette.name).toEqual('kibana_palette'); }); + it('should not overwrite the palette with the legacy one if the palette already exists in the saved object', () => { + const migratedTestDoc = migrate(getTestDoc('area', undefined, undefined, true)); + const { palette } = JSON.parse(migratedTestDoc.attributes.visState).params; + + expect(palette.name).toEqual('default'); + }); + describe('labels.filter', () => { it('should keep existing categoryAxes labels.filter value', () => { const migratedTestDoc = migrate(getTestDoc('area', [{ labels: { filter: false } }])); diff --git a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts index 2c72208b8b2837..afb59266d0dbf8 100644 --- a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts +++ b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts @@ -859,6 +859,7 @@ const migrateVislibAreaLineBarTypes: SavedObjectMigrationFn = (doc) => const isHorizontalBar = visState.type === 'horizontal_bar'; const isLineOrArea = visState?.params?.type === CHART_TYPE_AREA || visState?.params?.type === CHART_TYPE_LINE; + const hasPalette = visState?.params?.palette; return { ...doc, attributes: { @@ -867,10 +868,12 @@ const migrateVislibAreaLineBarTypes: SavedObjectMigrationFn = (doc) => ...visState, params: { ...visState.params, - palette: { - type: 'palette', - name: 'kibana_palette', - }, + ...(!hasPalette && { + palette: { + type: 'palette', + name: 'kibana_palette', + }, + }), categoryAxes: visState.params.categoryAxes && decorateAxes(visState.params.categoryAxes, !isHorizontalBar), diff --git a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx index e8c3289d4ce411..4f5679a14b0b79 100644 --- a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx +++ b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx @@ -93,7 +93,7 @@ export const getTopNavConfig = ( /** * Called when the user clicks "Save" button. */ - async function doSave(saveOptions: SavedObjectSaveOpts) { + async function doSave(saveOptions: SavedObjectSaveOpts & { dashboardId?: string }) { const newlyCreated = !Boolean(savedVis.id) || savedVis.copyOnSave; // vis.title was not bound and it's needed to reflect title into visState stateContainer.transitions.setVis({ @@ -118,7 +118,7 @@ export const getTopNavConfig = ( 'data-test-subj': 'saveVisualizationSuccess', }); - if (originatingApp && saveOptions.returnToOrigin) { + if ((originatingApp && saveOptions.returnToOrigin) || saveOptions.dashboardId) { if (!embeddableId) { const appPath = `${VisualizeConstants.EDIT_PATH}/${encodeURIComponent(id)}`; @@ -127,16 +127,26 @@ export const getTopNavConfig = ( setActiveUrl(appPath); } + const app = originatingApp || 'dashboards'; + + let path; + if (saveOptions.dashboardId) { + path = + saveOptions.dashboardId === 'new' ? '#/create' : `#/view/${saveOptions.dashboardId}`; + } + if (newlyCreated && stateTransfer) { - stateTransfer.navigateToWithEmbeddablePackage(originatingApp, { + stateTransfer.navigateToWithEmbeddablePackage(app, { state: { type: VISUALIZE_EMBEDDABLE_TYPE, input: { savedObjectId: id }, embeddableId, }, + path, }); } else { - application.navigateToApp(originatingApp); + // TODO: need the same thing here? + application.navigateToApp(app, { path }); } } else { if (setOriginatingApp && originatingApp && newlyCreated) { @@ -321,7 +331,11 @@ export const getTopNavConfig = ( newDescription, returnToOrigin, dashboardId, - }: OnSaveProps & { returnToOrigin?: boolean } & { dashboardId?: string | null }) => { + addToLibrary, + }: OnSaveProps & { returnToOrigin?: boolean } & { + dashboardId?: string | null; + addToLibrary?: boolean; + }) => { const currentTitle = savedVis.title; savedVis.title = newTitle; embeddableHandler.updateInput({ title: newTitle }); @@ -337,9 +351,12 @@ export const getTopNavConfig = ( isTitleDuplicateConfirmed, onTitleDuplicate, returnToOrigin, + dashboardId: !!dashboardId ? dashboardId : undefined, }; - if (dashboardId) { + // If we're adding to a dashboard and not saving to library, + // we'll want to use a by-value operation + if (dashboardId && !addToLibrary) { const appPath = `${VisualizeConstants.LANDING_PAGE_PATH}`; // Manually insert a new url so the back button will open the saved visualization. @@ -369,6 +386,8 @@ export const getTopNavConfig = ( return { id: true }; } + // We're adding the viz to a library so we need to save it and then + // add to a dashboard if necessary const response = await doSave(saveOptions); // If the save wasn't successful, put the original values back. if (!response.id || response.error) { diff --git a/test/api_integration/apis/saved_objects/export.ts b/test/api_integration/apis/saved_objects/export.ts index 8af2dbdea31dc8..87cdf5a8b0c46a 100644 --- a/test/api_integration/apis/saved_objects/export.ts +++ b/test/api_integration/apis/saved_objects/export.ts @@ -553,7 +553,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.body).to.eql({ statusCode: 400, error: 'Bad Request', - message: `Can't export more than 10001 objects`, + message: `Can't export more than 10001 objects. If your server has enough memory, this limit can be increased by adjusting the \"savedObjects.maxImportExportSize\" setting.`, }); }); await supertest diff --git a/test/api_integration/apis/telemetry/utils/schema_to_config_schema.test.js b/test/api_integration/apis/telemetry/utils/schema_to_config_schema.test.js index f568a4338ebe55..2ae4e1723cc25f 100644 --- a/test/api_integration/apis/telemetry/utils/schema_to_config_schema.test.js +++ b/test/api_integration/apis/telemetry/utils/schema_to_config_schema.test.js @@ -151,5 +151,23 @@ describe(`assertTelemetryPayload`, () => { { im_only_passing_through_data: [{ docs: { field: 1 } }] } ) ).not.toThrow(); + + // Even when properties exist + expect(() => + assertTelemetryPayload( + { + root: { + properties: { + im_only_passing_through_data: { + type: 'pass_through', + properties: {}, + }, + }, + }, + plugins: { properties: {} }, + }, + { im_only_passing_through_data: [{ docs: { field: 1 } }] } + ) + ).not.toThrow(); }); }); diff --git a/test/api_integration/apis/telemetry/utils/schema_to_config_schema.ts b/test/api_integration/apis/telemetry/utils/schema_to_config_schema.ts index d5b18eb4bd2026..b45930682e3aa9 100644 --- a/test/api_integration/apis/telemetry/utils/schema_to_config_schema.ts +++ b/test/api_integration/apis/telemetry/utils/schema_to_config_schema.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -import { schema, ObjectType, Type } from '@kbn/config-schema'; +import type { ObjectType, Type } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; import { get } from 'lodash'; import { set } from '@elastic/safer-lodash-set'; import type { AllowedSchemaTypes } from 'src/plugins/usage_collection/server'; @@ -38,6 +39,11 @@ function isOneOfCandidate( * @param value */ function valueSchemaToConfigSchema(value: TelemetrySchemaValue): Type { + // We need to check the pass_through type on top of everything + if ((value as { type: 'pass_through' }).type === 'pass_through') { + return schema.any(); + } + if ('properties' in value) { const { DYNAMIC_KEY, ...properties } = value.properties; const schemas: Array> = [objectSchemaToConfigSchema({ properties })]; @@ -48,8 +54,6 @@ function valueSchemaToConfigSchema(value: TelemetrySchemaValue): Type { } else { const valueType = value.type; // Copied in here because of TS reasons, it's not available in the `default` case switch (value.type) { - case 'pass_through': - return schema.any(); case 'boolean': return schema.boolean(); case 'keyword': @@ -77,9 +81,11 @@ function valueSchemaToConfigSchema(value: TelemetrySchemaValue): Type { } function objectSchemaToConfigSchema(objectSchema: TelemetrySchemaObject): ObjectType { + const objectEntries = Object.entries(objectSchema.properties); + return schema.object( Object.fromEntries( - Object.entries(objectSchema.properties).map(([key, value]) => { + objectEntries.map(([key, value]) => { try { return [key, schema.maybe(valueSchemaToConfigSchema(value))]; } catch (err) { diff --git a/test/functional/apps/context/_discover_navigation.js b/test/functional/apps/context/_discover_navigation.js index 7756a915139e2c..db276c4b050025 100644 --- a/test/functional/apps/context/_discover_navigation.js +++ b/test/functional/apps/context/_discover_navigation.js @@ -105,7 +105,8 @@ export default function ({ getService, getPageObjects }) { await PageObjects.discover.waitForDocTableLoadingComplete(); }); - it('navigates to doc view from embeddable', async () => { + // flaky https://github.com/elastic/kibana/issues/93670 + it.skip('navigates to doc view from embeddable', async () => { await PageObjects.common.navigateToApp('discover'); await PageObjects.discover.saveSearch('my search'); await PageObjects.header.waitUntilLoadingHasFinished(); diff --git a/test/functional/apps/visualize/_add_to_dashboard.ts b/test/functional/apps/visualize/_add_to_dashboard.ts index 1d1bd62988f459..17d628db86d253 100644 --- a/test/functional/apps/visualize/_add_to_dashboard.ts +++ b/test/functional/apps/visualize/_add_to_dashboard.ts @@ -26,7 +26,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ]); describe('Add to Dashboard', function describeIndexTests() { - it('adding a new metric to a new dashboard', async function () { + it('adding a new metric to a new dashboard by value', async function () { await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickMetric(); await PageObjects.visualize.clickNewSearch(); @@ -36,6 +36,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.timeToVisualize.saveFromModal('My New Vis 1', { addToDashboard: 'new', + saveToLibrary: false, }); await PageObjects.dashboard.waitForRenderComplete(); @@ -43,10 +44,39 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const panelCount = await PageObjects.dashboard.getPanelCount(); expect(panelCount).to.eql(1); + const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists('My New Vis 1'); + expect(isLinked).to.be(false); + + await PageObjects.timeToVisualize.resetNewDashboard(); + }); + + it('adding a new metric to a new dashboard by reference', async function () { + await PageObjects.visualize.navigateToNewAggBasedVisualization(); + await PageObjects.visualize.clickMetric(); + await PageObjects.visualize.clickNewSearch(); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + + await testSubjects.click('visualizeSaveButton'); + + await PageObjects.timeToVisualize.saveFromModal('My Saved New Vis 1', { + addToDashboard: 'new', + saveToLibrary: true, + }); + + await PageObjects.dashboard.waitForRenderComplete(); + await dashboardExpect.metricValuesExist(['14,004']); + const panelCount = await PageObjects.dashboard.getPanelCount(); + expect(panelCount).to.eql(1); + + const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists( + 'My Saved New Vis 1' + ); + expect(isLinked).to.be(true); + await PageObjects.timeToVisualize.resetNewDashboard(); }); - it('adding a existing metric to a new dashboard', async function () { + it('adding a existing metric to a new dashboard by value', async function () { await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickMetric(); await PageObjects.visualize.clickNewSearch(); @@ -57,6 +87,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Save this new viz to library await PageObjects.timeToVisualize.saveFromModal('My New Vis 1', { addToDashboard: null, + saveToLibrary: true, }); await testSubjects.click('visualizeSaveButton'); @@ -68,6 +99,46 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.timeToVisualize.saveFromModal('My New Vis 1 Copy', { addToDashboard: 'new', saveAsNew: true, + saveToLibrary: false, + }); + + await PageObjects.dashboard.waitForRenderComplete(); + await dashboardExpect.metricValuesExist(['14,004']); + const panelCount = await PageObjects.dashboard.getPanelCount(); + expect(panelCount).to.eql(1); + + const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists( + 'My New Vis 1 Copy' + ); + expect(isLinked).to.be(false); + + await PageObjects.timeToVisualize.resetNewDashboard(); + }); + + it('adding a existing metric to a new dashboard by reference', async function () { + await PageObjects.visualize.navigateToNewAggBasedVisualization(); + await PageObjects.visualize.clickMetric(); + await PageObjects.visualize.clickNewSearch(); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + + await testSubjects.click('visualizeSaveButton'); + + // Save this new viz to library + await PageObjects.timeToVisualize.saveFromModal('Another New Vis 1', { + addToDashboard: null, + saveToLibrary: true, + }); + + await testSubjects.click('visualizeSaveButton'); + + // All the options should be disabled + await PageObjects.timeToVisualize.ensureDashboardOptionsAreDisabled(); + + // Save a new copy of this viz to a new dashboard + await PageObjects.timeToVisualize.saveFromModal('Another New Vis 1 Copy', { + addToDashboard: 'new', + saveAsNew: true, + saveToLibrary: true, }); await PageObjects.dashboard.waitForRenderComplete(); @@ -75,10 +146,46 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const panelCount = await PageObjects.dashboard.getPanelCount(); expect(panelCount).to.eql(1); + const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists( + 'Another New Vis 1 Copy' + ); + expect(isLinked).to.be(true); + await PageObjects.timeToVisualize.resetNewDashboard(); }); - it('adding a new metric to an existing dashboard', async function () { + it('adding a new metric to an existing dashboard by value', async function () { + await PageObjects.common.navigateToApp('dashboard'); + + await PageObjects.dashboard.clickNewDashboard(); + await PageObjects.dashboard.addVisualizations(['Visualization AreaChart']); + await PageObjects.dashboard.saveDashboard('My Excellent Dashboard'); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await listingTable.searchAndExpectItemsCount('dashboard', 'My Excellent Dashboard', 1); + + await PageObjects.visualize.navigateToNewAggBasedVisualization(); + await PageObjects.visualize.clickMetric(); + await PageObjects.visualize.clickNewSearch(); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + + await testSubjects.click('visualizeSaveButton'); + + await PageObjects.timeToVisualize.saveFromModal('My New Vis 2', { + addToDashboard: 'existing', + dashboardId: 'My Excellent Dashboard', + saveToLibrary: false, + }); + + await PageObjects.dashboard.waitForRenderComplete(); + await dashboardExpect.metricValuesExist(['14,004']); + const panelCount = await PageObjects.dashboard.getPanelCount(); + expect(panelCount).to.eql(2); + + const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists('My New Vis 2'); + expect(isLinked).to.be(false); + }); + + it('adding a new metric to an existing dashboard by reference', async function () { await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.clickNewDashboard(); @@ -94,18 +201,24 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.click('visualizeSaveButton'); - await PageObjects.timeToVisualize.saveFromModal('My New Vis 2', { + await PageObjects.timeToVisualize.saveFromModal('My Saved New Vis 2', { addToDashboard: 'existing', dashboardId: 'My Wonderful Dashboard', + saveToLibrary: true, }); await PageObjects.dashboard.waitForRenderComplete(); await dashboardExpect.metricValuesExist(['14,004']); const panelCount = await PageObjects.dashboard.getPanelCount(); expect(panelCount).to.eql(2); + + const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists( + 'My Saved New Vis 2' + ); + expect(isLinked).to.be(true); }); - it('adding a existing metric to an existing dashboard', async function () { + it('adding a existing metric to an existing dashboard by value', async function () { await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.clickNewDashboard(); @@ -124,6 +237,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Save this new viz to library await PageObjects.timeToVisualize.saveFromModal('My New Vis 2', { addToDashboard: null, + saveToLibrary: true, }); await testSubjects.click('visualizeSaveButton'); @@ -136,12 +250,64 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { addToDashboard: 'existing', dashboardId: 'My Very Cool Dashboard', saveAsNew: true, + saveToLibrary: false, + }); + + await PageObjects.dashboard.waitForRenderComplete(); + await dashboardExpect.metricValuesExist(['14,004']); + const panelCount = await PageObjects.dashboard.getPanelCount(); + expect(panelCount).to.eql(2); + + const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists( + 'My New Vis 2 Copy' + ); + expect(isLinked).to.be(false); + }); + + it('adding a existing metric to an existing dashboard by reference', async function () { + await PageObjects.common.navigateToApp('dashboard'); + + await PageObjects.dashboard.clickNewDashboard(); + await PageObjects.dashboard.addVisualizations(['Visualization AreaChart']); + await PageObjects.dashboard.saveDashboard('My Very Neat Dashboard'); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await listingTable.searchAndExpectItemsCount('dashboard', 'My Very Neat Dashboard', 1); + + await PageObjects.visualize.navigateToNewAggBasedVisualization(); + await PageObjects.visualize.clickMetric(); + await PageObjects.visualize.clickNewSearch(); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + + await testSubjects.click('visualizeSaveButton'); + + // Save this new viz to library + await PageObjects.timeToVisualize.saveFromModal('Neat Saved Vis 2', { + addToDashboard: null, + saveToLibrary: true, + }); + + await testSubjects.click('visualizeSaveButton'); + + // All the options should be disabled + await PageObjects.timeToVisualize.ensureDashboardOptionsAreDisabled(); + + // Save a new copy of this viz to an existing dashboard + await PageObjects.timeToVisualize.saveFromModal('Neat Saved Vis 2 Copy', { + addToDashboard: 'existing', + dashboardId: 'My Very Neat Dashboard', + saveAsNew: true, + saveToLibrary: true, }); await PageObjects.dashboard.waitForRenderComplete(); await dashboardExpect.metricValuesExist(['14,004']); const panelCount = await PageObjects.dashboard.getPanelCount(); expect(panelCount).to.eql(2); + + const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists( + 'Neat Saved Vis 2 Copy' + ); + expect(isLinked).to.be(true); }); }); } diff --git a/test/functional/page_objects/time_to_visualize_page.ts b/test/functional/page_objects/time_to_visualize_page.ts index 560f73cbcdbd88..458b4dd3e60a13 100644 --- a/test/functional/page_objects/time_to_visualize_page.ts +++ b/test/functional/page_objects/time_to_visualize_page.ts @@ -10,6 +10,7 @@ import { FtrProviderContext } from '../ftr_provider_context'; interface SaveModalArgs { addToDashboard?: 'new' | 'existing' | null; + saveToLibrary?: boolean; dashboardId?: string; saveAsNew?: boolean; redirectToOrigin?: boolean; @@ -35,7 +36,9 @@ export function TimeToVisualizePageProvider({ getService, getPageObjects }: FtrP const dashboardSelector = await testSubjects.find('add-to-dashboard-options'); await dashboardSelector.findByCssSelector(`input[id="new-dashboard-option"]:disabled`); await dashboardSelector.findByCssSelector(`input[id="existing-dashboard-option"]:disabled`); - await dashboardSelector.findByCssSelector(`input[id="add-to-library-option"]:disabled`); + + const librarySelector = await testSubjects.find('add-to-library-checkbox'); + await librarySelector.findByCssSelector(`input[id="add-to-library-checkbox"]:disabled`); } public async resetNewDashboard() { @@ -46,7 +49,13 @@ export function TimeToVisualizePageProvider({ getService, getPageObjects }: FtrP public async setSaveModalValues( vizName: string, - { saveAsNew, redirectToOrigin, addToDashboard, dashboardId }: SaveModalArgs = {} + { + saveAsNew, + redirectToOrigin, + addToDashboard, + dashboardId, + saveToLibrary, + }: SaveModalArgs = {} ) { await testSubjects.setValue('savedObjectTitle', vizName); @@ -57,13 +66,6 @@ export function TimeToVisualizePageProvider({ getService, getPageObjects }: FtrP await testSubjects.setEuiSwitch('saveAsNewCheckbox', state); } - const hasRedirectToOrigin = await testSubjects.exists('returnToOriginModeSwitch'); - if (hasRedirectToOrigin && redirectToOrigin !== undefined) { - const state = redirectToOrigin ? 'check' : 'uncheck'; - log.debug('redirect to origin checkbox exists. Setting its state to', state); - await testSubjects.setEuiSwitch('returnToOriginModeSwitch', state); - } - const hasDashboardSelector = await testSubjects.exists('add-to-dashboard-options'); if (hasDashboardSelector && addToDashboard !== undefined) { let option: DashboardPickerOption = 'add-to-library-option'; @@ -80,6 +82,40 @@ export function TimeToVisualizePageProvider({ getService, getPageObjects }: FtrP await find.clickByButtonText(dashboardId); } } + + const hasSaveToLibrary = await testSubjects.exists('add-to-library-checkbox'); + if (hasSaveToLibrary && saveToLibrary !== undefined) { + const libraryCheckbox = await find.byCssSelector('#add-to-library-checkbox'); + const isChecked = await libraryCheckbox.isSelected(); + const needsClick = isChecked !== saveToLibrary; + const state = saveToLibrary ? 'check' : 'uncheck'; + + log.debug('save to library checkbox exists. Setting its state to', state); + if (needsClick) { + const selector = await testSubjects.find('add-to-library-checkbox'); + const label = await selector.findByCssSelector(`label[for="add-to-library-checkbox"]`); + await label.click(); + } + } + + const hasRedirectToOrigin = await testSubjects.exists('returnToOriginModeSwitch'); + if (hasRedirectToOrigin && redirectToOrigin !== undefined) { + const state = redirectToOrigin ? 'check' : 'uncheck'; + log.debug('redirect to origin checkbox exists. Setting its state to', state); + await testSubjects.setEuiSwitch('returnToOriginModeSwitch', state); + } + } + + public async libraryNotificationExists(panelTitle: string) { + log.debug('searching for library modal on panel:', panelTitle); + const panel = await testSubjects.find( + `embeddablePanelHeading-${panelTitle.replace(/ /g, '')}` + ); + const libraryActionExists = await testSubjects.descendantExists( + 'embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION', + panel + ); + return libraryActionExists; } public async saveFromModal( diff --git a/test/functional/services/field_editor.ts b/test/functional/services/field_editor.ts new file mode 100644 index 00000000000000..5cd1f2c4f6202c --- /dev/null +++ b/test/functional/services/field_editor.ts @@ -0,0 +1,44 @@ +/* + * 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 { FtrProviderContext } from '../ftr_provider_context'; + +export function FieldEditorProvider({ getService }: FtrProviderContext) { + const browser = getService('browser'); + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + + class FieldEditor { + public async setName(name: string) { + await testSubjects.setValue('nameField > input', name); + } + public async enableValue() { + await testSubjects.setEuiSwitch('valueRow > toggle', 'check'); + } + public async disableValue() { + await testSubjects.setEuiSwitch('valueRow > toggle', 'uncheck'); + } + public async typeScript(script: string) { + const editor = await (await testSubjects.find('valueRow')).findByClassName( + 'react-monaco-editor-container' + ); + const textarea = await editor.findByClassName('monaco-mouse-cursor-text'); + + await textarea.click(); + await browser.pressKeys(script); + } + public async save() { + await retry.try(async () => { + await testSubjects.click('fieldSaveButton'); + await testSubjects.missingOrFail('fieldSaveButton', { timeout: 2000 }); + }); + } + } + + return new FieldEditor(); +} diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts index 07d5ef950d21ea..0dd7f20debcbdf 100644 --- a/test/functional/services/index.ts +++ b/test/functional/services/index.ts @@ -31,6 +31,7 @@ import { FilterBarProvider } from './filter_bar'; import { FlyoutProvider } from './flyout'; import { GlobalNavProvider } from './global_nav'; import { InspectorProvider } from './inspector'; +import { FieldEditorProvider } from './field_editor'; import { ManagementMenuProvider } from './management'; import { QueryBarProvider } from './query_bar'; import { RemoteProvider } from './remote'; @@ -74,6 +75,7 @@ export const services = { browser: BrowserProvider, pieChart: PieChartProvider, inspector: InspectorProvider, + fieldEditor: FieldEditorProvider, vegaDebugInspector: VegaDebugInspectorViewProvider, appsMenu: AppsMenuProvider, globalNav: GlobalNavProvider, diff --git a/test/functional/services/query_bar.ts b/test/functional/services/query_bar.ts index 5718835c0b5e44..2c4cd3b8db131f 100644 --- a/test/functional/services/query_bar.ts +++ b/test/functional/services/query_bar.ts @@ -45,7 +45,8 @@ export function QueryBarProvider({ getService, getPageObjects }: FtrProviderCont public async clearQuery(): Promise { await this.setQuery(''); - await PageObjects.common.pressTabKey(); + await PageObjects.common.pressTabKey(); // move outside of input into language switcher + await PageObjects.common.pressTabKey(); // move outside of language switcher so time picker appears } public async submitQuery(): Promise { diff --git a/test/interpreter_functional/test_suites/run_pipeline/esaggs.ts b/test/interpreter_functional/test_suites/run_pipeline/esaggs.ts index 7f6e9a64391656..f71fa58cd7cc54 100644 --- a/test/interpreter_functional/test_suites/run_pipeline/esaggs.ts +++ b/test/interpreter_functional/test_suites/run_pipeline/esaggs.ts @@ -49,7 +49,7 @@ export default function ({ to: '2015-09-22T00:00:00Z', }; const expression = ` - kibana_context timeRange='${JSON.stringify(timeRange)}' + kibana_context timeRange={timerange from='${timeRange.from}' to='${timeRange.to}'} | esaggs index={indexPatternLoad id='logstash-*'} aggs={aggCount id="1" enabled=true schema="metric"} `; @@ -63,7 +63,7 @@ export default function ({ to: '2015-09-22T00:00:00Z', }; const expression = ` - kibana_context timeRange='${JSON.stringify(timeRange)}' + kibana_context timeRange={timerange from='${timeRange.from}' to='${timeRange.to}'} | esaggs index={indexPatternLoad id='logstash-*'} timeFields='relatedContent.article:published_time' aggs={aggCount id="1" enabled=true schema="metric"} @@ -78,7 +78,7 @@ export default function ({ to: '2015-09-22T00:00:00Z', }; const expression = ` - kibana_context timeRange='${JSON.stringify(timeRange)}' + kibana_context timeRange={timerange from='${timeRange.from}' to='${timeRange.to}'} | esaggs index={indexPatternLoad id='logstash-*'} timeFields='relatedContent.article:published_time' timeFields='@timestamp' diff --git a/x-pack/.telemetryrc.json b/x-pack/.telemetryrc.json index ae85efcda32d53..b0a8b45c02de9e 100644 --- a/x-pack/.telemetryrc.json +++ b/x-pack/.telemetryrc.json @@ -1,5 +1,14 @@ -{ - "output": "plugins/telemetry_collection_xpack/schema/xpack_plugins.json", - "root": "plugins/", - "exclude": [] -} +[ + { + "output": "plugins/telemetry_collection_xpack/schema/xpack_plugins.json", + "root": "plugins/", + "exclude": [ + "plugins/monitoring/server/telemetry_collection/" + ] + }, + { + "output": "plugins/telemetry_collection_xpack/schema/xpack_monitoring.json", + "root": "plugins/monitoring/server/telemetry_collection/", + "exclude": [] + } +] 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 64663f8330b249..676ce1d27d2fcc 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts @@ -571,6 +571,132 @@ describe('7.11.2', () => { } as SavedObjectUnsanitizedDoc; expect(isAnyActionSupportIncidents(doc)).toBe(false); }); + + test('it does not transforms alerts when the right structure connectors is already applied', () => { + const migration7112 = getMigrations(encryptedSavedObjectsSetup)['7.11.2']; + const alert = getMockData({ + actions: [ + { + actionTypeId: '.server-log', + group: 'threshold met', + params: { + level: 'info', + message: 'log message', + }, + id: '99257478-e591-4560-b264-441bdd4fe1d9', + }, + { + actionTypeId: '.servicenow', + group: 'threshold met', + params: { + subAction: 'pushToService', + subActionParams: { + incident: { + short_description: 'SN short desc', + description: 'SN desc', + severity: '2', + impact: '2', + urgency: '2', + }, + comments: [{ commentId: '1', comment: 'sn comment' }], + }, + }, + id: '1266562a-4e1f-4305-99ca-1b44c469b26e', + }, + ], + }); + + expect(migration7112(alert, migrationContext)).toEqual(alert); + }); + + test('if incident attribute is an empty object, copy back the related attributes from subActionParams back to incident', () => { + const migration7112 = getMigrations(encryptedSavedObjectsSetup)['7.11.2']; + const alert = getMockData({ + actions: [ + { + actionTypeId: '.server-log', + group: 'threshold met', + params: { + level: 'info', + message: 'log message', + }, + id: '99257478-e591-4560-b264-441bdd4fe1d9', + }, + { + actionTypeId: '.servicenow', + group: 'threshold met', + params: { + subAction: 'pushToService', + subActionParams: { + short_description: 'SN short desc', + description: 'SN desc', + severity: '2', + impact: '2', + urgency: '2', + incident: {}, + comments: [{ commentId: '1', comment: 'sn comment' }], + }, + }, + id: '1266562a-4e1f-4305-99ca-1b44c469b26e', + }, + ], + }); + + expect(migration7112(alert, migrationContext)).toEqual({ + ...alert, + attributes: { + ...alert.attributes, + actions: [ + alert.attributes.actions![0], + { + actionTypeId: '.servicenow', + group: 'threshold met', + params: { + subAction: 'pushToService', + subActionParams: { + incident: { + short_description: 'SN short desc', + description: 'SN desc', + severity: '2', + impact: '2', + urgency: '2', + }, + comments: [{ commentId: '1', comment: 'sn comment' }], + }, + }, + id: '1266562a-4e1f-4305-99ca-1b44c469b26e', + }, + ], + }, + }); + }); + + test('custom action does not get migrated/loss', () => { + const migration7112 = getMigrations(encryptedSavedObjectsSetup)['7.11.2']; + const alert = getMockData({ + actions: [ + { + actionTypeId: '.mike', + group: 'threshold met', + params: { + subAction: 'pushToService', + subActionParams: { + short_description: 'SN short desc', + description: 'SN desc', + severity: '2', + impact: '2', + urgency: '2', + incident: {}, + comments: [{ commentId: '1', comment: 'sn comment' }], + }, + }, + id: '1266562a-4e1f-4305-99ca-1b44c469b26e', + }, + ], + }); + + expect(migration7112(alert, migrationContext)).toEqual(alert); + }); }); function getUpdatedAt(): string { diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.ts index f2f956a0a2b266..729290498561f3 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations.ts @@ -10,6 +10,7 @@ import { SavedObjectUnsanitizedDoc, SavedObjectMigrationFn, SavedObjectMigrationContext, + SavedObjectAttributes, } from '../../../../../src/core/server'; import { RawAlert, RawAlertAction } from '../types'; import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server'; @@ -180,113 +181,147 @@ function initializeExecutionStatus( }; } +function isEmptyObject(obj: {}) { + for (const attr in obj) { + if (Object.prototype.hasOwnProperty.call(obj, attr)) { + return false; + } + } + return true; +} + function restructureConnectorsThatSupportIncident( doc: SavedObjectUnsanitizedDoc ): SavedObjectUnsanitizedDoc { const { actions } = doc.attributes; const newActions = actions.reduce((acc, action) => { - if (action.params.subAction !== 'pushToService') { - return [...acc, action]; - } - - if (action.actionTypeId === '.servicenow') { - const { title, comments, comment, description, severity, urgency, impact } = action.params - .subActionParams as { - title: string; - description?: string; - severity?: string; - urgency?: string; - impact?: string; - comment?: string; - comments?: Array<{ commentId: string; comment: string }>; - }; - return [ - ...acc, - { - ...action, - params: { - subAction: 'pushToService', - subActionParams: { - incident: { - short_description: title, - description, - severity, - urgency, - impact, + if ( + ['.servicenow', '.jira', '.resilient'].includes(action.actionTypeId) && + action.params.subAction === 'pushToService' + ) { + // Future developer, we needed to do that because when we created this migration + // we forget to think about user already using 7.11.0 and having an incident attribute build the right way + // IMPORTANT -> if you change this code please do the same inside of this file + // x-pack/plugins/alerting/server/saved_objects/migrations.ts + const subActionParamsIncident = + (action.params?.subActionParams as SavedObjectAttributes)?.incident ?? null; + if (subActionParamsIncident != null && !isEmptyObject(subActionParamsIncident)) { + return [...acc, action]; + } + if (action.actionTypeId === '.servicenow') { + const { + title, + comments, + comment, + description, + severity, + urgency, + impact, + short_description: shortDescription, + } = action.params.subActionParams as { + title: string; + description?: string; + severity?: string; + urgency?: string; + impact?: string; + comment?: string; + comments?: Array<{ commentId: string; comment: string }>; + short_description?: string; + }; + return [ + ...acc, + { + ...action, + params: { + subAction: 'pushToService', + subActionParams: { + incident: { + short_description: shortDescription ?? title, + description, + severity, + urgency, + impact, + }, + comments: [ + ...(comments ?? []), + ...(comment != null ? [{ commentId: '1', comment }] : []), + ], }, - comments: [ - ...(comments ?? []), - ...(comment != null ? [{ commentId: '1', comment }] : []), - ], }, }, - }, - ] as RawAlertAction[]; - } - - if (action.actionTypeId === '.jira') { - const { title, comments, description, issueType, priority, labels, parent } = action.params - .subActionParams as { - title: string; - description: string; - issueType: string; - priority?: string; - labels?: string[]; - parent?: string; - comments?: unknown[]; - }; - return [ - ...acc, - { - ...action, - params: { - subAction: 'pushToService', - subActionParams: { - incident: { - summary: title, - description, - issueType, - priority, - labels, - parent, + ] as RawAlertAction[]; + } else if (action.actionTypeId === '.jira') { + const { + title, + comments, + description, + issueType, + priority, + labels, + parent, + summary, + } = action.params.subActionParams as { + title: string; + description: string; + issueType: string; + priority?: string; + labels?: string[]; + parent?: string; + comments?: unknown[]; + summary?: string; + }; + return [ + ...acc, + { + ...action, + params: { + subAction: 'pushToService', + subActionParams: { + incident: { + summary: summary ?? title, + description, + issueType, + priority, + labels, + parent, + }, + comments, }, - comments, }, }, - }, - ] as RawAlertAction[]; - } - - if (action.actionTypeId === '.resilient') { - const { title, comments, description, incidentTypes, severityCode } = action.params - .subActionParams as { - title: string; - description: string; - incidentTypes?: number[]; - severityCode?: number; - comments?: unknown[]; - }; - return [ - ...acc, - { - ...action, - params: { - subAction: 'pushToService', - subActionParams: { - incident: { - name: title, - description, - incidentTypes, - severityCode, + ] as RawAlertAction[]; + } else if (action.actionTypeId === '.resilient') { + const { title, comments, description, incidentTypes, severityCode, name } = action.params + .subActionParams as { + title: string; + description: string; + incidentTypes?: number[]; + severityCode?: number; + comments?: unknown[]; + name?: string; + }; + return [ + ...acc, + { + ...action, + params: { + subAction: 'pushToService', + subActionParams: { + incident: { + name: name ?? title, + description, + incidentTypes, + severityCode, + }, + comments, }, - comments, }, }, - }, - ] as RawAlertAction[]; + ] as RawAlertAction[]; + } } - return acc; + return [...acc, action]; }, [] as RawAlertAction[]); return { diff --git a/x-pack/plugins/cases/server/connectors/case/index.test.ts b/x-pack/plugins/cases/server/connectors/case/index.test.ts index 122f6bd77c6936..fa2b10a0ccbdb7 100644 --- a/x-pack/plugins/cases/server/connectors/case/index.test.ts +++ b/x-pack/plugins/cases/server/connectors/case/index.test.ts @@ -171,6 +171,34 @@ describe('case connector', () => { }, }, }, + { + test: 'servicenow-sir', + params: { + subAction: 'create', + subActionParams: { + title: 'Case from case connector!!', + tags: ['case', 'connector'], + description: 'Yo fields!!', + connector: { + id: 'servicenow-sir', + name: 'Servicenow SIR', + type: '.servicenow-sir', + fields: { + destIp: true, + sourceIp: true, + malwareHash: true, + malwareUrl: true, + category: 'ddos', + subcategory: '15', + priority: '1', + }, + }, + settings: { + syncAlerts: true, + }, + }, + }, + }, { test: 'none', params: { @@ -474,7 +502,7 @@ describe('case connector', () => { }); }); - it('succeeds when servicenow fields are valid', () => { + it('succeeds when servicenow ITMSM fields are valid', () => { const params: Record = { subAction: 'update', subActionParams: { @@ -508,6 +536,42 @@ describe('case connector', () => { }); }); + it('succeeds when servicenow SIR fields are valid', () => { + const params: Record = { + subAction: 'update', + subActionParams: { + id: 'case-id', + version: '123', + connector: { + id: 'servicenow-sir', + name: 'Servicenow SIR', + type: '.servicenow-sir', + fields: { + destIp: true, + sourceIp: true, + malwareHash: true, + malwareUrl: true, + category: 'ddos', + subcategory: '15', + priority: '1', + }, + }, + }, + }; + + expect(validateParams(caseActionType, params)).toEqual({ + ...params, + subActionParams: { + description: null, + tags: null, + title: null, + status: null, + settings: null, + ...(params.subActionParams as Record), + }, + }); + }); + it('set fields to null if they are missing', () => { const params: Record = { subAction: 'update', diff --git a/x-pack/plugins/cases/server/connectors/case/schema.ts b/x-pack/plugins/cases/server/connectors/case/schema.ts index ac34ad40cfa13a..1637cec7520be3 100644 --- a/x-pack/plugins/cases/server/connectors/case/schema.ts +++ b/x-pack/plugins/cases/server/connectors/case/schema.ts @@ -56,7 +56,7 @@ const ResilientFieldsSchema = schema.object({ severityCode: schema.nullable(schema.string()), }); -const ServiceNowFieldsSchema = schema.object({ +const ServiceNowITSMFieldsSchema = schema.object({ impact: schema.nullable(schema.string()), severity: schema.nullable(schema.string()), urgency: schema.nullable(schema.string()), @@ -64,11 +64,22 @@ const ServiceNowFieldsSchema = schema.object({ subcategory: schema.nullable(schema.string()), }); +const ServiceNowSIRFieldsSchema = schema.object({ + destIp: schema.nullable(schema.boolean()), + sourceIp: schema.nullable(schema.boolean()), + malwareHash: schema.nullable(schema.boolean()), + malwareUrl: schema.nullable(schema.boolean()), + priority: schema.nullable(schema.string()), + category: schema.nullable(schema.string()), + subcategory: schema.nullable(schema.string()), +}); + const NoneFieldsSchema = schema.nullable(schema.object({})); const ReducedConnectorFieldsSchema: { [x: string]: any } = { '.jira': JiraFieldsSchema, '.resilient': ResilientFieldsSchema, + '.servicenow-sir': ServiceNowSIRFieldsSchema, }; export const ConnectorProps = { @@ -78,6 +89,7 @@ export const ConnectorProps = { schema.literal('.servicenow'), schema.literal('.jira'), schema.literal('.resilient'), + schema.literal('.servicenow-sir'), schema.literal('.none'), ]), // Chain of conditional schemes @@ -92,7 +104,7 @@ export const ConnectorProps = { schema.conditional( schema.siblingRef('type'), '.servicenow', - ServiceNowFieldsSchema, + ServiceNowITSMFieldsSchema, NoneFieldsSchema ) ), diff --git a/x-pack/plugins/data_enhanced/common/search/session/index.ts b/x-pack/plugins/data_enhanced/common/search/session/index.ts index 45b5c16bca9579..bc09a1e0351e35 100644 --- a/x-pack/plugins/data_enhanced/common/search/session/index.ts +++ b/x-pack/plugins/data_enhanced/common/search/session/index.ts @@ -5,7 +5,12 @@ * 2.0. */ -export * from './status'; -export * from './types'; - -export const SEARCH_SESSIONS_TABLE_ID = 'searchSessionsMgmtUiTable'; +// TODO https://github.com/elastic/kibana/issues/92802 +export { + SEARCH_SESSION_TYPE, + SearchSessionSavedObjectAttributes, + SearchSessionFindOptions, + SearchSessionRequestInfo, + SearchSessionStatus, + SEARCH_SESSIONS_TABLE_ID, +} from '../../../../../../src/plugins/data/common/'; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx index 1a2b2cfb4ececd..0f449087c59329 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx @@ -14,6 +14,7 @@ import { DeleteButton } from './delete_button'; import { ExtendButton } from './extend_button'; import { InspectButton } from './inspect_button'; import { ACTION, OnActionComplete } from './types'; +import { RenameButton } from './rename_button'; export const getAction = ( api: SearchSessionsMgmtAPI, @@ -53,6 +54,13 @@ export const getAction = ( ), }; + case ACTION.RENAME: + return { + iconType: 'pencil', + textColor: 'default', + label: , + }; + default: // eslint-disable-next-line no-console console.error(`Unknown action: ${actionType}`); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/rename_button.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/rename_button.tsx new file mode 100644 index 00000000000000..870de78ea06caf --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/rename_button.tsx @@ -0,0 +1,122 @@ +/* + * 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 { + EuiButton, + EuiButtonEmpty, + EuiFieldText, + EuiForm, + EuiFormRow, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { useState } from 'react'; +import { SearchSessionsMgmtAPI } from '../../lib/api'; +import { TableText } from '../'; +import { OnActionComplete } from './types'; + +interface RenameButtonProps { + id: string; + name: string; + api: SearchSessionsMgmtAPI; + onActionComplete: OnActionComplete; +} + +const RenameDialog = ({ onDismiss, ...props }: RenameButtonProps & { onDismiss: () => void }) => { + const { id, name: originalName, api, onActionComplete } = props; + const [isLoading, setIsLoading] = useState(false); + const [newName, setNewName] = useState(originalName); + + const title = i18n.translate('xpack.data.mgmt.searchSessions.renameModal.title', { + defaultMessage: 'Edit search session name', + }); + const confirm = i18n.translate('xpack.data.mgmt.searchSessions.renameModal.renameButton', { + defaultMessage: 'Save', + }); + const cancel = i18n.translate('xpack.data.mgmt.searchSessions.renameModal.cancelButton', { + defaultMessage: 'Cancel', + }); + + const label = i18n.translate( + 'xpack.data.mgmt.searchSessions.renameModal.searchSessionNameInputLabel', + { + defaultMessage: 'Search session name', + } + ); + + const isNewNameValid = newName && originalName !== newName; + + return ( + + + {title} + + + + + + setNewName(e.target.value)} + /> + + + + + + {cancel} + + { + if (!isNewNameValid) return; + setIsLoading(true); + await api.sendRename(id, newName); + setIsLoading(false); + onDismiss(); + onActionComplete(); + }} + fill + isLoading={isLoading} + > + {confirm} + + + + ); +}; + +export const RenameButton = (props: RenameButtonProps) => { + const [showRenameDialog, setShowRenameDialog] = useState(false); + + const onClick = () => { + setShowRenameDialog(true); + }; + + const onDismiss = () => { + setShowRenameDialog(false); + }; + + return ( + <> + + + + {showRenameDialog ? : null} + + ); +}; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts index c94b6aa8495c7f..407666adbfab45 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts @@ -11,4 +11,5 @@ export enum ACTION { INSPECT = 'inspect', EXTEND = 'extend', DELETE = 'delete', + RENAME = 'rename', } diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts index 10b2ac3ec1d4c1..f1b65e4f338bc5 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts @@ -68,6 +68,7 @@ describe('Search Sessions Management API', () => { Object { "actions": Array [ "inspect", + "rename", "extend", "delete", ], diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts index 838b51994aa715..6b311908d0702c 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts @@ -25,6 +25,7 @@ type UrlGeneratorsStart = SharePluginStart['urlGenerators']; function getActions(status: SearchSessionStatus) { const actions: ACTION[] = []; actions.push(ACTION.INSPECT); + actions.push(ACTION.RENAME); if (status === SearchSessionStatus.IN_PROGRESS || status === SearchSessionStatus.COMPLETE) { actions.push(ACTION.EXTEND); actions.push(ACTION.DELETE); @@ -202,4 +203,23 @@ export class SearchSessionsMgmtAPI { }); } } + + // Change the user-facing name of a search session + public async sendRename(id: string, newName: string): Promise { + try { + await this.sessionsClient.rename(id, newName); + + this.deps.notifications.toasts.addSuccess({ + title: i18n.translate('xpack.data.mgmt.searchSessions.api.rename', { + defaultMessage: 'The search session was renamed', + }), + }); + } catch (err) { + this.deps.notifications.toasts.addError(err, { + title: i18n.translate('xpack.data.mgmt.searchSessions.api.renameError', { + defaultMessage: 'Failed to rename the search session', + }), + }); + } + } } diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx index 7e2c9c063daa4b..43d4367f85940e 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx @@ -173,6 +173,11 @@ export const createConnectedSearchSessionIndicator = ({ } }, [state]); + const searchSessionName = useObservable(sessionService.searchSessionName$); + const saveSearchSessionNameFn = useCallback(async (newName: string) => { + await sessionService.renameCurrentSession(newName); + }, []); + if (!sessionService.isSessionStorageReady()) return null; return ( @@ -189,6 +194,8 @@ export const createConnectedSearchSessionIndicator = ({ onOpened={onOpened} onViewSearchSessions={onViewSearchSessions} viewSearchSessionsLink={searchSessionsManagementUrl} + searchSessionName={searchSessionName} + saveSearchSessionNameFn={saveSearchSessionNameFn} /> ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/content_section/content_section.scss b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/components/index.ts similarity index 84% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/content_section/content_section.scss rename to x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/components/index.ts index 4d7b53e1b000e9..9093e1a2535e2e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/content_section/content_section.scss +++ b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/components/index.ts @@ -5,6 +5,4 @@ * 2.0. */ -.content-section { - padding-bottom: 44px; -} +export * from './search_session_name'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.scss b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/components/search_session_name/index.ts similarity index 51% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.scss rename to x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/components/search_session_name/index.ts index 551d95c3f24b46..9093e1a2535e2e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.scss +++ b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/components/search_session_name/index.ts @@ -5,18 +5,4 @@ * 2.0. */ -.wrapped-icon { - width: 30px; - height: 30px; - overflow: hidden; - margin-right: 4px; - position: relative; - display: flex; - justify-content: center; - align-items: center; - - img { - max-width: 100%; - max-height: 100%; - } -} +export * from './search_session_name'; diff --git a/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/components/search_session_name/search_session_name.tsx b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/components/search_session_name/search_session_name.tsx new file mode 100644 index 00000000000000..a5088ab19ba385 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/components/search_session_name/search_session_name.tsx @@ -0,0 +1,100 @@ +/* + * 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 React, { useEffect } from 'react'; +import { EuiButtonEmpty, EuiButtonIcon, EuiFieldText, EuiFlexGroup, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; + +export interface SearchSessionNameProps { + name: string; + editName: (newName: string) => Promise; +} + +export const SearchSessionName: React.FC = ({ name, editName }) => { + const [isEditing, setIsEditing] = React.useState(false); + const [newName, setNewName] = React.useState(name); + + const [isSaving, setIsSaving] = React.useState(false); + const isNewNameValid = !!newName; + + useEffect(() => { + if (!isEditing) { + setNewName(name); + } + }, [isEditing, name]); + + return !isEditing ? ( + + +

{name}

+
+ setIsEditing(true)} + /> +
+ ) : ( + { + setNewName(e.target.value); + }} + aria-label={i18n.translate('xpack.data.searchSessionName.ariaLabelText', { + defaultMessage: 'Search session name', + })} + data-test-subj={'searchSessionNameInput'} + append={ + { + if (!isNewNameValid) return; + if (newName !== name && editName) { + setIsSaving(true); + try { + await editName(newName!); + } catch (e) { + // handled by the service + } + } + + setIsSaving(false); + setIsEditing(false); + }} + disabled={!isNewNameValid} + isLoading={isSaving} + data-test-subj={'searchSessionNameSave'} + > + + + } + /> + ); +}; diff --git a/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.stories.tsx b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.stories.tsx index 62d95c1043800d..01b6c89a0ddc73 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.stories.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.stories.tsx @@ -10,34 +10,58 @@ import { storiesOf } from '@storybook/react'; import { SearchSessionIndicator } from './search_session_indicator'; import { SearchSessionState } from '../../../../../../../src/plugins/data/public'; -storiesOf('components/SearchSessionIndicator', module).add('default', () => ( - <> -
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
- -)); +storiesOf('components/SearchSessionIndicator', module).add('default', () => { + const [searchSessionName, setSearchSessionName] = React.useState('Discover session'); + + const saveSearchSessionNameFn = (newName: string) => + new Promise((resolve) => { + setTimeout(() => { + setSearchSessionName(newName); + resolve(void 0); + }, 1000); + }); + + return ( + <> +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ + ); +}); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.tsx index c27a42d8d3d601..d20abd4544a477 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.tsx @@ -21,9 +21,10 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { PartialClock, CheckInEmptyCircle } from './custom_icons'; +import { CheckInEmptyCircle, PartialClock } from './custom_icons'; import './search_session_indicator.scss'; import { SearchSessionState } from '../../../../../../../src/plugins/data/public'; +import { SearchSessionName } from './components'; export interface SearchSessionIndicatorProps { state: SearchSessionState; @@ -38,6 +39,9 @@ export interface SearchSessionIndicatorProps { saveDisabledReasonText?: string; onOpened?: (openedState: SearchSessionState) => void; + + searchSessionName?: string; + saveSearchSessionNameFn?: (newName: string) => Promise; } type ActionButtonProps = SearchSessionIndicatorProps & { buttonProps: EuiButtonEmptyProps }; @@ -365,7 +369,16 @@ export const SearchSessionIndicator = React.forwardRef< } >
- + {props.searchSessionName && props.saveSearchSessionNameFn ? ( + <> + + + + ) : null} +

{popover.title}

diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts index 8d70f1c049b1f3..57af8cada9890b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts @@ -32,8 +32,43 @@ export const SUCCESS_MESSAGE = i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.curations.deleteSuccessMessage', { defaultMessage: 'Successfully removed curation.' } ); +export const RESTORE_CONFIRMATION = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.restoreConfirmation', + { + defaultMessage: + 'Are you sure you want to clear your changes and return to your default results?', + } +); export const RESULT_ACTIONS_DIRECTIONS = i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.curations.resultActionsDescription', { defaultMessage: 'Promote results by clicking the star, hide them by clicking the eye.' } ); +export const PROMOTE_DOCUMENT_ACTION = { + title: i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.promoteButtonLabel', { + defaultMessage: 'Promote this result', + }), + iconType: 'starPlusEmpty', + iconColor: 'primary', +}; +export const DEMOTE_DOCUMENT_ACTION = { + title: i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.demoteButtonLabel', { + defaultMessage: 'Demote this result', + }), + iconType: 'starMinusFilled', + iconColor: 'primary', +}; +export const HIDE_DOCUMENT_ACTION = { + title: i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.hideButtonLabel', { + defaultMessage: 'Hide this result', + }), + iconType: 'eyeClosed', + iconColor: 'danger', +}; +export const SHOW_DOCUMENT_ACTION = { + title: i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.showButtonLabel', { + defaultMessage: 'Show this result', + }), + iconType: 'eye', + iconColor: 'primary', +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.test.tsx index 748b5670e1d1d0..ad4ba100145d98 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.test.tsx @@ -12,7 +12,7 @@ import { setMockActions, setMockValues, rerender } from '../../../../__mocks__'; import React from 'react'; import { useParams } from 'react-router-dom'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { EuiPageHeader } from '@elastic/eui'; @@ -22,6 +22,8 @@ import { Loading } from '../../../../shared/loading'; jest.mock('./curation_logic', () => ({ CurationLogic: jest.fn() })); import { CurationLogic } from './curation_logic'; +import { AddResultFlyout } from './results'; + import { Curation } from './'; describe('Curation', () => { @@ -31,9 +33,11 @@ describe('Curation', () => { const values = { dataLoading: false, queries: ['query A', 'query B'], + isFlyoutOpen: false, }; const actions = { loadCuration: jest.fn(), + resetCuration: jest.fn(), }; beforeEach(() => { @@ -59,6 +63,13 @@ describe('Curation', () => { expect(wrapper.find(Loading)).toHaveLength(1); }); + it('renders the add result flyout when open', () => { + setMockValues({ ...values, isFlyoutOpen: true }); + const wrapper = shallow(); + + expect(wrapper.find(AddResultFlyout)).toHaveLength(1); + }); + it('initializes CurationLogic with a curationId prop from URL param', () => { (useParams as jest.Mock).mockReturnValueOnce({ curationId: 'hello-world' }); shallow(); @@ -75,4 +86,33 @@ describe('Curation', () => { rerender(wrapper); expect(actions.loadCuration).toHaveBeenCalledTimes(2); }); + + describe('restore defaults button', () => { + let restoreDefaultsButton: ShallowWrapper; + let confirmSpy: jest.SpyInstance; + + beforeAll(() => { + const wrapper = shallow(); + const headerActions = wrapper.find(EuiPageHeader).prop('rightSideItems'); + restoreDefaultsButton = shallow(headerActions![0] as React.ReactElement); + + confirmSpy = jest.spyOn(window, 'confirm'); + }); + + afterAll(() => { + confirmSpy.mockRestore(); + }); + + it('resets the curation upon user confirmation', () => { + confirmSpy.mockReturnValueOnce(true); + restoreDefaultsButton.simulate('click'); + expect(actions.resetCuration).toHaveBeenCalled(); + }); + + it('does not reset the curation if the user cancels', () => { + confirmSpy.mockReturnValueOnce(false); + restoreDefaultsButton.simulate('click'); + expect(actions.resetCuration).not.toHaveBeenCalled(); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx index 221c2419b7448e..82679a2baddf05 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx @@ -10,18 +10,20 @@ import { useParams } from 'react-router-dom'; import { useValues, useActions } from 'kea'; -import { EuiPageHeader, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiPageHeader, EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { FlashMessages } from '../../../../shared/flash_messages'; import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome'; import { BreadcrumbTrail } from '../../../../shared/kibana_chrome/generate_breadcrumbs'; import { Loading } from '../../../../shared/loading'; -import { MANAGE_CURATION_TITLE } from '../constants'; +import { MANAGE_CURATION_TITLE, RESTORE_CONFIRMATION } from '../constants'; import { CurationLogic } from './curation_logic'; -import { OrganicDocuments } from './documents'; +import { PromotedDocuments, OrganicDocuments, HiddenDocuments } from './documents'; import { ActiveQuerySelect, ManageQueriesModal } from './queries'; +import { AddResultLogic, AddResultFlyout } from './results'; interface Props { curationsBreadcrumb: BreadcrumbTrail; @@ -29,8 +31,9 @@ interface Props { export const Curation: React.FC = ({ curationsBreadcrumb }) => { const { curationId } = useParams() as { curationId: string }; - const { loadCuration } = useActions(CurationLogic({ curationId })); + const { loadCuration, resetCuration } = useActions(CurationLogic({ curationId })); const { dataLoading, queries } = useValues(CurationLogic({ curationId })); + const { isFlyoutOpen } = useValues(AddResultLogic); useEffect(() => { loadCuration(); @@ -43,7 +46,18 @@ export const Curation: React.FC = ({ curationsBreadcrumb }) => { { + if (window.confirm(RESTORE_CONFIRMATION)) resetCuration(); + }} + > + {i18n.translate('xpack.enterpriseSearch.appSearch.actions.restoreDefaults', { + defaultMessage: 'Restore defaults', + })} + , + ]} responsive={false} /> @@ -59,11 +73,13 @@ export const Curation: React.FC = ({ curationsBreadcrumb }) => { - {/* TODO: PromotedDocuments section */} + + - {/* TODO: HiddenDocuments section */} + + - {/* TODO: AddResult flyout */} + {isFlyoutOpen && } ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts index 821dd214780279..17f7cd7cd154e6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts @@ -51,6 +51,10 @@ describe('CurationLogic', () => { queriesLoading: false, activeQuery: '', organicDocumentsLoading: false, + promotedIds: [], + promotedDocumentsLoading: false, + hiddenIds: [], + hiddenDocumentsLoading: false, }; beforeEach(() => { @@ -64,7 +68,7 @@ describe('CurationLogic', () => { describe('actions', () => { describe('onCurationLoad', () => { - it('should set curation, queries, activeQuery, & all loading states to false', () => { + it('should set curation, queries, activeQuery, promotedIds, hiddenIds, & all loading states to false', () => { mount(); CurationLogic.actions.onCurationLoad(MOCK_CURATION_RESPONSE); @@ -74,9 +78,13 @@ describe('CurationLogic', () => { curation: MOCK_CURATION_RESPONSE, queries: ['some search'], activeQuery: 'some search', + promotedIds: ['some-promoted-document'], + hiddenIds: ['some-hidden-document'], dataLoading: false, queriesLoading: false, organicDocumentsLoading: false, + promotedDocumentsLoading: false, + hiddenDocumentsLoading: false, }); }); @@ -95,6 +103,8 @@ describe('CurationLogic', () => { dataLoading: true, queriesLoading: true, organicDocumentsLoading: true, + promotedDocumentsLoading: true, + hiddenDocumentsLoading: true, }); CurationLogic.actions.onCurationError(); @@ -104,6 +114,8 @@ describe('CurationLogic', () => { dataLoading: false, queriesLoading: false, organicDocumentsLoading: false, + promotedDocumentsLoading: false, + hiddenDocumentsLoading: false, }); }); }); @@ -136,6 +148,121 @@ describe('CurationLogic', () => { }); }); }); + + describe('setPromotedIds', () => { + it('should set promotedIds state & promotedDocumentsLoading to true', () => { + mount(); + + CurationLogic.actions.setPromotedIds(['hello', 'world']); + + expect(CurationLogic.values).toEqual({ + ...DEFAULT_VALUES, + promotedIds: ['hello', 'world'], + promotedDocumentsLoading: true, + }); + }); + }); + + describe('addPromotedId', () => { + it('should set promotedIds state & promotedDocumentsLoading to true', () => { + mount({ promotedIds: ['hello'] }); + + CurationLogic.actions.addPromotedId('world'); + + expect(CurationLogic.values).toEqual({ + ...DEFAULT_VALUES, + promotedIds: ['hello', 'world'], + promotedDocumentsLoading: true, + }); + }); + }); + + describe('removePromotedId', () => { + it('should set promotedIds state & promotedDocumentsLoading to true', () => { + mount({ promotedIds: ['hello', 'deleteme', 'world'] }); + + CurationLogic.actions.removePromotedId('deleteme'); + + expect(CurationLogic.values).toEqual({ + ...DEFAULT_VALUES, + promotedIds: ['hello', 'world'], + promotedDocumentsLoading: true, + }); + }); + }); + + describe('clearPromotedId', () => { + it('should reset promotedIds state & set promotedDocumentsLoading to true', () => { + mount({ promotedIds: ['hello', 'world'] }); + + CurationLogic.actions.clearPromotedIds(); + + expect(CurationLogic.values).toEqual({ + ...DEFAULT_VALUES, + promotedIds: [], + promotedDocumentsLoading: true, + }); + }); + }); + + describe('addHiddenId', () => { + it('should set hiddenIds state & hiddenDocumentsLoading to true', () => { + mount({ hiddenIds: ['hello'] }); + + CurationLogic.actions.addHiddenId('world'); + + expect(CurationLogic.values).toEqual({ + ...DEFAULT_VALUES, + hiddenIds: ['hello', 'world'], + hiddenDocumentsLoading: true, + }); + }); + }); + + describe('removeHiddenId', () => { + it('should set hiddenIds state & hiddenDocumentsLoading to true', () => { + mount({ hiddenIds: ['hello', 'deleteme', 'world'] }); + + CurationLogic.actions.removeHiddenId('deleteme'); + + expect(CurationLogic.values).toEqual({ + ...DEFAULT_VALUES, + hiddenIds: ['hello', 'world'], + hiddenDocumentsLoading: true, + }); + }); + }); + + describe('clearHiddenId', () => { + it('should reset hiddenIds state & set hiddenDocumentsLoading to true', () => { + mount({ hiddenIds: ['hello', 'world'] }); + + CurationLogic.actions.clearHiddenIds(); + + expect(CurationLogic.values).toEqual({ + ...DEFAULT_VALUES, + hiddenIds: [], + hiddenDocumentsLoading: true, + }); + }); + }); + + describe('resetCuration', () => { + it('should clear promotedIds & hiddenIds & set dataLoading to true', () => { + mount({ promotedIds: ['hello'], hiddenIds: ['world'] }); + + CurationLogic.actions.resetCuration(); + + expect(CurationLogic.values).toEqual({ + ...DEFAULT_VALUES, + dataLoading: true, + promotedIds: [], + promotedDocumentsLoading: true, + hiddenIds: [], + hiddenDocumentsLoading: true, + }); + }); + }); }); describe('listeners', () => { @@ -187,6 +314,8 @@ describe('CurationLogic', () => { { queries: ['a', 'b', 'c'], activeQuery: 'b', + promotedIds: ['d', 'e', 'f'], + hiddenIds: ['g'], }, { curationId: 'cur-123456789' } ); @@ -199,7 +328,7 @@ describe('CurationLogic', () => { expect(http.put).toHaveBeenCalledWith( '/api/app_search/engines/some-engine/curations/cur-123456789', { - body: '{"queries":["a","b","c"],"query":"b","promoted":[],"hidden":[]}', // Uses state currently in CurationLogic + body: '{"queries":["a","b","c"],"query":"b","promoted":["d","e","f"],"hidden":["g"]}', // Uses state currently in CurationLogic } ); expect(CurationLogic.actions.onCurationLoad).toHaveBeenCalledWith(MOCK_CURATION_RESPONSE); @@ -249,6 +378,34 @@ describe('CurationLogic', () => { it('setActiveQuery', () => { CurationLogic.actions.setActiveQuery('test'); }); + + it('setPromotedIds', () => { + CurationLogic.actions.setPromotedIds(['test']); + }); + + it('addPromotedId', () => { + CurationLogic.actions.addPromotedId('test'); + }); + + it('removePromotedId', () => { + CurationLogic.actions.removePromotedId('test'); + }); + + it('clearPromotedIds', () => { + CurationLogic.actions.clearPromotedIds(); + }); + + it('addHiddenId', () => { + CurationLogic.actions.addHiddenId('test'); + }); + + it('removeHiddenId', () => { + CurationLogic.actions.removeHiddenId('test'); + }); + + it('clearHiddenIds', () => { + CurationLogic.actions.clearHiddenIds(); + }); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts index c3ee1aac57ace1..9fa2d353b4ef21 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts @@ -14,6 +14,7 @@ import { ENGINE_CURATIONS_PATH } from '../../../routes'; import { EngineLogic, generateEnginePath } from '../../engine'; import { Curation } from '../types'; +import { addDocument, removeDocument } from '../utils'; interface CurationValues { dataLoading: boolean; @@ -22,6 +23,10 @@ interface CurationValues { queriesLoading: boolean; activeQuery: string; organicDocumentsLoading: boolean; + promotedIds: string[]; + promotedDocumentsLoading: boolean; + hiddenIds: string[]; + hiddenDocumentsLoading: boolean; } interface CurationActions { @@ -31,6 +36,14 @@ interface CurationActions { onCurationError(): void; updateQueries(queries: Curation['queries']): { queries: Curation['queries'] }; setActiveQuery(query: string): { query: string }; + setPromotedIds(promotedIds: string[]): { promotedIds: string[] }; + addPromotedId(id: string): { id: string }; + removePromotedId(id: string): { id: string }; + clearPromotedIds(): void; + addHiddenId(id: string): { id: string }; + removeHiddenId(id: string): { id: string }; + clearHiddenIds(): void; + resetCuration(): void; } interface CurationProps { @@ -46,12 +59,21 @@ export const CurationLogic = kea ({ queries }), setActiveQuery: (query) => ({ query }), + setPromotedIds: (promotedIds) => ({ promotedIds }), + addPromotedId: (id) => ({ id }), + removePromotedId: (id) => ({ id }), + clearPromotedIds: true, + addHiddenId: (id) => ({ id }), + removeHiddenId: (id) => ({ id }), + clearHiddenIds: true, + resetCuration: true, }), reducers: () => ({ dataLoading: [ true, { loadCuration: () => true, + resetCuration: () => true, onCurationLoad: () => false, onCurationError: () => false, }, @@ -99,6 +121,46 @@ export const CurationLogic = kea false, }, ], + promotedIds: [ + [], + { + onCurationLoad: (_, { curation }) => curation.promoted.map((document) => document.id), + setPromotedIds: (_, { promotedIds }) => promotedIds, + addPromotedId: (promotedIds, { id }) => addDocument(promotedIds, id), + removePromotedId: (promotedIds, { id }) => removeDocument(promotedIds, id), + clearPromotedIds: () => [], + }, + ], + promotedDocumentsLoading: [ + false, + { + setPromotedIds: () => true, + addPromotedId: () => true, + removePromotedId: () => true, + clearPromotedIds: () => true, + onCurationLoad: () => false, + onCurationError: () => false, + }, + ], + hiddenIds: [ + [], + { + onCurationLoad: (_, { curation }) => curation.hidden.map((document) => document.id), + addHiddenId: (hiddenIds, { id }) => addDocument(hiddenIds, id), + removeHiddenId: (hiddenIds, { id }) => removeDocument(hiddenIds, id), + clearHiddenIds: () => [], + }, + ], + hiddenDocumentsLoading: [ + false, + { + addHiddenId: () => true, + removeHiddenId: () => true, + clearHiddenIds: () => true, + onCurationLoad: () => false, + onCurationError: () => false, + }, + ], }), listeners: ({ actions, values, props }) => ({ loadCuration: async () => { @@ -131,8 +193,8 @@ export const CurationLogic = kea actions.updateCuration(), + setPromotedIds: () => actions.updateCuration(), + addPromotedId: () => actions.updateCuration(), + removePromotedId: () => actions.updateCuration(), + clearPromotedIds: () => actions.updateCuration(), + addHiddenId: () => actions.updateCuration(), + removeHiddenId: () => actions.updateCuration(), + clearHiddenIds: () => actions.updateCuration(), + resetCuration: () => { + actions.clearPromotedIds(); + actions.clearHiddenIds(); + }, }), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/hidden_documents.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/hidden_documents.test.tsx new file mode 100644 index 00000000000000..7ffa45c2853206 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/hidden_documents.test.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setMockValues, setMockActions } from '../../../../../__mocks__'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiEmptyPrompt, EuiButtonEmpty } from '@elastic/eui'; + +import { DataPanel } from '../../../data_panel'; +import { CurationResult } from '../results'; + +import { HiddenDocuments } from './'; + +describe('HiddenDocuments', () => { + const values = { + curation: { + hidden: [ + { id: 'mock-document-1' }, + { id: 'mock-document-2' }, + { id: 'mock-document-3' }, + { id: 'mock-document-4' }, + { id: 'mock-document-5' }, + ], + }, + hiddenDocumentsLoading: false, + }; + const actions = { + removeHiddenId: jest.fn(), + clearHiddenIds: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockValues(values); + setMockActions(actions); + }); + + it('renders a list of hidden documents', () => { + const wrapper = shallow(); + + expect(wrapper.find(CurationResult)).toHaveLength(5); + }); + + it('renders an empty state & hides the panel actions when empty', () => { + setMockValues({ ...values, curation: { hidden: [] } }); + const wrapper = shallow(); + + expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); + expect(wrapper.find(DataPanel).prop('action')).toBe(false); + }); + + it('renders a loading state', () => { + setMockValues({ ...values, hiddenDocumentsLoading: true }); + const wrapper = shallow(); + + expect(wrapper.find(DataPanel).prop('isLoading')).toEqual(true); + }); + + describe('actions', () => { + it('renders results with an action button that un-hides the result', () => { + const wrapper = shallow(); + const result = wrapper.find(CurationResult).last(); + result.prop('actions')[0].onClick(); + + expect(actions.removeHiddenId).toHaveBeenCalledWith('mock-document-5'); + }); + + it('renders a restore all button that un-hides all hidden results', () => { + const wrapper = shallow(); + const panelActions = shallow(wrapper.find(DataPanel).prop('action') as React.ReactElement); + + panelActions.find(EuiButtonEmpty).simulate('click'); + expect(actions.clearHiddenIds).toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/hidden_documents.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/hidden_documents.tsx new file mode 100644 index 00000000000000..f2bc416b00341d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/hidden_documents.tsx @@ -0,0 +1,99 @@ +/* + * 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 React from 'react'; + +import { useValues, useActions } from 'kea'; + +import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiEmptyPrompt } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { DataPanel } from '../../../data_panel'; + +import { SHOW_DOCUMENT_ACTION } from '../../constants'; +import { CurationLogic } from '../curation_logic'; +import { AddResultButton, CurationResult, convertToResultFormat } from '../results'; + +export const HiddenDocuments: React.FC = () => { + const { clearHiddenIds, removeHiddenId } = useActions(CurationLogic); + const { curation, hiddenDocumentsLoading } = useValues(CurationLogic); + + const documents = curation.hidden; + const hasDocuments = documents.length > 0; + + return ( + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.title', + { defaultMessage: 'Hidden documents' } + )} + + } + subtitle={i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.description', + { defaultMessage: 'Hidden documents will not appear in organic results.' } + )} + action={ + hasDocuments && ( + + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.removeAllButtonLabel', + { defaultMessage: 'Restore all' } + )} + + + + ) + } + isLoading={hiddenDocumentsLoading} + > + {hasDocuments ? ( + documents.map((document) => ( + removeHiddenId(document.id), + }, + ]} + /> + )) + ) : ( + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.emptyTitle', + { defaultMessage: 'No documents are being hidden for this query' } + )} + + } + body={i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.emptyDescription', + { + defaultMessage: + 'Hide documents by clicking the eye icon on the organic results above, or search and hide a result manually.', + } + )} + actions={} + /> + )} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/index.ts index fdaadeb5ced956..3548f6f2980695 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/index.ts @@ -5,4 +5,6 @@ * 2.0. */ +export { PromotedDocuments } from './promoted_documents'; export { OrganicDocuments } from './organic_documents'; +export { HiddenDocuments } from './hidden_documents'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.test.tsx index fd26cb1acf7a61..2a83ecfcada444 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { setMockValues } from '../../../../../__mocks__'; +import { setMockValues, setMockActions } from '../../../../../__mocks__'; import React from 'react'; @@ -31,10 +31,15 @@ describe('OrganicDocuments', () => { activeQuery: 'world', organicDocumentsLoading: false, }; + const actions = { + addPromotedId: jest.fn(), + addHiddenId: jest.fn(), + }; beforeEach(() => { jest.clearAllMocks(); setMockValues(values); + setMockActions(actions); }); it('renders a list of organic results', () => { @@ -64,4 +69,22 @@ describe('OrganicDocuments', () => { expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); }); + + describe('actions', () => { + it('renders results with an action button that promotes the result', () => { + const wrapper = shallow(); + const result = wrapper.find(CurationResult).first(); + result.prop('actions')[1].onClick(); + + expect(actions.addPromotedId).toHaveBeenCalledWith('mock-document-1'); + }); + + it('renders results with an action button that hides the result', () => { + const wrapper = shallow(); + const result = wrapper.find(CurationResult).last(); + result.prop('actions')[0].onClick(); + + expect(actions.addHiddenId).toHaveBeenCalledWith('mock-document-3'); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.tsx index 3aa65a14e7a2f4..a3a761feefcd2d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { useValues } from 'kea'; +import { useValues, useActions } from 'kea'; import { EuiLoadingContent, EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -15,11 +15,16 @@ import { i18n } from '@kbn/i18n'; import { DataPanel } from '../../../data_panel'; import { Result } from '../../../result/types'; -import { RESULT_ACTIONS_DIRECTIONS } from '../../constants'; +import { + RESULT_ACTIONS_DIRECTIONS, + PROMOTE_DOCUMENT_ACTION, + HIDE_DOCUMENT_ACTION, +} from '../../constants'; import { CurationLogic } from '../curation_logic'; import { CurationResult } from '../results'; export const OrganicDocuments: React.FC = () => { + const { addPromotedId, addHiddenId } = useActions(CurationLogic); const { curation, activeQuery, organicDocumentsLoading } = useValues(CurationLogic); const documents = curation.organic; @@ -48,7 +53,16 @@ export const OrganicDocuments: React.FC = () => { addHiddenId(document.id.raw), + }, + { + ...PROMOTE_DOCUMENT_ACTION, + onClick: () => addPromotedId(document.id.raw), + }, + ]} /> )) ) : organicDocumentsLoading ? ( diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.test.tsx new file mode 100644 index 00000000000000..7240a443b76e90 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.test.tsx @@ -0,0 +1,116 @@ +/* + * 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 { setMockValues, setMockActions } from '../../../../../__mocks__'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiDragDropContext, EuiDraggable, EuiEmptyPrompt, EuiButtonEmpty } from '@elastic/eui'; + +import { DataPanel } from '../../../data_panel'; +import { CurationResult } from '../results'; + +import { PromotedDocuments } from './'; + +describe('PromotedDocuments', () => { + const values = { + curation: { + promoted: [ + { id: 'mock-document-1' }, + { id: 'mock-document-2' }, + { id: 'mock-document-3' }, + { id: 'mock-document-4' }, + ], + }, + promotedIds: ['mock-document-1', 'mock-document-2', 'mock-document-3', 'mock-document-4'], + promotedDocumentsLoading: false, + }; + const actions = { + setPromotedIds: jest.fn(), + clearPromotedIds: jest.fn(), + removePromotedId: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockValues(values); + setMockActions(actions); + }); + + const getDraggableChildren = (draggableWrapper: any) => { + return draggableWrapper.renderProp('children')({}, {}, {}); + }; + + it('renders a list of draggable promoted documents', () => { + const wrapper = shallow(); + + expect(wrapper.find(EuiDraggable)).toHaveLength(4); + + wrapper.find(EuiDraggable).forEach((draggableWrapper) => { + expect(getDraggableChildren(draggableWrapper).find(CurationResult).exists()).toBe(true); + }); + }); + + it('renders an empty state & hides the panel actions when empty', () => { + setMockValues({ ...values, curation: { promoted: [] } }); + const wrapper = shallow(); + + expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); + expect(wrapper.find(DataPanel).prop('action')).toBe(false); + }); + + it('renders a loading state', () => { + setMockValues({ ...values, promotedDocumentsLoading: true }); + const wrapper = shallow(); + + expect(wrapper.find(DataPanel).prop('isLoading')).toEqual(true); + }); + + describe('actions', () => { + it('renders results with an action button that demotes the result', () => { + const wrapper = shallow(); + const result = getDraggableChildren(wrapper.find(EuiDraggable).last()); + result.prop('actions')[0].onClick(); + + expect(actions.removePromotedId).toHaveBeenCalledWith('mock-document-4'); + }); + + it('renders a demote all button that demotes all hidden results', () => { + const wrapper = shallow(); + const panelActions = shallow(wrapper.find(DataPanel).prop('action') as React.ReactElement); + + panelActions.find(EuiButtonEmpty).simulate('click'); + expect(actions.clearPromotedIds).toHaveBeenCalled(); + }); + + describe('draggging', () => { + it('calls setPromotedIds with the reordered list when users are done dragging', () => { + const wrapper = shallow(); + wrapper.find(EuiDragDropContext).simulate('dragEnd', { + source: { index: 3 }, + destination: { index: 0 }, + }); + + expect(actions.setPromotedIds).toHaveBeenCalledWith([ + 'mock-document-4', + 'mock-document-1', + 'mock-document-2', + 'mock-document-3', + ]); + }); + + it('does not error if source/destination are unavailable on drag end', () => { + const wrapper = shallow(); + wrapper.find(EuiDragDropContext).simulate('dragEnd', {}); + + expect(actions.setPromotedIds).not.toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.tsx new file mode 100644 index 00000000000000..6b0a02aa2af58c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.tsx @@ -0,0 +1,124 @@ +/* + * 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 React from 'react'; + +import { useValues, useActions } from 'kea'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiEmptyPrompt, + EuiButtonEmpty, + EuiDragDropContext, + DropResult, + EuiDroppable, + EuiDraggable, + euiDragDropReorder, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { DataPanel } from '../../../data_panel'; + +import { DEMOTE_DOCUMENT_ACTION } from '../../constants'; +import { CurationLogic } from '../curation_logic'; +import { AddResultButton, CurationResult, convertToResultFormat } from '../results'; + +export const PromotedDocuments: React.FC = () => { + const { curation, promotedIds, promotedDocumentsLoading } = useValues(CurationLogic); + const documents = curation.promoted; + const hasDocuments = documents.length > 0; + + const { setPromotedIds, clearPromotedIds, removePromotedId } = useActions(CurationLogic); + const reorderPromotedIds = ({ source, destination }: DropResult) => { + if (source && destination) { + const reorderedIds = euiDragDropReorder(promotedIds, source.index, destination.index); + setPromotedIds(reorderedIds); + } + }; + + return ( + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.title', + { defaultMessage: 'Promoted documents' } + )} + + } + subtitle={i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.description', + { + defaultMessage: + 'Promoted results appear before organic results. Documents can be re-ordered.', + } + )} + action={ + hasDocuments && ( + + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.removeAllButtonLabel', + { defaultMessage: 'Demote all' } + )} + + + + ) + } + isLoading={promotedDocumentsLoading} + > + {hasDocuments ? ( + + + {documents.map((document, i: number) => ( + + {(provided) => ( + removePromotedId(document.id), + }, + ]} + dragHandleProps={provided.dragHandleProps} + /> + )} + + ))} + + + ) : ( + } + /> + )} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.test.tsx new file mode 100644 index 00000000000000..19fc7e1784d3d5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.test.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setMockActions } from '../../../../../__mocks__'; + +import React from 'react'; + +import { shallow, ShallowWrapper } from 'enzyme'; + +import { EuiButton } from '@elastic/eui'; + +import { AddResultButton } from './'; + +describe('AddResultButton', () => { + const actions = { + openFlyout: jest.fn(), + }; + + let wrapper: ShallowWrapper; + + beforeAll(() => { + setMockActions(actions); + wrapper = shallow(); + }); + + it('renders', () => { + expect(wrapper.find(EuiButton)).toHaveLength(1); + }); + + it('opens the add result flyout on click', () => { + wrapper.find(EuiButton).simulate('click'); + expect(actions.openFlyout).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.tsx new file mode 100644 index 00000000000000..025dda65f4fb8b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useActions } from 'kea'; + +import { EuiButton } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { AddResultLogic } from './'; + +export const AddResultButton: React.FC = () => { + const { openFlyout } = useActions(AddResultLogic); + + return ( + + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.addResult.buttonLabel', { + defaultMessage: 'Add result manually', + })} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_flyout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_flyout.test.tsx new file mode 100644 index 00000000000000..e12267d0eb1367 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_flyout.test.tsx @@ -0,0 +1,145 @@ +/* + * 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 { setMockActions, setMockValues } from '../../../../../__mocks__'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiFlyout, EuiFieldSearch, EuiEmptyPrompt } from '@elastic/eui'; + +import { CurationResult, AddResultFlyout } from './'; + +describe('AddResultFlyout', () => { + const values = { + dataLoading: false, + searchQuery: '', + searchResults: [], + promotedIds: [], + hiddenIds: [], + }; + const actions = { + search: jest.fn(), + closeFlyout: jest.fn(), + addPromotedId: jest.fn(), + removePromotedId: jest.fn(), + addHiddenId: jest.fn(), + removeHiddenId: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockValues(values); + setMockActions(actions); + }); + + it('renders a closeable flyout', () => { + const wrapper = shallow(); + expect(wrapper.find(EuiFlyout)).toHaveLength(1); + + wrapper.find(EuiFlyout).simulate('close'); + expect(actions.closeFlyout).toHaveBeenCalled(); + }); + + describe('search input', () => { + it('renders isLoading state correctly', () => { + setMockValues({ ...values, dataLoading: true }); + const wrapper = shallow(); + + expect(wrapper.find(EuiFieldSearch).prop('isLoading')).toEqual(true); + }); + + it('renders value correctly', () => { + setMockValues({ ...values, searchQuery: 'hello world' }); + const wrapper = shallow(); + + expect(wrapper.find(EuiFieldSearch).prop('value')).toEqual('hello world'); + }); + + it('calls search on input change', () => { + const wrapper = shallow(); + wrapper.find(EuiFieldSearch).simulate('change', { target: { value: 'lorem ipsum' } }); + + expect(actions.search).toHaveBeenCalledWith('lorem ipsum'); + }); + }); + + describe('search results', () => { + it('renders an empty state', () => { + setMockValues({ ...values, searchResults: [] }); + const wrapper = shallow(); + + expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); + expect(wrapper.find(CurationResult)).toHaveLength(0); + }); + + it('renders a result component for each item in searchResults', () => { + setMockValues({ + ...values, + searchResults: [ + { id: { raw: 'doc-1' } }, + { id: { raw: 'doc-2' } }, + { id: { raw: 'doc-3' } }, + ], + }); + const wrapper = shallow(); + + expect(wrapper.find(CurationResult)).toHaveLength(3); + }); + + describe('actions', () => { + it('renders a hide result button if the document ID is not already in the hiddenIds list', () => { + setMockValues({ + ...values, + searchResults: [{ id: { raw: 'visible-document' } }], + hiddenIds: ['hidden-document'], + }); + const wrapper = shallow(); + wrapper.find(CurationResult).prop('actions')[0].onClick(); + + expect(actions.addHiddenId).toHaveBeenCalledWith('visible-document'); + }); + + it('renders a show result button if the document ID is already in the hiddenIds list', () => { + setMockValues({ + ...values, + searchResults: [{ id: { raw: 'hidden-document' } }], + hiddenIds: ['hidden-document'], + }); + const wrapper = shallow(); + wrapper.find(CurationResult).prop('actions')[0].onClick(); + + expect(actions.removeHiddenId).toHaveBeenCalledWith('hidden-document'); + }); + + it('renders a promote result button if the document ID is not already in the promotedIds list', () => { + setMockValues({ + ...values, + searchResults: [{ id: { raw: 'some-document' } }], + promotedIds: ['promoted-document'], + }); + const wrapper = shallow(); + wrapper.find(CurationResult).prop('actions')[1].onClick(); + + expect(actions.addPromotedId).toHaveBeenCalledWith('some-document'); + }); + + it('renders a demote result button if the document ID is already in the promotedIds list', () => { + setMockValues({ + ...values, + searchResults: [{ id: { raw: 'promoted-document' } }], + promotedIds: ['promoted-document'], + }); + const wrapper = shallow(); + wrapper.find(CurationResult).prop('actions')[1].onClick(); + + expect(actions.removePromotedId).toHaveBeenCalledWith('promoted-document'); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_flyout.tsx new file mode 100644 index 00000000000000..6363919e32cc95 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_flyout.tsx @@ -0,0 +1,121 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useValues, useActions } from 'kea'; + +import { + EuiPortal, + EuiFlyout, + EuiFlyoutHeader, + EuiFlyoutBody, + EuiTitle, + EuiText, + EuiSpacer, + EuiFieldSearch, + EuiEmptyPrompt, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { FlashMessages } from '../../../../../shared/flash_messages'; + +import { + RESULT_ACTIONS_DIRECTIONS, + PROMOTE_DOCUMENT_ACTION, + DEMOTE_DOCUMENT_ACTION, + HIDE_DOCUMENT_ACTION, + SHOW_DOCUMENT_ACTION, +} from '../../constants'; +import { CurationLogic } from '../curation_logic'; + +import { AddResultLogic, CurationResult } from './'; + +export const AddResultFlyout: React.FC = () => { + const { searchQuery, searchResults, dataLoading } = useValues(AddResultLogic); + const { search, closeFlyout } = useActions(AddResultLogic); + + const { promotedIds, hiddenIds } = useValues(CurationLogic); + const { addPromotedId, removePromotedId, addHiddenId, removeHiddenId } = useActions( + CurationLogic + ); + + return ( + + + + +

+ {i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.addResult.title', { + defaultMessage: 'Add result to curation', + })} +

+
+ +

{RESULT_ACTIONS_DIRECTIONS}

+
+
+ }> + search(e.target.value)} + isLoading={dataLoading} + placeholder={i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.addResult.searchPlaceholder', + { defaultMessage: 'Search engine documents' } + )} + fullWidth + autoFocus + /> + + + {searchResults.length > 0 ? ( + searchResults.map((result) => { + const id = result.id.raw; + const isPromoted = promotedIds.includes(id); + const isHidden = hiddenIds.includes(id); + + return ( + removeHiddenId(id), + } + : { + ...HIDE_DOCUMENT_ACTION, + onClick: () => addHiddenId(id), + }, + isPromoted + ? { + ...DEMOTE_DOCUMENT_ACTION, + onClick: () => removePromotedId(id), + } + : { + ...PROMOTE_DOCUMENT_ACTION, + onClick: () => addPromotedId(id), + }, + ]} + /> + ); + }) + ) : ( + + )} + +
+
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_logic.test.ts new file mode 100644 index 00000000000000..a722ab96fc5741 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_logic.test.ts @@ -0,0 +1,134 @@ +/* + * 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 { LogicMounter, mockHttpValues, mockFlashMessageHelpers } from '../../../../../__mocks__'; +import '../../../../__mocks__/engine_logic.mock'; + +import { nextTick } from '@kbn/test/jest'; + +import { AddResultLogic } from './'; + +describe('AddResultLogic', () => { + const { mount } = new LogicMounter(AddResultLogic); + const { http } = mockHttpValues; + const { flashAPIErrors } = mockFlashMessageHelpers; + + const MOCK_SEARCH_RESPONSE = { + results: [ + { id: { raw: 'document-1' }, _meta: { id: 'document-1', engine: 'some-engine' } }, + { id: { raw: 'document-2' }, _meta: { id: 'document-2', engine: 'some-engine' } }, + { id: { raw: 'document-3' }, _meta: { id: 'document-3', engine: 'some-engine' } }, + ], + }; + + const DEFAULT_VALUES = { + isFlyoutOpen: false, + dataLoading: false, + searchQuery: '', + searchResults: [], + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('has expected default values', () => { + mount(); + expect(AddResultLogic.values).toEqual(DEFAULT_VALUES); + }); + + describe('actions', () => { + describe('openFlyout', () => { + it('sets isFlyoutOpen to true and resets the searchQuery term', () => { + mount({ isFlyoutOpen: false, searchQuery: 'a previous search' }); + + AddResultLogic.actions.openFlyout(); + + expect(AddResultLogic.values).toEqual({ + ...DEFAULT_VALUES, + isFlyoutOpen: true, + searchQuery: '', + }); + }); + }); + + describe('closeFlyout', () => { + it('sets isFlyoutOpen to false', () => { + mount({ isFlyoutOpen: true }); + + AddResultLogic.actions.closeFlyout(); + + expect(AddResultLogic.values).toEqual({ + ...DEFAULT_VALUES, + isFlyoutOpen: false, + }); + }); + }); + + describe('search', () => { + it('sets searchQuery & dataLoading to true', () => { + mount({ searchQuery: '', dataLoading: false }); + + AddResultLogic.actions.search('hello world'); + + expect(AddResultLogic.values).toEqual({ + ...DEFAULT_VALUES, + searchQuery: 'hello world', + dataLoading: true, + }); + }); + }); + + describe('onSearch', () => { + it('sets searchResults & dataLoading to false', () => { + mount({ searchResults: [], dataLoading: true }); + + AddResultLogic.actions.onSearch(MOCK_SEARCH_RESPONSE); + + expect(AddResultLogic.values).toEqual({ + ...DEFAULT_VALUES, + searchResults: MOCK_SEARCH_RESPONSE.results, + dataLoading: false, + }); + }); + }); + }); + + describe('listeners', () => { + describe('search', () => { + beforeAll(() => jest.useFakeTimers()); + afterAll(() => jest.useRealTimers()); + + it('should make a GET API call with a search query', async () => { + http.get.mockReturnValueOnce(Promise.resolve(MOCK_SEARCH_RESPONSE)); + mount(); + jest.spyOn(AddResultLogic.actions, 'onSearch'); + + AddResultLogic.actions.search('hello world'); + jest.runAllTimers(); + await nextTick(); + + expect(http.get).toHaveBeenCalledWith( + '/api/app_search/engines/some-engine/curation_search', + { query: { query: 'hello world' } } + ); + expect(AddResultLogic.actions.onSearch).toHaveBeenCalledWith(MOCK_SEARCH_RESPONSE); + }); + + it('handles errors', async () => { + http.get.mockReturnValueOnce(Promise.reject('error')); + mount(); + + AddResultLogic.actions.search('test'); + jest.runAllTimers(); + await nextTick(); + + expect(flashAPIErrors).toHaveBeenCalledWith('error'); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_logic.ts new file mode 100644 index 00000000000000..808f4c86971eec --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_logic.ts @@ -0,0 +1,84 @@ +/* + * 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 { kea, MakeLogicType } from 'kea'; + +import { flashAPIErrors } from '../../../../../shared/flash_messages'; +import { HttpLogic } from '../../../../../shared/http'; + +import { EngineLogic } from '../../../engine'; +import { Result } from '../../../result/types'; + +interface AddResultValues { + isFlyoutOpen: boolean; + dataLoading: boolean; + searchQuery: string; + searchResults: Result[]; +} + +interface AddResultActions { + openFlyout(): void; + closeFlyout(): void; + search(query: string): { query: string }; + onSearch({ results }: { results: Result[] }): { results: Result[] }; +} + +export const AddResultLogic = kea>({ + path: ['enterprise_search', 'app_search', 'curation_add_result_logic'], + actions: () => ({ + openFlyout: true, + closeFlyout: true, + search: (query) => ({ query }), + onSearch: ({ results }) => ({ results }), + }), + reducers: () => ({ + isFlyoutOpen: [ + false, + { + openFlyout: () => true, + closeFlyout: () => false, + }, + ], + dataLoading: [ + false, + { + search: () => true, + onSearch: () => false, + }, + ], + searchQuery: [ + '', + { + search: (_, { query }) => query, + openFlyout: () => '', + }, + ], + searchResults: [ + [], + { + onSearch: (_, { results }) => results, + }, + ], + }), + listeners: ({ actions }) => ({ + search: async ({ query }, breakpoint) => { + await breakpoint(250); + + const { http } = HttpLogic.values; + const { engineName } = EngineLogic.values; + + try { + const response = await http.get(`/api/app_search/engines/${engineName}/curation_search`, { + query: { query }, + }); + actions.onSearch(response); + } catch (e) { + flashAPIErrors(e); + } + }, + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/index.ts index bbdb87bbe4fa9b..8de177ba587ce5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/index.ts @@ -5,4 +5,8 @@ * 2.0. */ +export { AddResultLogic } from './add_result_logic'; +export { AddResultFlyout } from './add_result_flyout'; +export { AddResultButton } from './add_result_button'; export { CurationResult } from './curation_result'; +export { convertToResultFormat } from './utils'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.test.ts new file mode 100644 index 00000000000000..7bc05f34511a00 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.test.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 { convertToResultFormat, convertIdToMeta } from './utils'; + +describe('convertToResultFormat', () => { + it('converts curation results to a format that the Result component can use', () => { + expect( + convertToResultFormat({ + id: 'some-id', + someField: 'some flat string', + anotherField: '123456', + }) + ).toEqual({ + _meta: { + id: 'some-id', + }, + id: { + raw: 'some-id', + snippet: null, + }, + someField: { + raw: 'some flat string', + snippet: null, + }, + anotherField: { + raw: '123456', + snippet: null, + }, + }); + }); +}); + +describe('convertIdToMeta', () => { + it('creates an approximate _meta object based on the curation result ID', () => { + expect(convertIdToMeta('some-id')).toEqual({ id: 'some-id' }); + expect(convertIdToMeta('some-engine|some-id')).toEqual({ + id: 'some-id', + engine: 'some-engine', + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.ts new file mode 100644 index 00000000000000..b5a5bf1b5a90a5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.ts @@ -0,0 +1,52 @@ +/* + * 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 { Result } from '../../../result/types'; +import { CurationResult } from '../../types'; + +/** + * The `promoted` and `hidden` keys from the internal curations endpoints + * currently return a document data structure that our Result component can't + * correctly parse - we need to attempt to naively transform the data in order + * to display it in a Result + * + * TODO: Ideally someday we can update our internal curations endpoint to return + * the same Result-ready data structure that the `organic` endpoint uses, and + * remove this file when that happens + */ + +export const convertToResultFormat = (document: CurationResult): Result => { + const result = {} as Result; + + // Convert `key: 'value'` into `key: { raw: 'value' }` + Object.entries(document).forEach(([key, value]) => { + result[key] = { + raw: value, + snippet: null, // Don't try to provide a snippet, we can't really guesstimate it + }; + }); + + // Add the _meta obj needed by Result + result._meta = convertIdToMeta(document.id); + + return result; +}; + +export const convertIdToMeta = (id: string): Result['_meta'] => { + const splitId = id.split('|'); + const isMetaEngine = splitId.length > 1; + + return isMetaEngine + ? { + engine: splitId[0], + id: splitId[1], + } + : ({ id } as Result['_meta']); + // Note: We're casting this as _meta even though `engine` is missing, + // since for source engines the engine shouldn't matter / be displayed, + // but if needed we could likely populate this from EngineLogic.values +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/utils.test.ts index 435b76458db069..51618ed4e37419 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/utils.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/utils.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { convertToDate } from './utils'; +import { convertToDate, addDocument, removeDocument } from './utils'; describe('convertToDate', () => { it('converts the English-only server timestamps to a parseable Date', () => { @@ -16,3 +16,23 @@ describe('convertToDate', () => { expect(date.getFullYear()).toEqual(1970); }); }); + +describe('addDocument', () => { + it('adds a new document to the end of the document array without mutating the original array', () => { + const originalDocuments = ['hello']; + const newDocuments = addDocument(originalDocuments, 'world'); + + expect(newDocuments).toEqual(['hello', 'world']); + expect(newDocuments).not.toBe(originalDocuments); // Would fail if we had mutated the array + }); +}); + +describe('removeDocument', () => { + it('removes a specific document from the array without mutating the original array', () => { + const originalDocuments = ['lorem', 'ipsum', 'dolor', 'sit', 'amet']; + const newDocuments = removeDocument(originalDocuments, 'dolor'); + + expect(newDocuments).toEqual(['lorem', 'ipsum', 'sit', 'amet']); + expect(newDocuments).not.toBe(originalDocuments); // Would fail if we had mutated the array + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/utils.ts index 2ef73e1de4e91b..8af2636128304b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/utils.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/utils.ts @@ -14,3 +14,14 @@ export const convertToDate = (serverDateString: string): Date => { .replace('AM', ' AM'); return new Date(readableDateString); }; + +export const addDocument = (documentArray: string[], newDocument: string) => { + return [...documentArray, newDocument]; +}; + +export const removeDocument = (documentArray: string[], deletedDocument: string) => { + const newArray = [...documentArray]; + const indexToDelete = newArray.indexOf(deletedDocument); + newArray.splice(indexToDelete, 1); + return newArray; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/constants.ts index 433f23db756267..d5e7035348b452 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/constants.ts @@ -7,7 +7,21 @@ import { i18n } from '@kbn/i18n'; +import { FieldResultSetting } from './types'; + export const RESULT_SETTINGS_TITLE = i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.resultSettings.title', { defaultMessage: 'Result Settings' } ); + +export const DEFAULT_FIELD_SETTINGS: FieldResultSetting = { + raw: true, + snippet: false, + snippetFallback: false, +}; + +export const DISABLED_FIELD_SETTINGS: FieldResultSetting = { + raw: false, + snippet: false, + snippetFallback: false, +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/index.ts index b605aa5714e91a..3336de732a5080 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/index.ts @@ -6,4 +6,5 @@ */ export { RESULT_SETTINGS_TITLE } from './constants'; +export { ResultSettingsLogic } from './result_settings_logic'; export { ResultSettings } from './result_settings'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts new file mode 100644 index 00000000000000..91479403746459 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts @@ -0,0 +1,398 @@ +/* + * 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 { LogicMounter } from '../../../__mocks__'; + +import { Schema, SchemaConflicts, SchemaTypes } from '../../../shared/types'; + +import { OpenModal, ServerFieldResultSettingObject } from './types'; + +import { ResultSettingsLogic } from '.'; + +describe('ResultSettingsLogic', () => { + const { mount } = new LogicMounter(ResultSettingsLogic); + + const DEFAULT_VALUES = { + dataLoading: true, + saving: false, + openModal: OpenModal.None, + nonTextResultFields: {}, + resultFields: {}, + serverResultFields: {}, + textResultFields: {}, + lastSavedResultFields: {}, + schema: {}, + schemaConflicts: {}, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('has expected default values', () => { + mount(); + expect(ResultSettingsLogic.values).toEqual(DEFAULT_VALUES); + }); + + describe('actions', () => { + describe('initializeResultFields', () => { + const serverResultFields: ServerFieldResultSettingObject = { + foo: { raw: { size: 5 } }, + bar: { raw: { size: 5 } }, + }; + const schema: Schema = { + foo: 'text' as SchemaTypes, + bar: 'number' as SchemaTypes, + baz: 'text' as SchemaTypes, + }; + const schemaConflicts: SchemaConflicts = { + foo: { + text: ['foo'], + number: ['foo'], + geolocation: [], + date: [], + }, + }; + + it('will initialize all result field state within the UI, based on provided server data', () => { + mount({ + dataLoading: true, + saving: true, + openModal: OpenModal.ConfirmSaveModal, + }); + + ResultSettingsLogic.actions.initializeResultFields( + serverResultFields, + schema, + schemaConflicts + ); + + expect(ResultSettingsLogic.values).toEqual({ + ...DEFAULT_VALUES, + dataLoading: false, + saving: false, + // It converts the passed server result fields to a client results field and stores it + // as resultFields + resultFields: { + foo: { + raw: true, + rawSize: 5, + snippet: false, + snippetFallback: false, + }, + // Baz was not part of the original serverResultFields, it was injected based on the schema + baz: { + raw: false, + snippet: false, + snippetFallback: false, + }, + bar: { + raw: true, + rawSize: 5, + snippet: false, + snippetFallback: false, + }, + }, + // It also saves it as lastSavedResultFields + lastSavedResultFields: { + foo: { + raw: true, + rawSize: 5, + snippet: false, + snippetFallback: false, + }, + // Baz was not part of the original serverResultFields, it was injected based on the schema + baz: { + raw: false, + snippet: false, + snippetFallback: false, + }, + bar: { + raw: true, + rawSize: 5, + snippet: false, + snippetFallback: false, + }, + }, + // The resultFields are also partitioned to either nonTextResultFields or textResultFields + // depending on their type within the passed schema + nonTextResultFields: { + bar: { + raw: true, + rawSize: 5, + snippet: false, + snippetFallback: false, + }, + }, + textResultFields: { + // Baz was not part of the original serverResultFields, it was injected based on the schema + baz: { + raw: false, + snippet: false, + snippetFallback: false, + }, + foo: { + raw: true, + rawSize: 5, + snippet: false, + snippetFallback: false, + }, + }, + // It stores the originally passed results as serverResultFields + serverResultFields: { + foo: { raw: { size: 5 } }, + // Baz was not part of the original serverResultFields, it was injected based on the schema + baz: {}, + bar: { raw: { size: 5 } }, + }, + // The modal should be reset back to closed if it had been opened previously + openModal: OpenModal.None, + // Stores the provided schema details + schema, + schemaConflicts, + }); + }); + + it('default schema conflicts data if none was provided', () => { + mount(); + + ResultSettingsLogic.actions.initializeResultFields(serverResultFields, schema); + + expect(ResultSettingsLogic.values.schemaConflicts).toEqual({}); + }); + }); + + describe('openConfirmSaveModal', () => { + mount({ + openModal: OpenModal.None, + }); + + ResultSettingsLogic.actions.openConfirmSaveModal(); + + expect(ResultSettingsLogic.values).toEqual({ + ...DEFAULT_VALUES, + openModal: OpenModal.ConfirmSaveModal, + }); + }); + + describe('openConfirmResetModal', () => { + mount({ + openModal: OpenModal.None, + }); + + ResultSettingsLogic.actions.openConfirmResetModal(); + + expect(ResultSettingsLogic.values).toEqual({ + ...DEFAULT_VALUES, + openModal: OpenModal.ConfirmResetModal, + }); + }); + + describe('closeModals', () => { + it('should close open modals', () => { + mount({ + openModal: OpenModal.ConfirmSaveModal, + }); + + ResultSettingsLogic.actions.closeModals(); + + expect(ResultSettingsLogic.values).toEqual({ + ...DEFAULT_VALUES, + openModal: OpenModal.None, + }); + }); + }); + + describe('clearAllFields', () => { + it('should remove all settings that have been set for each field', () => { + mount({ + nonTextResultFields: { + foo: { raw: false, snippet: false, snippetFallback: false }, + bar: { raw: true, snippet: false, snippetFallback: true }, + }, + textResultFields: { + qux: { raw: false, snippet: false, snippetFallback: false }, + quux: { raw: true, snippet: false, snippetFallback: true }, + }, + resultFields: { + quuz: { raw: false, snippet: false, snippetFallback: false }, + corge: { raw: true, snippet: false, snippetFallback: true }, + }, + serverResultFields: { + grault: { raw: { size: 5 } }, + garply: { raw: true }, + }, + }); + + ResultSettingsLogic.actions.clearAllFields(); + + expect(ResultSettingsLogic.values).toEqual({ + ...DEFAULT_VALUES, + nonTextResultFields: { + foo: {}, + bar: {}, + }, + textResultFields: { + qux: {}, + quux: {}, + }, + resultFields: { + quuz: {}, + corge: {}, + }, + serverResultFields: { + grault: {}, + garply: {}, + }, + }); + }); + }); + + describe('resetAllFields', () => { + it('should reset all settings to their default values per field', () => { + mount({ + nonTextResultFields: { + foo: { raw: true, snippet: true, snippetFallback: true }, + bar: { raw: true, snippet: true, snippetFallback: true }, + }, + textResultFields: { + qux: { raw: true, snippet: true, snippetFallback: true }, + quux: { raw: true, snippet: true, snippetFallback: true }, + }, + resultFields: { + quuz: { raw: true, snippet: true, snippetFallback: true }, + corge: { raw: true, snippet: true, snippetFallback: true }, + }, + serverResultFields: { + grault: { raw: { size: 5 } }, + garply: { raw: true }, + }, + }); + + ResultSettingsLogic.actions.resetAllFields(); + + expect(ResultSettingsLogic.values).toEqual({ + ...DEFAULT_VALUES, + nonTextResultFields: { + bar: { raw: true, snippet: false, snippetFallback: false }, + foo: { raw: true, snippet: false, snippetFallback: false }, + }, + textResultFields: { + qux: { raw: true, snippet: false, snippetFallback: false }, + quux: { raw: true, snippet: false, snippetFallback: false }, + }, + resultFields: { + quuz: { raw: true, snippet: false, snippetFallback: false }, + corge: { raw: true, snippet: false, snippetFallback: false }, + }, + serverResultFields: { + grault: { raw: {} }, + garply: { raw: {} }, + }, + }); + }); + + it('should close open modals', () => { + mount({ + openModal: OpenModal.ConfirmSaveModal, + }); + + ResultSettingsLogic.actions.resetAllFields(); + + expect(ResultSettingsLogic.values).toEqual({ + ...DEFAULT_VALUES, + openModal: OpenModal.None, + }); + }); + }); + + describe('updateField', () => { + const initialValues = { + nonTextResultFields: { + foo: { raw: true, snippet: true, snippetFallback: true }, + bar: { raw: true, snippet: true, snippetFallback: true }, + }, + textResultFields: { + foo: { raw: true, snippet: true, snippetFallback: true }, + bar: { raw: true, snippet: true, snippetFallback: true }, + }, + resultFields: { + foo: { raw: true, snippet: true, snippetFallback: true }, + bar: { raw: true, snippet: true, snippetFallback: true }, + }, + serverResultFields: { + foo: { raw: { size: 5 } }, + bar: { raw: true }, + }, + }; + + it('should update settings for an individual field', () => { + mount(initialValues); + + ResultSettingsLogic.actions.updateField('foo', { + raw: true, + snippet: false, + snippetFallback: false, + }); + + expect(ResultSettingsLogic.values).toEqual({ + ...DEFAULT_VALUES, + // the settings for foo are updated below for any *ResultFields state in which they appear + nonTextResultFields: { + foo: { raw: true, snippet: false, snippetFallback: false }, + bar: { raw: true, snippet: true, snippetFallback: true }, + }, + textResultFields: { + foo: { raw: true, snippet: false, snippetFallback: false }, + bar: { raw: true, snippet: true, snippetFallback: true }, + }, + resultFields: { + foo: { raw: true, snippet: false, snippetFallback: false }, + bar: { raw: true, snippet: true, snippetFallback: true }, + }, + serverResultFields: { + // Note that the specified settings for foo get converted to a "server" format here + foo: { raw: {} }, + bar: { raw: true }, + }, + }); + }); + + it('should do nothing if the specified field does not exist', () => { + mount(initialValues); + + ResultSettingsLogic.actions.updateField('baz', { + raw: false, + snippet: false, + snippetFallback: false, + }); + + // 'baz' does not exist in state, so nothing is updated + expect(ResultSettingsLogic.values).toEqual({ + ...DEFAULT_VALUES, + ...initialValues, + }); + }); + }); + + describe('saving', () => { + it('sets saving to true and close any open modals', () => { + mount({ + saving: false, + }); + + ResultSettingsLogic.actions.saving(); + + expect(ResultSettingsLogic.values).toEqual({ + ...DEFAULT_VALUES, + saving: true, + openModal: OpenModal.None, + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts new file mode 100644 index 00000000000000..b2ffd3de19f047 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts @@ -0,0 +1,190 @@ +/* + * 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 { kea, MakeLogicType } from 'kea'; + +import { Schema, SchemaConflicts } from '../../../shared/types'; + +import { + FieldResultSetting, + FieldResultSettingObject, + OpenModal, + ServerFieldResultSettingObject, +} from './types'; + +import { + clearAllFields, + clearAllServerFields, + convertServerResultFieldsToResultFields, + convertToServerFieldResultSetting, + resetAllFields, + resetAllServerFields, + splitResultFields, +} from './utils'; + +interface ResultSettingsActions { + openConfirmResetModal(): void; + openConfirmSaveModal(): void; + closeModals(): void; + initializeResultFields( + serverResultFields: ServerFieldResultSettingObject, + schema: Schema, + schemaConflicts?: SchemaConflicts + ): { + serverResultFields: ServerFieldResultSettingObject; + resultFields: FieldResultSettingObject; + schema: Schema; + schemaConflicts: SchemaConflicts; + nonTextResultFields: FieldResultSettingObject; + textResultFields: FieldResultSettingObject; + }; + clearAllFields(): void; + resetAllFields(): void; + updateField( + fieldName: string, + settings: FieldResultSetting + ): { fieldName: string; settings: FieldResultSetting }; + saving(): void; +} + +interface ResultSettingsValues { + dataLoading: boolean; + saving: boolean; + openModal: OpenModal; + nonTextResultFields: FieldResultSettingObject; + textResultFields: FieldResultSettingObject; + resultFields: FieldResultSettingObject; + serverResultFields: ServerFieldResultSettingObject; + lastSavedResultFields: FieldResultSettingObject; + schema: Schema; + schemaConflicts: SchemaConflicts; +} + +export const ResultSettingsLogic = kea>({ + path: ['enterprise_search', 'app_search', 'result_settings_logic'], + actions: () => ({ + openConfirmResetModal: () => true, + openConfirmSaveModal: () => true, + closeModals: () => true, + initializeResultFields: (serverResultFields, schema, schemaConflicts) => { + const resultFields = convertServerResultFieldsToResultFields(serverResultFields, schema); + Object.keys(schema).forEach((fieldName) => { + if (!serverResultFields.hasOwnProperty(fieldName)) { + serverResultFields[fieldName] = {}; + } + }); + + return { + serverResultFields, + resultFields, + schema, + schemaConflicts, + ...splitResultFields(resultFields, schema), + }; + }, + clearAllFields: () => true, + resetAllFields: () => true, + updateField: (fieldName, settings) => ({ fieldName, settings }), + saving: () => true, + }), + reducers: () => ({ + dataLoading: [ + true, + { + initializeResultFields: () => false, + }, + ], + saving: [ + false, + { + initializeResultFields: () => false, + saving: () => true, + }, + ], + openModal: [ + OpenModal.None, + { + initializeResultFields: () => OpenModal.None, + closeModals: () => OpenModal.None, + resetAllFields: () => OpenModal.None, + openConfirmResetModal: () => OpenModal.ConfirmResetModal, + openConfirmSaveModal: () => OpenModal.ConfirmSaveModal, + saving: () => OpenModal.None, + }, + ], + nonTextResultFields: [ + {}, + { + initializeResultFields: (_, { nonTextResultFields }) => nonTextResultFields, + clearAllFields: (nonTextResultFields) => clearAllFields(nonTextResultFields), + resetAllFields: (nonTextResultFields) => resetAllFields(nonTextResultFields), + updateField: (nonTextResultFields, { fieldName, settings }) => + nonTextResultFields.hasOwnProperty(fieldName) + ? { ...nonTextResultFields, [fieldName]: settings } + : nonTextResultFields, + }, + ], + textResultFields: [ + {}, + { + initializeResultFields: (_, { textResultFields }) => textResultFields, + clearAllFields: (textResultFields) => clearAllFields(textResultFields), + resetAllFields: (textResultFields) => resetAllFields(textResultFields), + updateField: (textResultFields, { fieldName, settings }) => + textResultFields.hasOwnProperty(fieldName) + ? { ...textResultFields, [fieldName]: settings } + : textResultFields, + }, + ], + resultFields: [ + {}, + { + initializeResultFields: (_, { resultFields }) => resultFields, + clearAllFields: (resultFields) => clearAllFields(resultFields), + resetAllFields: (resultFields) => resetAllFields(resultFields), + updateField: (resultFields, { fieldName, settings }) => + resultFields.hasOwnProperty(fieldName) + ? { ...resultFields, [fieldName]: settings } + : resultFields, + }, + ], + serverResultFields: [ + {}, + { + initializeResultFields: (_, { serverResultFields }) => serverResultFields, + clearAllFields: (serverResultFields) => clearAllServerFields(serverResultFields), + resetAllFields: (serverResultFields) => resetAllServerFields(serverResultFields), + updateField: (serverResultFields, { fieldName, settings }) => { + return serverResultFields.hasOwnProperty(fieldName) + ? { + ...serverResultFields, + [fieldName]: convertToServerFieldResultSetting(settings), + } + : serverResultFields; + }, + }, + ], + lastSavedResultFields: [ + {}, + { + initializeResultFields: (_, { resultFields }) => resultFields, + }, + ], + schema: [ + {}, + { + initializeResultFields: (_, { schema }) => schema, + }, + ], + schemaConflicts: [ + {}, + { + initializeResultFields: (_, { schemaConflicts }) => schemaConflicts || {}, + }, + ], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/types.ts new file mode 100644 index 00000000000000..da763dfe7cdc4a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/types.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export enum OpenModal { + None, + ConfirmResetModal, + ConfirmSaveModal, +} +export interface ServerFieldResultSetting { + raw?: + | { + size?: number; + } + | boolean; + snippet?: + | { + size?: number; + fallback?: boolean; + } + | boolean; +} + +export type ServerFieldResultSettingObject = Record; + +export interface FieldResultSetting { + raw: boolean; + rawSize?: number; + snippet: boolean; + snippetSize?: number; + snippetFallback: boolean; +} + +export type FieldResultSettingObject = Record; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.test.ts new file mode 100644 index 00000000000000..2482ecab5892c4 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.test.ts @@ -0,0 +1,174 @@ +/* + * 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 { SchemaTypes } from '../../../shared/types'; + +import { + convertServerResultFieldsToResultFields, + convertToServerFieldResultSetting, + clearAllServerFields, + clearAllFields, + resetAllServerFields, + resetAllFields, + splitResultFields, +} from './utils'; + +describe('clearAllFields', () => { + it('will reset every key in an object back to an empty object', () => { + expect( + clearAllFields({ + foo: { raw: false, snippet: false, snippetFallback: false }, + bar: { raw: true, snippet: false, snippetFallback: true }, + }) + ).toEqual({ + foo: {}, + bar: {}, + }); + }); +}); + +describe('clearAllServerFields', () => { + it('will reset every key in an object back to an empty object', () => { + expect( + clearAllServerFields({ + foo: { raw: { size: 5 } }, + bar: { raw: true }, + }) + ).toEqual({ + foo: {}, + bar: {}, + }); + }); +}); + +describe('resetAllFields', () => { + it('will reset every key in an object back to a default object', () => { + expect( + resetAllFields({ + foo: { raw: false, snippet: true, snippetFallback: true }, + bar: { raw: false, snippet: true, snippetFallback: true }, + }) + ).toEqual({ + foo: { raw: true, snippet: false, snippetFallback: false }, + bar: { raw: true, snippet: false, snippetFallback: false }, + }); + }); +}); + +describe('resetAllServerFields', () => { + it('will reset every key in an object back to a default object', () => { + expect( + resetAllServerFields({ + foo: { raw: { size: 5 } }, + bar: { snippet: true }, + }) + ).toEqual({ + foo: { raw: {} }, + bar: { raw: {} }, + }); + }); +}); + +describe('convertServerResultFieldsToResultFields', () => { + it('will convert a server settings object to a format that the front-end expects', () => { + expect( + convertServerResultFieldsToResultFields( + { + foo: { + raw: { size: 5 }, + snippet: { size: 3, fallback: true }, + }, + }, + { + foo: 'text' as SchemaTypes, + } + ) + ).toEqual({ + foo: { + raw: true, + rawSize: 5, + snippet: true, + snippetFallback: true, + snippetSize: 3, + }, + }); + }); +}); + +describe('convertToServerFieldResultSetting', () => { + it('will convert a settings object to a format that the server expects', () => { + expect( + convertToServerFieldResultSetting({ + raw: true, + rawSize: 5, + snippet: true, + snippetFallback: true, + snippetSize: 3, + }) + ).toEqual({ + raw: { size: 5 }, + snippet: { size: 3, fallback: true }, + }); + }); + + it('will not include snippet or raw information if they are set to false', () => { + expect( + convertToServerFieldResultSetting({ + raw: false, + rawSize: 5, + snippet: false, + snippetFallback: true, + snippetSize: 3, + }) + ).toEqual({}); + }); + + it('will not include sizes if they are not included, or fallback if it is false', () => { + expect( + convertToServerFieldResultSetting({ + raw: true, + snippet: true, + snippetFallback: false, + }) + ).toEqual({ + raw: {}, + snippet: {}, + }); + }); +}); + +describe('splitResultFields', () => { + it('will split results based on their schema type', () => { + expect( + splitResultFields( + { + foo: { + raw: true, + rawSize: 5, + snippet: false, + snippetFallback: false, + }, + bar: { + raw: true, + rawSize: 5, + snippet: false, + snippetFallback: false, + }, + }, + { + foo: 'text' as SchemaTypes, + bar: 'number' as SchemaTypes, + } + ) + ).toEqual({ + nonTextResultFields: { + bar: { raw: true, rawSize: 5, snippet: false, snippetFallback: false }, + }, + textResultFields: { foo: { raw: true, rawSize: 5, snippet: false, snippetFallback: false } }, + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.ts new file mode 100644 index 00000000000000..0311132542d993 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.ts @@ -0,0 +1,117 @@ +/* + * 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 { Schema } from '../../../shared/types'; + +import { DEFAULT_FIELD_SETTINGS, DISABLED_FIELD_SETTINGS } from './constants'; +import { + FieldResultSetting, + FieldResultSettingObject, + ServerFieldResultSetting, + ServerFieldResultSettingObject, +} from './types'; + +const updateAllFields = ( + fields: FieldResultSettingObject | ServerFieldResultSettingObject, + newValue: FieldResultSetting | {} +) => { + return Object.keys(fields).reduce( + (acc, fieldName) => ({ ...acc, [fieldName]: { ...newValue } }), + {} + ); +}; + +const convertToFieldResultSetting = (serverFieldResultSetting: ServerFieldResultSetting) => { + const fieldResultSetting: FieldResultSetting = { + raw: !!serverFieldResultSetting.raw, + snippet: !!serverFieldResultSetting.snippet, + snippetFallback: !!( + serverFieldResultSetting.snippet && + typeof serverFieldResultSetting.snippet === 'object' && + serverFieldResultSetting.snippet.fallback + ), + }; + + if ( + serverFieldResultSetting.raw && + typeof serverFieldResultSetting.raw === 'object' && + serverFieldResultSetting.raw.size + ) { + fieldResultSetting.rawSize = serverFieldResultSetting.raw.size; + } + + if ( + serverFieldResultSetting.snippet && + typeof serverFieldResultSetting.snippet === 'object' && + serverFieldResultSetting.snippet.size + ) { + fieldResultSetting.snippetSize = serverFieldResultSetting.snippet.size; + } + + return fieldResultSetting; +}; + +export const clearAllFields = (fields: FieldResultSettingObject) => updateAllFields(fields, {}); + +export const clearAllServerFields = (fields: ServerFieldResultSettingObject) => + updateAllFields(fields, {}); + +export const resetAllFields = (fields: FieldResultSettingObject) => + updateAllFields(fields, DEFAULT_FIELD_SETTINGS); + +export const resetAllServerFields = (fields: ServerFieldResultSettingObject) => + updateAllFields(fields, { raw: {} }); + +export const convertServerResultFieldsToResultFields = ( + serverResultFields: ServerFieldResultSettingObject, + schema: Schema +) => { + const resultFields: FieldResultSettingObject = Object.keys(schema).reduce( + (acc: FieldResultSettingObject, fieldName: string) => ({ + ...acc, + [fieldName]: serverResultFields[fieldName] + ? convertToFieldResultSetting(serverResultFields[fieldName]) + : DISABLED_FIELD_SETTINGS, + }), + {} + ); + return resultFields; +}; + +export const convertToServerFieldResultSetting = (fieldResultSetting: FieldResultSetting) => { + const serverFieldResultSetting: ServerFieldResultSetting = {}; + if (fieldResultSetting.raw) { + serverFieldResultSetting.raw = {}; + if (fieldResultSetting.rawSize) { + serverFieldResultSetting.raw.size = fieldResultSetting.rawSize; + } + } + + if (fieldResultSetting.snippet) { + serverFieldResultSetting.snippet = {}; + if (fieldResultSetting.snippetFallback) { + serverFieldResultSetting.snippet.fallback = fieldResultSetting.snippetFallback; + } + if (fieldResultSetting.snippetSize) { + serverFieldResultSetting.snippet.size = fieldResultSetting.snippetSize; + } + } + + return serverFieldResultSetting; +}; + +export const splitResultFields = (resultFields: FieldResultSettingObject, schema: Schema) => { + const textResultFields: FieldResultSettingObject = {}; + const nonTextResultFields: FieldResultSettingObject = {}; + const keys = Object.keys(schema); + keys.forEach((fieldName) => { + (schema[fieldName] === 'text' ? textResultFields : nonTextResultFields)[fieldName] = + resultFields[fieldName]; + }); + + return { textResultFields, nonTextResultFields }; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.tsx index c79865d25ecd7b..95d7920ae0435c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { EuiButtonEmpty, EuiText } from '@elastic/eui'; +import { EuiButtonEmpty, EuiText, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { externalUrl, getWorkplaceSearchUrl } from '../../../shared/enterprise_search_url'; import { NAV } from '../../constants'; @@ -16,23 +16,17 @@ export const WorkplaceSearchHeaderActions: React.FC = () => { if (!externalUrl.enterpriseSearchUrl) return null; return ( - <> - - {NAV.PERSONAL_DASHBOARD} - - - {NAV.SEARCH} - - + + + + {NAV.PERSONAL_DASHBOARD} + + + + + {NAV.SEARCH} + + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/content_section/content_section.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/content_section/content_section.test.tsx index 21280926d7aae5..d9c2d70e78c083 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/content_section/content_section.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/content_section/content_section.test.tsx @@ -25,7 +25,7 @@ describe('ContentSection', () => { const wrapper = shallow(); expect(wrapper.prop('data-test-subj')).toEqual('contentSection'); - expect(wrapper.prop('className')).toEqual('test content-section'); + expect(wrapper.prop('className')).toEqual('test'); expect(wrapper.find('.children')).toHaveLength(1); }); @@ -48,7 +48,7 @@ describe('ContentSection', () => { ); expect(wrapper.find(EuiSpacer).first().prop('size')).toEqual('s'); - expect(wrapper.find(EuiSpacer)).toHaveLength(1); + expect(wrapper.find(EuiSpacer)).toHaveLength(2); expect(wrapper.find('.header')).toHaveLength(1); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/content_section/content_section.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/content_section/content_section.tsx index e606263ac6f1cf..d9a4ed7eee8b86 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/content_section/content_section.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/content_section/content_section.tsx @@ -12,8 +12,6 @@ import { EuiSpacer } from '@elastic/eui'; import { SpacerSizeTypes } from '../../../types'; import { ViewContentHeader } from '../view_content_header'; -import './content_section.scss'; - interface ContentSectionProps { children: React.ReactNode; className?: string; @@ -35,7 +33,7 @@ export const ContentSection: React.FC = ({ headerSpacer, testSubj, }) => ( -
+
{title && ( <> @@ -44,5 +42,6 @@ export const ContentSection: React.FC = ({ )} {children} +
); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/license_callout/license_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/license_callout/license_callout.tsx index f0e21803a4f581..f278cda96ae832 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/license_callout/license_callout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/license_callout/license_callout.tsx @@ -19,22 +19,17 @@ export const LicenseCallout: React.FC = ({ message }) => { const title = ( <> {message}{' '} - + Explore Platinum features ); return ( -
+
-
+
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.test.tsx index ee079970a5ebbf..4d980ca2f53138 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.test.tsx @@ -14,16 +14,10 @@ import { EuiIcon } from '@elastic/eui'; import { SourceIcon } from './'; describe('SourceIcon', () => { - it('renders unwrapped icon', () => { + it('renders', () => { const wrapper = shallow(); expect(wrapper.find(EuiIcon)).toHaveLength(1); expect(wrapper.find('.user-group-source')).toHaveLength(0); }); - - it('renders wrapped icon', () => { - const wrapper = shallow(); - - expect(wrapper.find('.wrapped-icon')).toHaveLength(1); - }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx index 1d1462542a3f6d..34d6c2401b300e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx @@ -11,33 +11,15 @@ import { camelCase } from 'lodash'; import { EuiIcon, IconSize } from '@elastic/eui'; -import './source_icon.scss'; - import { images } from '../assets/source_icons'; interface SourceIconProps { serviceType: string; name: string; className?: string; - wrapped?: boolean; size?: IconSize; } -export const SourceIcon: React.FC = ({ - name, - serviceType, - className, - wrapped, - size = 'xxl', -}) => { - const icon = ( - - ); - return wrapped ? ( -
- {icon} -
- ) : ( - <>{icon} - ); -}; +export const SourceIcon: React.FC = ({ name, serviceType, className, size }) => ( + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.scss b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.scss deleted file mode 100644 index fb8a47d134269b..00000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.scss +++ /dev/null @@ -1,15 +0,0 @@ -.source-row { - &__icon { - width: 24px; - height: 24px; - } - - &__name { - font-weight: 500; - } - - &__actions a { - opacity: 1.0; - pointer-events: auto; - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.test.tsx index 9661471bb1dd70..b3ce0a01d5dd4b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.test.tsx @@ -73,7 +73,7 @@ describe('SourceRow', () => { }; const wrapper = shallow(); - expect(wrapper.find('.source-row__document-count').contains('Remote')).toBeTruthy(); + expect(wrapper.find('[data-test-subj="SourceDocumentCount"]').contains('Remote')).toBeTruthy(); }); it('renders details link', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.tsx index a6b2878de6449e..38d7945ca17532 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.tsx @@ -7,7 +7,6 @@ import React from 'react'; -import classNames from 'classnames'; // Prefer importing entire lodash library, e.g. import { get } from "lodash" // eslint-disable-next-line no-restricted-imports import _kebabCase from 'lodash/kebabCase'; @@ -35,8 +34,6 @@ import { import { ContentSourceDetails } from '../../../types'; import { SourceIcon } from '../source_icon'; -import './source_row.scss'; - const CREDENTIALS_INVALID_ERROR_REASON = 1; export interface ISourceRow { @@ -72,15 +69,6 @@ export const SourceRow: React.FC = ({ const showFix = isOrganization && hasError && allowsReauth && errorReason === CREDENTIALS_INVALID_ERROR_REASON; - const rowClass = classNames( - 'source-row', - { 'content-section--disabled': !searchable }, - { 'source-row source-row--error': hasError } - ); - - // eslint-disable-next-line @typescript-eslint/naming-convention - const imageClass = classNames('source-row__icon', { 'source-row__icon--loading': isIndexing }); - const fixLink = ( = ({ ); return ( - + = ({ responsive={false} > - - - - {name} + + {name} @@ -138,17 +120,13 @@ export const SourceRow: React.FC = ({ )} - + {statusMessage} - + {isFederatedSource ? remoteTooltip : parseInt(documentCount, 10).toLocaleString('en-US')} {onSearchableToggle && ( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/sources_table/sources_table.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/sources_table/sources_table.tsx index 66e7e2e752a1ec..9bc3d6ec2f1f49 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/sources_table/sources_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/sources_table/sources_table.tsx @@ -27,7 +27,7 @@ export const SourcesTable: React.FC = ({ if (onSearchableToggle) headerItems.push('Searchable'); return ( - + {sources.map((source) => ( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_header.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_header.tsx index 2ecb3c98565b72..a4910f3a68ea22 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_header.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_header.tsx @@ -34,11 +34,16 @@ export const AddSourceHeader: React.FC = ({ responsive={false} > - + -

+

{name}

diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx index 372187485f277a..8819367cacd1fc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx @@ -126,7 +126,7 @@ export const AddSourceList: React.FC = () => { - + = ({ sour title={name} description={<>} isDisabled={disabled} - icon={ - - } + icon={} /> ); @@ -79,7 +73,7 @@ export const AvailableSourcesList: React.FC = ({ sour }; const visibleSources = ( - + {sources.map((source, i) => ( {getSourceCard(source)} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_completed.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_completed.tsx index 1d4f1f2fca980d..8edef425f414c1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_completed.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_completed.tsx @@ -48,7 +48,7 @@ export const ConfigCompleted: React.FC = ({ header, privateSourcesEnabled, }) => ( -
+ <> {header} = ({ to={getSourcesPath(ADD_SOURCE_PATH, true)} fill={accountContextOnly} color={accountContextOnly ? 'primary' : undefined} - className="eui-textNoWrap" > {CONFIG_COMPLETED_CONFIGURE_NEW_BUTTON} @@ -148,7 +147,6 @@ export const ConfigCompleted: React.FC = ({ = ({ )} -
+ ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_intro.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_intro.tsx index 917886d69bd19d..8a1cdf0b84274b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_intro.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_intro.tsx @@ -43,7 +43,7 @@ export const ConfigurationIntro: React.FC = ({ advanceStep, header, }) => ( -
+ <> {header} = ({ -
+ ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_custom.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_custom.tsx index 901c4fc3e707bf..b142ddcf4937e3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_custom.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_custom.tsx @@ -48,7 +48,7 @@ export const ConfigureCustom: React.FC = ({ setCustomSourceNameValue(e.target.value); return ( -
+ <> {header}
@@ -93,6 +93,6 @@ export const ConfigureCustom: React.FC = ({ -
+ ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_oauth.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_oauth.tsx index 69a2fbd1495c70..ba92e5d790245d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_oauth.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_oauth.tsx @@ -99,9 +99,9 @@ export const ConfigureOauth: React.FC = ({ name, onFormCrea ); return ( -
+ <> {header} {sectionLoading ? : configfieldsForm} -
+ ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.tsx index 5f64913410d4c0..ac19043a30ca6b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.tsx @@ -79,16 +79,12 @@ export const ConfiguredSourcesList: React.FC = ({ responsive={false} > - +

- {name}  + {name} {!connected && !accountContextOnly && isOrganization && diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx index 2290a659127974..a34641784b1623 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx @@ -263,30 +263,28 @@ export const ConnectInstance: React.FC = ({ ); return ( -
-
- - - - - {header} - - - - - + + + + + + {header} - - {formFields} - - - -
+ + + + + + + {formFields} + + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/re_authenticate.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/re_authenticate.tsx index 61682dbb87d587..f57118b952eac7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/re_authenticate.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/re_authenticate.tsx @@ -49,7 +49,7 @@ export const ReAuthenticate: React.FC = ({ name, header }) }; return ( -
+ <> {header}
@@ -86,6 +86,6 @@ export const ReAuthenticate: React.FC = ({ name, header }) -
+ ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx index 5aae4b352a1fba..1bf8239a6b3994 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx @@ -60,7 +60,7 @@ export const SaveCustom: React.FC = ({ isOrganization, header, }) => ( -
+ <> {header} @@ -205,5 +205,5 @@ export const SaveCustom: React.FC = ({ -
+ ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx index aab16c9114e8ab..ad16260b1de7c6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx @@ -187,12 +187,7 @@ export const SourceFeatures: React.FC = ({ features, objTy {includedFeatures.map((featureId, i) => ( - + diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx index 681c7a21463c19..e39a8d17e406c9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx @@ -125,7 +125,7 @@ export const DisplaySettings: React.FC = ({ tabId }) => { onTabClick={onSelectedTabChanged} /> ) : ( - + {DISPLAY_SETTINGS_EMPTY_TITLE}

} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx index 786184943e317f..dc925e21460da1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx @@ -116,7 +116,7 @@ export const Overview: React.FC = () => { const emptyState = ( <> - + {SOURCES_NO_CONTENT_TITLE}} iconType="documents" @@ -127,12 +127,10 @@ export const Overview: React.FC = () => { ); return ( -
-
- -

{CONTENT_SUMMARY_TITLE}

-
-
+ <> + +

{CONTENT_SUMMARY_TITLE}

+
{!summary && } {!!summary && @@ -157,7 +155,7 @@ export const Overview: React.FC = () => { ))} -
+ ); }; @@ -165,7 +163,7 @@ export const Overview: React.FC = () => { const emptyState = ( <> - + {EMPTY_ACTIVITY_TITLE}} iconType="clock" @@ -213,15 +211,13 @@ export const Overview: React.FC = () => { ); return ( -
-
- -

{RECENT_ACTIVITY_TITLE}

-
-
+ <> + +

{RECENT_ACTIVITY_TITLE}

+
{activities.length === 0 ? emptyState : activitiesTable} -
+ ); }; @@ -234,11 +230,7 @@ export const Overview: React.FC = () => { {groups.map((group, index) => ( - + {group.name} @@ -306,7 +298,7 @@ export const Overview: React.FC = () => {

{DOCUMENT_PERMISSIONS_TITLE}

- + @@ -455,7 +447,7 @@ export const Overview: React.FC = () => { - + diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema.tsx index 77d1002a9ad26f..f31f7049ebf36b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema.tsx @@ -140,7 +140,7 @@ export const Schema: React.FC = () => { ) : ( - + {SCHEMA_EMPTY_SCHEMA_TITLE}} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx index 1a6d97bbf75ba9..cc086f9c829d9f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx @@ -106,7 +106,7 @@ export const SourceContent: React.FC = () => { const isCustomSource = serviceType === CUSTOM_SERVICE_TYPE; const emptyState = ( - + {emptyMessage}} iconType="documents" diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_info_card.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_info_card.tsx index 25c78afbe4e05f..1c3c44887946ac 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_info_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_info_card.tsx @@ -35,18 +35,13 @@ export const SourceInfoCard: React.FC = ({ }) => ( - + - + -
{sourceName}
+
{sourceName}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources.scss b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources.scss index 437b8010d68918..f142567fb621f0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources.scss +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources.scss @@ -10,11 +10,6 @@ .source-card-configured { padding: 8px; - &__icon { - width: 2em; - height: 2em; - } - &__not-connected-tooltip { position: relative; top: 3px; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx index 247df5556ada01..1ef44a5f26ae07 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx @@ -62,7 +62,7 @@ export const SourcesView: React.FC = ({ children }) => { gutterSize="s" > - + {i18n.translate('xpack.enterpriseSearch.workplaceSearch.sourcesView.modal.heading', { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_source_prioritization.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_source_prioritization.tsx index df7435bd254619..bee8b4e632a649 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_source_prioritization.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_source_prioritization.tsx @@ -106,7 +106,7 @@ export const GroupSourcePrioritization: React.FC = () => { const hasSources = contentSources.length > 0; const zeroState = ( - + { ); const sourceTable = ( - + {SOURCE_TABLE_HEADER} {PRIORITY_TABLE_HEADER} @@ -143,14 +143,12 @@ export const GroupSourcePrioritization: React.FC = () => { - - - - {name} + + {name} - + { min={1} max={10} step={1} + showInput value={activeSourcePriorities[id]} onChange={(e: ChangeEvent | MouseEvent) => handleSliderChange(id, e) } /> - -
- {activeSourcePriorities[id]} -
-
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_sources.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_sources.tsx index 97739e46caba4c..26d56c7435d000 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_sources.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_sources.tsx @@ -7,6 +7,8 @@ import React, { useState } from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + import { SourceIcon } from '../../../components/shared/source_icon'; import { MAX_TABLE_ROW_ICONS } from '../../../constants'; import { ContentSource } from '../../../types'; @@ -26,9 +28,13 @@ export const GroupSources: React.FC = ({ groupSources }) => { return ( <> - {visibleSources.map((source, index) => ( - - ))} + + {visibleSources.map((source, index) => ( + + + + ))} + {hiddenSources.length > 0 && ( { return ( <> - + {users.slice(firstItem, lastItem + 1).map((user: User) => ( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/groups_table.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/groups_table.tsx index 95292116cba058..deaf223afa6b36 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/groups_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/groups_table.tsx @@ -75,7 +75,7 @@ export const GroupsTable: React.FC<{}> = () => { <> {showPagination ? : clearFiltersLink} - + {GROUP_TABLE_HEADER} {SOURCES_TABLE_HEADER} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/source_option_item.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/source_option_item.tsx index e2da597a83598b..7983b3a13f4cdd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/source_option_item.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/source_option_item.tsx @@ -22,7 +22,7 @@ interface SourceOptionItemProps { export const SourceOptionItem: React.FC = ({ source }) => ( - + diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview.tsx index 0f8f4b6def46ca..9242bd8b6fbddf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview.tsx @@ -54,13 +54,8 @@ export const Overview: React.FC = () => { initializeOverview(); }, [initializeOverview]); - // TODO: Remove div wrapper once the Overview page is using the full Layout if (dataLoading) { - return ( -
- -
- ); + return ; } const hideOnboarding = hasUsers && hasOrgSources && isOldAccount && orgName !== defaultOrgName; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx index 4658379cd40c7f..cf402f4525f9e5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx @@ -163,7 +163,7 @@ export const RoleMapping: React.FC = ({ isNew }) => {

{GROUP_ASSIGNMENT_TITLE}

-
+
= ({ const panelDisabled = !isEnabled || !hasPlatinumLicense; const sectionDisabled = !sectionEnabled; - const panelClass = classNames('euiPanel--outline euiPanel--noShadow', { - 'euiPanel--disabled': panelDisabled, - }); - const tableClass = classNames({ 'euiTable--disabled': sectionDisabled }); const emptyState = ( <> - + {isRemote ? REMOTE_SOURCES_EMPTY_TABLE_TITLE : STANDARD_SOURCES_EMPTY_TABLE_TITLE} @@ -175,7 +171,12 @@ export const PrivateSourcesTable: React.FC = ({ ); return ( - + {sectionHeading} {hasSources && sourcesTable} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security.tsx index 669015794baefb..1248d9caf7e7de 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security.tsx @@ -74,10 +74,6 @@ export const Security: React.FC = () => { if (dataLoading) return ; - const panelClass = classNames('euiPanel--noShadow', { - 'euiPanel--disabled': !hasPlatinumLicense, - }); - const savePrivateSources = () => { saveSourceRestrictions(); hideConfirmModal(); @@ -116,7 +112,13 @@ export const Security: React.FC = () => { ); const allSourcesToggle = ( - + { - + - - - {name} -    - {accountContextOnly && {PRIVATE_SOURCE}} - + + + {name} + + {accountContextOnly && {PRIVATE_SOURCE}} + + diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/oauth_application.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/oauth_application.tsx index 3f2e55d23722c2..929508bdf7b230 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/oauth_application.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/oauth_application.tsx @@ -92,12 +92,7 @@ export const OauthApplication: React.FC = () => { }; const licenseModal = ( - + diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/source_config.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/source_config.tsx index 47a24e7912c3c9..5372917b3eba2a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/source_config.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/source_config.tsx @@ -18,6 +18,11 @@ import { AddSourceHeader } from '../../content_sources/components/add_source/add import { AddSourceLogic } from '../../content_sources/components/add_source/add_source_logic'; import { SaveConfig } from '../../content_sources/components/add_source/save_config'; import { staticSourceData } from '../../content_sources/source_data'; +import { + CONFIRM_REMOVE_CONFIG_TITLE, + CONFIRM_REMOVE_CONFIG_CONFIRM_BUTTON_TEXT, + CONFIRM_REMOVE_CONFIG_CANCEL_BUTTON_TEXT, +} from '../constants'; import { SettingsLogic } from '../settings_logic'; interface SourceConfigProps { @@ -60,6 +65,9 @@ export const SourceConfig: React.FC = ({ sourceIndex }) => { onConfirm={() => deleteSourceConfig(serviceType, name)} onCancel={hideConfirmModal} buttonColor="danger" + title={CONFIRM_REMOVE_CONFIG_TITLE} + confirmButtonText={CONFIRM_REMOVE_CONFIG_CONFIRM_BUTTON_TEXT} + cancelButtonText={CONFIRM_REMOVE_CONFIG_CANCEL_BUTTON_TEXT} > {i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.settings.confirmRemoveConfig.message', diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/constants.ts new file mode 100644 index 00000000000000..b40edc1d0d1bdc --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/constants.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const CONFIRM_REMOVE_CONFIG_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.settings.confirmRemoveConfigTitle', + { + defaultMessage: 'Remove configuration', + } +); + +export const CONFIRM_REMOVE_CONFIG_CONFIRM_BUTTON_TEXT = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.settings.confirmRemoveConfigConfirmButtonText', + { + defaultMessage: 'Remove', + } +); + +export const CONFIRM_REMOVE_CONFIG_CANCEL_BUTTON_TEXT = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.settings.confirmRemoveConfigCancelButtonText', + { + defaultMessage: 'Cancel', + } +); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/curations.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/curations.test.ts index 08e123a98cd314..045d3d12e8bcf3 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/curations.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/curations.test.ts @@ -229,4 +229,39 @@ describe('curations routes', () => { }); }); }); + + describe('GET /api/app_search/engines/{engineName}/curation_search', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'get', + path: '/api/app_search/engines/{engineName}/curation_search', + }); + + registerCurationsRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/api/as/v1/engines/:engineName/search.json', + }); + }); + + describe('validates', () => { + it('required query param', () => { + const request = { query: { query: 'some query' } }; + mockRouter.shouldValidate(request); + }); + + it('missing query', () => { + const request = { query: {} }; + mockRouter.shouldThrow(request); + }); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/curations.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/curations.ts index 3cacab96d19686..4811ceeac408b2 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/curations.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/curations.ts @@ -115,4 +115,21 @@ export function registerCurationsRoutes({ path: '/as/engines/:engineName/curations/find_or_create', }) ); + + router.get( + { + path: '/api/app_search/engines/{engineName}/curation_search', + validate: { + params: schema.object({ + engineName: schema.string(), + }), + query: schema.object({ + query: schema.string(), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/api/as/v1/engines/:engineName/search.json', + }) + ); } diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts index b56c5880dba430..6638aa82b32ad1 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts @@ -859,6 +859,10 @@ export function registerOauthConnectorParamsRoute({ kibana_host: schema.string(), code: schema.maybe(schema.string()), session_state: schema.maybe(schema.string()), + authuser: schema.maybe(schema.string()), + prompt: schema.maybe(schema.string()), + hd: schema.maybe(schema.string()), + scope: schema.maybe(schema.string()), state: schema.string(), oauth_token: schema.maybe(schema.string()), oauth_verifier: schema.maybe(schema.string()), diff --git a/x-pack/plugins/fleet/common/services/license.ts b/x-pack/plugins/fleet/common/services/license.ts index 767c94436effb1..07214b64edc3e0 100644 --- a/x-pack/plugins/fleet/common/services/license.ts +++ b/x-pack/plugins/fleet/common/services/license.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Observable, Subscription } from 'rxjs'; +import type { Observable, Subscription } from 'rxjs'; import type { ILicense } from '../../../licensing/common/types'; diff --git a/x-pack/plugins/fleet/common/types/models/agent.ts b/x-pack/plugins/fleet/common/types/models/agent.ts index 0e558f8ebdcdbb..35b123b2c64ea4 100644 --- a/x-pack/plugins/fleet/common/types/models/agent.ts +++ b/x-pack/plugins/fleet/common/types/models/agent.ts @@ -5,7 +5,11 @@ * 2.0. */ -import { AGENT_TYPE_EPHEMERAL, AGENT_TYPE_PERMANENT, AGENT_TYPE_TEMPORARY } from '../../constants'; +import type { + AGENT_TYPE_EPHEMERAL, + AGENT_TYPE_PERMANENT, + AGENT_TYPE_TEMPORARY, +} from '../../constants'; import type { FullAgentPolicy } from './agent_policy'; diff --git a/x-pack/plugins/fleet/common/types/models/agent_policy.ts b/x-pack/plugins/fleet/common/types/models/agent_policy.ts index 3325a8db713aa6..7e5b799e484d67 100644 --- a/x-pack/plugins/fleet/common/types/models/agent_policy.ts +++ b/x-pack/plugins/fleet/common/types/models/agent_policy.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { agentPolicyStatuses } from '../../constants'; +import type { agentPolicyStatuses } from '../../constants'; import type { DataType, ValueOf } from '../../types'; import type { PackagePolicy, PackagePolicyPackage } from './package_policy'; diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index 2ef9faa5f10f0b..5ea997d2178889 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -9,7 +9,7 @@ // TODO: Update when https://github.com/elastic/kibana/issues/53021 is closed import type { SavedObject, SavedObjectAttributes, SavedObjectReference } from 'src/core/public'; -import { +import type { ASSETS_SAVED_OBJECT_TYPE, agentAssetTypes, dataTypes, diff --git a/x-pack/plugins/fleet/common/types/models/output.ts b/x-pack/plugins/fleet/common/types/models/output.ts index dc121a00b1c011..462828a115bc67 100644 --- a/x-pack/plugins/fleet/common/types/models/output.ts +++ b/x-pack/plugins/fleet/common/types/models/output.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { outputType } from '../../constants'; +import type { outputType } from '../../constants'; import type { ValueOf } from '../index'; export type OutputType = typeof outputType; diff --git a/x-pack/plugins/fleet/common/types/models/settings.ts b/x-pack/plugins/fleet/common/types/models/settings.ts index 56557fb6703b46..bb345a67bec41a 100644 --- a/x-pack/plugins/fleet/common/types/models/settings.ts +++ b/x-pack/plugins/fleet/common/types/models/settings.ts @@ -8,8 +8,6 @@ import type { SavedObjectAttributes } from 'src/core/public'; export interface BaseSettings { - agent_auto_upgrade: boolean; - package_auto_upgrade: boolean; kibana_urls: string[]; kibana_ca_sha256?: string; has_seen_add_data_notice?: boolean; diff --git a/x-pack/plugins/fleet/public/applications/fleet/app.tsx b/x-pack/plugins/fleet/public/applications/fleet/app.tsx index 57758d616d9088..2c24468b147826 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/app.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/app.tsx @@ -6,16 +6,17 @@ */ import React, { memo, useEffect, useState } from 'react'; -import { AppMountParameters } from 'kibana/public'; +import type { AppMountParameters } from 'kibana/public'; import { EuiCode, EuiEmptyPrompt, EuiErrorBoundary, EuiPanel } from '@elastic/eui'; -import { createHashHistory, History } from 'history'; +import type { History } from 'history'; +import { createHashHistory } from 'history'; import { Router, Redirect, Route, Switch } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; import useObservable from 'react-use/lib/useObservable'; -import { FleetConfigType, FleetStartServices } from '../../plugin'; +import type { FleetConfigType, FleetStartServices } from '../../plugin'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import { EuiThemeProvider } from '../../../../../../src/plugins/kibana_react/common'; @@ -39,7 +40,7 @@ import { DataStreamApp } from './sections/data_stream'; import { FleetApp } from './sections/agents'; import { IngestManagerOverview } from './sections/overview'; import { ProtectedRoute } from './index'; -import { UIExtensionsStorage } from './types'; +import type { UIExtensionsStorage } from './types'; import { UIExtensionsContext } from './hooks/use_ui_extension'; const ErrorLayout = ({ children }: { children: JSX.Element }) => ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/context_menu_actions.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/context_menu_actions.tsx index 253cc1c0189dce..f25415101b9aca 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/context_menu_actions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/context_menu_actions.tsx @@ -14,9 +14,9 @@ import { EuiPopover, EuiButton, } from '@elastic/eui'; -import { EuiButtonProps } from '@elastic/eui/src/components/button/button'; -import { EuiContextMenuProps } from '@elastic/eui/src/components/context_menu/context_menu'; -import { EuiContextMenuPanelProps } from '@elastic/eui/src/components/context_menu/context_menu_panel'; +import type { EuiButtonProps } from '@elastic/eui/src/components/button/button'; +import type { EuiContextMenuProps } from '@elastic/eui/src/components/context_menu/context_menu'; +import type { EuiContextMenuPanelProps } from '@elastic/eui/src/components/context_menu/context_menu_panel'; type Props = { button?: { diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/enrollment_instructions/manual/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/enrollment_instructions/manual/index.tsx index 6243597cf003cc..6f1adfc8cf9c13 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/enrollment_instructions/manual/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/enrollment_instructions/manual/index.tsx @@ -10,7 +10,7 @@ import styled from 'styled-components'; import { EuiText, EuiSpacer, EuiLink, EuiTitle, EuiCodeBlock } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EnrollmentAPIKey } from '../../../types'; +import type { EnrollmentAPIKey } from '../../../types'; interface Props { kibanaUrl: string; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/extension_wrapper.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/extension_wrapper.tsx index bb22586122295d..9c7ebc03899e7d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/extension_wrapper.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/extension_wrapper.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { memo, ReactNode, Suspense } from 'react'; +import type { ReactNode } from 'react'; +import React, { memo, Suspense } from 'react'; import { EuiErrorBoundary } from '@elastic/eui'; import { Loading } from './loading'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/header.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/header.tsx index d12311cf16d3d7..f04ae88913c242 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/header.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/header.tsx @@ -8,8 +8,8 @@ import React, { memo } from 'react'; import styled from 'styled-components'; import { EuiFlexGroup, EuiFlexItem, EuiTabs, EuiTab, EuiSpacer } from '@elastic/eui'; -import { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab'; -import { EuiFlexItemProps } from '@elastic/eui/src/components/flex/flex_item'; +import type { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab'; +import type { EuiFlexItemProps } from '@elastic/eui/src/components/flex/flex_item'; const Container = styled.div` border-bottom: ${(props) => props.theme.eui.euiBorderThin}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/home_integration/tutorial_module_notice.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/home_integration/tutorial_module_notice.tsx index 526b6513173efe..51870b482c3f29 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/home_integration/tutorial_module_notice.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/home_integration/tutorial_module_notice.tsx @@ -8,7 +8,7 @@ import React, { memo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiText, EuiLink, EuiSpacer } from '@elastic/eui'; -import { TutorialModuleNoticeComponent } from 'src/plugins/home/public'; +import type { TutorialModuleNoticeComponent } from 'src/plugins/home/public'; import { useGetPackages, useLink, useCapabilities } from '../../hooks'; import { pkgKeyFromPackageInfo } from '../../services/pkg_key_from_package_info'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/link_and_revision.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/link_and_revision.tsx index a8672f6a4942e5..071a9dc5943b2e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/link_and_revision.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/link_and_revision.tsx @@ -7,8 +7,9 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { CSSProperties, memo } from 'react'; -import { EuiLinkProps } from '@elastic/eui/src/components/link/link'; +import type { CSSProperties } from 'react'; +import React, { memo } from 'react'; +import type { EuiLinkProps } from '@elastic/eui/src/components/link/link'; const MIN_WIDTH: CSSProperties = { minWidth: 0 }; const NO_WRAP_WHITE_SPACE: CSSProperties = { whiteSpace: 'nowrap' }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/linked_agent_count.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/linked_agent_count.tsx index 3b788126a570f2..e42917700284ee 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/linked_agent_count.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/linked_agent_count.tsx @@ -6,7 +6,8 @@ */ import React, { memo } from 'react'; -import { EuiLink, EuiLinkAnchorProps } from '@elastic/eui'; +import type { EuiLinkAnchorProps } from '@elastic/eui'; +import { EuiLink } from '@elastic/eui'; import { useLink } from '../hooks'; import { AGENT_SAVED_OBJECT_TYPE } from '../constants'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/loading.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/loading.tsx index db2f9c492fa2aa..3e6f000694dbc5 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/loading.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/loading.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; -import { EuiLoadingSpinnerSize } from '@elastic/eui/src/components/loading/loading_spinner'; +import type { EuiLoadingSpinnerSize } from '@elastic/eui/src/components/loading/loading_spinner'; export const Loading: React.FunctionComponent<{ size?: EuiLoadingSpinnerSize }> = ({ size }) => ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/package_icon.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/package_icon.tsx index 5b8fddd2d5e053..cb0b02527f756a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/package_icon.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/package_icon.tsx @@ -6,9 +6,11 @@ */ import React from 'react'; -import { EuiIcon, EuiIconProps } from '@elastic/eui'; +import type { EuiIconProps } from '@elastic/eui'; +import { EuiIcon } from '@elastic/eui'; -import { usePackageIconType, UsePackageIconType } from '../hooks'; +import type { UsePackageIconType } from '../hooks'; +import { usePackageIconType } from '../hooks'; export const PackageIcon: React.FunctionComponent< UsePackageIconType & Omit diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx index a0e10d2106cf35..a5b937d4590d70 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx @@ -7,11 +7,8 @@ import React, { useState, useEffect, useMemo } from 'react'; -import { - QueryStringInput, - IFieldType, - esKuery, -} from '../../../../../../../src/plugins/data/public'; +import type { IFieldType } from '../../../../../../../src/plugins/data/public'; +import { QueryStringInput, esKuery } from '../../../../../../../src/plugins/data/public'; import { useStartServices } from '../hooks'; import { INDEX_NAME, AGENT_SAVED_OBJECT_TYPE } from '../constants'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/settings_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/settings_flyout.tsx index c668097cdb47e3..146f40cd75d498 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/settings_flyout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/settings_flyout.tsx @@ -20,7 +20,6 @@ import { EuiFlyoutFooter, EuiForm, EuiFormRow, - EuiRadioGroup, EuiComboBox, EuiCodeEditor, } from '@elastic/eui'; @@ -171,73 +170,6 @@ export const SettingFlyout: React.FunctionComponent = ({ onClose }) => { const body = ( - {}} - legend={{ - children: ( - -

- -

-
- ), - }} - /> - - {}} - legend={{ - children: ( - -

- -

-
- ), - }} - /> -

{ forRoute: string; diff --git a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/use_request.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/use_request.ts index 91c45e3019cf58..b42c6e68fb499c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/use_request.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/use_request.ts @@ -9,18 +9,18 @@ import { useState, useEffect } from 'react'; import type { HttpSetup } from 'src/core/public'; import { - UseRequestConfig as _UseRequestConfig, sendRequest as _sendRequest, useRequest as _useRequest, } from '../../../../../../../../src/plugins/es_ui_shared/public'; import type { SendRequestConfig, SendRequestResponse, + UseRequestConfig, } from '../../../../../../../../src/plugins/es_ui_shared/public'; let httpClient: HttpSetup; -export type UseRequestConfig = _UseRequestConfig; +export type { UseRequestConfig } from '../../../../../../../../src/plugins/es_ui_shared/public'; /** * @internal diff --git a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_sorting.tsx b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_sorting.tsx index a11586766ab9ee..f6b17023874925 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_sorting.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_sorting.tsx @@ -6,7 +6,7 @@ */ import { useState } from 'react'; -import { CriteriaWithPagination } from '@elastic/eui/src/components/basic_table/basic_table'; +import type { CriteriaWithPagination } from '@elastic/eui/src/components/basic_table/basic_table'; export function useSorting(defaultSorting: CriteriaWithPagination['sort']) { const [sorting, setSorting] = useState['sort']>(defaultSorting); diff --git a/x-pack/plugins/fleet/public/applications/fleet/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/index.tsx index a66b28e5f05c92..7d31fb31b36a43 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/index.tsx @@ -7,13 +7,14 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { Redirect, Route, RouteProps } from 'react-router-dom'; -import { CoreStart, AppMountParameters } from 'src/core/public'; +import type { RouteProps } from 'react-router-dom'; +import { Redirect, Route } from 'react-router-dom'; +import type { CoreStart, AppMountParameters } from 'src/core/public'; -import { FleetConfigType, FleetStartServices } from '../../plugin'; +import type { FleetConfigType, FleetStartServices } from '../../plugin'; import { licenseService } from './hooks'; -import { UIExtensionsStorage } from './types'; +import type { UIExtensionsStorage } from './types'; import { AppRoutes, FleetAppContext, WithPermissionsAndSetup } from './app'; export interface ProtectedRouteProps extends RouteProps { diff --git a/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx b/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx index 001ad069f1b6f1..ba6367a861e9dd 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx @@ -10,7 +10,7 @@ import styled from 'styled-components'; import { EuiTabs, EuiTab, EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Section } from '../sections'; +import type { Section } from '../sections'; import { AlphaMessaging, SettingFlyout } from '../components'; import { useLink, useConfig } from '../hooks'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/layouts/with_header.tsx b/x-pack/plugins/fleet/public/applications/fleet/layouts/with_header.tsx index c8a8d47e6d802f..bb561ab82acaa6 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/layouts/with_header.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/layouts/with_header.tsx @@ -8,7 +8,8 @@ import React, { Fragment } from 'react'; import { EuiPageBody, EuiSpacer } from '@elastic/eui'; -import { Header, HeaderProps } from '../components'; +import type { HeaderProps } from '../components'; +import { Header } from '../components'; import { Page, ContentWrapper } from './without_header'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/mock/create_test_renderer.tsx b/x-pack/plugins/fleet/public/applications/fleet/mock/create_test_renderer.tsx index 0a1346c888acc7..c1fa1422fa8aba 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/mock/create_test_renderer.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/mock/create_test_renderer.tsx @@ -5,19 +5,21 @@ * 2.0. */ -import { createMemoryHistory, History, createHashHistory } from 'history'; +import type { History } from 'history'; +import { createMemoryHistory, createHashHistory } from 'history'; import React, { memo } from 'react'; -import { render as reactRender, RenderOptions, RenderResult, act } from '@testing-library/react'; +import type { RenderOptions, RenderResult } from '@testing-library/react'; +import { render as reactRender, act } from '@testing-library/react'; import { ScopedHistory } from '../../../../../../../src/core/public'; import { FleetAppContext } from '../app'; -import { FleetConfigType } from '../../../plugin'; -import { UIExtensionsStorage } from '../types'; +import type { FleetConfigType } from '../../../plugin'; +import type { UIExtensionsStorage } from '../types'; import { createConfigurationMock } from './plugin_configuration'; import { createStartMock } from './plugin_interfaces'; import { createStartServices } from './fleet_start_services'; -import { MockedFleetStart, MockedFleetStartServices } from './types'; +import type { MockedFleetStart, MockedFleetStartServices } from './types'; type UiRender = (ui: React.ReactElement, options?: RenderOptions) => RenderResult; diff --git a/x-pack/plugins/fleet/public/applications/fleet/mock/fleet_start_services.tsx b/x-pack/plugins/fleet/public/applications/fleet/mock/fleet_start_services.tsx index 4dfa0805bdbc51..ab75962c344df7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/mock/fleet_start_services.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/mock/fleet_start_services.tsx @@ -7,14 +7,15 @@ import React from 'react'; import { I18nProvider } from '@kbn/i18n/react'; -import { MockedKeys } from '@kbn/utility-types/jest'; +import type { MockedKeys } from '@kbn/utility-types/jest'; import { coreMock } from '../../../../../../../src/core/public/mocks'; -import { IStorage, Storage } from '../../../../../../../src/plugins/kibana_utils/public'; +import type { IStorage } from '../../../../../../../src/plugins/kibana_utils/public'; +import { Storage } from '../../../../../../../src/plugins/kibana_utils/public'; import { setHttpClient } from '../hooks/use_request'; import { createStartDepsMock } from './plugin_dependencies'; -import { MockedFleetStartServices } from './types'; +import type { MockedFleetStartServices } from './types'; // Taken from core. See: src/plugins/kibana_utils/public/storage/storage.test.ts const createMockStore = (): MockedKeys => { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx index 0db0f1f8cab728..9ee0b0a7b29ee4 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx @@ -9,7 +9,7 @@ import React, { memo, useState, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiContextMenuItem, EuiPortal } from '@elastic/eui'; -import { AgentPolicy } from '../../../types'; +import type { AgentPolicy } from '../../../types'; import { useCapabilities } from '../../../hooks'; import { ContextMenuActions } from '../../../components'; import { AgentEnrollmentFlyout } from '../../agents/components'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_copy_provider.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_copy_provider.tsx index d16a0351f1bbd3..ef623d30b88471 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_copy_provider.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_copy_provider.tsx @@ -10,7 +10,7 @@ import { EuiConfirmModal, EuiFormRow, EuiFieldText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { AgentPolicy } from '../../../types'; +import type { AgentPolicy } from '../../../types'; import { sendCopyAgentPolicy, useStartServices } from '../../../hooks'; interface Props { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx index c0da3a72dce62d..238cba217da8ec 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx @@ -26,7 +26,7 @@ import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; import { dataTypes } from '../../../../../../common'; -import { NewAgentPolicy, AgentPolicy } from '../../../types'; +import type { NewAgentPolicy, AgentPolicy } from '../../../types'; import { isValidNamespace } from '../../../services'; import { AgentPolicyDeleteProvider } from './agent_policy_delete_provider'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/confirm_deploy_modal.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/confirm_deploy_modal.tsx index f75aea703fd19f..fc34680eab0637 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/confirm_deploy_modal.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/confirm_deploy_modal.tsx @@ -10,7 +10,7 @@ import { EuiCallOut, EuiConfirmModal, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { AgentPolicy } from '../../../types'; +import type { AgentPolicy } from '../../../types'; export const ConfirmDeployAgentPolicyModal: React.FunctionComponent<{ onConfirm: () => void; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/package_policy_delete_provider.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/package_policy_delete_provider.tsx index 05b2bbf46b9e95..198886e9b9c7ff 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/package_policy_delete_provider.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/package_policy_delete_provider.tsx @@ -12,7 +12,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { useStartServices, sendRequest, sendDeletePackagePolicy, useConfig } from '../../../hooks'; import { AGENT_API_ROUTES, AGENT_SAVED_OBJECT_TYPE } from '../../../constants'; -import { AgentPolicy } from '../../../types'; +import type { AgentPolicy } from '../../../types'; interface Props { agentPolicy: AgentPolicy; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx index 0f51b603a49aa8..4aaf5343638bed 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx @@ -19,9 +19,9 @@ import { } from '@elastic/eui'; import { WithHeaderLayout } from '../../../../layouts'; -import { AgentPolicy, PackageInfo } from '../../../../types'; +import type { AgentPolicy, PackageInfo } from '../../../../types'; import { PackageIcon } from '../../../../components/package_icon'; -import { CreatePackagePolicyFrom } from '../types'; +import type { CreatePackagePolicyFrom } from '../types'; export const CreatePackagePolicyPageLayout: React.FunctionComponent<{ from: CreatePackagePolicyFrom; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx index c5c1033f1b8008..037c716b42a361 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx @@ -17,12 +17,9 @@ import { EuiButtonEmpty, } from '@elastic/eui'; -import { NewPackagePolicyInput, RegistryVarsEntry } from '../../../../types'; -import { - isAdvancedVar, - PackagePolicyConfigValidationResults, - validationHasErrors, -} from '../services'; +import type { NewPackagePolicyInput, RegistryVarsEntry } from '../../../../types'; +import type { PackagePolicyConfigValidationResults } from '../services'; +import { isAdvancedVar, validationHasErrors } from '../services'; import { PackagePolicyInputVarField } from './package_policy_input_var_field'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx index 92098d6971d35a..75d7a7549f721e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx @@ -19,17 +19,14 @@ import { EuiSpacer, } from '@elastic/eui'; -import { +import type { NewPackagePolicyInput, PackagePolicyInputStream, RegistryInput, RegistryStream, } from '../../../../types'; -import { - PackagePolicyInputValidationResults, - hasInvalidButRequiredVar, - countValidationErrors, -} from '../services'; +import type { PackagePolicyInputValidationResults } from '../services'; +import { hasInvalidButRequiredVar, countValidationErrors } from '../services'; import { PackagePolicyInputConfig } from './package_policy_input_config'; import { PackagePolicyInputStreamConfig } from './package_policy_input_stream'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx index b21e8ca2e2243d..3337af74371122 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx @@ -19,12 +19,13 @@ import { EuiButtonEmpty, } from '@elastic/eui'; -import { NewPackagePolicyInputStream, RegistryStream, RegistryVarsEntry } from '../../../../types'; -import { - isAdvancedVar, - PackagePolicyConfigValidationResults, - validationHasErrors, -} from '../services'; +import type { + NewPackagePolicyInputStream, + RegistryStream, + RegistryVarsEntry, +} from '../../../../types'; +import type { PackagePolicyConfigValidationResults } from '../services'; +import { isAdvancedVar, validationHasErrors } from '../services'; import { PackagePolicyInputVarField } from './package_policy_input_var_field'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_var_field.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_var_field.tsx index 1a53607711a0aa..15712f9042eb9a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_var_field.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_var_field.tsx @@ -18,7 +18,7 @@ import { EuiFieldPassword, } from '@elastic/eui'; -import { RegistryVarsEntry } from '../../../../types'; +import type { RegistryVarsEntry } from '../../../../types'; import 'brace/mode/yaml'; import 'brace/theme/textmate'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.test.tsx index d9d43dc6ef975d..11c672aa0596d2 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.test.tsx @@ -9,9 +9,10 @@ import { Route } from 'react-router-dom'; import React from 'react'; import { act } from 'react-test-renderer'; -import { createTestRendererMock, MockedFleetStartServices, TestRenderer } from '../../../mock'; +import type { MockedFleetStartServices, TestRenderer } from '../../../mock'; +import { createTestRendererMock } from '../../../mock'; import { PAGE_ROUTING_PATHS, pagePathGetters, PLUGIN_ID } from '../../../constants'; -import { CreatePackagePolicyRouteState } from '../../../types'; +import type { CreatePackagePolicyRouteState } from '../../../types'; import { CreatePackagePolicyPage } from './index'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx index bf6540db7dee7d..f312220d9faec5 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { useState, useEffect, useMemo, useCallback, ReactEventHandler } from 'react'; +import type { ReactEventHandler } from 'react'; +import React, { useState, useEffect, useMemo, useCallback } from 'react'; import { useRouteMatch, useHistory } from 'react-router-dom'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; @@ -19,10 +20,10 @@ import { EuiFlexItem, EuiSpacer, } from '@elastic/eui'; -import { EuiStepProps } from '@elastic/eui/src/components/steps/step'; -import { ApplicationStart } from 'kibana/public'; +import type { EuiStepProps } from '@elastic/eui/src/components/steps/step'; +import type { ApplicationStart } from 'kibana/public'; -import { +import type { AgentPolicy, PackageInfo, NewPackagePolicy, @@ -41,17 +42,14 @@ import { ConfirmDeployAgentPolicyModal } from '../components'; import { useIntraAppState } from '../../../hooks/use_intra_app_state'; import { useUIExtension } from '../../../hooks/use_ui_extension'; import { ExtensionWrapper } from '../../../components/extension_wrapper'; -import { PackagePolicyEditExtensionComponentProps } from '../../../types'; +import type { PackagePolicyEditExtensionComponentProps } from '../../../types'; import { PLUGIN_ID } from '../../../../../../common/constants'; import { pkgKeyFromPackageInfo } from '../../../services/pkg_key_from_package_info'; import { CreatePackagePolicyPageLayout } from './components'; -import { CreatePackagePolicyFrom, PackagePolicyFormState } from './types'; -import { - PackagePolicyValidationResults, - validatePackagePolicy, - validationHasErrors, -} from './services'; +import type { CreatePackagePolicyFrom, PackagePolicyFormState } from './types'; +import type { PackagePolicyValidationResults } from './services'; +import { validatePackagePolicy, validationHasErrors } from './services'; import { StepSelectPackage } from './step_select_package'; import { StepSelectAgentPolicy } from './step_select_agent_policy'; import { StepConfigurePackagePolicy } from './step_configure_package'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx index 35619f528f9070..87c5af56f31b26 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx @@ -15,7 +15,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { +import type { PackageInfo, RegistryStream, NewPackagePolicy, @@ -23,9 +23,9 @@ import { } from '../../../types'; import { Loading } from '../../../components'; -import { PackagePolicyValidationResults } from './services'; +import type { PackagePolicyValidationResults } from './services'; import { PackagePolicyInputPanel } from './components'; -import { CreatePackagePolicyFrom } from './types'; +import type { CreatePackagePolicyFrom } from './types'; const findStreamsForInputType = ( inputType: string, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx index fdcb4e15112a63..25234e5da3f817 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx @@ -19,12 +19,12 @@ import { EuiFlexItem, } from '@elastic/eui'; -import { AgentPolicy, PackageInfo, PackagePolicy, NewPackagePolicy } from '../../../types'; +import type { AgentPolicy, PackageInfo, PackagePolicy, NewPackagePolicy } from '../../../types'; import { packageToPackagePolicyInputs } from '../../../services'; import { Loading } from '../../../components'; import { pkgKeyFromPackageInfo } from '../../../services/pkg_key_from_package_info'; -import { PackagePolicyValidationResults } from './services'; +import type { PackagePolicyValidationResults } from './services'; export const StepDefinePackagePolicy: React.FunctionComponent<{ agentPolicy: AgentPolicy; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx index 5359075ed19c37..53fab8bda4a8c8 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx @@ -9,11 +9,11 @@ import React, { useEffect, useState, useMemo } from 'react'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import type { EuiComboBoxOptionOption } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiComboBox, - EuiComboBoxOptionOption, EuiTextColor, EuiPortal, EuiFormRow, @@ -21,7 +21,7 @@ import { } from '@elastic/eui'; import { Error } from '../../../components'; -import { AgentPolicy, PackageInfo, GetAgentPoliciesResponseItem } from '../../../types'; +import type { AgentPolicy, PackageInfo, GetAgentPoliciesResponseItem } from '../../../types'; import { isPackageLimited, doesAgentPolicyAlreadyIncludePackage } from '../../../services'; import { useGetPackageInfoByKey, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_package.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_package.tsx index 346dba971a2b42..80faba37e9a435 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_package.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_package.tsx @@ -11,7 +11,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, EuiSelectable, EuiSpacer } from '@elastic/eui'; import { Error } from '../../../components'; -import { AgentPolicy, PackageInfo, PackagePolicy, GetPackagesResponse } from '../../../types'; +import type { AgentPolicy, PackageInfo, PackagePolicy, GetPackagesResponse } from '../../../types'; import { useGetOneAgentPolicy, useGetPackages, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/index.tsx index c131183f20bdcf..04ffa42a8cc333 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/index.tsx @@ -7,7 +7,7 @@ import React, { memo } from 'react'; -import { AgentPolicy, PackagePolicy } from '../../../../../types'; +import type { AgentPolicy, PackagePolicy } from '../../../../../types'; import { NoPackagePolicies } from './no_package_policies'; import { PackagePoliciesTable } from './package_policies_table'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx index 899c457bbd9f9d..db88de0ba720b4 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx @@ -8,9 +8,9 @@ import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import type { EuiInMemoryTableProps } from '@elastic/eui'; import { EuiInMemoryTable, - EuiInMemoryTableProps, EuiBadge, EuiContextMenuItem, EuiButton, @@ -19,7 +19,7 @@ import { EuiText, } from '@elastic/eui'; -import { AgentPolicy, PackagePolicy } from '../../../../../types'; +import type { AgentPolicy, PackagePolicy } from '../../../../../types'; import { PackageIcon, ContextMenuActions } from '../../../../../components'; import { PackagePolicyDeleteProvider, DangerEuiContextMenuItem } from '../../../components'; import { useCapabilities, useLink } from '../../../../../hooks'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx index c10c1976913906..e91d32a78ec963 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx @@ -12,7 +12,7 @@ import { EuiBottomBar, EuiFlexGroup, EuiFlexItem, EuiButtonEmpty, EuiButton } fr import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { AgentPolicy } from '../../../../../types'; +import type { AgentPolicy } from '../../../../../types'; import { useLink, useStartServices, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/hooks/use_agent_status.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/hooks/use_agent_status.tsx index 8171dc12483a84..1147e9cf254fa3 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/hooks/use_agent_status.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/hooks/use_agent_status.tsx @@ -8,9 +8,9 @@ import React from 'react'; import { useRequest } from '../../../../hooks'; -import { GetAgentStatusResponse } from '../../../../types'; +import type { GetAgentStatusResponse } from '../../../../types'; import { agentRouteService } from '../../../../services'; -import { UseRequestConfig } from '../../../../hooks/use_request/use_request'; +import type { UseRequestConfig } from '../../../../hooks/use_request/use_request'; type RequestOptions = Pick, 'pollIntervalMs'>; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx index d365d9407ffc0e..350d6439c9d3d4 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx @@ -20,10 +20,10 @@ import { EuiDescriptionListTitle, EuiDescriptionListDescription, } from '@elastic/eui'; -import { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab'; +import type { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab'; import styled from 'styled-components'; -import { AgentPolicy, AgentPolicyDetailsDeployAgentAction } from '../../../types'; +import type { AgentPolicy, AgentPolicyDetailsDeployAgentAction } from '../../../types'; import { PAGE_ROUTING_PATHS } from '../../../constants'; import { useGetOneAgentPolicy, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx index 05ad1848a0bf55..8df8b7ebcd4cfb 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx @@ -18,7 +18,7 @@ import { EuiSpacer, } from '@elastic/eui'; -import { AgentPolicy, PackageInfo, UpdatePackagePolicy } from '../../../types'; +import type { AgentPolicy, PackageInfo, UpdatePackagePolicy } from '../../../types'; import { useLink, useBreadcrumbs, @@ -33,12 +33,9 @@ import { import { Loading, Error } from '../../../components'; import { ConfirmDeployAgentPolicyModal } from '../components'; import { CreatePackagePolicyPageLayout } from '../create_package_policy_page/components'; -import { - PackagePolicyValidationResults, - validatePackagePolicy, - validationHasErrors, -} from '../create_package_policy_page/services'; -import { +import type { PackagePolicyValidationResults } from '../create_package_policy_page/services'; +import { validatePackagePolicy, validationHasErrors } from '../create_package_policy_page/services'; +import type { PackagePolicyFormState, CreatePackagePolicyFrom, } from '../create_package_policy_page/types'; @@ -46,8 +43,8 @@ import { StepConfigurePackagePolicy } from '../create_package_policy_page/step_c import { StepDefinePackagePolicy } from '../create_package_policy_page/step_define_package_policy'; import { useUIExtension } from '../../../hooks/use_ui_extension'; import { ExtensionWrapper } from '../../../components/extension_wrapper'; -import { GetOnePackagePolicyResponse } from '../../../../../../common/types/rest_spec'; -import { PackagePolicyEditExtensionComponentProps } from '../../../types'; +import type { GetOnePackagePolicyResponse } from '../../../../../../common/types/rest_spec'; +import type { PackagePolicyEditExtensionComponentProps } from '../../../types'; import { pkgKeyFromPackageInfo } from '../../../services/pkg_key_from_package_info'; export const EditPackagePolicyPage = memo(() => { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx index 3e103775ae4949..33dbbb25c5d42c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx @@ -9,6 +9,7 @@ import React, { useState } from 'react'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import type { EuiFlyoutProps } from '@elastic/eui'; import { EuiFlyout, EuiFlyoutHeader, @@ -20,12 +21,11 @@ import { EuiButtonEmpty, EuiButton, EuiText, - EuiFlyoutProps, EuiSpacer, } from '@elastic/eui'; import { dataTypes } from '../../../../../../../common'; -import { NewAgentPolicy, AgentPolicy } from '../../../../types'; +import type { NewAgentPolicy, AgentPolicy } from '../../../../types'; import { useCapabilities, useStartServices, sendCreateAgentPolicy } from '../../../../hooks'; import { AgentPolicyForm, agentPolicyFormValidation } from '../../components'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx index 795c1934c33615..55788da05bcea4 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx @@ -6,6 +6,7 @@ */ import React, { useCallback, useMemo, useState } from 'react'; +import type { EuiTableActionsColumnType, EuiTableFieldDataColumnType } from '@elastic/eui'; import { EuiSpacer, EuiText, @@ -15,16 +16,14 @@ import { EuiEmptyPrompt, EuiBasicTable, EuiLink, - EuiTableActionsColumnType, - EuiTableFieldDataColumnType, EuiTextColor, } from '@elastic/eui'; -import { CriteriaWithPagination } from '@elastic/eui/src/components/basic_table/basic_table'; +import type { CriteriaWithPagination } from '@elastic/eui/src/components/basic_table/basic_table'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, FormattedDate } from '@kbn/i18n/react'; import { useHistory } from 'react-router-dom'; -import { AgentPolicy } from '../../../types'; +import type { AgentPolicy } from '../../../types'; import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../../constants'; import { WithHeaderLayout } from '../../../layouts'; import { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx index 887e91e1c375d3..e5ad131bd7e051 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx @@ -9,7 +9,7 @@ import React, { memo, useState, useMemo } from 'react'; import { EuiPortal, EuiContextMenuItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Agent } from '../../../../types'; +import type { Agent } from '../../../../types'; import { useCapabilities, useKibanaVersion } from '../../../../hooks'; import { ContextMenuActions } from '../../../../components'; import { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx index bbd47b9afb8c9c..6e0206603a458e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx @@ -6,6 +6,7 @@ */ import React, { memo, useMemo } from 'react'; +import type { EuiBasicTableProps } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, @@ -16,12 +17,11 @@ import { EuiPanel, EuiButtonIcon, EuiBasicTable, - EuiBasicTableProps, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; -import { Agent, AgentPolicy, PackagePolicy, PackagePolicyInput } from '../../../../../types'; +import type { Agent, AgentPolicy, PackagePolicy, PackagePolicyInput } from '../../../../../types'; import { useLink } from '../../../../../hooks'; import { PackageIcon } from '../../../../../components'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx index 28d7b026eb7378..482861b3db9e8b 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx @@ -20,7 +20,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Agent, AgentPolicy } from '../../../../../types'; +import type { Agent, AgentPolicy } from '../../../../../types'; import { useKibanaVersion, useLink } from '../../../../../hooks'; import { isAgentUpgradeable } from '../../../../../services'; import { AgentPolicyPackageBadges } from '../../../components/agent_policy_package_badges'; @@ -55,7 +55,7 @@ export const AgentDetailsOverviewSection: React.FunctionComponent<{ {agentPolicy.name || agentPolicy.id} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/index.tsx index 656bb8509aa308..a0afa635beab8f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/index.tsx @@ -10,7 +10,7 @@ import styled from 'styled-components'; import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Agent, AgentPolicy } from '../../../../../types'; +import type { Agent, AgentPolicy } from '../../../../../types'; import { AgentDetailsOverviewSection } from './agent_details_overview'; import { AgentDetailsIntegrationsSection } from './agent_details_integrations'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx index ed2ae773bbb7ec..6bc1db2fc4c7df 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx @@ -28,9 +28,10 @@ import semverCoerce from 'semver/functions/coerce'; import { createStateContainerReactHelpers } from '../../../../../../../../../../../src/plugins/kibana_utils/public'; import { RedirectAppLinks } from '../../../../../../../../../../../src/plugins/kibana_react/public'; -import { TimeRange, esKuery } from '../../../../../../../../../../../src/plugins/data/public'; +import type { TimeRange } from '../../../../../../../../../../../src/plugins/data/public'; +import { esKuery } from '../../../../../../../../../../../src/plugins/data/public'; import { LogStream } from '../../../../../../../../../infra/public'; -import { Agent } from '../../../../../types'; +import type { Agent } from '../../../../../types'; import { useStartServices } from '../../../../../hooks'; import { DEFAULT_DATE_RANGE } from './constants'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/constants.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/constants.tsx index fc277de634ba8b..f4c6e19f09322b 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/constants.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/constants.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { AgentLogsState } from './agent_logs'; +import type { AgentLogsState } from './agent_logs'; export const AGENT_LOG_INDEX_PATTERN = 'logs-elastic_agent-*,logs-elastic_agent.*-*'; export const AGENT_DATASET = 'elastic_agent'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/index.tsx index 7e37e5fb49cb31..ff31ffef039251 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/index.tsx @@ -7,17 +7,20 @@ import React, { memo, useEffect, useState, useMemo } from 'react'; +import type { + INullableBaseStateContainer, + PureTransition, +} from '../../../../../../../../../../../src/plugins/kibana_utils/public'; import { createStateContainer, syncState, createKbnUrlStateStorage, - INullableBaseStateContainer, - PureTransition, getStateFromKbnUrl, } from '../../../../../../../../../../../src/plugins/kibana_utils/public'; import { DEFAULT_LOGS_STATE, STATE_STORAGE_KEY } from './constants'; -import { AgentLogsUI, AgentLogsProps, AgentLogsState, AgentLogsUrlStateHelper } from './agent_logs'; +import type { AgentLogsProps, AgentLogsState } from './agent_logs'; +import { AgentLogsUI, AgentLogsUrlStateHelper } from './agent_logs'; export const AgentLogs: React.FunctionComponent> = memo( ({ agent }) => { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/query_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/query_bar.tsx index 776c866e22f4b7..a05068534ab884 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/query_bar.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/query_bar.tsx @@ -8,10 +8,8 @@ import React, { memo, useState, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; -import { - QueryStringInput, - IFieldType, -} from '../../../../../../../../../../../src/plugins/data/public'; +import type { IFieldType } from '../../../../../../../../../../../src/plugins/data/public'; +import { QueryStringInput } from '../../../../../../../../../../../src/plugins/data/public'; import { useStartServices } from '../../../../../hooks'; import { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/select_log_level.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/select_log_level.tsx index d4ea76605b7773..5aadecf2ed27c3 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/select_log_level.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/select_log_level.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiSelect, EuiFormLabel, EuiButtonEmpty, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; -import { Agent } from '../../../../../types'; +import type { Agent } from '../../../../../types'; import { sendPostAgentAction, useStartServices } from '../../../../../hooks'; import { AGENT_LOG_LEVELS, DEFAULT_LOG_LEVEL } from './constants'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx index dc1e25401bf188..adeb56f489ea38 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx @@ -17,12 +17,12 @@ import { EuiDescriptionListTitle, EuiDescriptionListDescription, } from '@elastic/eui'; -import { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab'; +import type { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab'; import { FormattedMessage, FormattedRelative } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { EuiIconTip } from '@elastic/eui'; -import { Agent, AgentPolicy, AgentDetailsReassignPolicyAction } from '../../../types'; +import type { Agent, AgentPolicy, AgentDetailsReassignPolicyAction } from '../../../types'; import { PAGE_ROUTING_PATHS } from '../../../constants'; import { Loading, Error } from '../../../components'; import { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx index 1fe611dd97fcf1..c859d585f4d82b 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx @@ -20,7 +20,7 @@ import { import { FormattedMessage, FormattedNumber } from '@kbn/i18n/react'; import { SO_SEARCH_LIMIT } from '../../../../constants'; -import { Agent } from '../../../../types'; +import type { Agent } from '../../../../types'; import { AgentReassignAgentPolicyFlyout, AgentUnenrollAgentModal, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx index aface1fc9ac4b9..1beaf437ceb0e9 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx @@ -17,7 +17,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { AgentPolicy } from '../../../../types'; +import type { AgentPolicy } from '../../../../types'; import { SearchBar } from '../../../../components'; import { AGENTS_INDEX } from '../../../../constants'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/status_badges.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/status_badges.tsx index 755d895d3b7687..35f13dc90e6e68 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/status_badges.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/status_badges.tsx @@ -13,7 +13,7 @@ import { getColorForAgentStatus, getLabelForAgentStatus, } from '../../services/agent_status'; -import { SimplifiedAgentStatus } from '../../../../types'; +import type { SimplifiedAgentStatus } from '../../../../types'; export const AgentStatusBadges: React.FC<{ showInactive?: boolean; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/status_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/status_bar.tsx index 64bf580f15b5a6..728134768b7990 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/status_bar.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/status_bar.tsx @@ -10,7 +10,7 @@ import { EuiColorPaletteDisplay } from '@elastic/eui'; import React, { useMemo } from 'react'; import { AGENT_STATUSES, getColorForAgentStatus } from '../../services/agent_status'; -import { SimplifiedAgentStatus } from '../../../../types'; +import type { SimplifiedAgentStatus } from '../../../../types'; const StyledEuiColorPaletteDisplay = styled(EuiColorPaletteDisplay)` &.ingest-agent-status-bar { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_header.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_header.tsx index 9d3fb45ff2ff8b..ff809d360e744f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_header.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_header.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { Agent, SimplifiedAgentStatus } from '../../../../types'; +import type { Agent, SimplifiedAgentStatus } from '../../../../types'; import { AgentStatusBar } from './status_bar'; import { AgentBulkActions } from './bulk_actions'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx index a66105a15f17cc..faf5da5f17f6bb 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx @@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage, FormattedRelative } from '@kbn/i18n/react'; import { AgentEnrollmentFlyout } from '../components'; -import { Agent, AgentPolicy, SimplifiedAgentStatus } from '../../../types'; +import type { Agent, AgentPolicy, SimplifiedAgentStatus } from '../../../types'; import { usePagination, useCapabilities, @@ -48,7 +48,7 @@ import { } from '../components'; import { AgentTableHeader } from './components/table_header'; -import { SelectionMode } from './components/bulk_actions'; +import type { SelectionMode } from './components/bulk_actions'; import { SearchAndFilterBar } from './components/search_and_filter_bar'; const REFRESH_INTERVAL_MS = 30000; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/agent_policy_selection.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/agent_policy_selection.tsx index 09d66b9b7cfadf..8d639b48681e36 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/agent_policy_selection.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/agent_policy_selection.tsx @@ -11,7 +11,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiSelect, EuiSpacer, EuiText, EuiButtonEmpty } from '@elastic/eui'; import { SO_SEARCH_LIMIT } from '../../../../constants'; -import { AgentPolicy, GetEnrollmentAPIKeysResponse } from '../../../../types'; +import type { AgentPolicy, GetEnrollmentAPIKeysResponse } from '../../../../types'; import { sendGetEnrollmentAPIKeys, useStartServices } from '../../../../hooks'; import { AgentPolicyPackageBadges } from '../agent_policy_package_badges'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/index.tsx index e013e354881613..fadb7ab425ceb3 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/index.tsx @@ -23,7 +23,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { AgentPolicy } from '../../../../types'; +import type { AgentPolicy } from '../../../../types'; import { ManagedInstructions } from './managed_instructions'; import { StandaloneInstructions } from './standalone_instructions'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/managed_instructions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/managed_instructions.tsx index 5a22a47c2593c7..4925f60f19e26a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/managed_instructions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/managed_instructions.tsx @@ -7,11 +7,11 @@ import React, { useState } from 'react'; import { EuiSteps, EuiLink, EuiText, EuiSpacer } from '@elastic/eui'; -import { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps'; +import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { AgentPolicy } from '../../../../types'; +import type { AgentPolicy } from '../../../../types'; import { useGetOneEnrollmentAPIKey, useStartServices, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/standalone_instructions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/standalone_instructions.tsx index 6e4f4168a64c5c..7ccdfe05724f11 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/standalone_instructions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/standalone_instructions.tsx @@ -18,11 +18,11 @@ import { EuiCopy, EuiLink, } from '@elastic/eui'; -import { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps'; +import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { AgentPolicy } from '../../../../types'; +import type { AgentPolicy } from '../../../../types'; import { useStartServices, useLink, sendGetOneAgentPolicyFull } from '../../../../hooks'; import { fullAgentPolicyToYaml, agentPolicyRouteService } from '../../../../services'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/steps.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/steps.tsx index 8ea2259ca80c33..1d5e8cbfc5cd6e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/steps.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/steps.tsx @@ -10,7 +10,7 @@ import { EuiText, EuiButton, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { AgentPolicy } from '../../../../types'; +import type { AgentPolicy } from '../../../../types'; import { EnrollmentStepAgentPolicy } from './agent_policy_selection'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx index 26ad0550ae71f9..eb693775a65c99 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { FormattedMessage, FormattedRelative } from '@kbn/i18n/react'; import { EuiBadge, EuiToolTip } from '@elastic/eui'; -import { Agent } from '../../../types'; +import type { Agent } from '../../../types'; interface Props { agent: Agent; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_package_badges.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_package_badges.tsx index e7868e4b6fe105..dcc87b0032d77f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_package_badges.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_package_badges.tsx @@ -9,7 +9,7 @@ import React, { useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiSpacer, EuiText, EuiFlexGroup, EuiFlexItem, EuiBadge } from '@elastic/eui'; -import { PackagePolicy, PackagePolicyPackage } from '../../../types'; +import type { PackagePolicy, PackagePolicyPackage } from '../../../types'; import { useGetOneAgentPolicy } from '../../../hooks'; import { PackageIcon } from '../../../components/package_icon'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_flyout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_flyout/index.tsx index 265adc29164c55..a23b27cf888d6a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_flyout/index.tsx @@ -24,7 +24,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Agent } from '../../../../types'; +import type { Agent } from '../../../../types'; import { sendPutAgentReassign, sendPostBulkAgentReassign, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx index fe550123fec2ab..696acb49abef3d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { EuiConfirmModal, EuiFormFieldset, EuiCheckbox } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Agent } from '../../../../types'; +import type { Agent } from '../../../../types'; import { sendPostAgentUnenroll, sendPostBulkAgentUnenroll, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx index 90c291f249ef57..e8a4049d129c14 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { EuiConfirmModal, EuiBetaBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Agent } from '../../../../types'; +import type { Agent } from '../../../../types'; import { sendPostAgentUpgrade, sendPostBulkAgentUpgrade, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/list_layout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/list_layout.tsx index 6c79ab6657fa7f..f31ee70da21bbb 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/list_layout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/list_layout.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiText, EuiFlexGroup, EuiFlexItem, EuiButton, EuiPortal } from '@elastic/eui'; -import { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab'; +import type { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab'; import { useRouteMatch } from 'react-router-dom'; import { PAGE_ROUTING_PATHS } from '../../../constants'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/confirm_delete_modal.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/confirm_delete_modal.tsx index 41dab3d6479e0f..a7dd7d4938669d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/confirm_delete_modal.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/confirm_delete_modal.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiConfirmModal, EuiCallOut } from '@elastic/eui'; -import { EnrollmentAPIKey } from '../../../../types'; +import type { EnrollmentAPIKey } from '../../../../types'; interface Props { onCancel: () => void; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx index 9644845ca4ff53..a21e59c5c220f0 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx @@ -24,7 +24,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { AgentPolicy } from '../../../../types'; +import type { AgentPolicy } from '../../../../types'; import { useInput, useStartServices, sendRequest } from '../../../../hooks'; import { enrollmentAPIKeyRouteService } from '../../../../services'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx index d7c66139ac77b5..a19cf714135fcd 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx @@ -7,6 +7,7 @@ import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; +import type { HorizontalAlignment } from '@elastic/eui'; import { EuiSpacer, EuiBasicTable, @@ -17,7 +18,6 @@ import { EuiToolTip, EuiIcon, EuiText, - HorizontalAlignment, } from '@elastic/eui'; import { FormattedMessage, FormattedDate } from '@kbn/i18n/react'; @@ -31,7 +31,7 @@ import { useStartServices, sendDeleteOneEnrollmentAPIKey, } from '../../../hooks'; -import { EnrollmentAPIKey } from '../../../types'; +import type { EnrollmentAPIKey } from '../../../types'; import { SearchBar } from '../../../components/search_bar'; import { NewEnrollmentTokenFlyout } from './components/new_enrollment_key_flyout'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/services/agent_status.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/services/agent_status.tsx index 301dd565f94c38..02b3a941437ecd 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/services/agent_status.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/services/agent_status.tsx @@ -8,7 +8,7 @@ import { euiPaletteColorBlindBehindText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { SimplifiedAgentStatus } from '../../../types'; +import type { SimplifiedAgentStatus } from '../../../types'; const visColors = euiPaletteColorBlindBehindText(); const colorToHexMap = { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/setup_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/setup_page/index.tsx index 860e081748f564..0fe75dd3a8bfc8 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/setup_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/setup_page/index.tsx @@ -27,7 +27,7 @@ import { import { useStartServices, sendPostFleetSetup } from '../../../hooks'; import { WithoutHeaderLayout } from '../../../layouts'; -import { GetFleetStatusResponse } from '../../../types'; +import type { GetFleetStatusResponse } from '../../../types'; export const RequirementItem: React.FunctionComponent<{ isMissing: boolean }> = ({ isMissing, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/components/data_stream_row_actions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/components/data_stream_row_actions.tsx index 0516a0a8829aff..4dedfc1bfdb4ee 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/components/data_stream_row_actions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/components/data_stream_row_actions.tsx @@ -9,7 +9,7 @@ import React, { memo } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { DataStream } from '../../../../types'; +import type { DataStream } from '../../../../types'; import { useKibanaLink } from '../../../../hooks'; import { ContextMenuActions } from '../../../../components'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx index dd525dcaf795b2..f8518dc2a86685 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx @@ -6,6 +6,7 @@ */ import React, { useMemo } from 'react'; +import type { EuiTableActionsColumnType, EuiTableFieldDataColumnType } from '@elastic/eui'; import { EuiBadge, EuiButton, @@ -14,13 +15,11 @@ import { EuiFlexItem, EuiEmptyPrompt, EuiInMemoryTable, - EuiTableActionsColumnType, - EuiTableFieldDataColumnType, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, FormattedDate } from '@kbn/i18n/react'; -import { DataStream } from '../../../types'; +import type { DataStream } from '../../../types'; import { WithHeaderLayout } from '../../../layouts'; import { useGetDataStreams, useStartServices, usePagination, useBreadcrumbs } from '../../../hooks'; import { PackageIcon } from '../../../components/package_icon'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/assets_facet_group.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/assets_facet_group.tsx index a88c6ceaea0c46..2380a1fb877569 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/assets_facet_group.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/assets_facet_group.tsx @@ -19,12 +19,12 @@ import { import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n/react'; -import { +import type { AssetsGroupedByServiceByType, AssetTypeToParts, KibanaAssetType, - entries, } from '../../../types'; +import { entries } from '../../../types'; import { AssetIcons, AssetTitleMap, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/icon_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/icon_panel.tsx index 9574ecbcf4e0c7..63c6897021f4e6 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/icon_panel.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/icon_panel.tsx @@ -9,7 +9,8 @@ import React from 'react'; import styled from 'styled-components'; import { EuiIcon, EuiPanel } from '@elastic/eui'; -import { usePackageIconType, UsePackageIconType } from '../../../hooks'; +import type { UsePackageIconType } from '../../../hooks'; +import { usePackageIconType } from '../../../hooks'; import { Loading } from '../../../components'; const PanelWrapper = styled.div` diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/icons.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/icons.tsx index d7ad6667b6db0b..86346fc2988dd3 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/icons.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/icons.tsx @@ -6,7 +6,8 @@ */ import React from 'react'; -import { EuiIconTip, EuiIconProps } from '@elastic/eui'; +import type { EuiIconProps } from '@elastic/eui'; +import { EuiIconTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; export const UpdateIcon = ({ size = 'm' }: { size?: EuiIconProps['size'] }) => ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_card.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_card.tsx index 43985a9d8efa4d..b4592a2283d6de 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_card.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_card.tsx @@ -9,7 +9,7 @@ import React from 'react'; import styled from 'styled-components'; import { EuiCard } from '@elastic/eui'; -import { PackageInfo, PackageListItem } from '../../../types'; +import type { PackageInfo, PackageListItem } from '../../../types'; import { useLink } from '../../../hooks'; import { PackageIcon } from '../../../components/package_icon'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_list_grid.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_list_grid.tsx index da8f2c690a0102..9ce223ea8e456a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_list_grid.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_list_grid.tsx @@ -5,7 +5,9 @@ * 2.0. */ -import React, { Fragment, ReactNode, useState } from 'react'; +import type { ReactNode } from 'react'; +import React, { Fragment, useState } from 'react'; +import type { Query } from '@elastic/eui'; import { EuiFlexGrid, EuiFlexGroup, @@ -15,13 +17,12 @@ import { // @ts-ignore EuiSearchBar, EuiText, - Query, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { Loading } from '../../../components'; -import { PackageList } from '../../../types'; +import type { PackageList } from '../../../types'; import { useLocalSearch, searchIdField } from '../hooks'; import { pkgKeyFromPackageInfo } from '../../../services/pkg_key_from_package_info'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/requirements.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/requirements.tsx index 3cb0f1e061fa39..040d930e88a71f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/requirements.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/requirements.tsx @@ -9,7 +9,8 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText, EuiTextColor, EuiTitle } f import React, { Fragment } from 'react'; import styled from 'styled-components'; -import { RequirementsByServiceName, ServiceName, entries } from '../../../types'; +import type { RequirementsByServiceName, ServiceName } from '../../../types'; +import { entries } from '../../../types'; import { ServiceTitleMap } from '../constants'; import { Version } from './version'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/version.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/version.tsx index cde8f1ebebfb5b..231ba2ef40b5a6 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/version.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/version.tsx @@ -8,7 +8,7 @@ import React from 'react'; import styled from 'styled-components'; -import { RequirementVersion } from '../../../types'; +import type { RequirementVersion } from '../../../types'; const CodeText = styled.span` font-family: ${(props) => props.theme.eui.euiCodeFontFamily}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/constants.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/constants.tsx index a50c35765e6613..de7e16e1e5d2b7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/constants.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/constants.tsx @@ -5,9 +5,10 @@ * 2.0. */ -import { IconType } from '@elastic/eui'; +import type { IconType } from '@elastic/eui'; -import { AssetType, ElasticsearchAssetType, KibanaAssetType, ServiceName } from '../../types'; +import type { AssetType, ServiceName } from '../../types'; +import { ElasticsearchAssetType, KibanaAssetType } from '../../types'; // only allow Kibana assets for the kibana key, ES asssets for elasticsearch, etc type ServiceNameToAssetTypes = Record, KibanaAssetType[]> & diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_links.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_links.tsx index 784b7923c2d6d2..08bc53e4eea7f1 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_links.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_links.tsx @@ -8,7 +8,11 @@ import { useStartServices } from '../../../hooks/use_core'; import { PLUGIN_ID } from '../../../constants'; import { epmRouteService } from '../../../services'; -import { PackageSpecIcon, PackageSpecScreenshot, RegistryImage } from '../../../../../../common'; +import type { + PackageSpecIcon, + PackageSpecScreenshot, + RegistryImage, +} from '../../../../../../common'; const removeRelativePath = (relativePath: string): string => new URL(relativePath, 'http://example.com').pathname; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_local_search.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_local_search.tsx index 9c4839ff859f0f..efdc2f3884542f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_local_search.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_local_search.tsx @@ -8,7 +8,7 @@ import { Search as LocalSearch } from 'js-search'; import { useEffect, useRef } from 'react'; -import { PackageList, PackageListItem } from '../../../types'; +import type { PackageList, PackageListItem } from '../../../types'; export type SearchField = keyof PackageListItem; export const searchIdField: SearchField = 'name'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_package_install.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_package_install.tsx index b5412ce5b57ea8..4f8242fe42613a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_package_install.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_package_install.tsx @@ -9,10 +9,10 @@ import createContainer from 'constate'; import React, { useCallback, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n/react'; -import { NotificationsStart } from 'src/core/public'; +import type { NotificationsStart } from 'src/core/public'; import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_react/public'; -import { PackageInfo } from '../../../types'; +import type { PackageInfo } from '../../../types'; import { sendInstallPackage, sendRemovePackage, useLink } from '../../../hooks'; import { InstallStatus } from '../../../types'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/components/icon_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/components/icon_panel.tsx index 74d2b2fbc680fd..6d5d52789a9754 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/components/icon_panel.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/components/icon_panel.tsx @@ -9,7 +9,8 @@ import React from 'react'; import styled from 'styled-components'; import { EuiIcon, EuiPanel } from '@elastic/eui'; -import { usePackageIconType, UsePackageIconType } from '../../../../../hooks'; +import type { UsePackageIconType } from '../../../../../hooks'; +import { usePackageIconType } from '../../../../../hooks'; import { Loading } from '../../../../../components'; const PanelWrapper = styled.div` diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/components/update_icon.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/components/update_icon.tsx index d7ad6667b6db0b..86346fc2988dd3 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/components/update_icon.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/components/update_icon.tsx @@ -6,7 +6,8 @@ */ import React from 'react'; -import { EuiIconTip, EuiIconProps } from '@elastic/eui'; +import type { EuiIconProps } from '@elastic/eui'; +import { EuiIconTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; export const UpdateIcon = ({ size = 'm' }: { size?: EuiIconProps['size'] }) => ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/custom/custom.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/custom/custom.tsx index 3ea41436c22d83..6f76acbe187c67 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/custom/custom.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/custom/custom.tsx @@ -10,7 +10,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { useUIExtension } from '../../../../../hooks/use_ui_extension'; import { useLink } from '../../../../../hooks'; -import { PackageInfo } from '../../../../../types'; +import type { PackageInfo } from '../../../../../types'; import { pkgKeyFromPackageInfo } from '../../../../../services/pkg_key_from_package_info'; import { ExtensionWrapper } from '../../../../../components/extension_wrapper'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.test.tsx index e17e9bdfe2ef20..a8e9ddf1c8f789 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.test.tsx @@ -10,21 +10,25 @@ import { Route } from 'react-router-dom'; import { act, cleanup } from '@testing-library/react'; import { PAGE_ROUTING_PATHS, pagePathGetters } from '../../../../constants'; -import { +import type { GetAgentPoliciesResponse, GetFleetStatusResponse, GetInfoResponse, GetPackagePoliciesResponse, GetStatsResponse, } from '../../../../../../../common/types/rest_spec'; -import { DetailViewPanelName, KibanaAssetType } from '../../../../../../../common/types/models'; +import type { + DetailViewPanelName, + KibanaAssetType, +} from '../../../../../../../common/types/models'; import { agentPolicyRouteService, epmRouteService, fleetSetupRouteService, packagePolicyRouteService, } from '../../../../../../../common/services'; -import { createTestRendererMock, MockedFleetStartServices, TestRenderer } from '../../../../mock'; +import type { MockedFleetStartServices, TestRenderer } from '../../../../mock'; +import { createTestRendererMock } from '../../../../mock'; import { Detail } from './index'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.tsx index 49e63ba6e935b9..a4f465cd3d6195 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.tsx @@ -4,7 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { ReactEventHandler, useCallback, useEffect, useMemo, useState } from 'react'; +import type { ReactEventHandler } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Redirect, Route, Switch, useHistory, useLocation, useParams } from 'react-router-dom'; import styled from 'styled-components'; import { @@ -27,15 +28,16 @@ import { useUIExtension } from '../../../../hooks/use_ui_extension'; import { PAGE_ROUTING_PATHS, PLUGIN_ID } from '../../../../constants'; import { useCapabilities, useGetPackageInfoByKey, useLink } from '../../../../hooks'; import { pkgKeyFromPackageInfo } from '../../../../services/pkg_key_from_package_info'; -import { +import type { CreatePackagePolicyRouteState, DetailViewPanelName, - InstallStatus, PackageInfo, } from '../../../../types'; +import { InstallStatus } from '../../../../types'; import { Error, Loading } from '../../../../components'; import { useBreadcrumbs } from '../../../../hooks'; -import { WithHeaderLayout, WithHeaderLayoutProps } from '../../../../layouts'; +import type { WithHeaderLayoutProps } from '../../../../layouts'; +import { WithHeaderLayout } from '../../../../layouts'; import { RELEASE_BADGE_DESCRIPTION, RELEASE_BADGE_LABEL } from '../../components/release_badge'; import { useGetPackageInstallStatus, useSetPackageInstallStatus } from '../../hooks'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/overview/details.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/overview/details.tsx index 3d6573feab4599..487df179803452 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/overview/details.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/overview/details.tsx @@ -14,15 +14,15 @@ import { EuiDescriptionList, EuiNotificationBadge, } from '@elastic/eui'; -import { EuiDescriptionListProps } from '@elastic/eui/src/components/description_list/description_list'; +import type { EuiDescriptionListProps } from '@elastic/eui/src/components/description_list/description_list'; -import { +import type { PackageInfo, PackageSpecCategory, AssetTypeToParts, KibanaAssetType, - entries, } from '../../../../../types'; +import { entries } from '../../../../../types'; import { useGetCategories } from '../../../../../hooks'; import { AssetTitleMap, DisplayedAssets, ServiceTitleMap } from '../../../constants'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/overview/overview.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/overview/overview.tsx index da8a5133d77e69..bd6bece14f3e35 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/overview/overview.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/overview/overview.tsx @@ -8,7 +8,7 @@ import React, { memo } from 'react'; import styled from 'styled-components'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { PackageInfo } from '../../../../../types'; +import type { PackageInfo } from '../../../../../types'; import { Screenshots } from './screenshots'; import { Readme } from './readme'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/overview/screenshots.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/overview/screenshots.tsx index 4a278967229ad6..6df3a082ecb93d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/overview/screenshots.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/overview/screenshots.tsx @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, EuiImage, EuiText, EuiPagination } from '@elastic/eui'; -import { ScreenshotItem } from '../../../../../types'; +import type { ScreenshotItem } from '../../../../../types'; import { useLinks } from '../../../hooks'; interface ScreenshotProps { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/policies/package_policies.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/policies/package_policies.tsx index a573cd49e61ce1..94b4b748cb1bd7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/policies/package_policies.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/policies/package_policies.tsx @@ -5,30 +5,24 @@ * 2.0. */ -import React, { memo, ReactNode, useCallback, useMemo } from 'react'; +import type { ReactNode } from 'react'; +import React, { memo, useCallback, useMemo } from 'react'; import { Redirect } from 'react-router-dom'; -import { - CriteriaWithPagination, - EuiBasicTable, - EuiLink, - EuiTableFieldDataColumnType, - EuiFlexGroup, - EuiFlexItem, -} from '@elastic/eui'; +import type { CriteriaWithPagination, EuiTableFieldDataColumnType } from '@elastic/eui'; +import { EuiBasicTable, EuiLink, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedRelative, FormattedMessage } from '@kbn/i18n/react'; import { InstallStatus } from '../../../../../types'; import { useLink, useUrlPagination } from '../../../../../hooks'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../constants'; -import { LinkAndRevision, LinkAndRevisionProps } from '../../../../../components'; +import type { LinkAndRevisionProps } from '../../../../../components'; +import { LinkAndRevision } from '../../../../../components'; import { LinkedAgentCount } from '../../../../../components/linked_agent_count'; import { useGetPackageInstallStatus } from '../../../hooks'; -import { - PackagePolicyAndAgentPolicy, - usePackagePoliciesWithAgentPolicy, -} from './use_package_policies_with_agent_policy'; +import type { PackagePolicyAndAgentPolicy } from './use_package_policies_with_agent_policy'; +import { usePackagePoliciesWithAgentPolicy } from './use_package_policies_with_agent_policy'; import { Persona } from './persona'; const IntegrationDetailsLink = memo<{ diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/policies/persona.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/policies/persona.tsx index 02e36df57009e4..100945327e8247 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/policies/persona.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/policies/persona.tsx @@ -4,9 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { CSSProperties, memo, useCallback } from 'react'; +import type { CSSProperties } from 'react'; +import React, { memo, useCallback } from 'react'; import { EuiAvatar, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; -import { EuiAvatarProps } from '@elastic/eui/src/components/avatar/avatar'; +import type { EuiAvatarProps } from '@elastic/eui/src/components/avatar/avatar'; const MIN_WIDTH: CSSProperties = { minWidth: 0 }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/settings/installation_button.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/settings/installation_button.tsx index dd71d4090c86b9..5edb695188a2dd 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/settings/installation_button.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/settings/installation_button.tsx @@ -9,7 +9,8 @@ import { EuiButton } from '@elastic/eui'; import React, { Fragment, useCallback, useMemo, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { PackageInfo, InstallStatus } from '../../../../../types'; +import type { PackageInfo } from '../../../../../types'; +import { InstallStatus } from '../../../../../types'; import { useCapabilities } from '../../../../../hooks'; import { useUninstallPackage, useGetPackageInstallStatus, useInstallPackage } from '../../../hooks'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/settings/settings.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/settings/settings.tsx index 94c03f76cddaf1..55ef530d36bbad 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/settings/settings.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/settings/settings.tsx @@ -12,7 +12,8 @@ import semverLt from 'semver/functions/lt'; import { EuiTitle, EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer } from '@elastic/eui'; -import { InstallStatus, PackageInfo } from '../../../../../types'; +import type { PackageInfo } from '../../../../../types'; +import { InstallStatus } from '../../../../../types'; import { useGetPackagePolicies } from '../../../../../hooks'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../constants'; import { useGetPackageInstallStatus } from '../../../hooks'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/home/category_facets.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/home/category_facets.tsx index 6a754ca86d7aa4..e4d81f1d04118b 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/home/category_facets.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/home/category_facets.tsx @@ -9,7 +9,7 @@ import { EuiFacetButton, EuiFacetGroup } from '@elastic/eui'; import React from 'react'; import { Loading } from '../../../../components'; -import { CategorySummaryItem, CategorySummaryList } from '../../../../types'; +import type { CategorySummaryItem, CategorySummaryList } from '../../../../types'; export function CategoryFacets({ isLoading, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/home/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/home/index.tsx index 8f43adef71b10c..e1aafde8772e18 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/home/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/home/index.tsx @@ -7,14 +7,14 @@ import React, { useState } from 'react'; import { useRouteMatch, Switch, Route, useLocation, useHistory } from 'react-router-dom'; -import { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab'; +import type { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab'; import { i18n } from '@kbn/i18n'; import { installationStatuses } from '../../../../../../../common/constants'; import { PAGE_ROUTING_PATHS } from '../../../../constants'; import { useLink, useGetCategories, useGetPackages, useBreadcrumbs } from '../../../../hooks'; import { WithHeaderLayout } from '../../../../layouts'; -import { CategorySummaryItem } from '../../../../types'; +import type { CategorySummaryItem } from '../../../../types'; import { PackageListGrid } from '../../components/package_list_grid'; import { CategoryFacets } from './category_facets'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx index 9c3c76efddc1a6..79a4f08faa7522 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx @@ -17,7 +17,7 @@ import { import { SO_SEARCH_LIMIT } from '../../../constants'; import { useLink, useGetPackagePolicies } from '../../../hooks'; -import { AgentPolicy } from '../../../types'; +import type { AgentPolicy } from '../../../types'; import { Loading } from '../../agents/components'; import { OverviewStats } from './overview_stats'; diff --git a/x-pack/plugins/fleet/scripts/dev_agent/script.ts b/x-pack/plugins/fleet/scripts/dev_agent/script.ts index e937706080218d..b4bdea0c289965 100644 --- a/x-pack/plugins/fleet/scripts/dev_agent/script.ts +++ b/x-pack/plugins/fleet/scripts/dev_agent/script.ts @@ -7,7 +7,8 @@ import os from 'os'; -import { createFlagError, run, ToolingLog } from '@kbn/dev-utils'; +import type { ToolingLog } from '@kbn/dev-utils'; +import { createFlagError, run } from '@kbn/dev-utils'; import fetch from 'node-fetch'; import type { diff --git a/x-pack/plugins/fleet/server/collectors/agent_collectors.ts b/x-pack/plugins/fleet/server/collectors/agent_collectors.ts index 19be9a010889a5..de16f6555d4bd4 100644 --- a/x-pack/plugins/fleet/server/collectors/agent_collectors.ts +++ b/x-pack/plugins/fleet/server/collectors/agent_collectors.ts @@ -5,10 +5,10 @@ * 2.0. */ -import { SavedObjectsClient } from 'kibana/server'; +import type { SavedObjectsClient } from 'kibana/server'; import type { ElasticsearchClient } from 'kibana/server'; -import { FleetConfigType } from '../../common/types'; +import type { FleetConfigType } from '../../common/types'; import * as AgentService from '../services/agents'; import { isFleetServerSetup } from '../services/fleet_server'; diff --git a/x-pack/plugins/fleet/server/collectors/package_collectors.ts b/x-pack/plugins/fleet/server/collectors/package_collectors.ts index 20370231c7919e..01d1c2d966c780 100644 --- a/x-pack/plugins/fleet/server/collectors/package_collectors.ts +++ b/x-pack/plugins/fleet/server/collectors/package_collectors.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SavedObjectsClient } from 'kibana/server'; +import type { SavedObjectsClient } from 'kibana/server'; import _ from 'lodash'; import { getPackageSavedObjects } from '../services/epm/packages/get'; diff --git a/x-pack/plugins/fleet/server/errors/handlers.ts b/x-pack/plugins/fleet/server/errors/handlers.ts index 421f986ee8848c..073727729ad840 100644 --- a/x-pack/plugins/fleet/server/errors/handlers.ts +++ b/x-pack/plugins/fleet/server/errors/handlers.ts @@ -5,14 +5,16 @@ * 2.0. */ -import Boom, { isBoom } from '@hapi/boom'; +import type Boom from '@hapi/boom'; +import { isBoom } from '@hapi/boom'; +import { errors as LegacyESErrors } from 'elasticsearch'; + import type { IKibanaResponse, KibanaResponseFactory, RequestHandlerContext, } from 'src/core/server'; -import { KibanaRequest } from 'src/core/server'; -import { errors as LegacyESErrors } from 'elasticsearch'; +import type { KibanaRequest } from 'src/core/server'; import { appContextService } from '../services'; diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index a14f71a813ffc6..a62da8eb41a995 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { Observable } from 'rxjs'; +import type { Observable } from 'rxjs'; import { first } from 'rxjs/operators'; -import { +import type { CoreSetup, CoreStart, ElasticsearchServiceStart, @@ -20,23 +20,23 @@ import { RequestHandlerContext, KibanaRequest, } from 'kibana/server'; -import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import type { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; -import { LicensingPluginSetup, ILicense } from '../../licensing/server'; -import { +import type { LicensingPluginSetup, ILicense } from '../../licensing/server'; +import type { EncryptedSavedObjectsPluginStart, EncryptedSavedObjectsPluginSetup, } from '../../encrypted_saved_objects/server'; -import { SecurityPluginSetup, SecurityPluginStart } from '../../security/server'; -import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; -import { +import type { SecurityPluginSetup, SecurityPluginStart } from '../../security/server'; +import type { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; +import type { EsAssetReference, FleetConfigType, NewPackagePolicy, UpdatePackagePolicy, } from '../common'; -import { CloudSetup } from '../../cloud/server'; +import type { CloudSetup } from '../../cloud/server'; import { PLUGIN_ID, @@ -64,16 +64,18 @@ import { registerSettingsRoutes, registerAppRoutes, } from './routes'; +import type { + ESIndexPatternService, + AgentService, + AgentPolicyServiceInterface, + PackageService, +} from './services'; import { appContextService, licenseService, ESIndexPatternSavedObjectService, - ESIndexPatternService, - AgentService, - AgentPolicyServiceInterface, agentPolicyService, packagePolicyService, - PackageService, } from './services'; import { getAgentStatusById, diff --git a/x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts b/x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts index f7cc750ff1e6fc..f1e32f325dd0c4 100644 --- a/x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts @@ -10,7 +10,7 @@ import type { RequestHandler } from 'kibana/server'; import type { TypeOf } from '@kbn/config-schema'; -import { PostNewAgentActionRequestSchema } from '../../types/rest_spec'; +import type { PostNewAgentActionRequestSchema } from '../../types/rest_spec'; import type { ActionsService } from '../../services/agents'; import type { PostNewAgentActionResponse } from '../../../common/types/rest_spec'; import { defaultIngestErrorHandler } from '../../errors'; diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index 424aaab94f2b4b..e6188a83c49e9b 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -20,7 +20,7 @@ import type { PostAgentEnrollRequest, PostBulkAgentReassignResponse, } from '../../../common/types'; -import { +import type { GetAgentsRequestSchema, GetOneAgentRequestSchema, UpdateAgentRequestSchema, diff --git a/x-pack/plugins/fleet/server/routes/agent/index.ts b/x-pack/plugins/fleet/server/routes/agent/index.ts index e68bc3a88cc1ba..ec75768e816fe6 100644 --- a/x-pack/plugins/fleet/server/routes/agent/index.ts +++ b/x-pack/plugins/fleet/server/routes/agent/index.ts @@ -37,7 +37,7 @@ import { } from '../../types'; import * as AgentService from '../../services/agents'; import { appContextService } from '../../services'; -import { FleetConfigType } from '../..'; +import type { FleetConfigType } from '../..'; import { getAgentsHandler, diff --git a/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts b/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts index 079783cef6d8bb..558a9a8afbb0b4 100644 --- a/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts +++ b/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts @@ -12,7 +12,10 @@ import type { PostAgentUnenrollResponse, PostBulkAgentUnenrollResponse, } from '../../../common/types'; -import { PostAgentUnenrollRequestSchema, PostBulkAgentUnenrollRequestSchema } from '../../types'; +import type { + PostAgentUnenrollRequestSchema, + PostBulkAgentUnenrollRequestSchema, +} from '../../types'; import { licenseService } from '../../services'; import * as AgentService from '../../services/agents'; import { defaultIngestErrorHandler } from '../../errors'; @@ -49,19 +52,20 @@ export const postBulkAgentsUnenrollHandler: RequestHandler< body: { message: 'Requires Gold license' }, }); } + const soClient = context.core.savedObjects.client; const esClient = context.core.elasticsearch.client.asInternalUser; - const unenrollAgents = - request.body?.force === true ? AgentService.forceUnenrollAgents : AgentService.unenrollAgents; + const agentOptions = Array.isArray(request.body.agents) + ? { agentIds: request.body.agents } + : { kuery: request.body.agents }; try { - if (Array.isArray(request.body.agents)) { - await unenrollAgents(soClient, esClient, { agentIds: request.body.agents }); - } else { - await unenrollAgents(soClient, esClient, { kuery: request.body.agents }); - } - + await AgentService.unenrollAgents(soClient, esClient, { + ...agentOptions, + force: request.body?.force, + }); const body: PostBulkAgentUnenrollResponse = {}; + return response.ok({ body }); } catch (error) { return defaultIngestErrorHandler({ error, response }); diff --git a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts index 58149922f8e4ae..279018ef4212cd 100644 --- a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts +++ b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts @@ -10,7 +10,7 @@ import type { TypeOf } from '@kbn/config-schema'; import semverCoerce from 'semver/functions/coerce'; import type { PostAgentUpgradeResponse, PostBulkAgentUpgradeResponse } from '../../../common/types'; -import { PostAgentUpgradeRequestSchema, PostBulkAgentUpgradeRequestSchema } from '../../types'; +import type { PostAgentUpgradeRequestSchema, PostBulkAgentUpgradeRequestSchema } from '../../types'; import * as AgentService from '../../services/agents'; import { appContextService } from '../../services'; import { defaultIngestErrorHandler } from '../../errors'; diff --git a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts index 2a47d512167061..0d37979ef9acb5 100644 --- a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts @@ -13,7 +13,7 @@ import { fullAgentPolicyToYaml } from '../../../common/services'; import { appContextService, agentPolicyService, packagePolicyService } from '../../services'; import { getAgentsByKuery } from '../../services/agents'; import { AGENT_SAVED_OBJECT_TYPE } from '../../constants'; -import { +import type { GetAgentPoliciesRequestSchema, GetOneAgentPolicyRequestSchema, CreateAgentPolicyRequestSchema, diff --git a/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts b/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts index 2a5fbc1bb6522f..c85dc06c382864 100644 --- a/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts +++ b/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts @@ -8,7 +8,7 @@ import type { RequestHandler } from 'src/core/server'; import type { TypeOf } from '@kbn/config-schema'; -import { +import type { GetEnrollmentAPIKeysRequestSchema, PostEnrollmentAPIKeyRequestSchema, DeleteEnrollmentAPIKeyRequestSchema, diff --git a/x-pack/plugins/fleet/server/routes/epm/handlers.ts b/x-pack/plugins/fleet/server/routes/epm/handlers.ts index f466997f9e0591..293a3e9e28237d 100644 --- a/x-pack/plugins/fleet/server/routes/epm/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/epm/handlers.ts @@ -23,7 +23,7 @@ import type { IBulkInstallPackageHTTPError, GetStatsResponse, } from '../../../common'; -import { +import type { GetCategoriesRequestSchema, GetPackagesRequestSchema, GetFileRequestSchema, diff --git a/x-pack/plugins/fleet/server/routes/install_script/index.ts b/x-pack/plugins/fleet/server/routes/install_script/index.ts index c3b3541871d03d..673fe237700cf5 100644 --- a/x-pack/plugins/fleet/server/routes/install_script/index.ts +++ b/x-pack/plugins/fleet/server/routes/install_script/index.ts @@ -7,7 +7,7 @@ import url from 'url'; -import { BasePath, KibanaRequest } from 'src/core/server'; +import type { BasePath, KibanaRequest } from 'src/core/server'; import type { IRouter } from 'src/core/server'; import { INSTALL_SCRIPT_API_ROUTES } from '../../constants'; diff --git a/x-pack/plugins/fleet/server/routes/limited_concurrency.ts b/x-pack/plugins/fleet/server/routes/limited_concurrency.ts index 78bdc4bb7554cb..6f9a2bc95ea205 100644 --- a/x-pack/plugins/fleet/server/routes/limited_concurrency.ts +++ b/x-pack/plugins/fleet/server/routes/limited_concurrency.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { KibanaRequest } from 'kibana/server'; +import type { KibanaRequest } from 'kibana/server'; import type { CoreSetup, LifecycleResponseFactory, diff --git a/x-pack/plugins/fleet/server/routes/output/handler.ts b/x-pack/plugins/fleet/server/routes/output/handler.ts index fe2ee8e9bee189..ebd3d213d33c3c 100644 --- a/x-pack/plugins/fleet/server/routes/output/handler.ts +++ b/x-pack/plugins/fleet/server/routes/output/handler.ts @@ -8,7 +8,7 @@ import type { RequestHandler } from 'src/core/server'; import type { TypeOf } from '@kbn/config-schema'; -import { GetOneOutputRequestSchema, PutOutputRequestSchema } from '../../types'; +import type { GetOneOutputRequestSchema, PutOutputRequestSchema } from '../../types'; import type { GetOneOutputResponse, GetOutputsResponse } from '../../../common'; import { outputService } from '../../services/output'; import { defaultIngestErrorHandler } from '../../errors'; diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts index 6359fa45fd0f38..7f2b9d93e2df7c 100644 --- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts +++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts @@ -6,14 +6,14 @@ */ import { httpServerMock, httpServiceMock } from 'src/core/server/mocks'; -import { KibanaRequest } from 'kibana/server'; +import type { KibanaRequest } from 'kibana/server'; import type { IRouter, RequestHandler, RouteConfig } from 'kibana/server'; import { PACKAGE_POLICY_API_ROUTES } from '../../../common/constants'; import { appContextService, packagePolicyService } from '../../services'; import { createAppContextStartContractMock, xpackMocks } from '../../mocks'; import type { PackagePolicyServiceInterface, ExternalCallback } from '../..'; -import { CreatePackagePolicyRequestSchema } from '../../types/rest_spec'; +import type { CreatePackagePolicyRequestSchema } from '../../types/rest_spec'; import { registerRoutes } from './index'; diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts index 14d7e38752cf21..5e8abd5966e3af 100644 --- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts @@ -11,7 +11,7 @@ import Boom from '@hapi/boom'; import { SavedObjectsErrorHelpers } from '../../../../../../src/core/server'; import type { RequestHandler } from '../../../../../../src/core/server'; import { appContextService, packagePolicyService } from '../../services'; -import { +import type { GetPackagePoliciesRequestSchema, GetOnePackagePolicyRequestSchema, CreatePackagePolicyRequestSchema, diff --git a/x-pack/plugins/fleet/server/routes/setup/handlers.ts b/x-pack/plugins/fleet/server/routes/setup/handlers.ts index b306b4d6802ad7..a7fdcf78f4be99 100644 --- a/x-pack/plugins/fleet/server/routes/setup/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/setup/handlers.ts @@ -11,7 +11,7 @@ import type { TypeOf } from '@kbn/config-schema'; import { outputService, appContextService } from '../../services'; import type { GetFleetStatusResponse, PostIngestSetupResponse } from '../../../common'; import { setupIngestManager, setupFleet } from '../../services/setup'; -import { PostFleetSetupRequestSchema } from '../../types'; +import type { PostFleetSetupRequestSchema } from '../../types'; import { defaultIngestErrorHandler } from '../../errors'; export const getFleetStatusHandler: RequestHandler = async (context, request, response) => { diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index 923f4704aa652e..b3edf9e449e3d5 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -39,6 +39,8 @@ import { migratePackagePolicyToV7120, } from './migrations/to_v7_12_0'; +import { migrateSettingsToV7130 } from './migrations/to_v7_13_0'; + /* * Saved object types and mappings * @@ -57,8 +59,6 @@ const getSavedObjectTypes = ( }, mappings: { properties: { - agent_auto_upgrade: { type: 'keyword' }, - package_auto_upgrade: { type: 'keyword' }, kibana_urls: { type: 'keyword' }, kibana_ca_sha256: { type: 'keyword' }, has_seen_add_data_notice: { type: 'boolean', index: false }, @@ -66,6 +66,7 @@ const getSavedObjectTypes = ( }, migrations: { '7.10.0': migrateSettingsToV7100, + '7.13.0': migrateSettingsToV7130, }, }, [AGENT_SAVED_OBJECT_TYPE]: { diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_11_0.test.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_11_0.test.ts index 71a87793727a8e..13a4b185e5ea8a 100644 --- a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_11_0.test.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_11_0.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SavedObjectMigrationContext, SavedObjectUnsanitizedDoc } from 'kibana/server'; +import type { SavedObjectMigrationContext, SavedObjectUnsanitizedDoc } from 'kibana/server'; import type { PackagePolicy } from '../../../../common'; diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_11_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_11_0.ts index 445b84995353b5..0c7b20ed282611 100644 --- a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_11_0.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_11_0.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SavedObjectMigrationFn, SavedObjectUnsanitizedDoc } from 'kibana/server'; +import type { SavedObjectMigrationFn, SavedObjectUnsanitizedDoc } from 'kibana/server'; import { cloneDeep } from 'lodash'; import type { PackagePolicy } from '../../../../common'; diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_12_0.test.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_12_0.test.ts index e2f73270efbca1..d64fb55ba65a24 100644 --- a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_12_0.test.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_12_0.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SavedObjectMigrationContext, SavedObjectUnsanitizedDoc } from 'kibana/server'; +import type { SavedObjectMigrationContext, SavedObjectUnsanitizedDoc } from 'kibana/server'; import type { PackagePolicy } from '../../../../common'; diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_12_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_12_0.ts index c10a89fea6b18f..9d961af74a12a5 100644 --- a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_12_0.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_12_0.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SavedObjectMigrationFn, SavedObjectUnsanitizedDoc } from 'kibana/server'; +import type { SavedObjectMigrationFn, SavedObjectUnsanitizedDoc } from 'kibana/server'; import { cloneDeep } from 'lodash'; import type { PackagePolicy } from '../../../../common'; diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_13_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_13_0.ts new file mode 100644 index 00000000000000..5c660d4309ac7b --- /dev/null +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_13_0.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SavedObjectMigrationFn } from 'kibana/server'; + +import type { Settings } from '../../types'; + +export const migrateSettingsToV7130: SavedObjectMigrationFn< + Settings & { + package_auto_upgrade: string; + agent_auto_upgrade: string; + }, + Settings +> = (settingsDoc) => { + // @ts-expect-error + delete settingsDoc.attributes.package_auto_upgrade; + // @ts-expect-error + delete settingsDoc.attributes.agent_auto_upgrade; + + return settingsDoc; +}; diff --git a/x-pack/plugins/fleet/server/services/agent_policy_update.ts b/x-pack/plugins/fleet/server/services/agent_policy_update.ts index beab39d5146e48..dc566b2c435a68 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy_update.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy_update.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { KibanaRequest } from 'src/core/server'; +import type { KibanaRequest } from 'src/core/server'; import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server'; import { generateEnrollmentAPIKey, deleteEnrollmentApiKeyForAgentPolicyId } from './api_keys'; diff --git a/x-pack/plugins/fleet/server/services/agents/acks.ts b/x-pack/plugins/fleet/server/services/agents/acks.ts index fd52d2d5d3c457..a29937e1257eb1 100644 --- a/x-pack/plugins/fleet/server/services/agents/acks.ts +++ b/x-pack/plugins/fleet/server/services/agents/acks.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { KibanaRequest } from 'src/core/server'; +import type { KibanaRequest } from 'src/core/server'; import type { ElasticsearchClient, SavedObjectsBulkCreateObject, diff --git a/x-pack/plugins/fleet/server/services/agents/authenticate.test.ts b/x-pack/plugins/fleet/server/services/agents/authenticate.test.ts index 240de8ccfd5a55..eaa240165e8534 100644 --- a/x-pack/plugins/fleet/server/services/agents/authenticate.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/authenticate.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { KibanaRequest } from 'kibana/server'; +import type { KibanaRequest } from 'kibana/server'; import { elasticsearchServiceMock } from 'src/core/server/mocks'; import { authenticateAgentWithAccessToken } from './authenticate'; diff --git a/x-pack/plugins/fleet/server/services/agents/authenticate.ts b/x-pack/plugins/fleet/server/services/agents/authenticate.ts index dfbf190a551860..0d0d520528dad2 100644 --- a/x-pack/plugins/fleet/server/services/agents/authenticate.ts +++ b/x-pack/plugins/fleet/server/services/agents/authenticate.ts @@ -6,7 +6,7 @@ */ import Boom from '@hapi/boom'; -import { KibanaRequest } from 'src/core/server'; +import type { KibanaRequest } from 'src/core/server'; import type { ElasticsearchClient } from 'src/core/server'; import type { Agent } from '../../types'; diff --git a/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.ts b/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.ts index 15d48b40f77c1c..7dc19f63a5adba 100644 --- a/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.ts +++ b/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.ts @@ -7,7 +7,8 @@ import semverParse from 'semver/functions/parse'; import semverLt from 'semver/functions/lt'; -import { timer, from, Observable, TimeoutError, of, EMPTY } from 'rxjs'; +import type { Observable } from 'rxjs'; +import { timer, from, TimeoutError, of, EMPTY } from 'rxjs'; import { omit } from 'lodash'; import { shareReplay, @@ -21,7 +22,7 @@ import { timeout, take, } from 'rxjs/operators'; -import { KibanaRequest } from 'src/core/server'; +import type { KibanaRequest } from 'src/core/server'; import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server'; import type { Agent, AgentAction, AgentPolicyAction, AgentPolicyActionV7_9 } from '../../../types'; diff --git a/x-pack/plugins/fleet/server/services/agents/unenroll.ts b/x-pack/plugins/fleet/server/services/agents/unenroll.ts index 14f9aa46e9fa69..8cf7396eaa8de4 100644 --- a/x-pack/plugins/fleet/server/services/agents/unenroll.ts +++ b/x-pack/plugins/fleet/server/services/agents/unenroll.ts @@ -56,14 +56,18 @@ export async function unenrollAgent( export async function unenrollAgents( soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, - options: GetAgentsOptions + options: GetAgentsOptions & { force?: boolean } ) { + // start with all agents specified const agents = await getAgents(esClient, options); - // Filter to agents that are not already unenrolled, or unenrolling - const agentsEnrolled = agents.filter( - (agent) => !agent.unenrollment_started_at && !agent.unenrolled_at - ); + // Filter to those not already unenrolled, or unenrolling + const agentsEnrolled = agents.filter((agent) => { + if (options.force) { + return !agent.unenrolled_at; + } + return !agent.unenrollment_started_at && !agent.unenrolled_at; + }); // And which are allowed to unenroll const settled = await Promise.allSettled( agentsEnrolled.map((agent) => @@ -71,30 +75,59 @@ export async function unenrollAgents( ) ); const agentsToUpdate = agentsEnrolled.filter((_, index) => settled[index].status === 'fulfilled'); - const now = new Date().toISOString(); - // Create unenroll action for each agent - await bulkCreateAgentActions( - soClient, - esClient, - agentsToUpdate.map((agent) => ({ - agent_id: agent.id, - created_at: now, - type: 'UNENROLL', - })) - ); + if (options.force) { + // Get all API keys that need to be invalidated + const apiKeys = agentsToUpdate.reduce((keys, agent) => { + if (agent.access_api_key_id) { + keys.push(agent.access_api_key_id); + } + if (agent.default_api_key_id) { + keys.push(agent.default_api_key_id); + } + + return keys; + }, []); + + // Invalidate all API keys + if (apiKeys.length) { + await APIKeyService.invalidateAPIKeys(soClient, apiKeys); + } + // Update the necessary agents + return bulkUpdateAgents( + esClient, + agentsToUpdate.map((agent) => ({ + agentId: agent.id, + data: { + active: false, + unenrolled_at: now, + }, + })) + ); + } else { + // Create unenroll action for each agent + await bulkCreateAgentActions( + soClient, + esClient, + agentsToUpdate.map((agent) => ({ + agent_id: agent.id, + created_at: now, + type: 'UNENROLL', + })) + ); - // Update the necessary agents - return bulkUpdateAgents( - esClient, - agentsToUpdate.map((agent) => ({ - agentId: agent.id, - data: { - unenrollment_started_at: now, - }, - })) - ); + // Update the necessary agents + return bulkUpdateAgents( + esClient, + agentsToUpdate.map((agent) => ({ + agentId: agent.id, + data: { + unenrollment_started_at: now, + }, + })) + ); + } } export async function forceUnenrollAgent( @@ -118,41 +151,3 @@ export async function forceUnenrollAgent( unenrolled_at: new Date().toISOString(), }); } - -export async function forceUnenrollAgents( - soClient: SavedObjectsClientContract, - esClient: ElasticsearchClient, - options: GetAgentsOptions -) { - // Filter to agents that are not already unenrolled - const agents = await getAgents(esClient, options); - const agentsToUpdate = agents.filter((agent) => !agent.unenrolled_at); - const now = new Date().toISOString(); - const apiKeys: string[] = []; - - // Get all API keys that need to be invalidated - agentsToUpdate.forEach((agent) => { - if (agent.access_api_key_id) { - apiKeys.push(agent.access_api_key_id); - } - if (agent.default_api_key_id) { - apiKeys.push(agent.default_api_key_id); - } - }); - - // Invalidate all API keys - if (apiKeys.length) { - APIKeyService.invalidateAPIKeys(soClient, apiKeys); - } - // Update the necessary agents - return bulkUpdateAgents( - esClient, - agentsToUpdate.map((agent) => ({ - agentId: agent.id, - data: { - active: false, - unenrolled_at: now, - }, - })) - ); -} diff --git a/x-pack/plugins/fleet/server/services/api_keys/index.ts b/x-pack/plugins/fleet/server/services/api_keys/index.ts index 849867bdd58796..bf229c829fda8f 100644 --- a/x-pack/plugins/fleet/server/services/api_keys/index.ts +++ b/x-pack/plugins/fleet/server/services/api_keys/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { KibanaRequest } from 'src/core/server'; +import type { KibanaRequest } from 'src/core/server'; import type { SavedObjectsClientContract } from 'src/core/server'; import { createAPIKey } from './security'; diff --git a/x-pack/plugins/fleet/server/services/app_context.ts b/x-pack/plugins/fleet/server/services/app_context.ts index cad730b81f98b9..21b519565e758d 100644 --- a/x-pack/plugins/fleet/server/services/app_context.ts +++ b/x-pack/plugins/fleet/server/services/app_context.ts @@ -5,10 +5,11 @@ * 2.0. */ -import { BehaviorSubject, Observable } from 'rxjs'; +import type { Observable } from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; import { first } from 'rxjs/operators'; import { kibanaPackageJson } from '@kbn/utils'; -import { KibanaRequest } from 'src/core/server'; +import type { KibanaRequest } from 'src/core/server'; import type { ElasticsearchClient, SavedObjectsServiceStart, diff --git a/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts b/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts index 985699efc43795..b483a4e534e6c8 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts @@ -29,7 +29,7 @@ import { listArtifacts, } from './artifacts'; -import { NewArtifact } from './types'; +import type { NewArtifact } from './types'; describe('When using the artifacts services', () => { let esClientMock: ReturnType; diff --git a/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts b/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts index f8ac5a5cb61e0b..2a5f39c4e8a26e 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts @@ -8,10 +8,11 @@ import { deflate } from 'zlib'; import { promisify } from 'util'; -import { createHash, BinaryLike } from 'crypto'; +import type { BinaryLike } from 'crypto'; +import { createHash } from 'crypto'; import uuid from 'uuid'; -import { ElasticsearchClient } from 'kibana/server'; +import type { ElasticsearchClient } from 'kibana/server'; import type { ListResult } from '../../../common'; import { FLEET_SERVER_ARTIFACTS_INDEX } from '../../../common'; @@ -20,7 +21,7 @@ import type { ESSearchHit, ESSearchResponse } from '../../../../../../typings/el import { ArtifactsElasticsearchError } from '../../errors'; import { isElasticsearchItemNotFoundError } from './utils'; -import { +import type { Artifact, ArtifactElasticsearchProperties, ArtifactEncodedMetadata, diff --git a/x-pack/plugins/fleet/server/services/artifacts/client.ts b/x-pack/plugins/fleet/server/services/artifacts/client.ts index 58837cb5257664..87b752c5a2069b 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/client.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/client.ts @@ -4,13 +4,13 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { ElasticsearchClient } from 'kibana/server'; +import type { ElasticsearchClient } from 'kibana/server'; -import { ListResult } from '../../../common'; +import type { ListResult } from '../../../common'; import { ArtifactsClientAccessDeniedError, ArtifactsClientError } from '../../errors'; -import { +import type { Artifact, ArtifactsClientCreateOptions, ArtifactEncodedMetadata, diff --git a/x-pack/plugins/fleet/server/services/artifacts/mappings.ts b/x-pack/plugins/fleet/server/services/artifacts/mappings.ts index 04368792400d6a..863eff5aac74cc 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/mappings.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/mappings.ts @@ -7,7 +7,7 @@ import type { ESSearchHit } from '../../../../../../typings/elasticsearch'; -import { Artifact, ArtifactElasticsearchProperties } from './types'; +import type { Artifact, ArtifactElasticsearchProperties } from './types'; import { ARTIFACT_DOWNLOAD_RELATIVE_PATH } from './constants'; export const esSearchHitToArtifact = < diff --git a/x-pack/plugins/fleet/server/services/artifacts/mocks.ts b/x-pack/plugins/fleet/server/services/artifacts/mocks.ts index 2f883118df1168..b1e01208a24ca0 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/mocks.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/mocks.ts @@ -6,13 +6,13 @@ */ import { URL } from 'url'; -import { ApiResponse } from '@elastic/elasticsearch'; +import type { ApiResponse } from '@elastic/elasticsearch'; import { ResponseError } from '@elastic/elasticsearch/lib/errors'; import { elasticsearchServiceMock } from '../../../../../../src/core/server/mocks'; import type { ESSearchHit, ESSearchResponse } from '../../../../../../typings/elasticsearch'; -import { Artifact, ArtifactElasticsearchProperties, ArtifactsClientInterface } from './types'; +import type { Artifact, ArtifactElasticsearchProperties, ArtifactsClientInterface } from './types'; export const createArtifactsClientMock = (): jest.Mocked => { return { diff --git a/x-pack/plugins/fleet/server/services/artifacts/types.ts b/x-pack/plugins/fleet/server/services/artifacts/types.ts index fee2e4768d42d9..bbad7974bde793 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/types.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/types.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { ListResult } from '../../../common'; -import { ListWithKuery } from '../../types'; +import type { ListResult } from '../../../common'; +import type { ListWithKuery } from '../../types'; export interface NewArtifact { compressionAlgorithm: 'none' | 'zlib'; diff --git a/x-pack/plugins/fleet/server/services/config.ts b/x-pack/plugins/fleet/server/services/config.ts index 128cb6b136c23d..f26099ad9c4db0 100644 --- a/x-pack/plugins/fleet/server/services/config.ts +++ b/x-pack/plugins/fleet/server/services/config.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Observable, Subscription } from 'rxjs'; +import type { Observable, Subscription } from 'rxjs'; import type { FleetConfigType } from '../'; diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform.test.ts index 32e062289f9948..732f03440ce9da 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform.test.ts @@ -19,7 +19,7 @@ jest.mock('./common', () => { }); import { ResponseError } from '@elastic/elasticsearch/lib/errors'; -import { DeeplyMockedKeys } from 'packages/kbn-utility-types/target/jest'; +import type { DeeplyMockedKeys } from 'packages/kbn-utility-types/target/jest'; import type { ElasticsearchClient, SavedObject, SavedObjectsClientContract } from 'kibana/server'; import { ElasticsearchAssetType } from '../../../../types'; diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.ts index 77348d2f080be2..98dbd3bd571621 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts @@ -19,7 +19,7 @@ import type { RegistryPackage, EpmPackageAdditions, } from '../../../../common/types'; -import { KibanaAssetType } from '../../../types'; +import type { KibanaAssetType } from '../../../types'; import type { Installation, PackageInfo } from '../../../types'; import { IngestManagerError } from '../../../errors'; import { appContextService } from '../../'; diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.ts index 4d2a5528270e48..011e77fb7e89b9 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -7,7 +7,7 @@ import semverGt from 'semver/functions/gt'; import semverLt from 'semver/functions/lt'; -import Boom from '@hapi/boom'; +import type Boom from '@hapi/boom'; import type { UnwrapPromise } from '@kbn/utility-types'; import type { ElasticsearchClient, SavedObject, SavedObjectsClientContract } from 'src/core/server'; @@ -21,7 +21,7 @@ import { PackageOutdatedError, } from '../../../errors'; import { PACKAGES_SAVED_OBJECT_TYPE, MAX_TIME_COMPLETE_INSTALL } from '../../../constants'; -import { KibanaAssetType } from '../../../types'; +import type { KibanaAssetType } from '../../../types'; import type { AssetReference, Installation, diff --git a/x-pack/plugins/fleet/server/services/epm/registry/index.ts b/x-pack/plugins/fleet/server/services/epm/registry/index.ts index 258bc230376164..0a755228e0b98b 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/index.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/index.ts @@ -9,7 +9,7 @@ import { URL } from 'url'; import mime from 'mime-types'; import semverValid from 'semver/functions/valid'; -import { Response } from 'node-fetch'; +import type { Response } from 'node-fetch'; import { KibanaAssetType } from '../../../types'; import type { diff --git a/x-pack/plugins/fleet/server/services/epm/registry/proxy.ts b/x-pack/plugins/fleet/server/services/epm/registry/proxy.ts index 3c71fa0859fdb8..ec0a8458ee5f7b 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/proxy.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/proxy.ts @@ -6,8 +6,11 @@ */ import HttpProxyAgent from 'http-proxy-agent'; -import HttpsProxyAgent, { HttpsProxyAgent as IHttpsProxyAgent } from 'https-proxy-agent'; -import type { HttpsProxyAgentOptions } from 'https-proxy-agent'; +import HttpsProxyAgent from 'https-proxy-agent'; +import type { + HttpsProxyAgentOptions, + HttpsProxyAgent as IHttpsProxyAgent, +} from 'https-proxy-agent'; import { appContextService } from '../../index'; diff --git a/x-pack/plugins/fleet/server/services/epm/registry/requests.ts b/x-pack/plugins/fleet/server/services/epm/registry/requests.ts index 9cba2262dbc6b6..40943aa0cffff5 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/requests.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/requests.ts @@ -5,8 +5,8 @@ * 2.0. */ -import fetch, { FetchError, Response } from 'node-fetch'; -import type { RequestInit } from 'node-fetch'; +import fetch, { FetchError } from 'node-fetch'; +import type { RequestInit, Response } from 'node-fetch'; import pRetry from 'p-retry'; import { streamToString } from '../streams'; diff --git a/x-pack/plugins/fleet/server/services/fleet_server/elastic_index.ts b/x-pack/plugins/fleet/server/services/fleet_server/elastic_index.ts index d58228f57a1b28..9015b56441fb44 100644 --- a/x-pack/plugins/fleet/server/services/fleet_server/elastic_index.ts +++ b/x-pack/plugins/fleet/server/services/fleet_server/elastic_index.ts @@ -8,7 +8,8 @@ import type { ElasticsearchClient } from 'kibana/server'; import hash from 'object-hash'; -import { FLEET_SERVER_INDICES, FLEET_SERVER_INDICES_VERSION } from '../../../common'; +import type { FLEET_SERVER_INDICES } from '../../../common'; +import { FLEET_SERVER_INDICES_VERSION } from '../../../common'; import { appContextService } from '../app_context'; import ESFleetAgentIndex from './elasticsearch/fleet_agents.json'; diff --git a/x-pack/plugins/fleet/server/services/fleet_server/saved_object_migrations.ts b/x-pack/plugins/fleet/server/services/fleet_server/saved_object_migrations.ts index fab3ac4a728135..1d5a788c3a2c20 100644 --- a/x-pack/plugins/fleet/server/services/fleet_server/saved_object_migrations.ts +++ b/x-pack/plugins/fleet/server/services/fleet_server/saved_object_migrations.ts @@ -6,7 +6,7 @@ */ import { isBoom } from '@hapi/boom'; -import { KibanaRequest } from 'src/core/server'; +import type { KibanaRequest } from 'src/core/server'; import { ENROLLMENT_API_KEYS_INDEX, diff --git a/x-pack/plugins/fleet/server/services/index.ts b/x-pack/plugins/fleet/server/services/index.ts index 5b4b8b65b20388..e9a8024a032e5b 100644 --- a/x-pack/plugins/fleet/server/services/index.ts +++ b/x-pack/plugins/fleet/server/services/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { KibanaRequest } from 'kibana/server'; +import type { KibanaRequest } from 'kibana/server'; import type { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server'; import type { AgentStatus, Agent, EsAssetReference } from '../types'; diff --git a/x-pack/plugins/fleet/server/services/package_policy.test.ts b/x-pack/plugins/fleet/server/services/package_policy.test.ts index b5c465e443ea0b..b3e726bdf7c9e4 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.test.ts @@ -12,7 +12,7 @@ import { } from 'src/core/server/mocks'; import type { SavedObjectsUpdateResponse } from 'src/core/server'; -import { KibanaRequest } from 'kibana/server'; +import type { KibanaRequest } from 'kibana/server'; import type { PackageInfo, PackagePolicySOAttributes } from '../types'; import { createPackagePolicyMock } from '../../common/mocks'; diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 877d332bc5680f..418a10225edadf 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { KibanaRequest } from 'src/core/server'; +import type { KibanaRequest } from 'src/core/server'; import type { ElasticsearchClient, RequestHandlerContext, @@ -38,7 +38,7 @@ import type { PackagePolicySOAttributes, RegistryPackage, } from '../types'; -import { ExternalCallback } from '..'; +import type { ExternalCallback } from '..'; import { agentPolicyService } from './agent_policy'; import { outputService } from './output'; diff --git a/x-pack/plugins/fleet/server/services/settings.ts b/x-pack/plugins/fleet/server/services/settings.ts index 3a322ebb7dd9df..03348a2fcc4bb1 100644 --- a/x-pack/plugins/fleet/server/services/settings.ts +++ b/x-pack/plugins/fleet/server/services/settings.ts @@ -82,8 +82,6 @@ export function createDefaultSettings(): BaseSettings { }); return { - agent_auto_upgrade: true, - package_auto_upgrade: true, kibana_urls: [cloudUrl || flagsUrl || defaultUrl].flat(), }; } diff --git a/x-pack/plugins/fleet/server/types/rest_spec/settings.ts b/x-pack/plugins/fleet/server/types/rest_spec/settings.ts index fa885d45c1115b..9bbebbe86ccaaf 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/settings.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/settings.ts @@ -13,8 +13,6 @@ export const GetSettingsRequestSchema = {}; export const PutSettingsRequestSchema = { body: schema.object({ - agent_auto_upgrade: schema.maybe(schema.boolean()), - package_auto_upgrade: schema.maybe(schema.boolean()), kibana_urls: schema.maybe( schema.arrayOf(schema.uri({ scheme: ['http', 'https'] }), { validate: (value) => { diff --git a/x-pack/plugins/infra/server/types.ts b/x-pack/plugins/infra/server/types.ts index 5cae0158619460..1c51a5549cb41f 100644 --- a/x-pack/plugins/infra/server/types.ts +++ b/x-pack/plugins/infra/server/types.ts @@ -5,8 +5,7 @@ * 2.0. */ -import type { RequestHandlerContext } from 'src/core/server'; -import type { SearchRequestHandlerContext } from '../../../../src/plugins/data/server'; +import type { DataRequestHandlerContext } from '../../../../src/plugins/data/server'; import { MlPluginSetup } from '../../ml/server'; export type MlSystem = ReturnType; @@ -27,7 +26,6 @@ export type InfraRequestHandlerContext = InfraMlRequestHandlerContext & /** * @internal */ -export interface InfraPluginRequestHandlerContext extends RequestHandlerContext { +export interface InfraPluginRequestHandlerContext extends DataRequestHandlerContext { infra: InfraRequestHandlerContext; - search: SearchRequestHandlerContext; } diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/drag_and_drop_text_list.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/drag_and_drop_text_list.tsx index abe4eb0fa59168..03bdc2ceb95792 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/drag_and_drop_text_list.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/drag_and_drop_text_list.tsx @@ -44,7 +44,15 @@ interface Props { /** * Validation to be applied to every text item */ - textValidation?: ValidationFunc; + textValidations?: Array>; + /** + * Serializer to be applied to every text item + */ + textSerializer?: (v: string) => O; + /** + * Deserializer to be applied to every text item + */ + textDeserializer?: (v: unknown) => string; } const i18nTexts = { @@ -63,7 +71,9 @@ function DragAndDropTextListComponent({ onAdd, onRemove, addLabel, - textValidation, + textValidations, + textDeserializer, + textSerializer, }: Props): JSX.Element { const [droppableId] = useState(() => uuid.v4()); const [firstItemId] = useState(() => uuid.v4()); @@ -133,9 +143,11 @@ function DragAndDropTextListComponent({ path={item.path} config={{ - validations: textValidation - ? [{ validator: textValidation }] + validations: textValidations + ? textValidations.map((validator) => ({ validator })) : undefined, + deserializer: textDeserializer, + serializer: textSerializer, }} readDefaultValueOnForm={!item.isNew} > diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/dissect.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/dissect.tsx index 6652ad277cc265..3864581317e385 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/dissect.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/dissect.tsx @@ -22,7 +22,7 @@ import { import { FieldNameField } from './common_fields/field_name_field'; import { IgnoreMissingField } from './common_fields/ignore_missing_field'; -import { EDITOR_PX_HEIGHT, from } from './shared'; +import { EDITOR_PX_HEIGHT, from, to, isJSONStringValidator } from './shared'; const { emptyField } = fieldValidators; @@ -34,6 +34,8 @@ const getFieldsConfig = (esDocUrl: string): Record => { label: i18n.translate('xpack.ingestPipelines.pipelineEditor.dissectForm.patternFieldLabel', { defaultMessage: 'Pattern', }), + deserializer: to.escapeBackslashes, + serializer: from.unescapeBackslashes, helpText: ( => { ) ), }, + { + validator: isJSONStringValidator, + }, ], }, /* Optional field config */ diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/grok.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/grok.tsx index f15441ea1f92ba..ae2d341c58c303 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/grok.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/grok.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { flow } from 'lodash'; import React, { FunctionComponent } from 'react'; import { i18n } from '@kbn/i18n'; @@ -22,7 +23,7 @@ import { XJsonEditor, DragAndDropTextList } from '../field_components'; import { FieldNameField } from './common_fields/field_name_field'; import { IgnoreMissingField } from './common_fields/ignore_missing_field'; -import { FieldsConfig, to, from, EDITOR_PX_HEIGHT } from './shared'; +import { FieldsConfig, to, from, EDITOR_PX_HEIGHT, isJSONStringValidator } from './shared'; const { isJsonField, emptyField } = fieldValidators; @@ -46,7 +47,10 @@ const patternsValidation: ValidationFunc = ({ value, f } }; -const patternValidation = emptyField(valueRequiredMessage); +const patternValidations: Array> = [ + emptyField(valueRequiredMessage), + isJSONStringValidator, +]; const fieldsConfig: FieldsConfig = { /* Required field configs */ @@ -54,6 +58,8 @@ const fieldsConfig: FieldsConfig = { label: i18n.translate('xpack.ingestPipelines.pipelineEditor.grokForm.patternsFieldLabel', { defaultMessage: 'Patterns', }), + deserializer: flow(String, to.escapeBackslashes), + serializer: from.unescapeBackslashes, helpText: i18n.translate('xpack.ingestPipelines.pipelineEditor.grokForm.patternsHelpText', { defaultMessage: 'Grok expressions used to match and extract named capture groups. Uses the first matching expression.', @@ -133,7 +139,9 @@ export const Grok: FunctionComponent = () => { onAdd={addItem} onRemove={removeItem} addLabel={i18nTexts.addPatternLabel} - textValidation={patternValidation} + textValidations={patternValidations} + textDeserializer={fieldsConfig.patterns?.deserializer} + textSerializer={fieldsConfig.patterns?.serializer} /> ); }} diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/gsub.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/gsub.tsx index edfa59ea80281c..11d06f3cca6fbd 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/gsub.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/gsub.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { flow } from 'lodash'; import React, { FunctionComponent } from 'react'; import { i18n } from '@kbn/i18n'; @@ -12,7 +13,7 @@ import { FIELD_TYPES, fieldValidators, UseField, Field } from '../../../../../.. import { TextEditor } from '../field_components'; -import { EDITOR_PX_HEIGHT, FieldsConfig } from './shared'; +import { EDITOR_PX_HEIGHT, FieldsConfig, from, to, isJSONStringValidator } from './shared'; import { FieldNameField } from './common_fields/field_name_field'; import { IgnoreMissingField } from './common_fields/ignore_missing_field'; import { TargetField } from './common_fields/target_field'; @@ -26,7 +27,8 @@ const fieldsConfig: FieldsConfig = { label: i18n.translate('xpack.ingestPipelines.pipelineEditor.gsubForm.patternFieldLabel', { defaultMessage: 'Pattern', }), - deserializer: String, + deserializer: flow(String, to.escapeBackslashes), + serializer: from.unescapeBackslashes, helpText: i18n.translate('xpack.ingestPipelines.pipelineEditor.gsubForm.patternFieldHelpText', { defaultMessage: 'Regular expression used to match substrings in the field.', }), @@ -38,6 +40,9 @@ const fieldsConfig: FieldsConfig = { }) ), }, + { + validator: isJSONStringValidator, + }, ], }, diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/shared.test.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/shared.test.ts new file mode 100644 index 00000000000000..4b01f22a9383d5 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/shared.test.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 { from, to } from './shared'; + +describe('shared', () => { + describe('deserialization helpers', () => { + // This is the text that will be passed to the text input + test('to.escapeBackslashes', () => { + // this input loaded from the server + const input1 = 'my\ttab'; + expect(to.escapeBackslashes(input1)).toBe('my\\ttab'); + + // this input loaded from the server + const input2 = 'my\\ttab'; + expect(to.escapeBackslashes(input2)).toBe('my\\\\ttab'); + + // this input loaded from the server + const input3 = '\t\n\rOK'; + expect(to.escapeBackslashes(input3)).toBe('\\t\\n\\rOK'); + + const input4 = `%{clientip} %{ident} %{auth} [%{@timestamp}] \"%{verb} %{request} HTTP/%{httpversion}\" %{status} %{size}`; + expect(to.escapeBackslashes(input4)).toBe( + '%{clientip} %{ident} %{auth} [%{@timestamp}] \\"%{verb} %{request} HTTP/%{httpversion}\\" %{status} %{size}' + ); + }); + }); + + describe('serialization helpers', () => { + test('from.unescapeBackslashes', () => { + // user typed in "my\ttab" + const input1 = 'my\\ttab'; + expect(from.unescapeBackslashes(input1)).toBe('my\ttab'); + + // user typed in "my\\tab" + const input2 = 'my\\\\ttab'; + expect(from.unescapeBackslashes(input2)).toBe('my\\ttab'); + + // user typed in "\t\n\rOK" + const input3 = '\\t\\n\\rOK'; + expect(from.unescapeBackslashes(input3)).toBe('\t\n\rOK'); + + const input5 = `%{clientip} %{ident} %{auth} [%{@timestamp}] \\"%{verb} %{request} HTTP/%{httpversion}\\" %{status} %{size}`; + expect(from.unescapeBackslashes(input5)).toBe( + `%{clientip} %{ident} %{auth} [%{@timestamp}] \"%{verb} %{request} HTTP/%{httpversion}\" %{status} %{size}` + ); + }); + }); +}); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/shared.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/shared.ts index 399da3c05c7831..bafba412c767f0 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/shared.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/shared.ts @@ -5,11 +5,12 @@ * 2.0. */ -import { FunctionComponent } from 'react'; +import type { FunctionComponent } from 'react'; import * as rt from 'io-ts'; +import { i18n } from '@kbn/i18n'; import { isRight } from 'fp-ts/lib/Either'; -import { FieldConfig } from '../../../../../../shared_imports'; +import { FieldConfig, ValidationFunc } from '../../../../../../shared_imports'; export const arrayOfStrings = rt.array(rt.string); @@ -36,6 +37,17 @@ export const to = { arrayOfStrings: (v: unknown): string[] => isArrayOfStrings(v) ? v : typeof v === 'string' && v.length ? [v] : [], jsonString: (v: unknown) => (v ? JSON.stringify(v, null, 2) : '{}'), + /** + * Useful when deserializing strings that will be rendered inside of text areas or text inputs. We want + * a string like: "my\ttab" to render the same, not to render as "mytab". + */ + escapeBackslashes: (v: unknown) => { + if (typeof v === 'string') { + const s = JSON.stringify(v); + return s.slice(1, s.length - 1); + } + return v; + }, }; /** @@ -69,6 +81,41 @@ export const from = { optionalArrayOfStrings: (v: string[]) => (v.length ? v : undefined), undefinedIfValue: (value: unknown) => (v: boolean) => (v === value ? undefined : v), emptyStringToUndefined: (v: unknown) => (v === '' ? undefined : v), + /** + * Useful when serializing user input from a