diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_detection_engine.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_detection_engine.yml index 2558da1f9ff5f..985a3e796d8fc 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_detection_engine.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_detection_engine.yml @@ -61,9 +61,9 @@ steps: - exit_status: '1' limit: 2 - - label: Running exception_lists:common:lists:qa:serverless:release - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh exception_lists:common:lists:qa:serverless:release - key: exception_lists:common:lists:qa:serverless:release + - label: Running exception_lists:auth:common:qa:serverless:release + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh exception_lists:auth:common:qa:serverless:release + key: exception_lists:auth:common:qa:serverless:release agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-prod @@ -75,9 +75,9 @@ steps: - exit_status: '1' limit: 2 - - label: Running exception_lists:items:lists:qa:serverless:release - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh exception_lists:items:lists:qa:serverless:release - key: exception_lists:items:lists:qa:serverless:release + - label: Running exception_lists:auth:items:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh exception_lists:auth:items:qa:serverless + key: exception_lists:auth:items:qa:serverless agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-prod diff --git a/.eslintrc.js b/.eslintrc.js index 3c67594513c0e..730c9599f23f9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1026,7 +1026,9 @@ module.exports = { */ { files: ['x-pack/plugins/fleet/**/*.{js,mjs,ts,tsx}'], + plugins: ['testing-library'], rules: { + 'testing-library/await-async-utils': 'error', '@typescript-eslint/consistent-type-imports': 'error', 'import/order': [ 'warn', @@ -1954,6 +1956,16 @@ module.exports = { }, }, + /** + * Cloud Security Team overrides + */ + { + files: ['x-pack/plugins/cloud_security_posture/**/*.{js,mjs,ts,tsx}'], + plugins: ['testing-library'], + rules: { + 'testing-library/await-async-utils': 'error', + }, + }, /** * Code inside .buildkite runs separately from everything else in CI, before bootstrap, with ts-node. It needs a few tweaks because of this. */ @@ -1978,6 +1990,34 @@ module.exports = { 'max-classes-per-file': 'off', }, }, + { + files: [ + // logsShared depends on o11y/private plugins, but platform plugins depend on it + 'x-pack/plugins/observability_solution/logs_shared/**', + + // this plugin depends on visTypeTimeseries plugin (for TSVB viz) which is platform/private ATM + 'x-pack/plugins/observability_solution/infra/**', + + // TODO @kibana/operations + 'scripts/create_observability_rules.js', // is importing "@kbn/observability-alerting-test-data" (observability/private) + 'src/cli_setup/**', // is importing "@kbn/interactive-setup-plugin" (platform/private) + 'src/dev/build/tasks/install_chromium.ts', // is importing "@kbn/screenshotting-plugin" (platform/private) + + // @kbn/osquery-plugin could be categorised as Security, but @kbn/infra-plugin (observability) depends on it! + 'x-pack/plugins/osquery/**', + + // For now, we keep the exception to let tests depend on anythying. + // Ideally, we need to classify the solution specific ones to reduce CI times + 'test/**', + 'x-pack/test_serverless/**', + 'x-pack/test/**', + 'x-pack/test/plugin_functional/plugins/resolver_test/**', + ], + rules: { + '@kbn/imports/no_group_crossing_manifests': 'warn', + '@kbn/imports/no_group_crossing_imports': 'warn', + }, + }, ], }; diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 44a46aa54a270..981834b68eda6 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -94,7 +94,7 @@ Deprecated functionality does not have an immediate impact on your application, you make the necessary updates after you upgrade to 8.16.0. [discrete] -* The Logs Stream is now hidden by default in favor of the Logs Explorer app. +.The Logs Stream is now hidden by default in favor of the Logs Explorer app. [%collapsible] ==== *Details* + @@ -105,7 +105,7 @@ You can still show the Logs Stream app again by navigating to Stack Management > ==== [discrete] -* Deprecates the Observability AI Assistant specific advanced setting `observability:aiAssistantLogsIndexPattern`. +.Deprecates the Observability AI Assistant specific advanced setting `observability:aiAssistantLogsIndexPattern`. [%collapsible] ==== *Details* + diff --git a/docs/canvas/canvas-expression-lifecycle.asciidoc b/docs/canvas/canvas-expression-lifecycle.asciidoc deleted file mode 100644 index a20181c4b3808..0000000000000 --- a/docs/canvas/canvas-expression-lifecycle.asciidoc +++ /dev/null @@ -1,263 +0,0 @@ -[role="xpack"] -[[canvas-expression-lifecycle]] -== Canvas expression lifecycle - -Elements in Canvas are all created using an *expression language* that defines how to retrieve, manipulate, and ultimately visualize data. The goal is to allow you to do most of what you need without understanding the *expression language*, but learning how it works unlocks a lot of Canvas's power. - - -[[canvas-expressions-always-start-with-a-function]] -=== Expressions always start with a function - -Expressions simply execute <> in a specific order, which produce some output value. That output can then be inserted into another function, and another after that, until it produces the output you need. - -To use demo dataset available in Canvas to produce a table, run the following expression: - -[source,text] ----- -/* Simple demo table */ -filters -| demodata -| table -| render ----- - -This expression starts out with the <> function, which provides the value of any time filters or dropdown filters in the workpad. This is then inserted into <>, a function that returns exactly what you expect, demo data. Because the <> function receives the filter information from the <> function before it, it applies those filters to reduce the set of data it returns. We call the output from the previous function _context_. - -The filtered <> becomes the _context_ of the next function, <>, which creates a table visualization from this data set. The <> function isn’t strictly required, but by being explicit, you have the option of providing arguments to control things like the font used in the table. The output of the <> function becomes the _context_ of the <> function. Like the <>, the <> function isn’t required either, but it allows access to other arguments, such as styling the border of the element or injecting custom CSS. - -It is possible to add comments to the expression by starting them with a `//` sequence or by using `/*` and `*/` to enclose multi-line comments. - -[[canvas-function-arguments]] -=== Function arguments - -Let’s look at another expression, which uses the same <> function, but instead produces a pie chart. - -image::images/canvas-functions-can-take-arguments-pie-chart.png[Pie chart showing output of demodata function] -[source,text] ----- -filters -| demodata -| pointseries color="state" size="max(price)" -| pie -| render ----- - -To produce a filtered set of random data, the expression uses the <> and <> functions. This time, however, the output becomes the context for the <> function, which is a way to aggregate your data, similar to how Elasticsearch works, but more generalized. In this case, the data is split up using the `color` and `size` dimensions, using arguments on the <> function. Each unique value in the state column will have an associated size value, which in this case, will be the maximum value of the price column. - -If the expression stopped there, it would produce a `pointseries` data type as the output of this expression. But instead of looking at the raw values, the result is inserted into the <> function, which will produce an output that will render a pie visualization. And just like before, this is inserted into the <> function, which is useful for its arguments. - -The end result is a simple pie chart that uses the default color palette, but the <> function can take additional arguments that control how it gets rendered. For example, you can provide a `hole` argument to turn your pie chart into a donut chart by changing the expression to: - - -image::images/canvas-functions-can-take-arguments-donut-chart.png[Alternative output as donut chart] -[source,text] ----- -filters -| demodata -| pointseries color="state" size="max(price)" -| pie hole=50 -| render ----- - - -[[canvas-aliases-and-unnamed-arguments]] -=== Aliases and unnamed arguments - -Argument definitions have one canonical name, which is always provided in the underlying code. When argument definitions are used in an expression, they often include aliases that make them easier or faster to type. - -For example, the <> function has 2 arguments: - -* `expression` - Produces a calculated value. -* `name` - The name of column. - -The `expression` argument includes some aliases, namely `exp`, `fn`, and `function`. That means that you can use any of those four options to provide that argument’s value. - -So `mapColumn name=newColumn fn={string example}` is equal to `mapColumn name=newColumn expression={string example}`. - -There’s also a special type of alias which allows you to leave off the argument’s name entirely. The alias for this is an underscore, which indicates that the argument is an _unnamed_ argument and can be provided without explicitly naming it in the expression. The `name` argument here uses the _unnamed_ alias, which means that you can further simplify our example to `mapColumn newColumn fn={string example}`. - -NOTE: There can only be one _unnamed_ argument for each function. - - -[[canvas-change-your-expression-change-your-output]] -=== Change your expression, change your output -You can substitute one function for another to change the output. For example, you could change the visualization by swapping out the <> function for another renderer, a function that returns a `render` data type. - -Let’s change that last pie chart into a bubble chart by replacing the <> function with the <> function. This is possible because both functions can accept a `pointseries` data type as their _context_. Switching the functions will work, but it won’t produce a useful visualization on its own since you don’t have the x-axis and y-axis defined. You will also need to modify the <> function to change its output. In this case, you can change the `size` argument to `y`, so the maximum price values are plotted on the y-axis, and add an `x` argument using the `@timestamp` field in the data to plot those values over time. This leaves you with the following expression and produces a bubble chart showing the max price of each state over time: - -image::images/canvas-change-your-expression-chart.png[Bubble Chart, with price along x axis, and time along y axis] -[source,text] ----- -filters -| demodata -| pointseries color="state" y="max(price)" x="@timestamp" -| plot -| render ----- - -Similar to the <> function, the <> function takes arguments that control the design elements of the visualization. As one example, passing a `legend` argument with a value of `false` to the function will hide the legend on the chart. - -image::images/canvas-change-your-expression-chart-no-legend.png[Bubble Chart Without Legend] -[source,text,subs=+quotes] ----- -filters -| demodata -| pointseries color="state" y="max(price)" x="@timestamp" -| plot *legend=false* -| render ----- - - -[[canvas-fetch-and-manipulate-data]] -=== Fetch and manipulate data -So far, you have only seen expressions as a way to produce visualizations, but that’s not really what’s happening. Expressions only produce data, which is then used to create something, which in the case of Canvas, means rendering an element. An element can be a visualization, driven by data, but it can also be something much simpler, like a static image. Either way, an expression is used to produce an output that is used to render the desired result. For example, here’s an expression that shows an image: - -[source,text] ----- -image dataurl=https://placekitten.com/160/160 mode="cover" ----- - -But as mentioned, this doesn’t actually _render that image_, but instead it _produces some output that can be used to render that image_. That’s an important distinction, and you can see the actual output by adding in the render function and telling it to produce debug output. For example: - -[source,text] ----- -image dataurl=https://placekitten.com/160/160 mode="cover" -| render as=debug ----- - -The follow appears as JSON output: - -[source,JSON] ----- -{ - "type": "image", - "mode": "cover", - "dataurl": "https://placekitten.com/160/160" -} ----- - -NOTE: You may need to expand the element’s size to see the whole output. - -Canvas uses this output’s data type to map to a specific renderer and passes the entire output into it. It’s up to the image render function to produce an image on the workpad’s page. In this case, the expression produces some JSON output, but expressions can also produce other, simpler data, like a string or a number. Typically, useful results use JSON. - -Canvas uses the output to render an element, but other applications can use expressions to do pretty much anything. As stated previously, expressions simply execute functions, and the functions are all written in Javascript. That means if you can do something in Javascript, you can do it with an expression. - -This can include: - -* Sending emails -* Sending notifications -* Reading from a file -* Writing to a file -* Controlling devices with WebUSB or Web Bluetooth -* Consuming external APIs - -If your Javascript works in the environment where the code will run, such as in Node.js or in a browser, you can do it with an expression. - -[[canvas-expressions-compose-functions-with-subexpressions]] -=== Compose functions with sub-expressions - -You may have noticed another syntax in examples from other sections, namely expressions inside of curly brackets. These are called sub-expressions, and they can be used to provide a calculated value to another expression, instead of just a static one. - -A simple example of this is when you upload your own images to a Canvas workpad. That upload becomes an asset, and that asset can be retrieved using the `asset` function. Usually you’ll just do this from the UI, adding an image element to the page and uploading your image from the control in the sidebar, or picking an existing asset from there as well. In both cases, the system will consume that asset via the `asset` function, and you’ll end up with an expression similar to this: - -[source,text] ----- -image dataurl={asset 3cb3ec3a-84d7-48fa-8709-274ad5cc9e0b} ----- - -Sub-expressions are executed before the function that uses them is executed. In this case, `asset` will be run first, it will produce a value, the base64-encoded value of the image and that value will be used as the value for the `dataurl` argument in the <> function. After the asset function executes, you will get the following output: - -[source,text] ----- -image dataurl="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0" ----- - -Since all of the sub-expressions are now resolved into actual values, the <> function can be executed to produce its JSON output, just as it’s explained previously. In the case of images, the ability to nest sub-expressions is particularly useful to show one of several images conditionally. For example, you could swap between two images based on some calculated value by mixing in the <> function, like in this example expression: - -[source,text] ----- -demodata -| image dataurl={ - if condition={getCell price | gte 100} - then={asset "asset-3cb3ec3a-84d7-48fa-8709-274ad5cc9e0b"} - else={asset "asset-cbc11a1f-8f25-4163-94b4-2c3a060192e7"} -} ----- - -NOTE: The examples in this section can’t be copy and pasted directly, since the values used throughout will not exist in your workpad. - -Here, the expression to use for the value of the `condition` argument, `getCell price | gte 100`, runs first since it is nested deeper. - -The expression does the following: - -* Retrieves the value from the *price* column in the first row of the `demodata` data table -* Inputs the value to the `gte` function -* Compares the value to `100` -* Returns `true` if the value is 100 or greater, and `false` if the value is 100 or less - -That boolean value becomes the value for the `condition` argument. The output from the `then` expression is used as the output when `condition` is `true`. The output from the `else` expression is used when `condition` is false. In both cases, a base64-encoded image will be returned, and one of the two images will be displayed. - -You might be wondering how the <> function in the sub-expression accessed the data from the <> function, even though <> was not being directly inserted into <>. The answer is simple, but important to understand. When nested sub-expressions are executed, they automatically receive the same _context_, or output of the previous function that its parent function receives. In this specific expression, demodata’s data table is automatically provided to the nested expression’s `getCell` function, which allows that expression to pull out a value and compare it to another value. - -The passing of the _context_ is automatic, and it happens no matter how deeply you nest your sub-expressions. To demonstrate this, let’s modify the expression slightly to compare the value of the price against multiple conditions using the <> function. - -[source,text] ----- -demodata -| image dataurl={ - if condition={getCell price | all {gte 100} {neq 105}} - then={asset 3cb3ec3a-84d7-48fa-8709-274ad5cc9e0b} - else={asset cbc11a1f-8f25-4163-94b4-2c3a060192e7} -} ----- - -This time, `getCell price` is run, and the result is passed into the next function as the context. Then, each sub-expression of the <> function is run, with the context given to their parent, which in this case is the result of `getCell price`. If `all` of these sub-expressions evaluate to `true`, then the `if` condition argument will be true. - -Sub-expressions can seem a little foreign, especially if you aren’t a developer, but they’re worth getting familiar with, since they provide a ton of power and flexibility. Since you can nest any expression you want, you can also use this behavior to mix data from multiple indices, or even data from multiple sources. As an example, you could query an API for a value to use as part of the query provided to <>. - -This whole section is really just scratching the surface, but hopefully after reading it, you at least understand how to read expressions and make sense of what they are doing. With a little practice, you’ll get the hang of mixing _context_ and sub-expressions together to turn any input into your desired output. - -[[canvas-handling-context-and-argument-types]] -=== Handling context and argument types -If you look through the <>, you may notice that all of them define what a function accepts and what it returns. Additionally, every argument includes a type property that specifies the kind of data that can be used. These two types of values are actually the same, and can be used as a guide for how to deal with piping to other functions and using subexpressions for argument values. - -To explain how this works, consider the following expression from the previous section: - -[source,text] ----- -image dataurl={asset 3cb3ec3a-84d7-48fa-8709-274ad5cc9e0b} ----- - -If you <> for the `image` function, you’ll see that it accepts the `null` data type and returns an `image` data type. Accepting `null` effectively means that it does not use context at all, so if you insert anything to `image`, the value that was produced previously will be ignored. When the function executes, it will produce an `image` output, which is simply an object of type `image` that contains the information required to render an image. - -NOTE: The function does not render an image itself. - -As explained in the "<>" section, the output of an expression is just data. So the `image` type here is just a specific shape of data, not an actual image. - -Next, let’s take a look at the `asset` function. Like `image`, it accepts `null`, but it returns something different, a `string` in this case. Because `asset` will produce a string, its output can be used as the input for any function or argument that accepts a string. - -<> for the `dataurl` argument, its type is `string`, meaning it will accept any kind of string. There are some rules about the value of the string that the function itself enforces, but as far as the interpreter is concerned, that expression is valid because the argument accepts a string and the output of `asset` is a string. - -The interpreter also attempts to cast some input types into others, which allows you to use a string input even when the function or argument calls for a number. Keep in mind that it’s not able to convert any string value, but if the string is a number, it can easily be cast into a `number` type. Take the following expression for example: - -[source,text] ----- -string "0.4" -| revealImage image={asset asset-06511b39-ec44-408a-a5f3-abe2da44a426} ----- - -If you <> for the `revealImage` function, you’ll see that it accepts a `number` but the `string` function returns a `string` type. In this case, because the string value is a number, it can be converted into a `number` type and used without you having to do anything else. - -Most `primitive` types can be converted automatically, as you might expect. You just saw that a `string` can be cast into a `number`, but you can also pretty easily cast things into `boolean` too, and you can cast anything to `null`. - -There are other useful type casting options available. For example, something of type `datatable` can be cast to a type `pointseries` simply by only preserving specific columns from the data (namely x, y, size, color, and text). This allows you to treat your source data, which is generally of type `datatable`, like a `pointseries` type simply by convention. - -You can fetch data from Elasticsearch using `essql`, which allows you to aggregate the data, provide a custom name for the value, and insert that data directly to another function that only accepts `pointseries` even though `essql` will output a `datatable` type. This makes the following example expression valid: - -[source,text] ----- -essql "SELECT user AS x, sum(cost) AS y FROM index GROUP BY user" -| plot ----- - -In the docs you can see that `essql` returns a `datatable` type, but `plot` expects a `pointseries` context. This works because the `datatable` output will have the columns `x` and `y` as a result of using `AS` in the sql statement to name them. Because the data follows the convention of the `pointseries` data type, casting it into `pointseries` is possible, and it can be passed directly to `plot` as a result. diff --git a/docs/canvas/canvas-tutorial.asciidoc b/docs/canvas/canvas-tutorial.asciidoc index 5016dbec88aac..666d8885c2885 100644 --- a/docs/canvas/canvas-tutorial.asciidoc +++ b/docs/canvas/canvas-tutorial.asciidoc @@ -9,15 +9,11 @@ To familiarize yourself with *Canvas*, add the Sample eCommerce orders data, the To create a workpad of the eCommerce store data, add the data set, then create the workpad. -. On the home page, click *Try sample data*. +. <>. -. Click *Other sample data sets*. +. Go to **Canvas** using the navigation menu or the <>. -. On the *Sample eCommerce orders* card, click *Add data*. - -. Open the main menu, then click *Canvas*. - -. On the *Canvas workpads* page, click *Create workpad*. +. Select *Create workpad*. [float] === Customize your workpad with images diff --git a/docs/concepts/data-views.asciidoc b/docs/concepts/data-views.asciidoc index 2eba42aed3051..2a260e611a060 100644 --- a/docs/concepts/data-views.asciidoc +++ b/docs/concepts/data-views.asciidoc @@ -168,7 +168,7 @@ and field popularity data. Deleting a {data-source} does not remove any indices WARNING: Deleting a {data-source} breaks all visualizations, saved searches, and other saved objects that reference the data view. -. Open the main menu, and then click *Stack Management > Data Views*. +. Go to the **Data Views** management page using the navigation menu or the <>. . Find the {data-source} that you want to delete, and then click image:management/index-patterns/images/delete.png[Delete icon] in the *Actions* column. diff --git a/docs/concepts/esql.asciidoc b/docs/concepts/esql.asciidoc index 0390f9f6e2bc7..a3a091a4c6d0a 100644 --- a/docs/concepts/esql.asciidoc +++ b/docs/concepts/esql.asciidoc @@ -8,15 +8,12 @@ Based on the query, Lens suggestions in Discover create a visualization of the q {esql} comes with its own dedicated {esql} Compute Engine for greater efficiency. With one query you can search, aggregate, calculate and perform data transformations without leaving **Discover**. Write your query directly in **Discover** or use the **Dev Tools** with the {ref}/esql-rest.html[{esql} API]. -Here's how to use {esql} in the data view selector in **Discover**: +You can switch to the ES|QL mode of Discover from the application menu bar. -[role="screenshot"] -image:images/esql-data-view-menu.png[An image of the Discover UI where users can access the {esql} feature, width=30%, align="center"] - -{esql} also features in-app help, so you can get started faster and don't have to leave the application to check syntax. +{esql} also features in-app help and suggestions, so you can get started faster and don't have to leave the application to check syntax. [role="screenshot"] -image:images/esql-in-app-help.png[An image of the Discover UI where users can browse the in-app help] +image:images/esql-in-app-help.png[The ES|QL syntax reference and the autocomplete menu] You can also use ES|QL queries to create panels on your dashboards, create enrich policies, and create alerting rules. diff --git a/docs/concepts/images/esql-in-app-help.png b/docs/concepts/images/esql-in-app-help.png index 5f00248c10af2..00db2cf8e50c8 100644 Binary files a/docs/concepts/images/esql-in-app-help.png and b/docs/concepts/images/esql-in-app-help.png differ diff --git a/docs/dev-tools/grokdebugger/index.asciidoc b/docs/dev-tools/grokdebugger/index.asciidoc index 6a809c13fcb93..0932df0c7abfb 100644 --- a/docs/dev-tools/grokdebugger/index.asciidoc +++ b/docs/dev-tools/grokdebugger/index.asciidoc @@ -36,7 +36,8 @@ is automatically enabled in {kib}. NOTE: If you're using {stack-security-features}, you must have the `manage_pipeline` permission to use the Grok Debugger. -. Open the main menu, click *Dev Tools*, then click *Grok Debugger*. +. Find the *Grok Debugger* by navigating to the *Developer tools* page using the +navigation menu or the <>. . In *Sample Data*, enter a message that is representative of the data that you want to parse. For example: + diff --git a/docs/dev-tools/painlesslab/index.asciidoc b/docs/dev-tools/painlesslab/index.asciidoc index 387c0522dd1ca..84aa13b4590ca 100644 --- a/docs/dev-tools/painlesslab/index.asciidoc +++ b/docs/dev-tools/painlesslab/index.asciidoc @@ -12,7 +12,8 @@ process {ref}/docs-reindex.html[reindexed data], define complex <>, and work with data in other contexts. -To get started, open the main menu, click *Dev Tools*, and then click *Painless Lab*. +Find *Painless Lab* by navigating to the *Developer tools* page using the +navigation menu or the <>. [role="screenshot"] image::dev-tools/painlesslab/images/painless-lab.png[Painless Lab] diff --git a/docs/dev-tools/searchprofiler/index.asciidoc b/docs/dev-tools/searchprofiler/index.asciidoc index c323427318d54..7ce6e9fa48b39 100644 --- a/docs/dev-tools/searchprofiler/index.asciidoc +++ b/docs/dev-tools/searchprofiler/index.asciidoc @@ -14,9 +14,8 @@ poorly performing queries much faster. [[search-profiler-getting-started]] === Get started -*{searchprofiler}* is automatically enabled in {kib}. Open the main menu, -click *Dev Tools*, and then click *{searchprofiler}* -to get started. +. Find the *{searchprofiler}* by navigating to the *Developer tools* page using the +navigation menu or the <>. *{searchprofiler}* displays the names of the indices searched, the shards in each index, and how long it took for the query to complete. To try it out, replace the default `match_all` query diff --git a/docs/developer/getting-started/sample-data.asciidoc b/docs/developer/getting-started/sample-data.asciidoc index 2454c9d8a6146..a932db91c0377 100644 --- a/docs/developer/getting-started/sample-data.asciidoc +++ b/docs/developer/getting-started/sample-data.asciidoc @@ -8,7 +8,7 @@ There are a couple ways to easily get data ingested into {es}. The easiest is to install one or more of our available sample data packages. If you have no data, you should be prompted to install when running {kib} for the first time. You can also access and install the sample data packages -by going to the home page and clicking "add sample data". +by going to the **Integrations** page and selecting **Sample data**. [discrete] === makelogs script @@ -27,5 +27,5 @@ Make sure to execute `node scripts/makelogs` *after* {es} is up and running! [discrete] === CSV upload -You can also use the CSV uploader provided on the {kib} home page. +You can also use the CSV uploader provided on the **Upload file** page available in the list of **Integrations**. Navigate to **Add data** > **Upload file** to upload your data from a file. \ No newline at end of file diff --git a/docs/discover/document-explorer.asciidoc b/docs/discover/document-explorer.asciidoc index 071c9f9875028..921e0504f4596 100644 --- a/docs/discover/document-explorer.asciidoc +++ b/docs/discover/document-explorer.asciidoc @@ -1,8 +1,7 @@ [[document-explorer]] -== Explore your documents +== Customize the Discover view Fine tune your explorations by customizing *Discover* to bring out the the best view of your documents. -Adjust the chart height, modify the document table, and look inside a document. [role="screenshot"] image::images/hello-field.png[A view of the Discover app] @@ -10,34 +9,27 @@ image::images/hello-field.png[A view of the Discover app] [float] [[document-explorer-c]] -=== Hide or resize the chart +=== Hide or resize areas -Hide or resize the chart for a better fit. +* You can hide and show the chart and the fields list using the available collapse and expand button in the corresponding area. -* To turn off the display of the chart, click -image:images/chart-icon.png[icon button for opening Show/Hide chart menu, width=24px] -to open the *Chart options* menu, and then click *Hide chart*. - -* To change the chart height, drag the resize handle -image:images/resize-icon.png[two-line icon for increasing or decreasing the height of the chart, width=24px] +* Adjust the width and height of each area by dragging their border to the size you want. -The chart size is saved in your browser. - -* To reset the height, open the *Chart options* menu, and then select *Reset to default height*. +The size of each area is saved in your browser for the next time you open **Discover**. [float] [[document-explorer-customize]] === Modify the document table -Customize the appearance of the document table and its contents by resizing the columns and rows, -sorting and modifying the fields, and filtering the documents. +Customize the appearance of the document table and its contents to your liking. + +image:images/discover-customize-table.png[Options to customize the table in Discover] [float] [[document-explorer-columns]] ==== Reorder and resize the columns -* To move a single column, click its header. In the dropdown menu, -click *Move left* or *Move right*. +* To move a single column, open the column's contextual options, and select *Move left* or *Move right* in the available options. * To move multiple columns, click *Columns*. In the pop-up, drag the column names to their new order. @@ -46,17 +38,31 @@ In the pop-up, drag the column names to their new order. + Column widths are stored with a saved search. When you visualize saved searches on dashboards, the saved search appears the same as in **Discover**. +[float] +[[document-explorer-density]] +==== Customize the table density + +You can adjust the density of the table from the **Display options** located in the table toolbar. This can be particularly useful when scrolling through many results. [float] [[document-explorer-row-height]] ==== Adjust the row height To set the row height to one or more lines, or automatically -adjust the height to fit the contents, click the row height icon -image:images/row-height-icon.png[icon to open the Row height pop-up]. +adjust the height to fit the contents, open the **Display options** in the table toolbar, and adjust it as you need. + +You can define different settings for the header row and body rows. + +[float] +[[document-explorer-sample-size]] +==== Limit the sample size + +When the number of results returned by your search query (displayed at the top of the **Documents** or **Results** tab) is greater than the value of <>, the number of results displayed in the table is limited to the configured value by default. You can adjust the initial sample size for searches to any number between 10 and `discover:sampleSize` from the **Display options** located in the table toolbar. + +On the last page of the table, a message indicates that you've reached the end of the loaded search results. From that message, you can choose to load more results to continue exploring. + +image:images/discover-limit-sample-size.png[Limit sample size in Discover] -[role="screenshot"] -image::images/document-explorer-row-height.png[Row height settings for the document table, width="50%"] [float] [[document-explorer-sort-data]] @@ -70,7 +76,7 @@ column header, and then select the sort order. To sort by multiple fields: -. Click the *field sorted* option. +. Click the *Sort fields* option. + [role="screenshot"] image::images/document-explorer-sort-data.png[Pop-up in document table for sorting columns, width="50%"] @@ -106,62 +112,18 @@ Narrow your results to a subset of documents so you're comparing just the data o . Select the documents you want to compare. -. Click the *documents selected* option, and then select *Show selected documents only*. +. Click the *Selected* option, and then select *Show selected documents only*. + [role="screenshot"] -image::images/document-explorer-compare-data.png[Compare data in the document table, width="50%"] - -[float] -[[document-explorer-configure-table]] -==== Set the number of rows per page - -To change the numbers of rows you want to display on each page, use the *Rows per page* menu. The default is 100 rows per page. - -[role="screenshot"] -image::images/document-table-rows-per-page.png["Menu with options for setting the number of rows in the document table"] +image::images/document-explorer-compare-data.png[Compare data in the document table, width="40%"] +You can also compare individual field values using the <>. [float] -[[document-explorer-expand-documents]] - -=== Go inside a document - -Dive into an individual document to inspect its fields, set filters, and view -the documents that occurred before and after it. - -. Click the expand icon -image:images/expand-icon-2.png[double arrow icon to open a flyout with the document details]. -+ -You can view the document in two ways. The **Table** view displays the document fields row-by-row. -The **JSON** (JavaScript Object Notation) view allows you to look at how {es} returns the document. -+ -[role="screenshot"] -image::images/document-table-expanded.png[Expanded view of the document table] -+ -. In the *Table* view, scan through the fields and their values, or search for a field by name. - -. When you find a field of interest, -hover your mouse over the *Actions* column -to: -.. Filter the results to include or exclude specific fields or values. -.. Toggle the field in or out the document table. -.. Pin the field so it stays at the top. - -. To navigate to the next and previous documents, click the < and > arrows at the top of the view. +[[document-explorer-configure-table]] +==== Set the number of results per page -. To create a view of the document that you can bookmark and share, click **Single document**. -+ -[role="screenshot"] -image::images/discover-view-single-document.png[Discover single document view] -+ -The link is valid for the time the document is available in Elasticsearch. To create a customized view of the document, -you can create <>. +To change the numbers of results you want to display on each page, use the *Rows per page* menu. The default is 100 results per page. -. To view documents that occurred before or after the event you are looking at, click **Surrounding documents**. -+ -Documents are displayed using the same set of columns as the *Discover* view from which -the context was opened. The filters you applied are also carried over. Pinned -filters remain active, while other filters are copied in a disabled state. -+ [role="screenshot"] -image::images/discover-context.png[Image showing context view feature, with anchor documents highlighted in blue] +image::images/document-table-rows-per-page.png["Menu with options for setting the number of results in the document table"] diff --git a/docs/discover/field-statistics.asciidoc b/docs/discover/field-statistics.asciidoc index 8dccc0d4a5bbd..dc83d226ff364 100644 --- a/docs/discover/field-statistics.asciidoc +++ b/docs/discover/field-statistics.asciidoc @@ -12,7 +12,7 @@ for the data and its cardinality? This example explores the fields in the <>, or you can use your own data. -. Open the main menu, and click *Discover*. +. Go to *Discover*. . Expand the {data-source} dropdown, and select *Kibana Sample Data Logs*. diff --git a/docs/discover/get-started-discover.asciidoc b/docs/discover/get-started-discover.asciidoc new file mode 100644 index 0000000000000..ec44f977f4aac --- /dev/null +++ b/docs/discover/get-started-discover.asciidoc @@ -0,0 +1,356 @@ +[[discover-get-started]] +== Explore fields and data with Discover + +Learn how to use *Discover* to: + +- **Select** and **filter** your {es} data. +- **Explore** the fields and content of your data in depth. +- **Present** your findings in a visualization. + +*Prerequisites:* + +- If you don’t already have {kib}, https://www.elastic.co/cloud/elasticsearch-service/signup?baymax=docs-body&elektra=docs[start a free trial] on Elastic Cloud. +- You must have data in {es}. Examples on this page use the +<>, but you can use your own data. +- You should have an understanding of {ref}/documents-indices.html[{es} documents and indices] +and <>. + + +[float] +[[find-the-data-you-want-to-use]] +=== Load data into Discover + +Select the data you want to explore, and then specify the time range in which to view that data. + +. Find **Discover** in the navigation menu or by using the <>. + +. Select the data view that contains the data you want to explore. ++ +TIP: {kib} requires a <> to access your Elasticsearch data. A {data-source} can point to one or more indices, {ref}/data-streams.html[data streams], or {ref}/alias.html[index aliases]. When adding data to {es} using one of the many integrations available, sometimes data views are created automatically, but you can also create your own. ++ +If you're using sample data, data views are automatically created and are ready to use. ++ +[role="screenshot"] +image::images/discover-data-view.png[How to set the {data-source} in Discover, width="40%"] + +. If needed, adjust the <>, for example by setting it to the *Last 7 days*. ++ +The range selection is based on the default time field in your data view. +If you are using the sample data, this value was set when the data view was created. +If you are using your own data view, and it does not have a time field, the range selection is not available. + +**Discover** is populated with your data and you can view various areas with different information: + +* All fields detected are listed in a dedicated panel. +* A chart allows you to visualize your data. +* A table displays the results of your search. +By default, the table includes a column for the time field and a *Summary* column with an overview of each result. +You can modify the document table to display your fields of interest. + +You can later filter the data that shows in the chart and in the table by specifying a query and changing the time range. + +[float] +[[explore-fields-in-your-data]] +=== Explore the fields in your data + +**Discover** provides utilities designed to help you make sense of your data: + +. In the sidebar, check the available fields. It's very common to have hundreds of fields. Use the search at the top of that sidebar to look for specific terms in the field names. ++ +In this example, we've entered `ma` in the search field to find the `manufacturer` field. ++ +[role="screenshot"] +image:images/discover-sidebar-available-fields.png[Fields list that displays the top five search results, width=40%] ++ +TIP: You can combine multiple keywords or characters. For example, `geo dest` finds `geo.dest` and `geo.src.dest`. + +. Select a field to view its most frequent values. ++ +**Discover** shows the top 10 values and the number of records used to calculate those values. + +. Select the *Plus* icon to add fields to the results table. +You can also drag them from the list into the table. ++ +[role="screenshot"] +image::images/discover-add-icon.png[How to add a field as a column in the table, width="50%"] ++ +When you add fields to the table, the **Summary** column is replaced. ++ +[role="screenshot"] +image:images/document-table.png[Document table with fields for manufacturer, customer_first_name, and customer_last_name] + +. Arrange the view to your liking to display the fields and data you care most about using the various display options of **Discover**. For example, you can change the order and size of columns, expand the table to be in full screen or collapse the chart and the list of fields. Check <>. + +. **Save** your changes to be able to open the same view later on and explore your data further. + + +[float] +[[add-field-in-discover]] +==== Add a field to your {data-source} + +What happens if you forgot to define an important value as a separate field? Or, what if you +want to combine two fields and treat them as one? This is where {ref}/runtime.html[runtime fields] come into play. +You can add a runtime field to your {data-source} from inside of **Discover**, +and then use that field for analysis and visualizations, +the same way you do with other fields. + +. In the sidebar, select *Add a field*. + +. Select the **Type** of the new field. + +. **Name** the field. Name it in a way that corresponds to the way other fields of the data view are named. +You can set a custom label and description for the field to make it more recognizable in your data view. + +. Define the value that you want the field to show. By default, the field value is retrieved from the source data if it already contains a field with the same name. You can customize this with the following options: + +** **Set value**: Define a script that will determine the value to show for the field. For more information on adding fields and Painless scripting language examples, +refer to <>. +** **Set format**: Set your preferred format for displaying the value. Changing the format can affect the value and prevent highlighting in Discover. + +. In the advanced settings, you can adjust the field popularity to make it appear higher or lower in the fields list. By default, Discover orders popular fields from most selected to least selected. + +. **Save** your new field. + +You can now find it in the list of fields and add it to the table. + +In the following example, we're adding 2 fields: A simple "Hello world" field, and a second field that combines and transforms the `customer_first_name` and `customer_last_name` fields of the sample data into a single "customer" field: + +**Hello world field example**: + +* **Name**: `hello` +* **Type**: `Keyword` +* **Set value**: enabled +* **Script**: ++ +```ts +emit("Hello World!"); +``` + +**Customer field example**: + +* **Name**: `customer` +* **Type**: `Keyword` +* **Set value**: enabled +* **Script**: ++ +```ts +String str = doc['customer_first_name.keyword'].value; +char ch1 = str.charAt(0); +emit(doc['customer_last_name.keyword'].value + ", " + ch1); +``` + +[float] +==== Visualize aggregated fields +If a field can be {ref}/search-aggregations.html[aggregated], you can quickly +visualize it in detail by opening it in **Lens** from **Discover**. **Lens** is the default visualization editor in {kib}. + +. In the list of fields, find an aggregatable field. For example, with the sample data, you can look for `day_of_week`. ++ +[role="screenshot"] +image:images/discover-day-of-week.png[Top values for the day_of_week field, plus Visualize button, width=50%] + +. In the popup, click **Visualize**. ++ +{kib} creates a **Lens** visualization best suited for this field. + +. In **Lens**, from the *Available fields* list, drag and drop more fields to refine the visualization. In this example, we're adding the `manufacturer.keyword` field onto the workspace, which automatically adds a breakdown of the top values to the visualization. ++ +[role="screenshot"] +image:images/discover-from-visualize.png[Visualization that opens from Discover based on your data] + +. Save the visualization if you'd like to add it to a dashboard or keep it in the Visualize library for later use. + +For geo point fields (image:images/geoip-icon.png[Geo point field icon, width=20px]), +if you click **Visualize**, +your data appears in a map. + +[role="screenshot"] +image:images/discover-maps.png[Map containing documents] + + +[float] +[[compare-documents-in-discover]] +==== Compare documents + +You can use *Discover* to compare and diff the field values of multiple results or documents in the table. + +. Select the results you want to compare from the Documents or Results tab in Discover. + +. From the **Selected** menu in the table toolbar, choose **Compare selected**. The comparison view opens and shows the selected results next to each other. + +. Compare the values of each field. By default the first result selected shows as the reference for displaying differences in the other results. When the value remains the same for a given field, it's displayed in green. When the value differs, it's displayed in red. ++ +TIP: You can change the result used as reference by selecting **Pin for comparison** in the contextual menu of any other result. ++ +image:images/discover-compare-rows.png[Comparison view in Discover] + +. Optionally, customize the **Comparison settings** to your liking. You can for example choose to not highlight the differences, to show them more granularly at the line, word, or character level, or even to hide fields where the value matches for all results. + +. Exit the comparison view at any time using the **Exit comparison mode** button. + +[float] +[[copy-row-content]] +==== Copy results as text or JSON + +You can quickly copy the content currently displayed in the table for one or several results to your clipboard. + +. Select the results you want to copy. + +. Open the **Selected** menu in the table toolbar, and select **Copy selection as text** or **Copy documents as JSON**. + +The content is copied to your clipboard in the selected format. +Fields that are not currently added to the table are ignored. + +[float] +[[look-inside-a-document]] +==== Explore individual result or document details in depth + +[[document-explorer-expand-documents]] +Dive into an individual document to view its fields and the documents +that occurred before and after it. + +. In the document table, click the expand icon +image:images/expand-icon-2.png[double arrow icon to open a flyout with the document details] +to show document details. ++ +[role="screenshot"] +image:images/document-table-expanded.png[Table view with document expanded] + +. Scan through the fields and their values. You can filter the table in several ways: +** If you find a field of interest, +hover your mouse over the *Field* or *Value* columns for filters and additional options. +** Use the search above the table to filter for specific fields or values, or filter by field type using the options to the right of the search field. +** You can pin some fields by clicking the left column to keep them displayed even if you filter the table. ++ +TIP: You can restrict the fields listed in the detailed view to just the fields that you explicitly added to the **Discover** table, using the **Selected only** toggle. In ES|QL mode, you also have an option to hide fields with null values. + +. To navigate to a view of the document that you can bookmark and share, select ** View single document**. + +. To view documents that occurred before or after the event you are looking at, select +**View surrounding documents**. + + + + +[float] +[[search-in-discover]] +=== Search and filter data + +[float] +==== Default mode: Search and filter using KQL + +One of the unique capabilities of **Discover** is the ability to combine +free text search with filtering based on structured data. +To search all fields, enter a simple string in the query bar. + +[role="screenshot"] +image:images/discover-search-field.png[Search field in Discover] + +To search particular fields and +build more complex queries, use the <>. +As you type, KQL prompts you with the fields you can search and the operators +you can use to build a structured query. + +For example, search the ecommerce sample data for documents where the country matches US: + +. Enter `g`, and then select *geoip.country_iso_code*. +. Select *:* for equals, and *US* for the value, and then click the refresh button or press the Enter key. +. For a more complex search, try: ++ +```ts +geoip.country_iso_code : US and products.taxless_price >= 75 +``` + +[[filter-in-discover]] +With the query input, you can filter data using the KQL or Lucene languages. You can also use the **Add filter** function available next to the query input to build your filters one by one or define them as Query DSL. + +For example, exclude results from the ecommerce sample data view where day of week is not Wednesday: + +. Click image:images/add-icon.png[Add icon] next to the query bar. +. In the *Add filter* pop-up, set the field to *day_of_week*, the operator to *is not*, +and the value to *Wednesday*. ++ +[role="screenshot"] +image:images/discover-add-filter.png[Add filter dialog in Discover] + +. Click **Add filter**. +. Continue your exploration by adding more filters. +. To remove a filter, click the close icon (x) next to its name in the filter bar. + +[float] +==== Search and filter using ES|QL + +You can use **Discover** with the Elasticsearch Query Language, ES|QL. When using ES|QL, +you don't have to select a data view. It's your query that determines the data to explore and display in Discover. + +You can switch to the ES|QL mode of Discover from the application menu bar. + +Note that in ES|QL mode, the **Documents** tab is named **Results**. + +Learn more about how to use ES|QL queries in <>. + + + +[float] +[[save-discover-search]] +==== Save your search for later use + +Save your search so you can use it later, generate a CSV report, or use it to create visualizations, dashboards, and Canvas workpads. +Saving a search saves the query text, filters, +and current view of *Discover*, including the columns selected in +the document table, the sort order, and the {data-source}. + +. In the application menu bar, click **Save**. + +. Give your search a title and a description. + +. Optionally store <> and the time range with the search. + +. Click **Save**. + +[float] +[[share-your-findings]] +==== Share your search + +To share your search and **Discover** view with a larger audience, click *Share* in the application menu bar. +For detailed information about the sharing options, refer to <>. + + +[float] +[[alert-from-Discover]] +=== Generate alerts + +From *Discover*, you can create a rule to periodically +check when data goes above or below a certain threshold within a given time interval. + +. Ensure that your data view, +query, and filters fetch the data for which you want an alert. +. In the application menu bar, click *Alerts > Create search threshold rule*. ++ +The *Create rule* form is pre-filled with the latest query sent to {es}. +. <> and <>. + +. Click *Save*. + +For more about this and other rules provided in {alert-features}, go to <>. + + +[float] +=== What’s next? + +* <>. + +* <> to better meet your needs. + +[float] +=== Troubleshooting + +This section references common questions and issues encountered when using Discover. +Also check the following blog post: {blog-ref}troubleshooting-guide-common-issues-kibana-discover-load[Learn how to resolve common issues with Discover.] + +**Some fields show as empty while they should not be, why is that?** + +This can happen in several cases: + +* With runtime fields and regular keyword fields, when the string exceeds the value set for the {ref}/ignore-above.html[ignore_above] setting used when indexing the data into {es}. +* Due to the structure of nested fields, a leaf field added to the table as a column will not contain values in any of its cells. Instead, add the root field as a column to view a JSON representation of its values. Learn more in https://www.elastic.co/de/blog/discover-uses-fields-api-in-7-12[this blog post]. \ No newline at end of file diff --git a/docs/discover/images/discover-add-filter.png b/docs/discover/images/discover-add-filter.png index 3ce158fc4fb84..f72d4074b4b85 100644 Binary files a/docs/discover/images/discover-add-filter.png and b/docs/discover/images/discover-add-filter.png differ diff --git a/docs/discover/images/discover-compare-rows.png b/docs/discover/images/discover-compare-rows.png new file mode 100644 index 0000000000000..868a17fd7ca2d Binary files /dev/null and b/docs/discover/images/discover-compare-rows.png differ diff --git a/docs/discover/images/discover-customize-table.png b/docs/discover/images/discover-customize-table.png new file mode 100644 index 0000000000000..a0aba47f6cd15 Binary files /dev/null and b/docs/discover/images/discover-customize-table.png differ diff --git a/docs/discover/images/discover-data-view.png b/docs/discover/images/discover-data-view.png index 869fc9b928811..e6c3a9aa832d5 100644 Binary files a/docs/discover/images/discover-data-view.png and b/docs/discover/images/discover-data-view.png differ diff --git a/docs/discover/images/discover-limit-sample-size.png b/docs/discover/images/discover-limit-sample-size.png new file mode 100644 index 0000000000000..1e8628ebace55 Binary files /dev/null and b/docs/discover/images/discover-limit-sample-size.png differ diff --git a/docs/discover/images/document-explorer-compare-data.png b/docs/discover/images/document-explorer-compare-data.png index 36560dcabd13e..2a980f8977393 100644 Binary files a/docs/discover/images/document-explorer-compare-data.png and b/docs/discover/images/document-explorer-compare-data.png differ diff --git a/docs/discover/images/document-table-expanded.png b/docs/discover/images/document-table-expanded.png index a6fee908b668f..f73c7d08fe09f 100644 Binary files a/docs/discover/images/document-table-expanded.png and b/docs/discover/images/document-table-expanded.png differ diff --git a/docs/discover/images/document-table.png b/docs/discover/images/document-table.png index 8fbabe4703b24..ab9141cbb9b54 100644 Binary files a/docs/discover/images/document-table.png and b/docs/discover/images/document-table.png differ diff --git a/docs/discover/images/esql-custom-time-series.png b/docs/discover/images/esql-custom-time-series.png new file mode 100644 index 0000000000000..1be4e5f137fc1 Binary files /dev/null and b/docs/discover/images/esql-custom-time-series.png differ diff --git a/docs/discover/images/esql-full-query.png b/docs/discover/images/esql-full-query.png index e4f5faeef3cf7..6bcfba71c4cd6 100644 Binary files a/docs/discover/images/esql-full-query.png and b/docs/discover/images/esql-full-query.png differ diff --git a/docs/discover/images/esql-limit.png b/docs/discover/images/esql-limit.png index b03ecdcc091e6..37a59e0c6c797 100644 Binary files a/docs/discover/images/esql-limit.png and b/docs/discover/images/esql-limit.png differ diff --git a/docs/discover/images/esql-machine-os-ram.png b/docs/discover/images/esql-machine-os-ram.png index ad46d88b219ff..8e2e548a7b317 100644 Binary files a/docs/discover/images/esql-machine-os-ram.png and b/docs/discover/images/esql-machine-os-ram.png differ diff --git a/docs/discover/images/esql-no-time-series.png b/docs/discover/images/esql-no-time-series.png new file mode 100644 index 0000000000000..779269582e7ba Binary files /dev/null and b/docs/discover/images/esql-no-time-series.png differ diff --git a/docs/discover/images/hello-field.png b/docs/discover/images/hello-field.png index 261cb00acfa4c..8aee22bf2a847 100644 Binary files a/docs/discover/images/hello-field.png and b/docs/discover/images/hello-field.png differ diff --git a/docs/discover/log-pattern-analysis.asciidoc b/docs/discover/log-pattern-analysis.asciidoc index b4bd9fec29ec9..5131b68a073b4 100644 --- a/docs/discover/log-pattern-analysis.asciidoc +++ b/docs/discover/log-pattern-analysis.asciidoc @@ -7,7 +7,7 @@ Log pattern analysis works on every text field. This example uses the <>, or you can use your own data. -. Open the main menu, and click *Discover*. +. Go to *Discover*. . Expand the {data-source} dropdown, and select *Kibana Sample Data Logs*. diff --git a/docs/discover/save-search.asciidoc b/docs/discover/save-search.asciidoc index 10abef2e4a1bb..024fd97ab107b 100644 --- a/docs/discover/save-search.asciidoc +++ b/docs/discover/save-search.asciidoc @@ -43,7 +43,7 @@ used for the saved search is also automatically selected. [float] === Add search results to a dashboard -. Open the main menu, and then click *Dashboard*. +. Go to *Dashboards*. . Open or create the dashboard, then click *Edit*. . Click *Add from library*. . From the *Types* dropdown, select *Saved search*. diff --git a/docs/discover/search-sessions.asciidoc b/docs/discover/search-sessions.asciidoc index e7d7466b5ae0c..fe1e945e676ff 100644 --- a/docs/discover/search-sessions.asciidoc +++ b/docs/discover/search-sessions.asciidoc @@ -52,8 +52,8 @@ image::images/search-session-awhile.png[Search Session indicator displaying the Once you save a search session, you can start a new search, navigate to a different application, or close the browser. -. To view your saved searches, open the main menu, and then click -*Stack Management > Search Sessions*. +. To view your saved searches, go to the +*Search Sessions* management page using the navigation menu or the <>. For a saved or completed session, you can also open this view from the search sessions popup. + diff --git a/docs/discover/search.asciidoc b/docs/discover/search.asciidoc index 4f4f8f5b48d10..439c5c443cc02 100644 --- a/docs/discover/search.asciidoc +++ b/docs/discover/search.asciidoc @@ -113,8 +113,7 @@ To save the current search: . Click *Save* in the toolbar. . Enter a name for the search and click *Save*. -To import, export, and delete saved searches, open the main menu, -then click *Stack Management > Saved Objects*. +To import, export, and delete saved searches, go to the *Saved Objects* management page using the navigation menu or the <>. ==== Open a saved search To load a saved search into Discover: diff --git a/docs/discover/try-esql.asciidoc b/docs/discover/try-esql.asciidoc index 53862be75f010..149ce80dbb349 100644 --- a/docs/discover/try-esql.asciidoc +++ b/docs/discover/try-esql.asciidoc @@ -5,11 +5,17 @@ The Elasticsearch Query Language, {esql}, makes it easier to explore your data w In this tutorial we'll use the {kib} sample web logs in Discover and Lens to explore the data and create visualizations. +[TIP] +==== +For the complete {esql} documentation, including tutorials, examples and the full syntax reference, refer to the {ref}/esql.html[{es} documentation]. +For a more detailed overview of {esql} in {kib}, refer to {ref}/esql-kibana.html[Use {esql} in Kibana]. +==== + [float] [[prerequisite]] === Prerequisite -To be able to select **Language {esql}** from the Data views menu the `enableESQL` setting must be enabled from **Stack Management > Advanced Settings**. It is enabled by default. +To view the {esql} option in **Discover**, the `enableESQL` setting must be enabled from Kibana's **Advanced Settings**. It is enabled by default. [float] [[tutorial-try-esql]] @@ -17,24 +23,24 @@ To be able to select **Language {esql}** from the Data views menu the `enableESQ To load the sample data: -. On the home page, click **Try sample data**. -. Click **Other sample data sets**. -. On the Sample web logs card, click **Add data**. -. Open the main menu and select *Discover*. -. From the Data views menu, select *Language {esql}*. +. <>. +. Go to *Discover*. +. Select *Try {esql}* from the application menu bar. Let's say we want to find out what operating system users have and how much RAM is on their machine. . Set the time range to **Last 7 days**. -. Expand image:images/expand-icon-2.png[An image of the expand icon] the query bar. -. Put each processing command on a new line for better readability. . Copy the query below: + [source,esql] ---- -FROM kibana_sample_data_logs -| KEEP machine.os, machine.ram +FROM kibana_sample_data_logs <1> +| KEEP machine.os, machine.ram <2> ---- +<1> We're specifically looking for data from the sample web logs we just installed. +<2> We're only keeping the `machine.os` and `machine.ram` fields in the results table. ++ +TIP: Put each processing command on a new line for better readability. + . Click **▶Run**. + @@ -57,12 +63,14 @@ FROM kibana_sample_data_logs | LIMIT 10 ---- + -. Click **▶Run**. +. Click **▶Run** again. You can notice that the table is now limited to 10 results. The visualization also updated automatically based on the query, and broke down the data for you. ++ +NOTE: When you don't specify any specific fields to retain using `KEEP`, the visualization isn't broken down automatically. Instead, an additional option appears above the visualization and lets you select a field manually. + [role="screenshot"] image:images/esql-limit.png[An image of the extended query result] -Let's sort the data by machine ram and filter out the destination GB. +We will now take it a step further to sort the data by machine ram and filter out the `GB` destination. . Copy the query below: + @@ -75,18 +83,51 @@ FROM kibana_sample_data_logs | LIMIT 10 ---- + -. Click **▶Run**. +. Click **▶Run** again. The table and visualization no longer show results for which the `geo.dest` field value is "GB", and the results are now sorted in descending order in the table based on the `machine.ram` field. + [role="screenshot"] image:images/esql-full-query.png[An image of the full query result] + . Click **Save** to save the query and visualization to a dashboard. -To make changes to the visualization you can use the visualization drop-down. To make changes to the colors used or the axes, or click the pencil icon. This opens an in-line editor where you can change the colors and axes of the visualization. +[float] +==== Edit the ES|QL visualization + +You can make changes to the visualization by clicking the pencil icon. This opens additional settings that let you adjust the chart type, axes, breakdown, colors, and information displayed to your liking. If you're not sure which route to go, check one of the suggestions available in the visualization editor. + +If you'd like to keep the visualization and add it to a dashboard, you can save it using the floppy disk icon. + +[float] +==== ES|QL and time series data + +By default, ES|QL identifies time series data when an index contains a `@timestamp` field. This enables the time range selector and visualization options for your query. + +If your index doesn't have an explicit `@timestamp` field, but has a different time field, you can still enable the time range selector and visualization options by calling the `?_start` and `?_tend` parameters in your query. + +For example, the eCommerce sample data set doesn't have a `@timestamp` field, but has an `order_date` field. + +By default, when querying this data set, time series capabilities aren't active. No visualization is generated and the time picker is disabled. + +[source,esql] +---- +FROM kibana_sample_data_ecommerce +| KEEP customer_first_name, email, products._id.keyword +---- + +image::images/esql-no-time-series.png[ESQL query without time series capabilities enabled] + +While still querying the same data set, by adding the `?_start` and `?_tend` parameters based on the `order_date` field, **Discover** enables times series capabilities. + +[source,esql] +---- +FROM kibana_sample_data_ecommerce +| WHERE order_date >= ?_tstart and order_date <= ?_tend +---- + +image::images/esql-custom-time-series.png[ESQL query with a custom time field enabled] + + + + -[TIP] -==== -For the complete {esql} documentation, including tutorials, examples and the full syntax reference, refer to the {ref}/esql.html[{es} documentation]. -For a more detailed overview of {esql} in {kib}, refer to {ref}/esql-kibana.html[Use {esql} in Kibana]. -==== diff --git a/docs/fleet/fleet.asciidoc b/docs/fleet/fleet.asciidoc index dfee4c36171dc..52c2825557001 100644 --- a/docs/fleet/fleet.asciidoc +++ b/docs/fleet/fleet.asciidoc @@ -31,7 +31,7 @@ You can make a complete clone of a whole managed dashboard. If you clone a panel To clone a dashboard: -. Open the main menu (≡) and click *Dashboards*. +. Go to *Dashboards*. . Click on the name of the managed dashboard to view the dashboard. . Click *Clone* in the toolbar. . Click *Save and return* after editing the dashboard. diff --git a/docs/getting-started/quick-start-guide.asciidoc b/docs/getting-started/quick-start-guide.asciidoc index 1a25f2f1ec9f2..6be9dbfa2edb2 100644 --- a/docs/getting-started/quick-start-guide.asciidoc +++ b/docs/getting-started/quick-start-guide.asciidoc @@ -22,18 +22,19 @@ include::{docs-root}/shared/cloud/ess-getting-started.asciidoc[] [float] [[gs-get-data-into-kibana]] -== Add the sample data +== Add sample data Sample data sets come with sample visualizations, dashboards, and more to help you explore {kib} before you ingest or add your own data. -. On the home page, click *Try sample data*. +. Open the **Integrations** page from the navigation menu or using the <>. -. Click *Other sample data sets*. +. In the list of integrations, select **Sample Data**. -. On the *Sample eCommerce orders* card, click *Add data*. -+ -[role="screenshot"] -image::images/addData_sampleDataCards_8.6.0.png[Add data UI for the sample data sets] +. On the page that opens, select *Other sample data sets*. + +. Install the sample data sets that you want. + +Once installed, you can access the sample data in the various {kib} apps available to you. [float] [[explore-the-data]] @@ -41,7 +42,7 @@ image::images/addData_sampleDataCards_8.6.0.png[Add data UI for the sample data *Discover* displays the data in an interactive histogram that shows the distribution of data, or documents, over time, and a table that lists the fields for each document that matches the {data-source}. To view a subset of the documents, you can apply filters to the data, and customize the table to display only the fields you want to explore. -. Open the main menu, then click *Discover*. +. Go to *Discover*. . Change the <> to *Last 7 days*. + @@ -67,7 +68,7 @@ image::images/availableFields_discover_8.4.0.png[Discover table that displays on A dashboard is a collection of panels that you can use to visualize the data. Panels contain visualizations, interactive controls, text, and more. -. Open the main menu, then click *Dashboard*. +. Go to *Dashboards*. . Click *[eCommerce] Revenue Dashboard*. + diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index d6ae2aecaf276..f6b8e6844ce04 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -20,7 +20,7 @@ indicator is displayed: [role="screenshot"] image::images/settings-read-only-badge.png[Example of Advanced Settings Management's read only access indicator in Kibana's header] -To add the privilege, open the main menu, then click *Stack Management > Roles*. +To add the privilege, go to the *Roles* management page using the navigation menu or the <>. For more information on granting access to {kib}, refer to <>. @@ -30,7 +30,7 @@ For more information on granting access to {kib}, refer to < Advanced Settings*. +. Go to the *Advanced settings* page using the navigation menu or the <>. . Click *Space Settings*. . Scroll or search for the setting. . Make your change, then click *Save changes*. @@ -644,7 +644,7 @@ Disable this option if you prefer to use the new heatmap charts with improved pe Change the settings that apply only to {kib} spaces. -. Open the main menu, then click *Stack Management > Advanced Settings*. +. Go to the *Advanced settings* page using the navigation menu or the <>. . Click *Global Settings*. . Scroll or search for the setting. . Make your change, then click *Save changes*. diff --git a/docs/management/connectors/action-types/servicenow-sir.asciidoc b/docs/management/connectors/action-types/servicenow-sir.asciidoc index 4c3b6d15bf59e..295a326b490b2 100644 --- a/docs/management/connectors/action-types/servicenow-sir.asciidoc +++ b/docs/management/connectors/action-types/servicenow-sir.asciidoc @@ -285,7 +285,8 @@ IMPORTANT: Deprecated connectors will continue to function with the rules they w To update a deprecated connector: -. Open the main menu and go to *{stack-manage-app} > {connectors-ui}*. +. Go to the *{connectors-ui}* page using the navigation menu or the +<>. . Select the deprecated connector to open the *Edit connector* flyout. . In the warning message, click *Update this connector*. . Complete the guided steps in the *Edit connector* flyout. diff --git a/docs/management/connectors/action-types/servicenow.asciidoc b/docs/management/connectors/action-types/servicenow.asciidoc index 83f8bd050d044..852db21e77544 100644 --- a/docs/management/connectors/action-types/servicenow.asciidoc +++ b/docs/management/connectors/action-types/servicenow.asciidoc @@ -338,7 +338,8 @@ IMPORTANT: Deprecated connectors will continue to function with the rules they w To update a deprecated connector: -. Open the main menu and go to *{stack-manage-app} > {connectors-ui}*. +. Go to the *{connectors-ui}* page using the navigation menu or the +<>. . Select the deprecated connector to open the *Edit connector* flyout. . In the warning message, click *Update this connector*. . Complete the guided steps in the *Edit connector* flyout. diff --git a/docs/management/connectors/pre-configured-connectors.asciidoc b/docs/management/connectors/pre-configured-connectors.asciidoc index 8f9536331bb1c..06a77a12beab3 100644 --- a/docs/management/connectors/pre-configured-connectors.asciidoc +++ b/docs/management/connectors/pre-configured-connectors.asciidoc @@ -66,7 +66,8 @@ Sensitive properties, such as passwords, can also be stored in the [[managing-preconfigured-connectors]] === View preconfigured connectors -When you open the main menu, click *{stack-manage-app} > {connectors-ui}*. +go to the *{connectors-ui}* page using the navigation menu or the +<>. Preconfigured connectors appear regardless of which space you are in. They are tagged as “preconfigured”, and you cannot delete them. diff --git a/docs/management/manage-data-views.asciidoc b/docs/management/manage-data-views.asciidoc index 936d764433fe9..4c6a0d77b7a9e 100644 --- a/docs/management/manage-data-views.asciidoc +++ b/docs/management/manage-data-views.asciidoc @@ -39,7 +39,7 @@ then define the field values by emitting a single value using the {ref}/modules-scripting-painless.html[Painless scripting language]. You can also add runtime fields in <> and <>. -. Open the main menu, then click *Stack Management > Data Views*. +. Go to the *Data Views* management page using the navigation menu or the <>. . Select the data view that you want to add the runtime field to, then click *Add field*. @@ -162,7 +162,7 @@ else { Edit the settings for runtime fields, or remove runtime fields from data views. -. Open the main menu, then click *Stack Management > Data Views*. +. Go to the *Data Views* management page using the navigation menu or the <>. . Select the data view that contains the runtime field you want to manage, then open the runtime field edit options or delete the runtime field. @@ -198,7 +198,7 @@ https://www.elastic.co/blog/using-painless-kibana-scripted-fields[Using Painless Create and add scripted fields to your data views. -. Open the main menu, then click *Stack Management > Data Views*. +. Go to the *Data Views* management page using the navigation menu or the <>. . Select the data view you want to add a scripted field to. @@ -214,7 +214,7 @@ For more information about scripted fields in {es}, refer to {ref}/modules-scrip [[update-scripted-field]] ==== Manage scripted fields -. Open the main menu, then click *Stack Management > Data Views*. +. Go to the *Data Views* management page using the navigation menu or the <>. . Select the data view that contains the scripted field you want to manage. @@ -230,7 +230,7 @@ exceptions when you view the dynamically generated data. {kib} uses the same field types as {es}, however, some {es} field types are unsupported in {kib}. To customize how {kib} displays data fields, use the formatting options. -. Open the main menu, then click *Stack Management > Data Views*. +. Go to the *Data Views* management page using the navigation menu or the <>. . Click the data view that contains the field you want to change. diff --git a/docs/management/managing-licenses.asciidoc b/docs/management/managing-licenses.asciidoc index 837a83f0aae38..14b359276356c 100644 --- a/docs/management/managing-licenses.asciidoc +++ b/docs/management/managing-licenses.asciidoc @@ -11,14 +11,16 @@ If you need more than 30 days to complete your evaluation, request an extended trial at {extendtrial}. To view the status of your license, start a trial, or install a new -license, open the main menu, then click *Stack Management > License Management*. +license, go to the *License Management* page using the navigation menu or the +<>. [discrete] === Required permissions The `manage` cluster privilege is required to access *License Management*. -To add the privilege, open the main menu, then click *Stack Management > Roles*. +To add the privilege, go to the *Roles* management page using the navigation menu or the +<>. [discrete] [[license-expiration]] diff --git a/docs/management/managing-saved-objects.asciidoc b/docs/management/managing-saved-objects.asciidoc index 231843081e7e1..1e2e5d194cd3e 100644 --- a/docs/management/managing-saved-objects.asciidoc +++ b/docs/management/managing-saved-objects.asciidoc @@ -4,7 +4,8 @@ Edit, import, export, and copy your saved objects. These objects include dashboards, visualizations, maps, {data-sources}, *Canvas* workpads, and other saved objects. -To get started, open the main menu, and then click *Stack Management > Saved Objects*. +You can find the *Saved Objects* page using the navigation menu or the +<>. [role="screenshot"] image::images/management-saved-objects.png[Saved Objects] @@ -14,7 +15,8 @@ image::images/management-saved-objects.png[Saved Objects] To access *Saved Objects*, you must have the required `Saved Objects Management` {kib} privilege. -To add the privilege, open the main menu, and then click *Stack Management > Roles*. +To add the privilege, go to the *Roles* management page using the navigation menu or the +<>. NOTE: Granting access to `Saved Objects Management` authorizes users to manage all saved objects in {kib}, including objects that are managed by diff --git a/docs/management/managing-tags.asciidoc b/docs/management/managing-tags.asciidoc index b9fbe85760786..20e5fa897c0ae 100644 --- a/docs/management/managing-tags.asciidoc +++ b/docs/management/managing-tags.asciidoc @@ -5,7 +5,8 @@ Use tags to categorize your saved objects, then filter for related objects based on shared tags. -To get started, open the main menu, and then click *Stack Management > Tags*. +To get started, go to the *Tags* management page using the navigation menu or the +<>. [role="screenshot"] image::images/tags/tag-management-section.png[Tags management] @@ -15,8 +16,8 @@ image::images/tags/tag-management-section.png[Tags management] To create tags, you must meet the minimum requirements. -* Access to *Tags* requires the `Tag Management` Kibana privilege. To add the privilege, open the main menu, -and then click *Stack Management > Roles*. +* Access to *Tags* requires the `Tag Management` Kibana privilege. To add the privilege, go to the *Roles* page using the navigation menu or the +<>. * The `read` privilege allows you to assign tags to the saved objects for which you have write permission. * The `write` privilege enables you to create, edit, and delete tags. diff --git a/docs/management/rollups/create_and_manage_rollups.asciidoc b/docs/management/rollups/create_and_manage_rollups.asciidoc index 2f9ede62c0b0f..c6e379c3d53aa 100644 --- a/docs/management/rollups/create_and_manage_rollups.asciidoc +++ b/docs/management/rollups/create_and_manage_rollups.asciidoc @@ -9,7 +9,8 @@ an index pattern, and then rolls it into a new index. Rollup indices are a good way to compactly store months or years of historical data for use in visualizations and reports. -To get started, open the main menu, then click *Stack Management > Rollup Jobs*. +You can go to the *Rollup Jobs* page using the navigation menu or the +<>. [role="screenshot"] image::images/management_rollup_list.png[List of currently active rollup jobs] @@ -23,7 +24,8 @@ detailed information. The `manage_rollup` cluster privilege is required to access *Rollup jobs*. -To add the privilege, open the main menu, then click *Stack Management > Roles*. +To add the privilege, go to the *Roles* management page using the navigation menu or the +<>. [float] [[create-and-manage-rollup-job]] @@ -142,7 +144,8 @@ rollup index, or you can remove or archive it using Your next step is to visualize your rolled up data in a vertical bar chart. Most visualizations support rolled up data, with the exception of Timelion and Vega visualizations. -. Open the main menu, then click *Stack Management > Data Views*. +. Go to the *Data Views* page using the navigation menu or the +<>. . Click *Create data view*, and select *Rollup data view* from the dropdown. @@ -153,7 +156,7 @@ The notation for a combination data view with both raw and rolled up data is `rollup_logstash,kibana_sample_data_logs`. In this data view, `rollup_logstash` matches the rollup index and `kibana_sample_data_logs` matches the raw data. -. Open the main menu, click *Dashboard*, then *Create dashboard*. +. Go to *Dashboards*, then select *Create dashboard*. . Set the <> to *Last 90 days*. diff --git a/docs/management/watcher-ui/index.asciidoc b/docs/management/watcher-ui/index.asciidoc index 2e941cb86ca0b..7f85376ad5698 100644 --- a/docs/management/watcher-ui/index.asciidoc +++ b/docs/management/watcher-ui/index.asciidoc @@ -8,8 +8,8 @@ Watches are helpful for analyzing mission-critical and business-critical streaming data. For example, you might watch application logs for performance outages or audit access logs for security threats. -To get started, open the main menu, -then click *Stack Management > Watcher*. +Go to the *Watcher* page using the navigation menu or the +<>. With this UI, you can: * <> @@ -39,7 +39,7 @@ and either of these Watcher roles: * `watcher_admin`. You can perform all Watcher actions, including create and edit watches. * `watcher_user`. You can view watches, but not create or edit them. -To manage roles, open the main menu, then click *Stack Management > Roles*, or use the {api-kibana}/group/endpoint-roles[role APIs]. +To manage roles, go to the *Roles* management page, or use the {api-kibana}/group/endpoint-roles[role APIs]. Watches are shared between all users with the same role. NOTE: If you are creating a threshold watch, you must also have the `view_index_metadata` index privilege. See diff --git a/docs/maps/asset-tracking-tutorial.asciidoc b/docs/maps/asset-tracking-tutorial.asciidoc index 32ab099575c92..b1ded453214f6 100644 --- a/docs/maps/asset-tracking-tutorial.asciidoc +++ b/docs/maps/asset-tracking-tutorial.asciidoc @@ -35,7 +35,8 @@ To get to the fun of visualizing and alerting on Portland public transport vehic [float] ==== Step 1: Set up an Elasticsearch index -. In Kibana, open the main menu, then click *Dev Tools*. +. In Kibana, go to *Developer tools* using the navigation menu or the +<>. . In *Console*, create the `tri_met_tracks` index lifecyle policy. This policy will keep the events in the hot data phase for 7 days. The data then moves to the warm phase. After 365 days in the warm phase, the data is deleted. + .ILM policy definition @@ -503,7 +504,7 @@ TIP: You may want to tweak this Data View to adjust the field names and number o [float] ==== Step 4: Explore the Portland TriMet data -. Open the main menu, and click *Discover*. +. Go to *Discover*. . Set the data view to *{ems-asset-index-name}*. . Open the <>, and set the time range to the last 15 minutes. . Expand a document and explore some of the fields that you will use later in this tutorial: `trimet.bearing`, `trimet.inCongestion`, `trimet.location`, and `trimet.vehicleID`. @@ -523,7 +524,7 @@ It's hard to get an overview of Portland vehicles by looking at individual event Create your map and set the theme for the default layer to dark mode. -. Open the main menu, and click *Maps*. +. Go to *Maps*. . Click *Create map*. . In the *Layers* list, click *Road map*, and then click *Edit layer settings*. . Open the *Tile service* dropdown, and select *Road map - dark*. diff --git a/docs/maps/geojson-upload.asciidoc b/docs/maps/geojson-upload.asciidoc index f4208663078af..8bd8a32e5d444 100644 --- a/docs/maps/geojson-upload.asciidoc +++ b/docs/maps/geojson-upload.asciidoc @@ -19,8 +19,8 @@ GeoJSON is the most commonly used and flexible option. Follow these instructions to upload a GeoJSON data file, or try the <>. -. Open the main menu, click *Maps*, and then click *Add layer*. -. Click *Uploaded GeoJSON*. +. Go to *Maps*, and select *Add layer*. +. Select *Uploaded GeoJSON*. + [role="screenshot"] image::maps/images/fu_gs_select_source_file_upload.png[] diff --git a/docs/maps/import-geospatial-data.asciidoc b/docs/maps/import-geospatial-data.asciidoc index e84ba3c3cbd27..47d05c5f1d00f 100644 --- a/docs/maps/import-geospatial-data.asciidoc +++ b/docs/maps/import-geospatial-data.asciidoc @@ -23,7 +23,7 @@ To upload GeoJSON files, shapefiles, and draw features in {kib} with *Maps*, you * The `create` and `create_index` index privileges for destination indices * To use the index in *Maps*, you must also have the `read` and `view_index_metadata` index privileges for destination indices -To upload delimited files (such as CSV, TSV, or JSON files) on the {kib} home page, you must also have: +To upload delimited files (such as CSV, TSV, or JSON files) from the **Upload file** integration, you must also have: * The `all` {kib} privilege for *Discover* * The `manage_pipeline` or `manage_ingest_pipelines` cluster privilege @@ -33,9 +33,9 @@ To upload delimited files (such as CSV, TSV, or JSON files) on the {kib} home pa [discrete] === Upload delimited files with latitude and longitude columns -On the {kib} home page, you can upload a file and import it into an {es} index with latitude and longitude columns combined into a `geo_point` field. +You can upload a file and import it into an {es} index with latitude and longitude columns combined into a `geo_point` field. -. Go to the {kib} home page and click *Upload a file*. +. Go to the *Integrations* page and select *Upload file*. . Select a file in one of the supported file formats. . Click *Import*. . Select the *Advanced* tab. diff --git a/docs/maps/maps-getting-started.asciidoc b/docs/maps/maps-getting-started.asciidoc index 39579d935275e..8dec40df5eb31 100644 --- a/docs/maps/maps-getting-started.asciidoc +++ b/docs/maps/maps-getting-started.asciidoc @@ -31,7 +31,7 @@ refer to <>. [[maps-create]] === Step 1. Create a map -. Open the main menu, and then click *Dashboard*. +. Go to *Dashboards*. . Click **Create dashboard**. . Set the time range to *Last 7 days*. . Click the **Create new Maps** icon image:maps/images/app_gis_icon.png[]. diff --git a/docs/maps/reverse-geocoding-tutorial.asciidoc b/docs/maps/reverse-geocoding-tutorial.asciidoc index 48151281fb07d..ec221dfc5fb95 100644 --- a/docs/maps/reverse-geocoding-tutorial.asciidoc +++ b/docs/maps/reverse-geocoding-tutorial.asciidoc @@ -26,12 +26,7 @@ GeoIP is a common way of transforming an IP address to a longitude and latitude. You’ll use the <> that comes with Kibana for this tutorial. Web logs sample data set has longitude and latitude. If your web log data does not contain longitude and latitude, use {ref}/geoip-processor.html[GeoIP processor] to transform an IP address into a {ref}/geo-point.html[geo_point] field. -To install web logs sample data set: - -. On the home page, click *Try sample data*. -. Expand *Other sample data sets*. -. On the *Sample web logs* card, click *Add data*. - +To install the web logs sample data set, refer to <>. [float] === Step 2: Index Combined Statistical Area (CSA) regions @@ -46,7 +41,7 @@ To get the CSA boundary data: . Go to the https://www.census.gov/geographies/mapping-files/time-series/geo/carto-boundary-file.html[Census Bureau’s website] and download the `cb_2018_us_csa_500k.zip` file. . Uncompress the zip file. -. In Kibana, open the main menu, and click *Maps*. +. In Kibana, go to *Maps*. . Click *Create map*. . Click *Add layer*. . Click *Upload file*. @@ -71,7 +66,8 @@ image::maps/images/reverse-geocoding-tutorial/csa_regions.png[Map showing metro === Step 3: Reverse geocoding To visualize CSA regions by web log traffic, the web log traffic must contain a CSA region identifier. You'll use {es} {ref}/enrich-processor.html[enrich processor] to add CSA region identifiers to the web logs sample data set. You can skip this step if your source data already contains region identifiers. -. Open the main menu, and then click *Dev Tools*. +. Go to *Developer tools* using the navigation menu or the +<>. . In *Console*, create a {ref}/geo-match-enrich-policy-type.html[geo_match enrichment policy]: + [source,js] @@ -142,7 +138,7 @@ PUT kibana_sample_data_logs/_settings } ---------------------------------- -. Open the main menu, and click *Discover*. +. Go to *Discover*. . Set the data view to *Kibana Sample Data Logs*. . Open the <>, and set the time range to the last 30 days. . Scan through the list of *Available fields* until you find the `csa.GEOID` field. You can also search for the field by name. @@ -158,7 +154,7 @@ image::maps/images/reverse-geocoding-tutorial/discover_enriched_web_log.png[View === Step 4: Visualize Combined Statistical Area (CSA) regions by web traffic Now that our web traffic contains CSA region identifiers, you'll visualize CSA regions by web traffic. -. Open the main menu, and click *Maps*. +. Go to *Maps*. . Click *Create map*. . Click *Add layer*. . Click *Choropleth*. diff --git a/docs/maps/search.asciidoc b/docs/maps/search.asciidoc index bfd293aa2352f..b094934bc6b4f 100644 --- a/docs/maps/search.asciidoc +++ b/docs/maps/search.asciidoc @@ -85,7 +85,7 @@ Create filters from your map to focus in on just the data you want. *Maps* provi To filter your dashboard by your map bounds as you pan and zoom your map: -. Open the main menu, and then click *Dashboard*. +. Go to *Dashboards*. . Select your dashboard from the list or click *Create dashboard*. . If your dashboard does not have a map, add a map panel. . Click the gear icon image:maps/images/gear_icon.png[gear icon] to open the map panel menu. diff --git a/docs/osquery/manage-integration.asciidoc b/docs/osquery/manage-integration.asciidoc index 69cf505e724a2..7d6131ce88bfa 100644 --- a/docs/osquery/manage-integration.asciidoc +++ b/docs/osquery/manage-integration.asciidoc @@ -53,7 +53,8 @@ Any changes you make to `packs` from this field are not reflected in the UI on t While this allows you to use advanced Osquery functionality like pack discovery queries, you do lose the ability to manage packs defined this way from the Osquery *Packs* page. ========================= -. From the {kib} main menu, click *Fleet*, then the *Agent policies* tab. +. Go to *Fleet* using the navigation menu or the +<>, then open the *Agent policies* tab. . Click the name of the agent policy where you want to adjust the Osquery configuration. The configuration changes you make only apply to the policy you select. @@ -136,7 +137,8 @@ and Osquerybeat in the agent directory. Refer to the {fleet-guide}/installation- To get more details in the logs, change the agent logging level to debug: -. Open the main menu, and then select **Fleet**. +. Go to **Fleet** using the navigation menu or the +<>. . Select the agent that you want to debug. diff --git a/docs/osquery/osquery.asciidoc b/docs/osquery/osquery.asciidoc index 4f0859ac21b19..ebfd58c973370 100644 --- a/docs/osquery/osquery.asciidoc +++ b/docs/osquery/osquery.asciidoc @@ -36,7 +36,8 @@ view live and scheduled query results, but you cannot run live queries or edit. To inspect hosts, run a query against one or more agents or policies, then view the results. -. Open the main menu, and then click *Osquery*. +. Go to *Osquery* using the navigation menu or the +<>. . In the *Live queries* view, click **New live query**. . Choose to run a single query or a query pack. . Select one or more agents or groups to query. Start typing in the search field, diff --git a/docs/setup/configuring-reporting.asciidoc b/docs/setup/configuring-reporting.asciidoc index 4213cf38b6398..61ef028d1504f 100644 --- a/docs/setup/configuring-reporting.asciidoc +++ b/docs/setup/configuring-reporting.asciidoc @@ -48,9 +48,10 @@ NOTE: If you use the default settings, you can still create a custom role that g . Create the reporting role. -.. Open the main menu, then click *Stack Management*. +.. Go to the *Roles* management page using the navigation menu or the +<>. -.. Click *Roles > Create role*. +.. Click *Create role*. . Specify the role settings. @@ -86,9 +87,10 @@ NOTE: If the *Reporting* options for application features are unavailable, and t . Assign the reporting role to a user. -.. Open the main menu, then click *Stack Management*. +.. Go to the *Users* management page using the navigation menu or the +<>. -.. Click *Users*, then click the user you want to assign the reporting role to. +.. Select the user you want to assign the reporting role to. .. From the *Roles* dropdown, select *custom_reporting_user*. diff --git a/docs/setup/connect-to-elasticsearch.asciidoc b/docs/setup/connect-to-elasticsearch.asciidoc index f5c8ce3e732f2..f6e6c71e25fbd 100644 --- a/docs/setup/connect-to-elasticsearch.asciidoc +++ b/docs/setup/connect-to-elasticsearch.asciidoc @@ -6,8 +6,7 @@ which are pre-packaged assets that are available for a wide array of popular services and platforms. With integrations, you can add monitoring for logs and metrics, protect systems from security threats, and more. -All integrations are available in a single view, and -{kib} guides you there from the *Welcome* screen, home page, and main menu. +All integrations are available in a single view on the **Integrations** page. [role="screenshot"] image::images/add-integration.png[Integrations page from which you can choose integrations to start collecting and analyzing data] diff --git a/docs/upgrade-notes.asciidoc b/docs/upgrade-notes.asciidoc index bdfb59a90de7b..84a27e1194a16 100644 --- a/docs/upgrade-notes.asciidoc +++ b/docs/upgrade-notes.asciidoc @@ -1674,19 +1674,16 @@ When you create *Lens* visualization, the default for the *Legend width* is now [discrete] [[deprecation-192003]] -* Deprecated the Observability AI Assistant specific advanced setting `observability:aiAssistantLogsIndexPattern`. (8.16) +.Deprecated the Observability AI Assistant specific advanced setting `observability:aiAssistantLogsIndexPattern`. (8.16) [%collapsible] ==== *Details* + The Observability AI Assistant specific advanced setting for Logs index patterns `observability:aiAssistantLogsIndexPattern` is deprecated and no longer used. The AI Assistant will now use the existing **Log sources** setting `observability:logSources` instead. For more information, refer to ({kibana-pull}192003[#192003]). - -//*Impact* + -//!!TODO!! ==== [discrete] [[deprecation-194519]] -* The Logs Stream was hidden by default in favor of the Logs Explorer app. (8.16) +.The Logs Stream was hidden by default in favor of the Logs Explorer app. (8.16) [%collapsible] ==== *Details* + diff --git a/docs/user/canvas.asciidoc b/docs/user/canvas.asciidoc index e7b4fdaf20921..21803b90034ad 100644 --- a/docs/user/canvas.asciidoc +++ b/docs/user/canvas.asciidoc @@ -43,7 +43,8 @@ To create workpads, you must meet the minimum requirements. * Make sure you have sufficient privileges to create and save workpads. When the read-only indicator appears, you have insufficient privileges, and the options to create and save workpads are unavailable. For more information, refer to <>. -To open *Canvas*, open the main menu, then click *Canvas*. +You can open *Canvas* using the navigation menu or the +<>. [float] [[start-with-a-blank-workpad]] @@ -175,7 +176,7 @@ To share workpads with a larger audience, click *Share* in the toolbar. For deta [[export-single-workpad]] == Export workpads -Want to export multiple workpads? Go to the *Canvas* home page, select the workpads you want to export, then click *Export*. +Want to export multiple workpads? Go to the *Canvas* page, select the workpads you want to export, then click *Export*. -- @@ -185,8 +186,6 @@ include::{kibana-root}/docs/canvas/canvas-present-workpad.asciidoc[] include::{kibana-root}/docs/canvas/canvas-tutorial.asciidoc[] -include::{kibana-root}/docs/canvas/canvas-expression-lifecycle.asciidoc[] - include::{kibana-root}/docs/canvas/canvas-function-reference.asciidoc[] include::{kibana-root}/docs/canvas/canvas-tinymath-functions.asciidoc[] diff --git a/docs/user/dashboard/aggregation-based.asciidoc b/docs/user/dashboard/aggregation-based.asciidoc index 9098ea6265291..f27d60928e6fe 100644 --- a/docs/user/dashboard/aggregation-based.asciidoc +++ b/docs/user/dashboard/aggregation-based.asciidoc @@ -140,13 +140,9 @@ a bar chart that displays the top five log traffic sources for every three hours Add the sample web logs data that you'll use to create the bar chart, then create the dashboard. -. On the home page, click *Try sample data*. +. <>. -. Click *Other sample data sets*. - -. On the *Sample web logs* card, click *Add data*. - -. Open the main menu, then click *Dashboard*. +. Go to *Dashboards*. . On the *Dashboards* page, click *Create dashboard*. diff --git a/docs/user/dashboard/create-visualizations.asciidoc b/docs/user/dashboard/create-visualizations.asciidoc index 5115677a4f51a..815f46d5711eb 100644 --- a/docs/user/dashboard/create-visualizations.asciidoc +++ b/docs/user/dashboard/create-visualizations.asciidoc @@ -213,7 +213,7 @@ You can then **Save** and add it to an existing or a new dashboard using the sav . From your dashboard, select **Add panel**. . Choose **ES|QL** under **Visualizations**. An ES|QL editor appears and lets you configure your query and its associated visualization. The **Suggestions** panel can help you find alternative ways to configure the visualization. + -TIP: Check the link:esql-language.html[ES|QL reference] to get familiar with the syntax and optimize your query. +TIP: Check the link:{ref}/esql-language.html[ES|QL reference] to get familiar with the syntax and optimize your query. . When editing your query or its configuration, run the query to update the preview of the visualization. + image:https://images.contentstack.io/v3/assets/bltefdd0b53724fa2ce/blt69dcceb4f1e12bc1/66c752d6aff77d384dc44209/edit-esql-visualization.gif[Previewing an ESQL visualization] @@ -232,7 +232,7 @@ The Maps editor has extensive documentation. For your reading comfort, we have m . From your dashboard, select **Add panel**. . Choose **Field statistics** under **Visualizations**. An ES|QL editor appears and lets you configure your query with the fields and information that you want to show. + -TIP: Check the link:esql-language.html[ES|QL reference] to get familiar with the syntax and optimize your query. +TIP: Check the link:{ref}/esql-language.html[ES|QL reference] to get familiar with the syntax and optimize your query. . When editing your query or its configuration, run the query to update the preview of the visualization. + image:https://images.contentstack.io/v3/assets/bltefdd0b53724fa2ce/blte2b1371159f5b9ff/66fc6ca13804eb2532b20727/field-statistics-preview-8.16.0.gif[Editing a field statistics dashboard panel and running the query to update the preview] @@ -289,7 +289,8 @@ To personalize your dashboards, add your own logos and graphics with the *Image* [role="screenshot"] image::images/dashboard_addImageEditor_8.7.0.png[Add image editor] -To manage your uploaded image files, open the main menu, then click *Stack Management > Kibana > Files*. +To manage your uploaded image files, go to the *Files* management page using the navigation menu or the +<>. [WARNING] diff --git a/docs/user/dashboard/lens-advanced.asciidoc b/docs/user/dashboard/lens-advanced.asciidoc index 88dbe958b146a..5107172c40b31 100644 --- a/docs/user/dashboard/lens-advanced.asciidoc +++ b/docs/user/dashboard/lens-advanced.asciidoc @@ -16,15 +16,9 @@ Before you begin, you should be familiar with the <>. Add the sample eCommerce data, and create and set up the dashboard. -. On the home page, click *Try sample data*. +. <>. -. Expand *Other sample data sets*. - -. On the *Sample eCommerce orders* card, click *Add data*. - -Create the dashboard where you'll display the visualization panels. - -. Open the main menu, then click *Dashboards*. +. Go to *Dashboards*. . On the *Dashboards* page, click *Create dashboard*. diff --git a/docs/user/dashboard/timelion.asciidoc b/docs/user/dashboard/timelion.asciidoc index 27222e6a40e84..000cad3bdbc1d 100644 --- a/docs/user/dashboard/timelion.asciidoc +++ b/docs/user/dashboard/timelion.asciidoc @@ -88,7 +88,7 @@ Set up Metricbeat, then create the dashboard. . To set up Metricbeat, go to {metricbeat-ref}/metricbeat-installation-configuration.html[Metricbeat quick start: installation and configuration] -. From {kib}, open the main menu, then click *Dashboard*. +. Go to *Dashboards*. . On the *Dashboards* page, click *Create dashboard*. diff --git a/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc b/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc index db0717522d928..4d299ba951296 100644 --- a/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc +++ b/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc @@ -18,15 +18,9 @@ Before you begin, you should be familiar with the <>. Add the sample web logs data, and create and set up the dashboard. -. On the home page, click *Try sample data*. +. <>. -. Expand *Other sample data sets*. - -. On the *Sample web logs* card, click *Add data*. - -Create the dashboard where you'll display the visualization panels. - -. Open the main menu, then click *Dashboards*. +. Go to *Dashboards*. . Click *Create dashboard*. diff --git a/docs/user/dashboard/tutorials.asciidoc b/docs/user/dashboard/tutorials.asciidoc index 6c25fd221fe2a..e7752279ba476 100644 --- a/docs/user/dashboard/tutorials.asciidoc +++ b/docs/user/dashboard/tutorials.asciidoc @@ -2,7 +2,7 @@ Learn more about building dashboards and creating visualizations with the following tutorials. -These tutorials use sample data available in {kib} as a way to get started more easily, but you can apply and adapt these instructions to your own data as well. +These tutorials use <> available in {kib} as a way to get started more easily, but you can apply and adapt these instructions to your own data as well. include::tutorial-create-a-dashboard-of-lens-panels.asciidoc[] diff --git a/docs/user/dashboard/vega.asciidoc b/docs/user/dashboard/vega.asciidoc index cbb1f5dbf8cda..4ae9c994a54bb 100644 --- a/docs/user/dashboard/vega.asciidoc +++ b/docs/user/dashboard/vega.asciidoc @@ -40,13 +40,9 @@ As you edit the specs, work in small steps, and frequently save your work. Small Before starting, add the eCommerce sample data that you'll use in your spec, then create the dashboard. -. On the home page, click *Try sample data*. +. <>. -. Click *Other sample data sets*. - -. On the *Sample eCommerce orders* card, click *Add data*. - -. Open the main menu, then click *Dashboard*. +. Go to *Dashboards*. . On the *Dashboards* page, click *Create dashboard*. @@ -90,7 +86,8 @@ To check your work, open and use the <> on a separate . Open {kib} on a new tab. -. Open the main menu, then click *Dev Tools*. +. Go to the *Developer tools* page using the navigation menu or the +<>. . On the *Console* editor, enter the aggregation, then click *Click to send request*: diff --git a/docs/user/discover.asciidoc b/docs/user/discover.asciidoc index ddd06b06c9cd8..7cab19889f278 100644 --- a/docs/user/discover.asciidoc +++ b/docs/user/discover.asciidoc @@ -8,6 +8,7 @@ What pages on your website contain a specific word or phrase? What events were logged most recently? What processes take longer than 500 milliseconds to respond? +[[save-your-search]] With *Discover*, you can quickly search and filter your data, get information about the structure of the fields, and display your findings in a visualization. You can also customize and save your searches and place them on a dashboard. @@ -16,331 +17,10 @@ You can also customize and save your searches and place them on a dashboard. image::images/hello-field.png[A view of the Discover app] -[float] -=== Explore and query your data - -This tutorial shows you how to use *Discover* to search large amounts of -data and understand what’s going on at any given time. - -You’ll learn to: - -- **Select** data for your exploration, set a time range for that data, -search it with the {kib} Query Language, and filter the results. -- **Explore** the details of your data, view individual documents, and create tables -that summarize the contents of the data. -- **Present** your findings in a visualization. - -At the end of this tutorial, you’ll be ready to start exploring with your own -data in *Discover*. - -*Prerequisites:* - -- If you don’t already have {kib}, set it up with https://www.elastic.co/cloud/elasticsearch-service/signup?baymax=docs-body&elektra=docs[our free trial]. -- You must have data in {es}. This tutorial uses the -<>, but you can use your own data. -- You should have an understanding of {ref}/documents-indices.html[{es} documents and indices] -and <>. - - -[float] -[[find-the-data-you-want-to-use]] -=== Find your data - -Tell {kib} where to find the data you want to explore, and then specify the time range in which to view that data. - -. Open the main menu, and select **Discover**. - -. Select the data you want to work with. -+ -{kib} uses a <> to tell it where to find -your {es} data. -To view the ecommerce sample data, open the {data-source} menu, and then select **Kibana Sample Data Ecommerce**. -+ -[role="screenshot"] -image::images/discover-data-view.png[How to set the {data-source} in Discover, width="40%"] - -+ -To create a data view for your own data, -click *Create a data view*. -For details, refer to <> - -. Adjust the <> to view data for the *Last 7 days*. -+ -The range selection is based on the default time field in your data. -If you are using the sample data, this value was set when you added the data. -If you are using your own data, and it does not have a time field, the range selection is not available. - -. To view the count of documents for a given time in the specified range, -click and drag the mouse over the chart. - -[float] -[[explore-fields-in-your-data]] -=== Explore the fields in your data - -**Discover** includes a table -that shows all the documents that match your search. -By default, the document table includes a column for the time field and a column that lists all other fields in the document. -You’ll modify the document table to display your fields of interest. - -. In the sidebar, enter `ma` in the search field to find the `manufacturer` field. -+ -[role="screenshot"] -image:images/discover-sidebar-available-fields.png[Fields list that displays the top five search results, width=50%] -+ -NOTE: You can use wildcards in field searches. For example, `goe*dest` finds `geo.dest` and `geo.src.dest`. - -. In the *Available fields* list, click `manufacturer` to view its most popular values. -+ -**Discover** shows the top 10 values and the number of records used to calculate those values. - -. Click image:images/add-icon.png[Add icon] to toggle the field into the document table. -You can also drag the field from the *Available fields* list into the document table. -+ -[role="screenshot"] -image::images/discover-add-icon.png[How to add a field as a column in the table, width="50%"] - -. Find the `customer_first_name` and `customer_last_name` fields and add -them to the document table. Your table should look similar to this: -+ -[role="screenshot"] -image:images/document-table.png[Document table with fields for manufacturer, customer_first_name, and customer_last_name] - - -. Optionally try out these actions: -+ -* To rearrange the table columns, click a -column header, and then select *Move left* or *Move right*. -+ -* To copy the name or values in a column to the clipboard, click the column header and select the desired **Copy** option. -+ -* To view more of the document table, -click -image:images/chart-icon.png[icon button for opening Show/Hide chart menu, width=24px] -to open the *Chart options* menu, -and then select *Hide chart*. -+ -* For keyboard shortcuts on the document table, click -image:images/keyboard-shortcut-icon.png[icon button for opening list of keyboard shortcuts, width=24px]. -+ -* To set the row height to one or more lines, or automatically -adjust the height to fit the contents, click -image:images/row-height-icon.png[icon to open the Row height pop-up, width=24px]. -+ -* To toggle the table in and out of fullscreen mode, click the fullscreen icon -image:images/fullscreen-icon.png[icon to display the document table in fullscreen mode]. - - - - - - -[float] -[[add-field-in-discover]] -=== Add a field to your {data-source} - -What happens if you forgot to define an important value as a separate field? Or, what if you -want to combine two fields and treat them as one? This is where {ref}/runtime.html[runtime fields] come into play. -You can add a runtime field to your {data-source} from inside of **Discover**, -and then use that field for analysis and visualizations, -the same way you do with other fields. - -. In the sidebar, click *Add a field*. - -. In the *Create field* form, enter `hello` for the name. - -. Turn on *Set value*. - -. Define the script using the Painless scripting language. Runtime fields require an `emit()`. -+ -```ts -emit("Hello World!"); -``` - -. Click *Save*. - -. In the sidebar, search for the *hello* field, and then add it to the document table. -+ -[role="screenshot"] -image:images/hello-field.png[hello field in the document tables] - -. Create a second field named `customer` that combines customer last name and first initial. -+ -```ts -String str = doc['customer_first_name.keyword'].value; -char ch1 = str.charAt(0); -emit(doc['customer_last_name.keyword'].value + ", " + ch1); -``` -. Remove `customer_first_name` and `customer_last_name` from the document table, and then add `customer`. -+ -[role="screenshot"] -image:images/customer.png[Customer last name, first initial in the document table] -+ -For more information on adding fields and Painless scripting language examples, -refer to <>. - - -[float] -[[search-in-discover]] -=== Search your data - -One of the unique capabilities of **Discover** is the ability to combine -free text search with filtering based on structured data. -To search all fields, enter a simple string in the query bar. - -[role="screenshot"] -image:images/discover-search-field.png[Search field in Discover] - - -To search particular fields and -build more complex queries, use the <>. -As you type, KQL prompts you with the fields you can search and the operators -you can use to build a structured query. - -Search the ecommerce data for documents where the country matches US: - -. Enter `g`, and then select *geoip.country_iso_code*. -. Select *:* for equals some value and *US*, and then click the refresh button or press the Enter key. -. For a more complex search, try: -+ -```ts -geoip.country_iso_code : US and products.taxless_price >= 75 -``` - -[float] -[[filter-in-discover]] -=== Filter your data - -Whereas the query defines the set of documents you are interested in, -filters enable you to zero in on subsets of those documents. -You can filter results to include or exclude specific fields, filter for a value in a range, -and more. - -Exclude documents where day of week is not Wednesday: - -. Click image:images/add-icon.png[Add icon] next to the query bar. -. In the *Add filter* pop-up, set the field to *day_of_week*, the operator to *is not*, -and the value to *Wednesday*. -+ -[role="screenshot"] -image:images/discover-add-filter.png[Add filter dialog in Discover] - -. Click **Add filter**. -. Continue your exploration by adding more filters. -. To remove a filter, -click the close icon (x) next to its name in the filter bar. - -[float] -[[look-inside-a-document]] -=== Look inside a document - -Dive into an individual document to view its fields and the documents -that occurred before and after it. - -. In the document table, click the expand icon -image:images/expand-icon-2.png[double arrow icon to open a flyout with the document details] -to show document details. -+ -[role="screenshot"] -image:images/document-table-expanded.png[Table view with document expanded] - -. Scan through the fields and their values. If you find a field of interest, -hover your mouse over the *Actions* column for filters and other options. - -. To create a view of the document that you can bookmark and share, click **Single document**. - -. To view documents that occurred before or after the event you are looking at, click -**Surrounding documents**. - - - -[float] -[[save-your-search]] -=== Save your search for later use - -Save your search so you can use it later, generate a CSV report, or use it to create visualizations, dashboards, and Canvas workpads. -Saving a search saves the query text, filters, -and current view of *Discover*, including the columns selected in -the document table, the sort order, and the {data-source}. - -. In the toolbar, click **Save**. - -. Give your search a title. - -. Optionally store <> and the time range with the search. - -. Click **Save**. - -[float] -=== Visualize your findings -If a field can be {ref}/search-aggregations.html[aggregated], you can quickly -visualize it from **Discover**. - -. In the sidebar, find and then click `day_of_week`. -+ -[role="screenshot"] -image:images/discover-day-of-week.png[Top values for the day_of_week field, plus Visualize button, width=50%] - - -. In the popup, click **Visualize**. -+ -{kib} creates a visualization best suited for this field. - -. From the *Available fields* list, drag and drop `manufacturer.keyword` onto the workspace. -+ -[role="screenshot"] -image:images/discover-from-visualize.png[Visualization that opens from Discover based on your data] - -. Save your visualization for use on a dashboard. -+ -For geo point fields (image:images/geoip-icon.png[Geo point field icon, width=20px]), -if you click **Visualize**, -your data appears in a map. -+ -[role="screenshot"] -image:images/discover-maps.png[Map containing documents] - -[float] -[[share-your-findings]] -=== Share your findings - -To share your findings with a larger audience, click *Share* in the *Discover* toolbar. -For detailed information about the sharing options, refer to <>. - -[float] -[[alert-from-Discover]] -=== Generate alerts - -From *Discover*, you can create a rule to periodically -check when data goes above or below a certain threshold within a given time interval. - -. Ensure that your data view, -query, and filters fetch the data for which you want an alert. -. In the toolbar, click *Alerts > Create search threshold rule*. -+ -The *Create rule* form is pre-filled with the latest query sent to {es}. -. <> and <>. - -. Click *Save*. - -For more about this and other rules provided in {alert-features}, go to <>. - - -[float] -=== What’s next? - -* <>. - -* <>. - -* <> to better meet your needs. - -[float] -=== Troubleshooting - -* {blog-ref}troubleshooting-guide-common-issues-kibana-discover-load[Learn how to resolve common issues with Discover.] +-- +include::{kibana-root}/docs/discover/get-started-discover.asciidoc[] --- include::{kibana-root}/docs/discover/document-explorer.asciidoc[] include::{kibana-root}/docs/discover/search-for-relevance.asciidoc[] diff --git a/docs/user/graph/getting-started.asciidoc b/docs/user/graph/getting-started.asciidoc index 03274bec76714..40d23ba249fd0 100644 --- a/docs/user/graph/getting-started.asciidoc +++ b/docs/user/graph/getting-started.asciidoc @@ -9,7 +9,7 @@ You must index data into {es} before you can create a graph. [[exploring-connections]] === Graph a data connection -. Open the main menu, then click *Graph*. +. Go to *Graph*. + If this is your first graph, follow the prompts to create it. For subsequent graphs, click *New*. diff --git a/docs/user/graph/index.asciidoc b/docs/user/graph/index.asciidoc index 5e7b689b8d8f1..d6d30dfa80682 100644 --- a/docs/user/graph/index.asciidoc +++ b/docs/user/graph/index.asciidoc @@ -71,7 +71,7 @@ affecting the cluster. Use *Graph* to reveal the relationships in your data. -. Open the main menu, and then click *Graph*. +. Go to *Graph*. + If you're new to {kib}, and don't yet have any data, follow the link to add sample data. This example uses the {kib} sample web logs data set. diff --git a/docs/user/images/hello-field.png b/docs/user/images/hello-field.png new file mode 100644 index 0000000000000..8aee22bf2a847 Binary files /dev/null and b/docs/user/images/hello-field.png differ diff --git a/docs/user/introduction.asciidoc b/docs/user/introduction.asciidoc index 48c9dfd91c9c6..cd04da190eac8 100644 --- a/docs/user/introduction.asciidoc +++ b/docs/user/introduction.asciidoc @@ -71,8 +71,7 @@ image::images/visualization-journey.png[User data analysis journey] | *1* | *Add data.* The best way to add data to the Elastic Stack is to use one of our many <>. -Alternatively, you can add a sample data set or upload a file. All three options are available -on the home page. +On the **Integrations** page, you can also find options to add sample data sets or to upload a file. | *2* | *Explore.* With <>, you can search your data for hidden diff --git a/docs/user/monitoring/monitoring-elastic-agent.asciidoc b/docs/user/monitoring/monitoring-elastic-agent.asciidoc index 33899e69ba269..2be91f08cdc0d 100644 --- a/docs/user/monitoring/monitoring-elastic-agent.asciidoc +++ b/docs/user/monitoring/monitoring-elastic-agent.asciidoc @@ -27,7 +27,7 @@ in the {ref}/monitoring-production.html[{es} monitoring documentation]. To collect {kib} monitoring data, add a {kib} integration to an {agent} and deploy it to the host where {kib} is running. -. Go to the {kib} home page and click **Add integrations**. +. Go to the **Integrations** page. + NOTE: If you're using a monitoring cluster, use the {kib} instance connected to the monitoring cluster. diff --git a/docs/user/monitoring/monitoring-kibana.asciidoc b/docs/user/monitoring/monitoring-kibana.asciidoc index 7f060d7aab738..65c5bdf868b9b 100644 --- a/docs/user/monitoring/monitoring-kibana.asciidoc +++ b/docs/user/monitoring/monitoring-kibana.asciidoc @@ -49,7 +49,8 @@ By default, if you are running {kib} locally, go to `http://localhost:5601/`. If {security-features} are enabled, log in. -- -... Open the main menu, then click *Stack Monitoring*. If data collection is +... Go to the *Stack Monitoring* page using the +<>. If data collection is disabled, you are prompted to turn it on. ** From the Console or command line, set `xpack.monitoring.collection.enabled` diff --git a/docs/user/monitoring/viewing-metrics.asciidoc b/docs/user/monitoring/viewing-metrics.asciidoc index 0aaf7ad6bd332..342a8da76cc35 100644 --- a/docs/user/monitoring/viewing-metrics.asciidoc +++ b/docs/user/monitoring/viewing-metrics.asciidoc @@ -86,7 +86,8 @@ By default, if you are running {kib} locally, go to `http://localhost:5601/`. If the Elastic {security-features} are enabled, log in. -- -. Open the main menu, then click *Stack Monitoring*. +. Go to the *Stack Monitoring* page using the +<>. + -- If data collection is disabled, you are prompted to turn on data collection. diff --git a/docs/user/reporting/automating-report-generation.asciidoc b/docs/user/reporting/automating-report-generation.asciidoc index f2102e7c0e2db..9587674b59e61 100644 --- a/docs/user/reporting/automating-report-generation.asciidoc +++ b/docs/user/reporting/automating-report-generation.asciidoc @@ -12,7 +12,7 @@ Create the POST URL that triggers a report to generate PDF and CSV reports. To create the POST URL for PDF reports: -. Open the main menu, then click *Dashboard*, *Visualize Library*, or *Canvas*. +. Go to *Dashboards*, *Visualize Library*, or *Canvas*. . Open the dashboard, visualization, or **Canvas** workpad you want to view as a report. @@ -24,7 +24,7 @@ To create the POST URL for PDF reports: To create the POST URL for CSV reports: -. Open the main menu, then click *Discover*. +. Go to *Discover*. . Open the saved search you want to share. diff --git a/docs/user/reporting/index.asciidoc b/docs/user/reporting/index.asciidoc index 5f09ed6907c1f..ed4fef61026f5 100644 --- a/docs/user/reporting/index.asciidoc +++ b/docs/user/reporting/index.asciidoc @@ -54,7 +54,7 @@ In the following dashboard, the shareable container is highlighted: [role="screenshot"] image::user/reporting/images/shareable-container.png["Shareable Container"] -. Open the main menu, then open the saved search, dashboard, visualization, or workpad you want to share. +. Open the saved search, dashboard, visualization, or workpad you want to share. . From the toolbar, click *Share*, then select the report option. @@ -94,7 +94,7 @@ include::reporting-pdf-limitations.asciidoc[] Create and share JSON files for workpads. -. Open the main menu, then click *Canvas*. +. Go to *Canvas*. . Open the workpad you want to share. @@ -118,7 +118,7 @@ change {kib} sizing, {ess-console}[edit the deployment]. beta[] Create and securely share static *Canvas* workpads on a website. To customize the behavior of the workpad on your website, you can choose to autoplay the pages or hide the workpad toolbar. -. Open the main menu, then click *Canvas*. +. Go to *Canvas*. . Open the workpad you want to share. @@ -140,7 +140,7 @@ Display your dashboards on an internal company website or personal web page with For information about granting access to embedded dashboards, refer to <>. -. Open the main menu, then open the dashboard you want to share. +. Open the dashboard you want to share. . Click *Share > Embed code*. diff --git a/docs/user/security/api-keys/index.asciidoc b/docs/user/security/api-keys/index.asciidoc index 2f9a0d337e3b9..bbc5f2834c2cb 100644 --- a/docs/user/security/api-keys/index.asciidoc +++ b/docs/user/security/api-keys/index.asciidoc @@ -13,7 +13,8 @@ You can use {kib} to manage your different API keys: * Cross-cluster API key: allows other clusters to connect to this cluster. * Managed API key: created and managed by Kibana to run background tasks. -To manage API keys, open the main menu, then click *Stack Management > Security > API Keys*. +To manage API keys, go to the *API Keys* management page using the navigation menu or the +<>. [role="screenshot"] image:images/api-keys.png["API Keys UI"] @@ -28,13 +29,15 @@ image:images/api-keys.png["API Keys UI"] * To create or update a *cross-cluster API key*, you must have the `manage_security` privilege and an Enterprise license. * To have a read-only view on the API keys, you must have access to the page and the `read_security` cluster privilege. -To manage roles, open the main menu, then click *Stack Management > Security > Roles*, or use the {api-kibana}/group/endpoint-roles[role APIs]. +To manage roles, go to the *Roles* management page using the navigation menu or the +<>, or use the {api-kibana}/group/endpoint-roles[role APIs]. [float] [[create-api-key]] === Create an API key -To create an API key, open the main menu, then click *Stack Management > Security > API Keys > Create API key*. +To create an API key, go to the *API Keys* management page using the navigation menu or the +<>, and select *Create API key*. [role="screenshot"] image:images/create-ccr-api-key.png["Create API Key UI"] @@ -48,7 +51,8 @@ Refer to the {ref}/security-api-create-cross-cluster-api-key.html[create cross-c [[udpate-api-key]] === Update an API key -To update an API key, open the main menu, click *Stack Management > Security > API Keys*, and then click on the name of the key. You cannot update the name or the type of API key. +To update an API key, go to the *API Keys* management page using the navigation menu or the +<>, and then click on the name of the key. You cannot update the name or the type of API key. Refer to the {ref}/security-api-update-api-key.html[update API key] documentation to learn more about updating user API keys. diff --git a/docs/user/security/index.asciidoc b/docs/user/security/index.asciidoc index 44d7c41391c35..3ea0245a21657 100644 --- a/docs/user/security/index.asciidoc +++ b/docs/user/security/index.asciidoc @@ -21,7 +21,8 @@ The `manage_security` cluster privilege is required to access all Security featu [float] === Users -To create and manage users, open the main menu, then click *Stack Management > Users*. +To create and manage users, go to the *Users* management page using the navigation menu or the +<>. You can also change their passwords and roles. For more information about authentication and built-in users, see {ref}/setting-up-authentication.html[Setting up user authentication]. @@ -29,7 +30,8 @@ authentication and built-in users, see [float] === Roles -To manage roles, open the main menu, then click *Stack Management > Roles*, or use +To manage roles, go to the *Roles* management page using the navigation menu or the +<>, or use the {api-kibana}/group/endpoint-roles[role APIs]. For more information on configuring roles for {kib}, see <>. For a more holistic overview of configuring roles for the entire stack, diff --git a/docs/user/security/role-mappings/index.asciidoc b/docs/user/security/role-mappings/index.asciidoc index df4ded4321c13..35de39e052236 100644 --- a/docs/user/security/role-mappings/index.asciidoc +++ b/docs/user/security/role-mappings/index.asciidoc @@ -8,7 +8,8 @@ describe which roles to assign to your users using a set of rules. Role mappings are required when authenticating via an external identity provider, such as Active Directory, Kerberos, PKI, OIDC, or SAML. Role mappings have no effect for users inside the `native` or `file` realms. -To manage your role mappings, open the main menu, then click *Stack Management > Role Mappings*. +You can find the *Role mappings* management page using the navigation menu or the +<>. With *Role mappings*, you can: @@ -27,7 +28,8 @@ The `manage_security` cluster privilege is required to manage Role Mappings. [float] === Create a role mapping -. Open the main menu, then click *Stack Management > Role Mappings*. +. Go to the *Role mappings* management page using the navigation menu or the +<>. . Click *Create role mapping*. . Give your role mapping a unique name, and choose which roles you wish to assign to your users. + diff --git a/docs/user/security/securing-kibana.asciidoc b/docs/user/security/securing-kibana.asciidoc index 98290bb093e41..0c05dd89ebecf 100644 --- a/docs/user/security/securing-kibana.asciidoc +++ b/docs/user/security/securing-kibana.asciidoc @@ -65,10 +65,12 @@ the `elastic` user or other built-in users, run the . [[kibana-roles]]Create roles and users to grant access to {kib}. + -- -To manage privileges in {kib}, open the main menu, then click *Stack Management > Roles*. The built-in `kibana_admin` role will grant +To manage privileges in {kib}, go to the *Roles* management page using the navigation menu or the +<>. The built-in `kibana_admin` role will grant access to {kib} with administrator privileges. Alternatively, you can create additional roles that grant limited access to {kib}. -If you're using the default native realm with Basic Authentication, open the main menu, then click *Stack Management > Users* to create +If you're using the default native realm with Basic Authentication, go to the *Users* management page using the navigation menu or the +<> to create users and assign roles, or use the {es} {ref}/security-api.html#security-user-apis[user management APIs]. For example, the following creates a user named `jacknich` and assigns it the `kibana_admin` role: diff --git a/docs/user/security/tutorials/how-to-secure-access-to-kibana.asciidoc b/docs/user/security/tutorials/how-to-secure-access-to-kibana.asciidoc index d62ccebb05657..3b4e4b02af677 100644 --- a/docs/user/security/tutorials/how-to-secure-access-to-kibana.asciidoc +++ b/docs/user/security/tutorials/how-to-secure-access-to-kibana.asciidoc @@ -52,8 +52,8 @@ Let’s work through an example together. Consider a marketing analyst who wants Create a Marketing space for your marketing analysts to use. -. Open the main menu, and select **Stack Management**. -. Under **{kib}**, select **Spaces**. +. Go to the *Spaces* management page using the navigation menu or the +<>. . Click **Create a space**. . Give this space a unique name. For example: `Marketing`. . Click **Create space**. @@ -75,8 +75,8 @@ In this example, a marketing analyst will need: To create the role: -. Open the main menu, and select **Stack Management**. -. Under **Security**, select **Roles**. +. Go to the *Roles* management page using the navigation menu or the +<>. . Click **Create role**. . Give this role a unique name. For example: `marketing_dashboards_role`. . For this example, you want to store all marketing data in the `acme-marketing-*` set of indices. To grant this access, locate the **Index privileges** section and enter: diff --git a/oas_docs/README.md b/oas_docs/README.md index e37eefaed4851..3312bc60771e0 100644 --- a/oas_docs/README.md +++ b/oas_docs/README.md @@ -45,8 +45,7 @@ Besides the scripts in the `oas_docs/scripts` folder, there is an `oas_docs/make | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `api-docs` | Builds ESS Kibana OpenAPI bundle | | `api-docs-serverless` | Builds Serverless Kibana OpenAPI bundle | -| `api-docs-lint` | Lints built result bundles | -| `api-docs-lint-errs` | Lints built result bundles for errors | +| `api-docs-lint` | Lints built result bundles | | `api-docs-preview` | Generates (ESS + Serverless) Kibana OpenAPI bundles preview | | `api-docs-overlay` | Applies [overlays](https://docs.bump.sh/help/specification-support/overlays/) from `overlays` folder to the Kibana OpenAPI bundles and generate `*.new.yaml` files. Overlays help to fine tune the result bundles. | | `api-docs-overlay-preview` | Generates a preview for bundles produced by `api-docs-overlay` | diff --git a/oas_docs/.spectral.yaml b/oas_docs/linters/.spectral.yaml similarity index 100% rename from oas_docs/.spectral.yaml rename to oas_docs/linters/.spectral.yaml diff --git a/oas_docs/linters/redocly.yaml b/oas_docs/linters/redocly.yaml new file mode 100644 index 0000000000000..139a503ba856c --- /dev/null +++ b/oas_docs/linters/redocly.yaml @@ -0,0 +1,52 @@ +extends: + - recommended +rules: +# Built-in rules + # Descriptions + parameter-description: warn + tag-description: warn + operation-description: off + # Document info + info-contact: warn + info-license: warn + # Examples + no-invalid-media-type-examples: + severity: warn + allowAdditionalProperties: false + no-invalid-schema-examples: + severity: warn + allowAdditionalProperties: false + # Operations + operation-operationId: error + operation-operationId-unique: error + operation-operationId-url-safe: warn + operation-summary: warn + # Parameters + path-parameters-defined: warn + # Paths + no-ambiguous-paths: warn + no-identical-paths: error + path-excludes-patterns: + severity: error + patterns: + - ^\/internal + # Responses + operation-4xx-response: off + operation-2xx-response: off + # Schema + spec: off + spec-strict-refs: off + # Tags + operation-tag-defined: off + tags-alphabetical: off + operation-singular-tag: off +# Custom rules + rule/operation-summary-length: + subject: + type: Operation + property: summary + message: Operation summary must have a minimum of 5 and maximum of 45 characters + severity: warn + assertions: + maxLength: 45 + minLength: 5 \ No newline at end of file diff --git a/oas_docs/makefile b/oas_docs/makefile index 2c30b9d874bbb..c97b5046c62a9 100644 --- a/oas_docs/makefile +++ b/oas_docs/makefile @@ -22,16 +22,12 @@ api-docs-stateful: ## Generate only kibana.yaml @node scripts/merge_ess_oas.js .PHONY: api-docs-lint -api-docs-lint: ## Run spectral API docs linter - @npx @stoplight/spectral-cli lint "output/*.yaml" --ruleset ".spectral.yaml" - -.PHONY: api-docs-lint-errs -api-docs-lint-errs: ## Run spectral API docs linter and return only errors - @npx @stoplight/spectral-cli lint "output/*.yaml" --ruleset ".spectral.yaml" -D +api-docs-lint: ## Run redocly API docs linter + @npx @redocly/cli lint "output/*.yaml" --config "linters/redocly.yaml" --format stylish --max-problems 500 .PHONY: api-docs-lint-stateful -api-docs-lint-stateful: ## Run spectral API docs linter on kibana.yaml - @npx @stoplight/spectral-cli lint "output/kibana.yaml" --ruleset ".spectral.yaml" +api-docs-lint-stateful: ## Run redocly API docs linter on kibana.yaml + @npx @redocly/cli lint "output/kibana.yaml" --config "linters/redocly.yaml" --format stylish --max-problems 500 .PHONY: api-docs-overlay api-docs-overlay: ## Run spectral API docs linter diff --git a/package.json b/package.json index 1a58f5d8c87a1..c4ae24e4be0d5 100644 --- a/package.json +++ b/package.json @@ -1197,13 +1197,13 @@ "p-settle": "4.1.1", "papaparse": "^5.2.0", "pbf": "3.2.1", - "pdfmake": "^0.2.7", + "pdfmake": "^0.2.15", "peggy": "^1.2.0", "polished": "^3.7.2", "pretty-ms": "6.0.0", "prop-types": "^15.8.1", "proxy-from-env": "1.0.0", - "puppeteer": "23.3.1", + "puppeteer": "23.7.0", "query-string": "^6.13.2", "rbush": "^3.0.1", "re-resizable": "^6.9.9", @@ -1518,9 +1518,9 @@ "@storybook/react": "^6.5.16", "@storybook/testing-react": "^1.3.0", "@storybook/theming": "^6.5.16", - "@testing-library/dom": "^8.19.0", + "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.5.0", - "@testing-library/react": "^12.1.5", + "@testing-library/react": "^16.0.1", "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^14.5.2", "@types/adm-zip": "^0.5.0", @@ -1715,6 +1715,7 @@ "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-perf": "^3.3.1", + "eslint-plugin-testing-library": "^6.4.0", "eslint-traverse": "^1.0.0", "exit-hook": "^2.2.0", "expect": "^29.7.0", diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_kibana_client.ts b/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_kibana_client.ts index 9533ded24dec5..58bfbb6f6d58c 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_kibana_client.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_kibana_client.ts @@ -16,10 +16,12 @@ import { getFetchAgent } from '../../../cli/utils/ssl'; export class ApmSynthtraceKibanaClient { private readonly logger: Logger; private target: string; + private headers: Record; - constructor(options: { logger: Logger; target: string }) { + constructor(options: { logger: Logger; target: string; headers?: Record }) { this.logger = options.logger; this.target = options.target; + this.headers = { ...kibanaHeaders(), ...(options.headers ?? {}) }; } getFleetApmPackagePath(packageVersion?: string): string { @@ -63,7 +65,7 @@ export class ApmSynthtraceKibanaClient { async () => { const res = await fetch(url, { method: 'POST', - headers: kibanaHeaders(), + headers: this.headers, body: '{"force":true}', agent: getFetchAgent(url), }); @@ -111,7 +113,7 @@ export class ApmSynthtraceKibanaClient { async () => { const res = await fetch(url, { method: 'DELETE', - headers: kibanaHeaders(), + headers: this.headers, body: '{"force":true}', agent: getFetchAgent(url), }); diff --git a/packages/kbn-check-mappings-update-cli/current_fields.json b/packages/kbn-check-mappings-update-cli/current_fields.json index 5dabd353a8a9a..5ca2fc677b167 100644 --- a/packages/kbn-check-mappings-update-cli/current_fields.json +++ b/packages/kbn-check-mappings-update-cli/current_fields.json @@ -1072,6 +1072,7 @@ "urls" ], "synthetics-param": [], + "synthetics-private-location": [], "synthetics-privates-locations": [], "tag": [ "color", diff --git a/packages/kbn-check-mappings-update-cli/current_mappings.json b/packages/kbn-check-mappings-update-cli/current_mappings.json index 414faf88b304f..1e9ff6ac20c79 100644 --- a/packages/kbn-check-mappings-update-cli/current_mappings.json +++ b/packages/kbn-check-mappings-update-cli/current_mappings.json @@ -3549,6 +3549,10 @@ "dynamic": false, "properties": {} }, + "synthetics-private-location": { + "dynamic": false, + "properties": {} + }, "synthetics-privates-locations": { "dynamic": false, "properties": {} diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 3136065a87a0b..09df44219e484 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -201,7 +201,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D deployTrainedModels: `${MACHINE_LEARNING_DOCS}ml-nlp-deploy-models.html`, documentLevelSecurity: `${ELASTICSEARCH_DOCS}document-level-security.html`, e5Model: `${MACHINE_LEARNING_DOCS}ml-nlp-e5.html`, - elser: `${ELASTICSEARCH_DOCS}semantic-search-elser.html`, + elser: `${ELASTICSEARCH_DOCS}semantic-search-semantic-text.html`, engines: `${ENTERPRISE_SEARCH_DOCS}engines.html`, indexApi: `${ELASTICSEARCH_DOCS}docs-index_.html`, inferenceApiCreate: `${ELASTICSEARCH_DOCS}put-inference-api.html`, diff --git a/packages/kbn-esql-ast/src/antlr/esql_lexer.g4 b/packages/kbn-esql-ast/src/antlr/esql_lexer.g4 index 0ffee7c0b0d4f..ad17de2984ad7 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_lexer.g4 +++ b/packages/kbn-esql-ast/src/antlr/esql_lexer.g4 @@ -107,6 +107,8 @@ WS : [ \r\n\t]+ -> channel(HIDDEN) ; +COLON : ':'; + // // Expression - used by most command // @@ -209,8 +211,8 @@ MINUS : '-'; ASTERISK : '*'; SLASH : '/'; PERCENT : '%'; +EXPRESSION_COLON : {this.isDevVersion()}? COLON -> type(COLON); -MATCH : 'match'; NESTED_WHERE : WHERE -> type(WHERE); NAMED_OR_POSITIONAL_PARAM @@ -479,7 +481,7 @@ SHOW_WS mode SETTING_MODE; SETTING_CLOSING_BRACKET : CLOSING_BRACKET -> type(CLOSING_BRACKET), popMode; -COLON : ':'; +SETTING_COLON : COLON -> type(COLON); SETTING : (ASPERAND | DIGIT| DOT | LETTER | UNDERSCORE)+ diff --git a/packages/kbn-esql-ast/src/antlr/esql_lexer.interp b/packages/kbn-esql-ast/src/antlr/esql_lexer.interp index 2566da379af73..8f9c5956dddd5 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_lexer.interp +++ b/packages/kbn-esql-ast/src/antlr/esql_lexer.interp @@ -23,6 +23,7 @@ null null null null +':' '|' null null @@ -62,7 +63,6 @@ null '*' '/' '%' -'match' null null ']' @@ -103,7 +103,6 @@ null null null null -':' null null null @@ -146,6 +145,7 @@ UNKNOWN_CMD LINE_COMMENT MULTILINE_COMMENT WS +COLON PIPE QUOTED_STRING INTEGER_LITERAL @@ -185,7 +185,6 @@ MINUS ASTERISK SLASH PERCENT -MATCH NAMED_OR_POSITIONAL_PARAM OPENING_BRACKET CLOSING_BRACKET @@ -226,7 +225,6 @@ INFO SHOW_LINE_COMMENT SHOW_MULTILINE_COMMENT SHOW_WS -COLON SETTING SETTING_LINE_COMMENT SETTTING_MULTILINE_COMMENT @@ -268,6 +266,7 @@ UNKNOWN_CMD LINE_COMMENT MULTILINE_COMMENT WS +COLON PIPE DIGIT LETTER @@ -317,7 +316,7 @@ MINUS ASTERISK SLASH PERCENT -MATCH +EXPRESSION_COLON NESTED_WHERE NAMED_OR_POSITIONAL_PARAM OPENING_BRACKET @@ -406,7 +405,7 @@ SHOW_LINE_COMMENT SHOW_MULTILINE_COMMENT SHOW_WS SETTING_CLOSING_BRACKET -COLON +SETTING_COLON SETTING SETTING_LINE_COMMENT SETTTING_MULTILINE_COMMENT @@ -466,4 +465,4 @@ METRICS_MODE CLOSING_METRICS_MODE atn: -[4, 0, 120, 1479, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 2, 83, 7, 83, 2, 84, 7, 84, 2, 85, 7, 85, 2, 86, 7, 86, 2, 87, 7, 87, 2, 88, 7, 88, 2, 89, 7, 89, 2, 90, 7, 90, 2, 91, 7, 91, 2, 92, 7, 92, 2, 93, 7, 93, 2, 94, 7, 94, 2, 95, 7, 95, 2, 96, 7, 96, 2, 97, 7, 97, 2, 98, 7, 98, 2, 99, 7, 99, 2, 100, 7, 100, 2, 101, 7, 101, 2, 102, 7, 102, 2, 103, 7, 103, 2, 104, 7, 104, 2, 105, 7, 105, 2, 106, 7, 106, 2, 107, 7, 107, 2, 108, 7, 108, 2, 109, 7, 109, 2, 110, 7, 110, 2, 111, 7, 111, 2, 112, 7, 112, 2, 113, 7, 113, 2, 114, 7, 114, 2, 115, 7, 115, 2, 116, 7, 116, 2, 117, 7, 117, 2, 118, 7, 118, 2, 119, 7, 119, 2, 120, 7, 120, 2, 121, 7, 121, 2, 122, 7, 122, 2, 123, 7, 123, 2, 124, 7, 124, 2, 125, 7, 125, 2, 126, 7, 126, 2, 127, 7, 127, 2, 128, 7, 128, 2, 129, 7, 129, 2, 130, 7, 130, 2, 131, 7, 131, 2, 132, 7, 132, 2, 133, 7, 133, 2, 134, 7, 134, 2, 135, 7, 135, 2, 136, 7, 136, 2, 137, 7, 137, 2, 138, 7, 138, 2, 139, 7, 139, 2, 140, 7, 140, 2, 141, 7, 141, 2, 142, 7, 142, 2, 143, 7, 143, 2, 144, 7, 144, 2, 145, 7, 145, 2, 146, 7, 146, 2, 147, 7, 147, 2, 148, 7, 148, 2, 149, 7, 149, 2, 150, 7, 150, 2, 151, 7, 151, 2, 152, 7, 152, 2, 153, 7, 153, 2, 154, 7, 154, 2, 155, 7, 155, 2, 156, 7, 156, 2, 157, 7, 157, 2, 158, 7, 158, 2, 159, 7, 159, 2, 160, 7, 160, 2, 161, 7, 161, 2, 162, 7, 162, 2, 163, 7, 163, 2, 164, 7, 164, 2, 165, 7, 165, 2, 166, 7, 166, 2, 167, 7, 167, 2, 168, 7, 168, 2, 169, 7, 169, 2, 170, 7, 170, 2, 171, 7, 171, 2, 172, 7, 172, 2, 173, 7, 173, 2, 174, 7, 174, 2, 175, 7, 175, 2, 176, 7, 176, 2, 177, 7, 177, 2, 178, 7, 178, 2, 179, 7, 179, 2, 180, 7, 180, 2, 181, 7, 181, 2, 182, 7, 182, 2, 183, 7, 183, 2, 184, 7, 184, 2, 185, 7, 185, 2, 186, 7, 186, 2, 187, 7, 187, 2, 188, 7, 188, 2, 189, 7, 189, 2, 190, 7, 190, 2, 191, 7, 191, 2, 192, 7, 192, 2, 193, 7, 193, 2, 194, 7, 194, 2, 195, 7, 195, 2, 196, 7, 196, 2, 197, 7, 197, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 4, 19, 578, 8, 19, 11, 19, 12, 19, 579, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 5, 20, 588, 8, 20, 10, 20, 12, 20, 591, 9, 20, 1, 20, 3, 20, 594, 8, 20, 1, 20, 3, 20, 597, 8, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 5, 21, 606, 8, 21, 10, 21, 12, 21, 609, 9, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 22, 4, 22, 617, 8, 22, 11, 22, 12, 22, 618, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 28, 1, 28, 3, 28, 638, 8, 28, 1, 28, 4, 28, 641, 8, 28, 11, 28, 12, 28, 642, 1, 29, 1, 29, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 3, 31, 652, 8, 31, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 3, 33, 659, 8, 33, 1, 34, 1, 34, 1, 34, 5, 34, 664, 8, 34, 10, 34, 12, 34, 667, 9, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 675, 8, 34, 10, 34, 12, 34, 678, 9, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 3, 34, 685, 8, 34, 1, 34, 3, 34, 688, 8, 34, 3, 34, 690, 8, 34, 1, 35, 4, 35, 693, 8, 35, 11, 35, 12, 35, 694, 1, 36, 4, 36, 698, 8, 36, 11, 36, 12, 36, 699, 1, 36, 1, 36, 5, 36, 704, 8, 36, 10, 36, 12, 36, 707, 9, 36, 1, 36, 1, 36, 4, 36, 711, 8, 36, 11, 36, 12, 36, 712, 1, 36, 4, 36, 716, 8, 36, 11, 36, 12, 36, 717, 1, 36, 1, 36, 5, 36, 722, 8, 36, 10, 36, 12, 36, 725, 9, 36, 3, 36, 727, 8, 36, 1, 36, 1, 36, 1, 36, 1, 36, 4, 36, 733, 8, 36, 11, 36, 12, 36, 734, 1, 36, 1, 36, 3, 36, 739, 8, 36, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 62, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 1, 67, 1, 67, 1, 68, 1, 68, 1, 69, 1, 69, 1, 70, 1, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 73, 1, 73, 1, 73, 1, 73, 1, 74, 1, 74, 1, 74, 3, 74, 871, 8, 74, 1, 74, 5, 74, 874, 8, 74, 10, 74, 12, 74, 877, 9, 74, 1, 74, 1, 74, 4, 74, 881, 8, 74, 11, 74, 12, 74, 882, 3, 74, 885, 8, 74, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 5, 77, 899, 8, 77, 10, 77, 12, 77, 902, 9, 77, 1, 77, 1, 77, 3, 77, 906, 8, 77, 1, 77, 4, 77, 909, 8, 77, 11, 77, 12, 77, 910, 3, 77, 913, 8, 77, 1, 78, 1, 78, 4, 78, 917, 8, 78, 11, 78, 12, 78, 918, 1, 78, 1, 78, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 81, 1, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 83, 1, 83, 1, 83, 1, 83, 1, 83, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 85, 1, 85, 1, 85, 1, 85, 1, 86, 1, 86, 1, 86, 1, 86, 1, 87, 1, 87, 1, 87, 1, 87, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 89, 1, 89, 1, 89, 1, 89, 1, 90, 1, 90, 1, 90, 1, 90, 1, 91, 1, 91, 1, 91, 1, 91, 1, 92, 1, 92, 1, 92, 1, 92, 1, 93, 1, 93, 1, 93, 1, 93, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 95, 1, 95, 1, 95, 3, 95, 996, 8, 95, 1, 96, 4, 96, 999, 8, 96, 11, 96, 12, 96, 1000, 1, 97, 1, 97, 1, 97, 1, 97, 1, 98, 1, 98, 1, 98, 1, 98, 1, 99, 1, 99, 1, 99, 1, 99, 1, 100, 1, 100, 1, 100, 1, 100, 1, 101, 1, 101, 1, 101, 1, 101, 1, 102, 1, 102, 1, 102, 1, 102, 1, 102, 1, 103, 1, 103, 1, 103, 1, 103, 1, 104, 1, 104, 1, 104, 1, 104, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 106, 1, 106, 1, 106, 1, 106, 1, 106, 1, 107, 1, 107, 1, 107, 1, 107, 3, 107, 1050, 8, 107, 1, 108, 1, 108, 3, 108, 1054, 8, 108, 1, 108, 5, 108, 1057, 8, 108, 10, 108, 12, 108, 1060, 9, 108, 1, 108, 1, 108, 3, 108, 1064, 8, 108, 1, 108, 4, 108, 1067, 8, 108, 11, 108, 12, 108, 1068, 3, 108, 1071, 8, 108, 1, 109, 1, 109, 4, 109, 1075, 8, 109, 11, 109, 12, 109, 1076, 1, 110, 1, 110, 1, 110, 1, 110, 1, 111, 1, 111, 1, 111, 1, 111, 1, 112, 1, 112, 1, 112, 1, 112, 1, 113, 1, 113, 1, 113, 1, 113, 1, 113, 1, 114, 1, 114, 1, 114, 1, 114, 1, 115, 1, 115, 1, 115, 1, 115, 1, 116, 1, 116, 1, 116, 1, 116, 1, 117, 1, 117, 1, 117, 1, 117, 1, 117, 1, 118, 1, 118, 1, 118, 1, 118, 1, 118, 1, 119, 1, 119, 1, 119, 1, 120, 1, 120, 1, 120, 1, 120, 1, 121, 1, 121, 1, 121, 1, 121, 1, 122, 1, 122, 1, 122, 1, 122, 1, 123, 1, 123, 1, 123, 1, 123, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 125, 1, 125, 1, 125, 1, 125, 1, 125, 1, 126, 1, 126, 1, 126, 1, 126, 1, 126, 1, 127, 1, 127, 1, 127, 1, 127, 1, 127, 1, 127, 1, 127, 1, 128, 1, 128, 1, 129, 4, 129, 1162, 8, 129, 11, 129, 12, 129, 1163, 1, 129, 1, 129, 3, 129, 1168, 8, 129, 1, 129, 4, 129, 1171, 8, 129, 11, 129, 12, 129, 1172, 1, 130, 1, 130, 1, 130, 1, 130, 1, 131, 1, 131, 1, 131, 1, 131, 1, 132, 1, 132, 1, 132, 1, 132, 1, 133, 1, 133, 1, 133, 1, 133, 1, 134, 1, 134, 1, 134, 1, 134, 1, 134, 1, 134, 1, 135, 1, 135, 1, 135, 1, 135, 1, 136, 1, 136, 1, 136, 1, 136, 1, 137, 1, 137, 1, 137, 1, 137, 1, 138, 1, 138, 1, 138, 1, 138, 1, 139, 1, 139, 1, 139, 1, 139, 1, 140, 1, 140, 1, 140, 1, 140, 1, 141, 1, 141, 1, 141, 1, 141, 1, 141, 1, 142, 1, 142, 1, 142, 1, 142, 1, 142, 1, 143, 1, 143, 1, 143, 1, 143, 1, 144, 1, 144, 1, 144, 1, 144, 1, 145, 1, 145, 1, 145, 1, 145, 1, 146, 1, 146, 1, 146, 1, 146, 1, 146, 1, 147, 1, 147, 1, 147, 1, 147, 1, 148, 1, 148, 1, 148, 1, 148, 1, 148, 1, 149, 1, 149, 1, 149, 1, 149, 1, 149, 1, 150, 1, 150, 1, 150, 1, 150, 1, 151, 1, 151, 1, 151, 1, 151, 1, 152, 1, 152, 1, 152, 1, 152, 1, 153, 1, 153, 1, 153, 1, 153, 1, 154, 1, 154, 1, 154, 1, 154, 1, 155, 1, 155, 1, 155, 1, 155, 1, 155, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 157, 1, 157, 1, 157, 1, 157, 1, 158, 1, 158, 1, 158, 1, 158, 1, 159, 1, 159, 1, 159, 1, 159, 1, 160, 1, 160, 1, 160, 1, 160, 1, 160, 1, 161, 1, 161, 1, 162, 1, 162, 1, 162, 1, 162, 1, 162, 4, 162, 1316, 8, 162, 11, 162, 12, 162, 1317, 1, 163, 1, 163, 1, 163, 1, 163, 1, 164, 1, 164, 1, 164, 1, 164, 1, 165, 1, 165, 1, 165, 1, 165, 1, 166, 1, 166, 1, 166, 1, 166, 1, 166, 1, 167, 1, 167, 1, 167, 1, 167, 1, 168, 1, 168, 1, 168, 1, 168, 1, 169, 1, 169, 1, 169, 1, 169, 1, 170, 1, 170, 1, 170, 1, 170, 1, 170, 1, 171, 1, 171, 1, 171, 1, 171, 1, 172, 1, 172, 1, 172, 1, 172, 1, 173, 1, 173, 1, 173, 1, 173, 1, 174, 1, 174, 1, 174, 1, 174, 1, 175, 1, 175, 1, 175, 1, 175, 1, 176, 1, 176, 1, 176, 1, 176, 1, 176, 1, 176, 1, 177, 1, 177, 1, 177, 1, 177, 1, 178, 1, 178, 1, 178, 1, 178, 1, 179, 1, 179, 1, 179, 1, 179, 1, 180, 1, 180, 1, 180, 1, 180, 1, 181, 1, 181, 1, 181, 1, 181, 1, 182, 1, 182, 1, 182, 1, 182, 1, 183, 1, 183, 1, 183, 1, 183, 1, 183, 1, 184, 1, 184, 1, 184, 1, 184, 1, 184, 1, 184, 1, 185, 1, 185, 1, 185, 1, 185, 1, 185, 1, 185, 1, 186, 1, 186, 1, 186, 1, 186, 1, 187, 1, 187, 1, 187, 1, 187, 1, 188, 1, 188, 1, 188, 1, 188, 1, 189, 1, 189, 1, 189, 1, 189, 1, 189, 1, 189, 1, 190, 1, 190, 1, 190, 1, 190, 1, 190, 1, 190, 1, 191, 1, 191, 1, 191, 1, 191, 1, 192, 1, 192, 1, 192, 1, 192, 1, 193, 1, 193, 1, 193, 1, 193, 1, 194, 1, 194, 1, 194, 1, 194, 1, 194, 1, 194, 1, 195, 1, 195, 1, 195, 1, 195, 1, 195, 1, 195, 1, 196, 1, 196, 1, 196, 1, 196, 1, 196, 1, 196, 1, 197, 1, 197, 1, 197, 1, 197, 1, 197, 2, 607, 676, 0, 198, 15, 1, 17, 2, 19, 3, 21, 4, 23, 5, 25, 6, 27, 7, 29, 8, 31, 9, 33, 10, 35, 11, 37, 12, 39, 13, 41, 14, 43, 15, 45, 16, 47, 17, 49, 18, 51, 19, 53, 20, 55, 21, 57, 22, 59, 23, 61, 24, 63, 0, 65, 0, 67, 0, 69, 0, 71, 0, 73, 0, 75, 0, 77, 0, 79, 0, 81, 0, 83, 25, 85, 26, 87, 27, 89, 28, 91, 29, 93, 30, 95, 31, 97, 32, 99, 33, 101, 34, 103, 35, 105, 36, 107, 37, 109, 38, 111, 39, 113, 40, 115, 41, 117, 42, 119, 43, 121, 44, 123, 45, 125, 46, 127, 47, 129, 48, 131, 49, 133, 50, 135, 51, 137, 52, 139, 53, 141, 54, 143, 55, 145, 56, 147, 57, 149, 58, 151, 59, 153, 60, 155, 61, 157, 62, 159, 63, 161, 0, 163, 64, 165, 65, 167, 66, 169, 67, 171, 0, 173, 68, 175, 69, 177, 70, 179, 71, 181, 0, 183, 0, 185, 72, 187, 73, 189, 74, 191, 0, 193, 0, 195, 0, 197, 0, 199, 0, 201, 0, 203, 75, 205, 0, 207, 76, 209, 0, 211, 0, 213, 77, 215, 78, 217, 79, 219, 0, 221, 0, 223, 0, 225, 0, 227, 0, 229, 0, 231, 0, 233, 80, 235, 81, 237, 82, 239, 83, 241, 0, 243, 0, 245, 0, 247, 0, 249, 0, 251, 0, 253, 84, 255, 0, 257, 85, 259, 86, 261, 87, 263, 0, 265, 0, 267, 88, 269, 89, 271, 0, 273, 90, 275, 0, 277, 91, 279, 92, 281, 93, 283, 0, 285, 0, 287, 0, 289, 0, 291, 0, 293, 0, 295, 0, 297, 0, 299, 0, 301, 94, 303, 95, 305, 96, 307, 0, 309, 0, 311, 0, 313, 0, 315, 0, 317, 0, 319, 97, 321, 98, 323, 99, 325, 0, 327, 100, 329, 101, 331, 102, 333, 103, 335, 0, 337, 104, 339, 105, 341, 106, 343, 107, 345, 108, 347, 0, 349, 0, 351, 0, 353, 0, 355, 0, 357, 0, 359, 0, 361, 109, 363, 110, 365, 111, 367, 0, 369, 0, 371, 0, 373, 0, 375, 112, 377, 113, 379, 114, 381, 0, 383, 0, 385, 0, 387, 115, 389, 116, 391, 117, 393, 0, 395, 0, 397, 118, 399, 119, 401, 120, 403, 0, 405, 0, 407, 0, 409, 0, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 35, 2, 0, 68, 68, 100, 100, 2, 0, 73, 73, 105, 105, 2, 0, 83, 83, 115, 115, 2, 0, 69, 69, 101, 101, 2, 0, 67, 67, 99, 99, 2, 0, 84, 84, 116, 116, 2, 0, 82, 82, 114, 114, 2, 0, 79, 79, 111, 111, 2, 0, 80, 80, 112, 112, 2, 0, 78, 78, 110, 110, 2, 0, 72, 72, 104, 104, 2, 0, 86, 86, 118, 118, 2, 0, 65, 65, 97, 97, 2, 0, 76, 76, 108, 108, 2, 0, 88, 88, 120, 120, 2, 0, 70, 70, 102, 102, 2, 0, 77, 77, 109, 109, 2, 0, 71, 71, 103, 103, 2, 0, 75, 75, 107, 107, 2, 0, 87, 87, 119, 119, 2, 0, 85, 85, 117, 117, 6, 0, 9, 10, 13, 13, 32, 32, 47, 47, 91, 91, 93, 93, 2, 0, 10, 10, 13, 13, 3, 0, 9, 10, 13, 13, 32, 32, 1, 0, 48, 57, 2, 0, 65, 90, 97, 122, 8, 0, 34, 34, 78, 78, 82, 82, 84, 84, 92, 92, 110, 110, 114, 114, 116, 116, 4, 0, 10, 10, 13, 13, 34, 34, 92, 92, 2, 0, 43, 43, 45, 45, 1, 0, 96, 96, 2, 0, 66, 66, 98, 98, 2, 0, 89, 89, 121, 121, 11, 0, 9, 10, 13, 13, 32, 32, 34, 34, 44, 44, 47, 47, 58, 58, 61, 61, 91, 91, 93, 93, 124, 124, 2, 0, 42, 42, 47, 47, 11, 0, 9, 10, 13, 13, 32, 32, 34, 35, 44, 44, 47, 47, 58, 58, 60, 60, 62, 63, 92, 92, 124, 124, 1507, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 1, 61, 1, 0, 0, 0, 1, 83, 1, 0, 0, 0, 1, 85, 1, 0, 0, 0, 1, 87, 1, 0, 0, 0, 1, 89, 1, 0, 0, 0, 1, 91, 1, 0, 0, 0, 1, 93, 1, 0, 0, 0, 1, 95, 1, 0, 0, 0, 1, 97, 1, 0, 0, 0, 1, 99, 1, 0, 0, 0, 1, 101, 1, 0, 0, 0, 1, 103, 1, 0, 0, 0, 1, 105, 1, 0, 0, 0, 1, 107, 1, 0, 0, 0, 1, 109, 1, 0, 0, 0, 1, 111, 1, 0, 0, 0, 1, 113, 1, 0, 0, 0, 1, 115, 1, 0, 0, 0, 1, 117, 1, 0, 0, 0, 1, 119, 1, 0, 0, 0, 1, 121, 1, 0, 0, 0, 1, 123, 1, 0, 0, 0, 1, 125, 1, 0, 0, 0, 1, 127, 1, 0, 0, 0, 1, 129, 1, 0, 0, 0, 1, 131, 1, 0, 0, 0, 1, 133, 1, 0, 0, 0, 1, 135, 1, 0, 0, 0, 1, 137, 1, 0, 0, 0, 1, 139, 1, 0, 0, 0, 1, 141, 1, 0, 0, 0, 1, 143, 1, 0, 0, 0, 1, 145, 1, 0, 0, 0, 1, 147, 1, 0, 0, 0, 1, 149, 1, 0, 0, 0, 1, 151, 1, 0, 0, 0, 1, 153, 1, 0, 0, 0, 1, 155, 1, 0, 0, 0, 1, 157, 1, 0, 0, 0, 1, 159, 1, 0, 0, 0, 1, 161, 1, 0, 0, 0, 1, 163, 1, 0, 0, 0, 1, 165, 1, 0, 0, 0, 1, 167, 1, 0, 0, 0, 1, 169, 1, 0, 0, 0, 1, 173, 1, 0, 0, 0, 1, 175, 1, 0, 0, 0, 1, 177, 1, 0, 0, 0, 1, 179, 1, 0, 0, 0, 2, 181, 1, 0, 0, 0, 2, 183, 1, 0, 0, 0, 2, 185, 1, 0, 0, 0, 2, 187, 1, 0, 0, 0, 2, 189, 1, 0, 0, 0, 3, 191, 1, 0, 0, 0, 3, 193, 1, 0, 0, 0, 3, 195, 1, 0, 0, 0, 3, 197, 1, 0, 0, 0, 3, 199, 1, 0, 0, 0, 3, 201, 1, 0, 0, 0, 3, 203, 1, 0, 0, 0, 3, 207, 1, 0, 0, 0, 3, 209, 1, 0, 0, 0, 3, 211, 1, 0, 0, 0, 3, 213, 1, 0, 0, 0, 3, 215, 1, 0, 0, 0, 3, 217, 1, 0, 0, 0, 4, 219, 1, 0, 0, 0, 4, 221, 1, 0, 0, 0, 4, 223, 1, 0, 0, 0, 4, 225, 1, 0, 0, 0, 4, 227, 1, 0, 0, 0, 4, 233, 1, 0, 0, 0, 4, 235, 1, 0, 0, 0, 4, 237, 1, 0, 0, 0, 4, 239, 1, 0, 0, 0, 5, 241, 1, 0, 0, 0, 5, 243, 1, 0, 0, 0, 5, 245, 1, 0, 0, 0, 5, 247, 1, 0, 0, 0, 5, 249, 1, 0, 0, 0, 5, 251, 1, 0, 0, 0, 5, 253, 1, 0, 0, 0, 5, 255, 1, 0, 0, 0, 5, 257, 1, 0, 0, 0, 5, 259, 1, 0, 0, 0, 5, 261, 1, 0, 0, 0, 6, 263, 1, 0, 0, 0, 6, 265, 1, 0, 0, 0, 6, 267, 1, 0, 0, 0, 6, 269, 1, 0, 0, 0, 6, 273, 1, 0, 0, 0, 6, 275, 1, 0, 0, 0, 6, 277, 1, 0, 0, 0, 6, 279, 1, 0, 0, 0, 6, 281, 1, 0, 0, 0, 7, 283, 1, 0, 0, 0, 7, 285, 1, 0, 0, 0, 7, 287, 1, 0, 0, 0, 7, 289, 1, 0, 0, 0, 7, 291, 1, 0, 0, 0, 7, 293, 1, 0, 0, 0, 7, 295, 1, 0, 0, 0, 7, 297, 1, 0, 0, 0, 7, 299, 1, 0, 0, 0, 7, 301, 1, 0, 0, 0, 7, 303, 1, 0, 0, 0, 7, 305, 1, 0, 0, 0, 8, 307, 1, 0, 0, 0, 8, 309, 1, 0, 0, 0, 8, 311, 1, 0, 0, 0, 8, 313, 1, 0, 0, 0, 8, 315, 1, 0, 0, 0, 8, 317, 1, 0, 0, 0, 8, 319, 1, 0, 0, 0, 8, 321, 1, 0, 0, 0, 8, 323, 1, 0, 0, 0, 9, 325, 1, 0, 0, 0, 9, 327, 1, 0, 0, 0, 9, 329, 1, 0, 0, 0, 9, 331, 1, 0, 0, 0, 9, 333, 1, 0, 0, 0, 10, 335, 1, 0, 0, 0, 10, 337, 1, 0, 0, 0, 10, 339, 1, 0, 0, 0, 10, 341, 1, 0, 0, 0, 10, 343, 1, 0, 0, 0, 10, 345, 1, 0, 0, 0, 11, 347, 1, 0, 0, 0, 11, 349, 1, 0, 0, 0, 11, 351, 1, 0, 0, 0, 11, 353, 1, 0, 0, 0, 11, 355, 1, 0, 0, 0, 11, 357, 1, 0, 0, 0, 11, 359, 1, 0, 0, 0, 11, 361, 1, 0, 0, 0, 11, 363, 1, 0, 0, 0, 11, 365, 1, 0, 0, 0, 12, 367, 1, 0, 0, 0, 12, 369, 1, 0, 0, 0, 12, 371, 1, 0, 0, 0, 12, 373, 1, 0, 0, 0, 12, 375, 1, 0, 0, 0, 12, 377, 1, 0, 0, 0, 12, 379, 1, 0, 0, 0, 13, 381, 1, 0, 0, 0, 13, 383, 1, 0, 0, 0, 13, 385, 1, 0, 0, 0, 13, 387, 1, 0, 0, 0, 13, 389, 1, 0, 0, 0, 13, 391, 1, 0, 0, 0, 14, 393, 1, 0, 0, 0, 14, 395, 1, 0, 0, 0, 14, 397, 1, 0, 0, 0, 14, 399, 1, 0, 0, 0, 14, 401, 1, 0, 0, 0, 14, 403, 1, 0, 0, 0, 14, 405, 1, 0, 0, 0, 14, 407, 1, 0, 0, 0, 14, 409, 1, 0, 0, 0, 15, 411, 1, 0, 0, 0, 17, 421, 1, 0, 0, 0, 19, 428, 1, 0, 0, 0, 21, 437, 1, 0, 0, 0, 23, 444, 1, 0, 0, 0, 25, 454, 1, 0, 0, 0, 27, 461, 1, 0, 0, 0, 29, 468, 1, 0, 0, 0, 31, 475, 1, 0, 0, 0, 33, 483, 1, 0, 0, 0, 35, 495, 1, 0, 0, 0, 37, 504, 1, 0, 0, 0, 39, 510, 1, 0, 0, 0, 41, 517, 1, 0, 0, 0, 43, 524, 1, 0, 0, 0, 45, 532, 1, 0, 0, 0, 47, 540, 1, 0, 0, 0, 49, 555, 1, 0, 0, 0, 51, 565, 1, 0, 0, 0, 53, 577, 1, 0, 0, 0, 55, 583, 1, 0, 0, 0, 57, 600, 1, 0, 0, 0, 59, 616, 1, 0, 0, 0, 61, 622, 1, 0, 0, 0, 63, 626, 1, 0, 0, 0, 65, 628, 1, 0, 0, 0, 67, 630, 1, 0, 0, 0, 69, 633, 1, 0, 0, 0, 71, 635, 1, 0, 0, 0, 73, 644, 1, 0, 0, 0, 75, 646, 1, 0, 0, 0, 77, 651, 1, 0, 0, 0, 79, 653, 1, 0, 0, 0, 81, 658, 1, 0, 0, 0, 83, 689, 1, 0, 0, 0, 85, 692, 1, 0, 0, 0, 87, 738, 1, 0, 0, 0, 89, 740, 1, 0, 0, 0, 91, 743, 1, 0, 0, 0, 93, 747, 1, 0, 0, 0, 95, 751, 1, 0, 0, 0, 97, 753, 1, 0, 0, 0, 99, 756, 1, 0, 0, 0, 101, 758, 1, 0, 0, 0, 103, 763, 1, 0, 0, 0, 105, 765, 1, 0, 0, 0, 107, 771, 1, 0, 0, 0, 109, 777, 1, 0, 0, 0, 111, 780, 1, 0, 0, 0, 113, 783, 1, 0, 0, 0, 115, 788, 1, 0, 0, 0, 117, 793, 1, 0, 0, 0, 119, 795, 1, 0, 0, 0, 121, 799, 1, 0, 0, 0, 123, 804, 1, 0, 0, 0, 125, 810, 1, 0, 0, 0, 127, 813, 1, 0, 0, 0, 129, 815, 1, 0, 0, 0, 131, 821, 1, 0, 0, 0, 133, 823, 1, 0, 0, 0, 135, 828, 1, 0, 0, 0, 137, 831, 1, 0, 0, 0, 139, 834, 1, 0, 0, 0, 141, 837, 1, 0, 0, 0, 143, 839, 1, 0, 0, 0, 145, 842, 1, 0, 0, 0, 147, 844, 1, 0, 0, 0, 149, 847, 1, 0, 0, 0, 151, 849, 1, 0, 0, 0, 153, 851, 1, 0, 0, 0, 155, 853, 1, 0, 0, 0, 157, 855, 1, 0, 0, 0, 159, 857, 1, 0, 0, 0, 161, 863, 1, 0, 0, 0, 163, 884, 1, 0, 0, 0, 165, 886, 1, 0, 0, 0, 167, 891, 1, 0, 0, 0, 169, 912, 1, 0, 0, 0, 171, 914, 1, 0, 0, 0, 173, 922, 1, 0, 0, 0, 175, 924, 1, 0, 0, 0, 177, 928, 1, 0, 0, 0, 179, 932, 1, 0, 0, 0, 181, 936, 1, 0, 0, 0, 183, 941, 1, 0, 0, 0, 185, 946, 1, 0, 0, 0, 187, 950, 1, 0, 0, 0, 189, 954, 1, 0, 0, 0, 191, 958, 1, 0, 0, 0, 193, 963, 1, 0, 0, 0, 195, 967, 1, 0, 0, 0, 197, 971, 1, 0, 0, 0, 199, 975, 1, 0, 0, 0, 201, 979, 1, 0, 0, 0, 203, 983, 1, 0, 0, 0, 205, 995, 1, 0, 0, 0, 207, 998, 1, 0, 0, 0, 209, 1002, 1, 0, 0, 0, 211, 1006, 1, 0, 0, 0, 213, 1010, 1, 0, 0, 0, 215, 1014, 1, 0, 0, 0, 217, 1018, 1, 0, 0, 0, 219, 1022, 1, 0, 0, 0, 221, 1027, 1, 0, 0, 0, 223, 1031, 1, 0, 0, 0, 225, 1035, 1, 0, 0, 0, 227, 1040, 1, 0, 0, 0, 229, 1049, 1, 0, 0, 0, 231, 1070, 1, 0, 0, 0, 233, 1074, 1, 0, 0, 0, 235, 1078, 1, 0, 0, 0, 237, 1082, 1, 0, 0, 0, 239, 1086, 1, 0, 0, 0, 241, 1090, 1, 0, 0, 0, 243, 1095, 1, 0, 0, 0, 245, 1099, 1, 0, 0, 0, 247, 1103, 1, 0, 0, 0, 249, 1107, 1, 0, 0, 0, 251, 1112, 1, 0, 0, 0, 253, 1117, 1, 0, 0, 0, 255, 1120, 1, 0, 0, 0, 257, 1124, 1, 0, 0, 0, 259, 1128, 1, 0, 0, 0, 261, 1132, 1, 0, 0, 0, 263, 1136, 1, 0, 0, 0, 265, 1141, 1, 0, 0, 0, 267, 1146, 1, 0, 0, 0, 269, 1151, 1, 0, 0, 0, 271, 1158, 1, 0, 0, 0, 273, 1167, 1, 0, 0, 0, 275, 1174, 1, 0, 0, 0, 277, 1178, 1, 0, 0, 0, 279, 1182, 1, 0, 0, 0, 281, 1186, 1, 0, 0, 0, 283, 1190, 1, 0, 0, 0, 285, 1196, 1, 0, 0, 0, 287, 1200, 1, 0, 0, 0, 289, 1204, 1, 0, 0, 0, 291, 1208, 1, 0, 0, 0, 293, 1212, 1, 0, 0, 0, 295, 1216, 1, 0, 0, 0, 297, 1220, 1, 0, 0, 0, 299, 1225, 1, 0, 0, 0, 301, 1230, 1, 0, 0, 0, 303, 1234, 1, 0, 0, 0, 305, 1238, 1, 0, 0, 0, 307, 1242, 1, 0, 0, 0, 309, 1247, 1, 0, 0, 0, 311, 1251, 1, 0, 0, 0, 313, 1256, 1, 0, 0, 0, 315, 1261, 1, 0, 0, 0, 317, 1265, 1, 0, 0, 0, 319, 1269, 1, 0, 0, 0, 321, 1273, 1, 0, 0, 0, 323, 1277, 1, 0, 0, 0, 325, 1281, 1, 0, 0, 0, 327, 1286, 1, 0, 0, 0, 329, 1291, 1, 0, 0, 0, 331, 1295, 1, 0, 0, 0, 333, 1299, 1, 0, 0, 0, 335, 1303, 1, 0, 0, 0, 337, 1308, 1, 0, 0, 0, 339, 1315, 1, 0, 0, 0, 341, 1319, 1, 0, 0, 0, 343, 1323, 1, 0, 0, 0, 345, 1327, 1, 0, 0, 0, 347, 1331, 1, 0, 0, 0, 349, 1336, 1, 0, 0, 0, 351, 1340, 1, 0, 0, 0, 353, 1344, 1, 0, 0, 0, 355, 1348, 1, 0, 0, 0, 357, 1353, 1, 0, 0, 0, 359, 1357, 1, 0, 0, 0, 361, 1361, 1, 0, 0, 0, 363, 1365, 1, 0, 0, 0, 365, 1369, 1, 0, 0, 0, 367, 1373, 1, 0, 0, 0, 369, 1379, 1, 0, 0, 0, 371, 1383, 1, 0, 0, 0, 373, 1387, 1, 0, 0, 0, 375, 1391, 1, 0, 0, 0, 377, 1395, 1, 0, 0, 0, 379, 1399, 1, 0, 0, 0, 381, 1403, 1, 0, 0, 0, 383, 1408, 1, 0, 0, 0, 385, 1414, 1, 0, 0, 0, 387, 1420, 1, 0, 0, 0, 389, 1424, 1, 0, 0, 0, 391, 1428, 1, 0, 0, 0, 393, 1432, 1, 0, 0, 0, 395, 1438, 1, 0, 0, 0, 397, 1444, 1, 0, 0, 0, 399, 1448, 1, 0, 0, 0, 401, 1452, 1, 0, 0, 0, 403, 1456, 1, 0, 0, 0, 405, 1462, 1, 0, 0, 0, 407, 1468, 1, 0, 0, 0, 409, 1474, 1, 0, 0, 0, 411, 412, 7, 0, 0, 0, 412, 413, 7, 1, 0, 0, 413, 414, 7, 2, 0, 0, 414, 415, 7, 2, 0, 0, 415, 416, 7, 3, 0, 0, 416, 417, 7, 4, 0, 0, 417, 418, 7, 5, 0, 0, 418, 419, 1, 0, 0, 0, 419, 420, 6, 0, 0, 0, 420, 16, 1, 0, 0, 0, 421, 422, 7, 0, 0, 0, 422, 423, 7, 6, 0, 0, 423, 424, 7, 7, 0, 0, 424, 425, 7, 8, 0, 0, 425, 426, 1, 0, 0, 0, 426, 427, 6, 1, 1, 0, 427, 18, 1, 0, 0, 0, 428, 429, 7, 3, 0, 0, 429, 430, 7, 9, 0, 0, 430, 431, 7, 6, 0, 0, 431, 432, 7, 1, 0, 0, 432, 433, 7, 4, 0, 0, 433, 434, 7, 10, 0, 0, 434, 435, 1, 0, 0, 0, 435, 436, 6, 2, 2, 0, 436, 20, 1, 0, 0, 0, 437, 438, 7, 3, 0, 0, 438, 439, 7, 11, 0, 0, 439, 440, 7, 12, 0, 0, 440, 441, 7, 13, 0, 0, 441, 442, 1, 0, 0, 0, 442, 443, 6, 3, 0, 0, 443, 22, 1, 0, 0, 0, 444, 445, 7, 3, 0, 0, 445, 446, 7, 14, 0, 0, 446, 447, 7, 8, 0, 0, 447, 448, 7, 13, 0, 0, 448, 449, 7, 12, 0, 0, 449, 450, 7, 1, 0, 0, 450, 451, 7, 9, 0, 0, 451, 452, 1, 0, 0, 0, 452, 453, 6, 4, 3, 0, 453, 24, 1, 0, 0, 0, 454, 455, 7, 15, 0, 0, 455, 456, 7, 6, 0, 0, 456, 457, 7, 7, 0, 0, 457, 458, 7, 16, 0, 0, 458, 459, 1, 0, 0, 0, 459, 460, 6, 5, 4, 0, 460, 26, 1, 0, 0, 0, 461, 462, 7, 17, 0, 0, 462, 463, 7, 6, 0, 0, 463, 464, 7, 7, 0, 0, 464, 465, 7, 18, 0, 0, 465, 466, 1, 0, 0, 0, 466, 467, 6, 6, 0, 0, 467, 28, 1, 0, 0, 0, 468, 469, 7, 18, 0, 0, 469, 470, 7, 3, 0, 0, 470, 471, 7, 3, 0, 0, 471, 472, 7, 8, 0, 0, 472, 473, 1, 0, 0, 0, 473, 474, 6, 7, 1, 0, 474, 30, 1, 0, 0, 0, 475, 476, 7, 13, 0, 0, 476, 477, 7, 1, 0, 0, 477, 478, 7, 16, 0, 0, 478, 479, 7, 1, 0, 0, 479, 480, 7, 5, 0, 0, 480, 481, 1, 0, 0, 0, 481, 482, 6, 8, 0, 0, 482, 32, 1, 0, 0, 0, 483, 484, 7, 16, 0, 0, 484, 485, 7, 11, 0, 0, 485, 486, 5, 95, 0, 0, 486, 487, 7, 3, 0, 0, 487, 488, 7, 14, 0, 0, 488, 489, 7, 8, 0, 0, 489, 490, 7, 12, 0, 0, 490, 491, 7, 9, 0, 0, 491, 492, 7, 0, 0, 0, 492, 493, 1, 0, 0, 0, 493, 494, 6, 9, 5, 0, 494, 34, 1, 0, 0, 0, 495, 496, 7, 6, 0, 0, 496, 497, 7, 3, 0, 0, 497, 498, 7, 9, 0, 0, 498, 499, 7, 12, 0, 0, 499, 500, 7, 16, 0, 0, 500, 501, 7, 3, 0, 0, 501, 502, 1, 0, 0, 0, 502, 503, 6, 10, 6, 0, 503, 36, 1, 0, 0, 0, 504, 505, 7, 6, 0, 0, 505, 506, 7, 7, 0, 0, 506, 507, 7, 19, 0, 0, 507, 508, 1, 0, 0, 0, 508, 509, 6, 11, 0, 0, 509, 38, 1, 0, 0, 0, 510, 511, 7, 2, 0, 0, 511, 512, 7, 10, 0, 0, 512, 513, 7, 7, 0, 0, 513, 514, 7, 19, 0, 0, 514, 515, 1, 0, 0, 0, 515, 516, 6, 12, 7, 0, 516, 40, 1, 0, 0, 0, 517, 518, 7, 2, 0, 0, 518, 519, 7, 7, 0, 0, 519, 520, 7, 6, 0, 0, 520, 521, 7, 5, 0, 0, 521, 522, 1, 0, 0, 0, 522, 523, 6, 13, 0, 0, 523, 42, 1, 0, 0, 0, 524, 525, 7, 2, 0, 0, 525, 526, 7, 5, 0, 0, 526, 527, 7, 12, 0, 0, 527, 528, 7, 5, 0, 0, 528, 529, 7, 2, 0, 0, 529, 530, 1, 0, 0, 0, 530, 531, 6, 14, 0, 0, 531, 44, 1, 0, 0, 0, 532, 533, 7, 19, 0, 0, 533, 534, 7, 10, 0, 0, 534, 535, 7, 3, 0, 0, 535, 536, 7, 6, 0, 0, 536, 537, 7, 3, 0, 0, 537, 538, 1, 0, 0, 0, 538, 539, 6, 15, 0, 0, 539, 46, 1, 0, 0, 0, 540, 541, 4, 16, 0, 0, 541, 542, 7, 1, 0, 0, 542, 543, 7, 9, 0, 0, 543, 544, 7, 13, 0, 0, 544, 545, 7, 1, 0, 0, 545, 546, 7, 9, 0, 0, 546, 547, 7, 3, 0, 0, 547, 548, 7, 2, 0, 0, 548, 549, 7, 5, 0, 0, 549, 550, 7, 12, 0, 0, 550, 551, 7, 5, 0, 0, 551, 552, 7, 2, 0, 0, 552, 553, 1, 0, 0, 0, 553, 554, 6, 16, 0, 0, 554, 48, 1, 0, 0, 0, 555, 556, 4, 17, 1, 0, 556, 557, 7, 13, 0, 0, 557, 558, 7, 7, 0, 0, 558, 559, 7, 7, 0, 0, 559, 560, 7, 18, 0, 0, 560, 561, 7, 20, 0, 0, 561, 562, 7, 8, 0, 0, 562, 563, 1, 0, 0, 0, 563, 564, 6, 17, 8, 0, 564, 50, 1, 0, 0, 0, 565, 566, 4, 18, 2, 0, 566, 567, 7, 16, 0, 0, 567, 568, 7, 3, 0, 0, 568, 569, 7, 5, 0, 0, 569, 570, 7, 6, 0, 0, 570, 571, 7, 1, 0, 0, 571, 572, 7, 4, 0, 0, 572, 573, 7, 2, 0, 0, 573, 574, 1, 0, 0, 0, 574, 575, 6, 18, 9, 0, 575, 52, 1, 0, 0, 0, 576, 578, 8, 21, 0, 0, 577, 576, 1, 0, 0, 0, 578, 579, 1, 0, 0, 0, 579, 577, 1, 0, 0, 0, 579, 580, 1, 0, 0, 0, 580, 581, 1, 0, 0, 0, 581, 582, 6, 19, 0, 0, 582, 54, 1, 0, 0, 0, 583, 584, 5, 47, 0, 0, 584, 585, 5, 47, 0, 0, 585, 589, 1, 0, 0, 0, 586, 588, 8, 22, 0, 0, 587, 586, 1, 0, 0, 0, 588, 591, 1, 0, 0, 0, 589, 587, 1, 0, 0, 0, 589, 590, 1, 0, 0, 0, 590, 593, 1, 0, 0, 0, 591, 589, 1, 0, 0, 0, 592, 594, 5, 13, 0, 0, 593, 592, 1, 0, 0, 0, 593, 594, 1, 0, 0, 0, 594, 596, 1, 0, 0, 0, 595, 597, 5, 10, 0, 0, 596, 595, 1, 0, 0, 0, 596, 597, 1, 0, 0, 0, 597, 598, 1, 0, 0, 0, 598, 599, 6, 20, 10, 0, 599, 56, 1, 0, 0, 0, 600, 601, 5, 47, 0, 0, 601, 602, 5, 42, 0, 0, 602, 607, 1, 0, 0, 0, 603, 606, 3, 57, 21, 0, 604, 606, 9, 0, 0, 0, 605, 603, 1, 0, 0, 0, 605, 604, 1, 0, 0, 0, 606, 609, 1, 0, 0, 0, 607, 608, 1, 0, 0, 0, 607, 605, 1, 0, 0, 0, 608, 610, 1, 0, 0, 0, 609, 607, 1, 0, 0, 0, 610, 611, 5, 42, 0, 0, 611, 612, 5, 47, 0, 0, 612, 613, 1, 0, 0, 0, 613, 614, 6, 21, 10, 0, 614, 58, 1, 0, 0, 0, 615, 617, 7, 23, 0, 0, 616, 615, 1, 0, 0, 0, 617, 618, 1, 0, 0, 0, 618, 616, 1, 0, 0, 0, 618, 619, 1, 0, 0, 0, 619, 620, 1, 0, 0, 0, 620, 621, 6, 22, 10, 0, 621, 60, 1, 0, 0, 0, 622, 623, 5, 124, 0, 0, 623, 624, 1, 0, 0, 0, 624, 625, 6, 23, 11, 0, 625, 62, 1, 0, 0, 0, 626, 627, 7, 24, 0, 0, 627, 64, 1, 0, 0, 0, 628, 629, 7, 25, 0, 0, 629, 66, 1, 0, 0, 0, 630, 631, 5, 92, 0, 0, 631, 632, 7, 26, 0, 0, 632, 68, 1, 0, 0, 0, 633, 634, 8, 27, 0, 0, 634, 70, 1, 0, 0, 0, 635, 637, 7, 3, 0, 0, 636, 638, 7, 28, 0, 0, 637, 636, 1, 0, 0, 0, 637, 638, 1, 0, 0, 0, 638, 640, 1, 0, 0, 0, 639, 641, 3, 63, 24, 0, 640, 639, 1, 0, 0, 0, 641, 642, 1, 0, 0, 0, 642, 640, 1, 0, 0, 0, 642, 643, 1, 0, 0, 0, 643, 72, 1, 0, 0, 0, 644, 645, 5, 64, 0, 0, 645, 74, 1, 0, 0, 0, 646, 647, 5, 96, 0, 0, 647, 76, 1, 0, 0, 0, 648, 652, 8, 29, 0, 0, 649, 650, 5, 96, 0, 0, 650, 652, 5, 96, 0, 0, 651, 648, 1, 0, 0, 0, 651, 649, 1, 0, 0, 0, 652, 78, 1, 0, 0, 0, 653, 654, 5, 95, 0, 0, 654, 80, 1, 0, 0, 0, 655, 659, 3, 65, 25, 0, 656, 659, 3, 63, 24, 0, 657, 659, 3, 79, 32, 0, 658, 655, 1, 0, 0, 0, 658, 656, 1, 0, 0, 0, 658, 657, 1, 0, 0, 0, 659, 82, 1, 0, 0, 0, 660, 665, 5, 34, 0, 0, 661, 664, 3, 67, 26, 0, 662, 664, 3, 69, 27, 0, 663, 661, 1, 0, 0, 0, 663, 662, 1, 0, 0, 0, 664, 667, 1, 0, 0, 0, 665, 663, 1, 0, 0, 0, 665, 666, 1, 0, 0, 0, 666, 668, 1, 0, 0, 0, 667, 665, 1, 0, 0, 0, 668, 690, 5, 34, 0, 0, 669, 670, 5, 34, 0, 0, 670, 671, 5, 34, 0, 0, 671, 672, 5, 34, 0, 0, 672, 676, 1, 0, 0, 0, 673, 675, 8, 22, 0, 0, 674, 673, 1, 0, 0, 0, 675, 678, 1, 0, 0, 0, 676, 677, 1, 0, 0, 0, 676, 674, 1, 0, 0, 0, 677, 679, 1, 0, 0, 0, 678, 676, 1, 0, 0, 0, 679, 680, 5, 34, 0, 0, 680, 681, 5, 34, 0, 0, 681, 682, 5, 34, 0, 0, 682, 684, 1, 0, 0, 0, 683, 685, 5, 34, 0, 0, 684, 683, 1, 0, 0, 0, 684, 685, 1, 0, 0, 0, 685, 687, 1, 0, 0, 0, 686, 688, 5, 34, 0, 0, 687, 686, 1, 0, 0, 0, 687, 688, 1, 0, 0, 0, 688, 690, 1, 0, 0, 0, 689, 660, 1, 0, 0, 0, 689, 669, 1, 0, 0, 0, 690, 84, 1, 0, 0, 0, 691, 693, 3, 63, 24, 0, 692, 691, 1, 0, 0, 0, 693, 694, 1, 0, 0, 0, 694, 692, 1, 0, 0, 0, 694, 695, 1, 0, 0, 0, 695, 86, 1, 0, 0, 0, 696, 698, 3, 63, 24, 0, 697, 696, 1, 0, 0, 0, 698, 699, 1, 0, 0, 0, 699, 697, 1, 0, 0, 0, 699, 700, 1, 0, 0, 0, 700, 701, 1, 0, 0, 0, 701, 705, 3, 103, 44, 0, 702, 704, 3, 63, 24, 0, 703, 702, 1, 0, 0, 0, 704, 707, 1, 0, 0, 0, 705, 703, 1, 0, 0, 0, 705, 706, 1, 0, 0, 0, 706, 739, 1, 0, 0, 0, 707, 705, 1, 0, 0, 0, 708, 710, 3, 103, 44, 0, 709, 711, 3, 63, 24, 0, 710, 709, 1, 0, 0, 0, 711, 712, 1, 0, 0, 0, 712, 710, 1, 0, 0, 0, 712, 713, 1, 0, 0, 0, 713, 739, 1, 0, 0, 0, 714, 716, 3, 63, 24, 0, 715, 714, 1, 0, 0, 0, 716, 717, 1, 0, 0, 0, 717, 715, 1, 0, 0, 0, 717, 718, 1, 0, 0, 0, 718, 726, 1, 0, 0, 0, 719, 723, 3, 103, 44, 0, 720, 722, 3, 63, 24, 0, 721, 720, 1, 0, 0, 0, 722, 725, 1, 0, 0, 0, 723, 721, 1, 0, 0, 0, 723, 724, 1, 0, 0, 0, 724, 727, 1, 0, 0, 0, 725, 723, 1, 0, 0, 0, 726, 719, 1, 0, 0, 0, 726, 727, 1, 0, 0, 0, 727, 728, 1, 0, 0, 0, 728, 729, 3, 71, 28, 0, 729, 739, 1, 0, 0, 0, 730, 732, 3, 103, 44, 0, 731, 733, 3, 63, 24, 0, 732, 731, 1, 0, 0, 0, 733, 734, 1, 0, 0, 0, 734, 732, 1, 0, 0, 0, 734, 735, 1, 0, 0, 0, 735, 736, 1, 0, 0, 0, 736, 737, 3, 71, 28, 0, 737, 739, 1, 0, 0, 0, 738, 697, 1, 0, 0, 0, 738, 708, 1, 0, 0, 0, 738, 715, 1, 0, 0, 0, 738, 730, 1, 0, 0, 0, 739, 88, 1, 0, 0, 0, 740, 741, 7, 30, 0, 0, 741, 742, 7, 31, 0, 0, 742, 90, 1, 0, 0, 0, 743, 744, 7, 12, 0, 0, 744, 745, 7, 9, 0, 0, 745, 746, 7, 0, 0, 0, 746, 92, 1, 0, 0, 0, 747, 748, 7, 12, 0, 0, 748, 749, 7, 2, 0, 0, 749, 750, 7, 4, 0, 0, 750, 94, 1, 0, 0, 0, 751, 752, 5, 61, 0, 0, 752, 96, 1, 0, 0, 0, 753, 754, 5, 58, 0, 0, 754, 755, 5, 58, 0, 0, 755, 98, 1, 0, 0, 0, 756, 757, 5, 44, 0, 0, 757, 100, 1, 0, 0, 0, 758, 759, 7, 0, 0, 0, 759, 760, 7, 3, 0, 0, 760, 761, 7, 2, 0, 0, 761, 762, 7, 4, 0, 0, 762, 102, 1, 0, 0, 0, 763, 764, 5, 46, 0, 0, 764, 104, 1, 0, 0, 0, 765, 766, 7, 15, 0, 0, 766, 767, 7, 12, 0, 0, 767, 768, 7, 13, 0, 0, 768, 769, 7, 2, 0, 0, 769, 770, 7, 3, 0, 0, 770, 106, 1, 0, 0, 0, 771, 772, 7, 15, 0, 0, 772, 773, 7, 1, 0, 0, 773, 774, 7, 6, 0, 0, 774, 775, 7, 2, 0, 0, 775, 776, 7, 5, 0, 0, 776, 108, 1, 0, 0, 0, 777, 778, 7, 1, 0, 0, 778, 779, 7, 9, 0, 0, 779, 110, 1, 0, 0, 0, 780, 781, 7, 1, 0, 0, 781, 782, 7, 2, 0, 0, 782, 112, 1, 0, 0, 0, 783, 784, 7, 13, 0, 0, 784, 785, 7, 12, 0, 0, 785, 786, 7, 2, 0, 0, 786, 787, 7, 5, 0, 0, 787, 114, 1, 0, 0, 0, 788, 789, 7, 13, 0, 0, 789, 790, 7, 1, 0, 0, 790, 791, 7, 18, 0, 0, 791, 792, 7, 3, 0, 0, 792, 116, 1, 0, 0, 0, 793, 794, 5, 40, 0, 0, 794, 118, 1, 0, 0, 0, 795, 796, 7, 9, 0, 0, 796, 797, 7, 7, 0, 0, 797, 798, 7, 5, 0, 0, 798, 120, 1, 0, 0, 0, 799, 800, 7, 9, 0, 0, 800, 801, 7, 20, 0, 0, 801, 802, 7, 13, 0, 0, 802, 803, 7, 13, 0, 0, 803, 122, 1, 0, 0, 0, 804, 805, 7, 9, 0, 0, 805, 806, 7, 20, 0, 0, 806, 807, 7, 13, 0, 0, 807, 808, 7, 13, 0, 0, 808, 809, 7, 2, 0, 0, 809, 124, 1, 0, 0, 0, 810, 811, 7, 7, 0, 0, 811, 812, 7, 6, 0, 0, 812, 126, 1, 0, 0, 0, 813, 814, 5, 63, 0, 0, 814, 128, 1, 0, 0, 0, 815, 816, 7, 6, 0, 0, 816, 817, 7, 13, 0, 0, 817, 818, 7, 1, 0, 0, 818, 819, 7, 18, 0, 0, 819, 820, 7, 3, 0, 0, 820, 130, 1, 0, 0, 0, 821, 822, 5, 41, 0, 0, 822, 132, 1, 0, 0, 0, 823, 824, 7, 5, 0, 0, 824, 825, 7, 6, 0, 0, 825, 826, 7, 20, 0, 0, 826, 827, 7, 3, 0, 0, 827, 134, 1, 0, 0, 0, 828, 829, 5, 61, 0, 0, 829, 830, 5, 61, 0, 0, 830, 136, 1, 0, 0, 0, 831, 832, 5, 61, 0, 0, 832, 833, 5, 126, 0, 0, 833, 138, 1, 0, 0, 0, 834, 835, 5, 33, 0, 0, 835, 836, 5, 61, 0, 0, 836, 140, 1, 0, 0, 0, 837, 838, 5, 60, 0, 0, 838, 142, 1, 0, 0, 0, 839, 840, 5, 60, 0, 0, 840, 841, 5, 61, 0, 0, 841, 144, 1, 0, 0, 0, 842, 843, 5, 62, 0, 0, 843, 146, 1, 0, 0, 0, 844, 845, 5, 62, 0, 0, 845, 846, 5, 61, 0, 0, 846, 148, 1, 0, 0, 0, 847, 848, 5, 43, 0, 0, 848, 150, 1, 0, 0, 0, 849, 850, 5, 45, 0, 0, 850, 152, 1, 0, 0, 0, 851, 852, 5, 42, 0, 0, 852, 154, 1, 0, 0, 0, 853, 854, 5, 47, 0, 0, 854, 156, 1, 0, 0, 0, 855, 856, 5, 37, 0, 0, 856, 158, 1, 0, 0, 0, 857, 858, 7, 16, 0, 0, 858, 859, 7, 12, 0, 0, 859, 860, 7, 5, 0, 0, 860, 861, 7, 4, 0, 0, 861, 862, 7, 10, 0, 0, 862, 160, 1, 0, 0, 0, 863, 864, 3, 45, 15, 0, 864, 865, 1, 0, 0, 0, 865, 866, 6, 73, 12, 0, 866, 162, 1, 0, 0, 0, 867, 870, 3, 127, 56, 0, 868, 871, 3, 65, 25, 0, 869, 871, 3, 79, 32, 0, 870, 868, 1, 0, 0, 0, 870, 869, 1, 0, 0, 0, 871, 875, 1, 0, 0, 0, 872, 874, 3, 81, 33, 0, 873, 872, 1, 0, 0, 0, 874, 877, 1, 0, 0, 0, 875, 873, 1, 0, 0, 0, 875, 876, 1, 0, 0, 0, 876, 885, 1, 0, 0, 0, 877, 875, 1, 0, 0, 0, 878, 880, 3, 127, 56, 0, 879, 881, 3, 63, 24, 0, 880, 879, 1, 0, 0, 0, 881, 882, 1, 0, 0, 0, 882, 880, 1, 0, 0, 0, 882, 883, 1, 0, 0, 0, 883, 885, 1, 0, 0, 0, 884, 867, 1, 0, 0, 0, 884, 878, 1, 0, 0, 0, 885, 164, 1, 0, 0, 0, 886, 887, 5, 91, 0, 0, 887, 888, 1, 0, 0, 0, 888, 889, 6, 75, 0, 0, 889, 890, 6, 75, 0, 0, 890, 166, 1, 0, 0, 0, 891, 892, 5, 93, 0, 0, 892, 893, 1, 0, 0, 0, 893, 894, 6, 76, 11, 0, 894, 895, 6, 76, 11, 0, 895, 168, 1, 0, 0, 0, 896, 900, 3, 65, 25, 0, 897, 899, 3, 81, 33, 0, 898, 897, 1, 0, 0, 0, 899, 902, 1, 0, 0, 0, 900, 898, 1, 0, 0, 0, 900, 901, 1, 0, 0, 0, 901, 913, 1, 0, 0, 0, 902, 900, 1, 0, 0, 0, 903, 906, 3, 79, 32, 0, 904, 906, 3, 73, 29, 0, 905, 903, 1, 0, 0, 0, 905, 904, 1, 0, 0, 0, 906, 908, 1, 0, 0, 0, 907, 909, 3, 81, 33, 0, 908, 907, 1, 0, 0, 0, 909, 910, 1, 0, 0, 0, 910, 908, 1, 0, 0, 0, 910, 911, 1, 0, 0, 0, 911, 913, 1, 0, 0, 0, 912, 896, 1, 0, 0, 0, 912, 905, 1, 0, 0, 0, 913, 170, 1, 0, 0, 0, 914, 916, 3, 75, 30, 0, 915, 917, 3, 77, 31, 0, 916, 915, 1, 0, 0, 0, 917, 918, 1, 0, 0, 0, 918, 916, 1, 0, 0, 0, 918, 919, 1, 0, 0, 0, 919, 920, 1, 0, 0, 0, 920, 921, 3, 75, 30, 0, 921, 172, 1, 0, 0, 0, 922, 923, 3, 171, 78, 0, 923, 174, 1, 0, 0, 0, 924, 925, 3, 55, 20, 0, 925, 926, 1, 0, 0, 0, 926, 927, 6, 80, 10, 0, 927, 176, 1, 0, 0, 0, 928, 929, 3, 57, 21, 0, 929, 930, 1, 0, 0, 0, 930, 931, 6, 81, 10, 0, 931, 178, 1, 0, 0, 0, 932, 933, 3, 59, 22, 0, 933, 934, 1, 0, 0, 0, 934, 935, 6, 82, 10, 0, 935, 180, 1, 0, 0, 0, 936, 937, 3, 165, 75, 0, 937, 938, 1, 0, 0, 0, 938, 939, 6, 83, 13, 0, 939, 940, 6, 83, 14, 0, 940, 182, 1, 0, 0, 0, 941, 942, 3, 61, 23, 0, 942, 943, 1, 0, 0, 0, 943, 944, 6, 84, 15, 0, 944, 945, 6, 84, 11, 0, 945, 184, 1, 0, 0, 0, 946, 947, 3, 59, 22, 0, 947, 948, 1, 0, 0, 0, 948, 949, 6, 85, 10, 0, 949, 186, 1, 0, 0, 0, 950, 951, 3, 55, 20, 0, 951, 952, 1, 0, 0, 0, 952, 953, 6, 86, 10, 0, 953, 188, 1, 0, 0, 0, 954, 955, 3, 57, 21, 0, 955, 956, 1, 0, 0, 0, 956, 957, 6, 87, 10, 0, 957, 190, 1, 0, 0, 0, 958, 959, 3, 61, 23, 0, 959, 960, 1, 0, 0, 0, 960, 961, 6, 88, 15, 0, 961, 962, 6, 88, 11, 0, 962, 192, 1, 0, 0, 0, 963, 964, 3, 165, 75, 0, 964, 965, 1, 0, 0, 0, 965, 966, 6, 89, 13, 0, 966, 194, 1, 0, 0, 0, 967, 968, 3, 167, 76, 0, 968, 969, 1, 0, 0, 0, 969, 970, 6, 90, 16, 0, 970, 196, 1, 0, 0, 0, 971, 972, 3, 337, 161, 0, 972, 973, 1, 0, 0, 0, 973, 974, 6, 91, 17, 0, 974, 198, 1, 0, 0, 0, 975, 976, 3, 99, 42, 0, 976, 977, 1, 0, 0, 0, 977, 978, 6, 92, 18, 0, 978, 200, 1, 0, 0, 0, 979, 980, 3, 95, 40, 0, 980, 981, 1, 0, 0, 0, 981, 982, 6, 93, 19, 0, 982, 202, 1, 0, 0, 0, 983, 984, 7, 16, 0, 0, 984, 985, 7, 3, 0, 0, 985, 986, 7, 5, 0, 0, 986, 987, 7, 12, 0, 0, 987, 988, 7, 0, 0, 0, 988, 989, 7, 12, 0, 0, 989, 990, 7, 5, 0, 0, 990, 991, 7, 12, 0, 0, 991, 204, 1, 0, 0, 0, 992, 996, 8, 32, 0, 0, 993, 994, 5, 47, 0, 0, 994, 996, 8, 33, 0, 0, 995, 992, 1, 0, 0, 0, 995, 993, 1, 0, 0, 0, 996, 206, 1, 0, 0, 0, 997, 999, 3, 205, 95, 0, 998, 997, 1, 0, 0, 0, 999, 1000, 1, 0, 0, 0, 1000, 998, 1, 0, 0, 0, 1000, 1001, 1, 0, 0, 0, 1001, 208, 1, 0, 0, 0, 1002, 1003, 3, 207, 96, 0, 1003, 1004, 1, 0, 0, 0, 1004, 1005, 6, 97, 20, 0, 1005, 210, 1, 0, 0, 0, 1006, 1007, 3, 83, 34, 0, 1007, 1008, 1, 0, 0, 0, 1008, 1009, 6, 98, 21, 0, 1009, 212, 1, 0, 0, 0, 1010, 1011, 3, 55, 20, 0, 1011, 1012, 1, 0, 0, 0, 1012, 1013, 6, 99, 10, 0, 1013, 214, 1, 0, 0, 0, 1014, 1015, 3, 57, 21, 0, 1015, 1016, 1, 0, 0, 0, 1016, 1017, 6, 100, 10, 0, 1017, 216, 1, 0, 0, 0, 1018, 1019, 3, 59, 22, 0, 1019, 1020, 1, 0, 0, 0, 1020, 1021, 6, 101, 10, 0, 1021, 218, 1, 0, 0, 0, 1022, 1023, 3, 61, 23, 0, 1023, 1024, 1, 0, 0, 0, 1024, 1025, 6, 102, 15, 0, 1025, 1026, 6, 102, 11, 0, 1026, 220, 1, 0, 0, 0, 1027, 1028, 3, 103, 44, 0, 1028, 1029, 1, 0, 0, 0, 1029, 1030, 6, 103, 22, 0, 1030, 222, 1, 0, 0, 0, 1031, 1032, 3, 99, 42, 0, 1032, 1033, 1, 0, 0, 0, 1033, 1034, 6, 104, 18, 0, 1034, 224, 1, 0, 0, 0, 1035, 1036, 4, 105, 3, 0, 1036, 1037, 3, 127, 56, 0, 1037, 1038, 1, 0, 0, 0, 1038, 1039, 6, 105, 23, 0, 1039, 226, 1, 0, 0, 0, 1040, 1041, 4, 106, 4, 0, 1041, 1042, 3, 163, 74, 0, 1042, 1043, 1, 0, 0, 0, 1043, 1044, 6, 106, 24, 0, 1044, 228, 1, 0, 0, 0, 1045, 1050, 3, 65, 25, 0, 1046, 1050, 3, 63, 24, 0, 1047, 1050, 3, 79, 32, 0, 1048, 1050, 3, 153, 69, 0, 1049, 1045, 1, 0, 0, 0, 1049, 1046, 1, 0, 0, 0, 1049, 1047, 1, 0, 0, 0, 1049, 1048, 1, 0, 0, 0, 1050, 230, 1, 0, 0, 0, 1051, 1054, 3, 65, 25, 0, 1052, 1054, 3, 153, 69, 0, 1053, 1051, 1, 0, 0, 0, 1053, 1052, 1, 0, 0, 0, 1054, 1058, 1, 0, 0, 0, 1055, 1057, 3, 229, 107, 0, 1056, 1055, 1, 0, 0, 0, 1057, 1060, 1, 0, 0, 0, 1058, 1056, 1, 0, 0, 0, 1058, 1059, 1, 0, 0, 0, 1059, 1071, 1, 0, 0, 0, 1060, 1058, 1, 0, 0, 0, 1061, 1064, 3, 79, 32, 0, 1062, 1064, 3, 73, 29, 0, 1063, 1061, 1, 0, 0, 0, 1063, 1062, 1, 0, 0, 0, 1064, 1066, 1, 0, 0, 0, 1065, 1067, 3, 229, 107, 0, 1066, 1065, 1, 0, 0, 0, 1067, 1068, 1, 0, 0, 0, 1068, 1066, 1, 0, 0, 0, 1068, 1069, 1, 0, 0, 0, 1069, 1071, 1, 0, 0, 0, 1070, 1053, 1, 0, 0, 0, 1070, 1063, 1, 0, 0, 0, 1071, 232, 1, 0, 0, 0, 1072, 1075, 3, 231, 108, 0, 1073, 1075, 3, 171, 78, 0, 1074, 1072, 1, 0, 0, 0, 1074, 1073, 1, 0, 0, 0, 1075, 1076, 1, 0, 0, 0, 1076, 1074, 1, 0, 0, 0, 1076, 1077, 1, 0, 0, 0, 1077, 234, 1, 0, 0, 0, 1078, 1079, 3, 55, 20, 0, 1079, 1080, 1, 0, 0, 0, 1080, 1081, 6, 110, 10, 0, 1081, 236, 1, 0, 0, 0, 1082, 1083, 3, 57, 21, 0, 1083, 1084, 1, 0, 0, 0, 1084, 1085, 6, 111, 10, 0, 1085, 238, 1, 0, 0, 0, 1086, 1087, 3, 59, 22, 0, 1087, 1088, 1, 0, 0, 0, 1088, 1089, 6, 112, 10, 0, 1089, 240, 1, 0, 0, 0, 1090, 1091, 3, 61, 23, 0, 1091, 1092, 1, 0, 0, 0, 1092, 1093, 6, 113, 15, 0, 1093, 1094, 6, 113, 11, 0, 1094, 242, 1, 0, 0, 0, 1095, 1096, 3, 95, 40, 0, 1096, 1097, 1, 0, 0, 0, 1097, 1098, 6, 114, 19, 0, 1098, 244, 1, 0, 0, 0, 1099, 1100, 3, 99, 42, 0, 1100, 1101, 1, 0, 0, 0, 1101, 1102, 6, 115, 18, 0, 1102, 246, 1, 0, 0, 0, 1103, 1104, 3, 103, 44, 0, 1104, 1105, 1, 0, 0, 0, 1105, 1106, 6, 116, 22, 0, 1106, 248, 1, 0, 0, 0, 1107, 1108, 4, 117, 5, 0, 1108, 1109, 3, 127, 56, 0, 1109, 1110, 1, 0, 0, 0, 1110, 1111, 6, 117, 23, 0, 1111, 250, 1, 0, 0, 0, 1112, 1113, 4, 118, 6, 0, 1113, 1114, 3, 163, 74, 0, 1114, 1115, 1, 0, 0, 0, 1115, 1116, 6, 118, 24, 0, 1116, 252, 1, 0, 0, 0, 1117, 1118, 7, 12, 0, 0, 1118, 1119, 7, 2, 0, 0, 1119, 254, 1, 0, 0, 0, 1120, 1121, 3, 233, 109, 0, 1121, 1122, 1, 0, 0, 0, 1122, 1123, 6, 120, 25, 0, 1123, 256, 1, 0, 0, 0, 1124, 1125, 3, 55, 20, 0, 1125, 1126, 1, 0, 0, 0, 1126, 1127, 6, 121, 10, 0, 1127, 258, 1, 0, 0, 0, 1128, 1129, 3, 57, 21, 0, 1129, 1130, 1, 0, 0, 0, 1130, 1131, 6, 122, 10, 0, 1131, 260, 1, 0, 0, 0, 1132, 1133, 3, 59, 22, 0, 1133, 1134, 1, 0, 0, 0, 1134, 1135, 6, 123, 10, 0, 1135, 262, 1, 0, 0, 0, 1136, 1137, 3, 61, 23, 0, 1137, 1138, 1, 0, 0, 0, 1138, 1139, 6, 124, 15, 0, 1139, 1140, 6, 124, 11, 0, 1140, 264, 1, 0, 0, 0, 1141, 1142, 3, 165, 75, 0, 1142, 1143, 1, 0, 0, 0, 1143, 1144, 6, 125, 13, 0, 1144, 1145, 6, 125, 26, 0, 1145, 266, 1, 0, 0, 0, 1146, 1147, 7, 7, 0, 0, 1147, 1148, 7, 9, 0, 0, 1148, 1149, 1, 0, 0, 0, 1149, 1150, 6, 126, 27, 0, 1150, 268, 1, 0, 0, 0, 1151, 1152, 7, 19, 0, 0, 1152, 1153, 7, 1, 0, 0, 1153, 1154, 7, 5, 0, 0, 1154, 1155, 7, 10, 0, 0, 1155, 1156, 1, 0, 0, 0, 1156, 1157, 6, 127, 27, 0, 1157, 270, 1, 0, 0, 0, 1158, 1159, 8, 34, 0, 0, 1159, 272, 1, 0, 0, 0, 1160, 1162, 3, 271, 128, 0, 1161, 1160, 1, 0, 0, 0, 1162, 1163, 1, 0, 0, 0, 1163, 1161, 1, 0, 0, 0, 1163, 1164, 1, 0, 0, 0, 1164, 1165, 1, 0, 0, 0, 1165, 1166, 3, 337, 161, 0, 1166, 1168, 1, 0, 0, 0, 1167, 1161, 1, 0, 0, 0, 1167, 1168, 1, 0, 0, 0, 1168, 1170, 1, 0, 0, 0, 1169, 1171, 3, 271, 128, 0, 1170, 1169, 1, 0, 0, 0, 1171, 1172, 1, 0, 0, 0, 1172, 1170, 1, 0, 0, 0, 1172, 1173, 1, 0, 0, 0, 1173, 274, 1, 0, 0, 0, 1174, 1175, 3, 273, 129, 0, 1175, 1176, 1, 0, 0, 0, 1176, 1177, 6, 130, 28, 0, 1177, 276, 1, 0, 0, 0, 1178, 1179, 3, 55, 20, 0, 1179, 1180, 1, 0, 0, 0, 1180, 1181, 6, 131, 10, 0, 1181, 278, 1, 0, 0, 0, 1182, 1183, 3, 57, 21, 0, 1183, 1184, 1, 0, 0, 0, 1184, 1185, 6, 132, 10, 0, 1185, 280, 1, 0, 0, 0, 1186, 1187, 3, 59, 22, 0, 1187, 1188, 1, 0, 0, 0, 1188, 1189, 6, 133, 10, 0, 1189, 282, 1, 0, 0, 0, 1190, 1191, 3, 61, 23, 0, 1191, 1192, 1, 0, 0, 0, 1192, 1193, 6, 134, 15, 0, 1193, 1194, 6, 134, 11, 0, 1194, 1195, 6, 134, 11, 0, 1195, 284, 1, 0, 0, 0, 1196, 1197, 3, 95, 40, 0, 1197, 1198, 1, 0, 0, 0, 1198, 1199, 6, 135, 19, 0, 1199, 286, 1, 0, 0, 0, 1200, 1201, 3, 99, 42, 0, 1201, 1202, 1, 0, 0, 0, 1202, 1203, 6, 136, 18, 0, 1203, 288, 1, 0, 0, 0, 1204, 1205, 3, 103, 44, 0, 1205, 1206, 1, 0, 0, 0, 1206, 1207, 6, 137, 22, 0, 1207, 290, 1, 0, 0, 0, 1208, 1209, 3, 269, 127, 0, 1209, 1210, 1, 0, 0, 0, 1210, 1211, 6, 138, 29, 0, 1211, 292, 1, 0, 0, 0, 1212, 1213, 3, 233, 109, 0, 1213, 1214, 1, 0, 0, 0, 1214, 1215, 6, 139, 25, 0, 1215, 294, 1, 0, 0, 0, 1216, 1217, 3, 173, 79, 0, 1217, 1218, 1, 0, 0, 0, 1218, 1219, 6, 140, 30, 0, 1219, 296, 1, 0, 0, 0, 1220, 1221, 4, 141, 7, 0, 1221, 1222, 3, 127, 56, 0, 1222, 1223, 1, 0, 0, 0, 1223, 1224, 6, 141, 23, 0, 1224, 298, 1, 0, 0, 0, 1225, 1226, 4, 142, 8, 0, 1226, 1227, 3, 163, 74, 0, 1227, 1228, 1, 0, 0, 0, 1228, 1229, 6, 142, 24, 0, 1229, 300, 1, 0, 0, 0, 1230, 1231, 3, 55, 20, 0, 1231, 1232, 1, 0, 0, 0, 1232, 1233, 6, 143, 10, 0, 1233, 302, 1, 0, 0, 0, 1234, 1235, 3, 57, 21, 0, 1235, 1236, 1, 0, 0, 0, 1236, 1237, 6, 144, 10, 0, 1237, 304, 1, 0, 0, 0, 1238, 1239, 3, 59, 22, 0, 1239, 1240, 1, 0, 0, 0, 1240, 1241, 6, 145, 10, 0, 1241, 306, 1, 0, 0, 0, 1242, 1243, 3, 61, 23, 0, 1243, 1244, 1, 0, 0, 0, 1244, 1245, 6, 146, 15, 0, 1245, 1246, 6, 146, 11, 0, 1246, 308, 1, 0, 0, 0, 1247, 1248, 3, 103, 44, 0, 1248, 1249, 1, 0, 0, 0, 1249, 1250, 6, 147, 22, 0, 1250, 310, 1, 0, 0, 0, 1251, 1252, 4, 148, 9, 0, 1252, 1253, 3, 127, 56, 0, 1253, 1254, 1, 0, 0, 0, 1254, 1255, 6, 148, 23, 0, 1255, 312, 1, 0, 0, 0, 1256, 1257, 4, 149, 10, 0, 1257, 1258, 3, 163, 74, 0, 1258, 1259, 1, 0, 0, 0, 1259, 1260, 6, 149, 24, 0, 1260, 314, 1, 0, 0, 0, 1261, 1262, 3, 173, 79, 0, 1262, 1263, 1, 0, 0, 0, 1263, 1264, 6, 150, 30, 0, 1264, 316, 1, 0, 0, 0, 1265, 1266, 3, 169, 77, 0, 1266, 1267, 1, 0, 0, 0, 1267, 1268, 6, 151, 31, 0, 1268, 318, 1, 0, 0, 0, 1269, 1270, 3, 55, 20, 0, 1270, 1271, 1, 0, 0, 0, 1271, 1272, 6, 152, 10, 0, 1272, 320, 1, 0, 0, 0, 1273, 1274, 3, 57, 21, 0, 1274, 1275, 1, 0, 0, 0, 1275, 1276, 6, 153, 10, 0, 1276, 322, 1, 0, 0, 0, 1277, 1278, 3, 59, 22, 0, 1278, 1279, 1, 0, 0, 0, 1279, 1280, 6, 154, 10, 0, 1280, 324, 1, 0, 0, 0, 1281, 1282, 3, 61, 23, 0, 1282, 1283, 1, 0, 0, 0, 1283, 1284, 6, 155, 15, 0, 1284, 1285, 6, 155, 11, 0, 1285, 326, 1, 0, 0, 0, 1286, 1287, 7, 1, 0, 0, 1287, 1288, 7, 9, 0, 0, 1288, 1289, 7, 15, 0, 0, 1289, 1290, 7, 7, 0, 0, 1290, 328, 1, 0, 0, 0, 1291, 1292, 3, 55, 20, 0, 1292, 1293, 1, 0, 0, 0, 1293, 1294, 6, 157, 10, 0, 1294, 330, 1, 0, 0, 0, 1295, 1296, 3, 57, 21, 0, 1296, 1297, 1, 0, 0, 0, 1297, 1298, 6, 158, 10, 0, 1298, 332, 1, 0, 0, 0, 1299, 1300, 3, 59, 22, 0, 1300, 1301, 1, 0, 0, 0, 1301, 1302, 6, 159, 10, 0, 1302, 334, 1, 0, 0, 0, 1303, 1304, 3, 167, 76, 0, 1304, 1305, 1, 0, 0, 0, 1305, 1306, 6, 160, 16, 0, 1306, 1307, 6, 160, 11, 0, 1307, 336, 1, 0, 0, 0, 1308, 1309, 5, 58, 0, 0, 1309, 338, 1, 0, 0, 0, 1310, 1316, 3, 73, 29, 0, 1311, 1316, 3, 63, 24, 0, 1312, 1316, 3, 103, 44, 0, 1313, 1316, 3, 65, 25, 0, 1314, 1316, 3, 79, 32, 0, 1315, 1310, 1, 0, 0, 0, 1315, 1311, 1, 0, 0, 0, 1315, 1312, 1, 0, 0, 0, 1315, 1313, 1, 0, 0, 0, 1315, 1314, 1, 0, 0, 0, 1316, 1317, 1, 0, 0, 0, 1317, 1315, 1, 0, 0, 0, 1317, 1318, 1, 0, 0, 0, 1318, 340, 1, 0, 0, 0, 1319, 1320, 3, 55, 20, 0, 1320, 1321, 1, 0, 0, 0, 1321, 1322, 6, 163, 10, 0, 1322, 342, 1, 0, 0, 0, 1323, 1324, 3, 57, 21, 0, 1324, 1325, 1, 0, 0, 0, 1325, 1326, 6, 164, 10, 0, 1326, 344, 1, 0, 0, 0, 1327, 1328, 3, 59, 22, 0, 1328, 1329, 1, 0, 0, 0, 1329, 1330, 6, 165, 10, 0, 1330, 346, 1, 0, 0, 0, 1331, 1332, 3, 61, 23, 0, 1332, 1333, 1, 0, 0, 0, 1333, 1334, 6, 166, 15, 0, 1334, 1335, 6, 166, 11, 0, 1335, 348, 1, 0, 0, 0, 1336, 1337, 3, 337, 161, 0, 1337, 1338, 1, 0, 0, 0, 1338, 1339, 6, 167, 17, 0, 1339, 350, 1, 0, 0, 0, 1340, 1341, 3, 99, 42, 0, 1341, 1342, 1, 0, 0, 0, 1342, 1343, 6, 168, 18, 0, 1343, 352, 1, 0, 0, 0, 1344, 1345, 3, 103, 44, 0, 1345, 1346, 1, 0, 0, 0, 1346, 1347, 6, 169, 22, 0, 1347, 354, 1, 0, 0, 0, 1348, 1349, 3, 267, 126, 0, 1349, 1350, 1, 0, 0, 0, 1350, 1351, 6, 170, 32, 0, 1351, 1352, 6, 170, 33, 0, 1352, 356, 1, 0, 0, 0, 1353, 1354, 3, 207, 96, 0, 1354, 1355, 1, 0, 0, 0, 1355, 1356, 6, 171, 20, 0, 1356, 358, 1, 0, 0, 0, 1357, 1358, 3, 83, 34, 0, 1358, 1359, 1, 0, 0, 0, 1359, 1360, 6, 172, 21, 0, 1360, 360, 1, 0, 0, 0, 1361, 1362, 3, 55, 20, 0, 1362, 1363, 1, 0, 0, 0, 1363, 1364, 6, 173, 10, 0, 1364, 362, 1, 0, 0, 0, 1365, 1366, 3, 57, 21, 0, 1366, 1367, 1, 0, 0, 0, 1367, 1368, 6, 174, 10, 0, 1368, 364, 1, 0, 0, 0, 1369, 1370, 3, 59, 22, 0, 1370, 1371, 1, 0, 0, 0, 1371, 1372, 6, 175, 10, 0, 1372, 366, 1, 0, 0, 0, 1373, 1374, 3, 61, 23, 0, 1374, 1375, 1, 0, 0, 0, 1375, 1376, 6, 176, 15, 0, 1376, 1377, 6, 176, 11, 0, 1377, 1378, 6, 176, 11, 0, 1378, 368, 1, 0, 0, 0, 1379, 1380, 3, 99, 42, 0, 1380, 1381, 1, 0, 0, 0, 1381, 1382, 6, 177, 18, 0, 1382, 370, 1, 0, 0, 0, 1383, 1384, 3, 103, 44, 0, 1384, 1385, 1, 0, 0, 0, 1385, 1386, 6, 178, 22, 0, 1386, 372, 1, 0, 0, 0, 1387, 1388, 3, 233, 109, 0, 1388, 1389, 1, 0, 0, 0, 1389, 1390, 6, 179, 25, 0, 1390, 374, 1, 0, 0, 0, 1391, 1392, 3, 55, 20, 0, 1392, 1393, 1, 0, 0, 0, 1393, 1394, 6, 180, 10, 0, 1394, 376, 1, 0, 0, 0, 1395, 1396, 3, 57, 21, 0, 1396, 1397, 1, 0, 0, 0, 1397, 1398, 6, 181, 10, 0, 1398, 378, 1, 0, 0, 0, 1399, 1400, 3, 59, 22, 0, 1400, 1401, 1, 0, 0, 0, 1401, 1402, 6, 182, 10, 0, 1402, 380, 1, 0, 0, 0, 1403, 1404, 3, 61, 23, 0, 1404, 1405, 1, 0, 0, 0, 1405, 1406, 6, 183, 15, 0, 1406, 1407, 6, 183, 11, 0, 1407, 382, 1, 0, 0, 0, 1408, 1409, 3, 207, 96, 0, 1409, 1410, 1, 0, 0, 0, 1410, 1411, 6, 184, 20, 0, 1411, 1412, 6, 184, 11, 0, 1412, 1413, 6, 184, 34, 0, 1413, 384, 1, 0, 0, 0, 1414, 1415, 3, 83, 34, 0, 1415, 1416, 1, 0, 0, 0, 1416, 1417, 6, 185, 21, 0, 1417, 1418, 6, 185, 11, 0, 1418, 1419, 6, 185, 34, 0, 1419, 386, 1, 0, 0, 0, 1420, 1421, 3, 55, 20, 0, 1421, 1422, 1, 0, 0, 0, 1422, 1423, 6, 186, 10, 0, 1423, 388, 1, 0, 0, 0, 1424, 1425, 3, 57, 21, 0, 1425, 1426, 1, 0, 0, 0, 1426, 1427, 6, 187, 10, 0, 1427, 390, 1, 0, 0, 0, 1428, 1429, 3, 59, 22, 0, 1429, 1430, 1, 0, 0, 0, 1430, 1431, 6, 188, 10, 0, 1431, 392, 1, 0, 0, 0, 1432, 1433, 3, 337, 161, 0, 1433, 1434, 1, 0, 0, 0, 1434, 1435, 6, 189, 17, 0, 1435, 1436, 6, 189, 11, 0, 1436, 1437, 6, 189, 9, 0, 1437, 394, 1, 0, 0, 0, 1438, 1439, 3, 99, 42, 0, 1439, 1440, 1, 0, 0, 0, 1440, 1441, 6, 190, 18, 0, 1441, 1442, 6, 190, 11, 0, 1442, 1443, 6, 190, 9, 0, 1443, 396, 1, 0, 0, 0, 1444, 1445, 3, 55, 20, 0, 1445, 1446, 1, 0, 0, 0, 1446, 1447, 6, 191, 10, 0, 1447, 398, 1, 0, 0, 0, 1448, 1449, 3, 57, 21, 0, 1449, 1450, 1, 0, 0, 0, 1450, 1451, 6, 192, 10, 0, 1451, 400, 1, 0, 0, 0, 1452, 1453, 3, 59, 22, 0, 1453, 1454, 1, 0, 0, 0, 1454, 1455, 6, 193, 10, 0, 1455, 402, 1, 0, 0, 0, 1456, 1457, 3, 173, 79, 0, 1457, 1458, 1, 0, 0, 0, 1458, 1459, 6, 194, 11, 0, 1459, 1460, 6, 194, 0, 0, 1460, 1461, 6, 194, 30, 0, 1461, 404, 1, 0, 0, 0, 1462, 1463, 3, 169, 77, 0, 1463, 1464, 1, 0, 0, 0, 1464, 1465, 6, 195, 11, 0, 1465, 1466, 6, 195, 0, 0, 1466, 1467, 6, 195, 31, 0, 1467, 406, 1, 0, 0, 0, 1468, 1469, 3, 89, 37, 0, 1469, 1470, 1, 0, 0, 0, 1470, 1471, 6, 196, 11, 0, 1471, 1472, 6, 196, 0, 0, 1472, 1473, 6, 196, 35, 0, 1473, 408, 1, 0, 0, 0, 1474, 1475, 3, 61, 23, 0, 1475, 1476, 1, 0, 0, 0, 1476, 1477, 6, 197, 15, 0, 1477, 1478, 6, 197, 11, 0, 1478, 410, 1, 0, 0, 0, 65, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 579, 589, 593, 596, 605, 607, 618, 637, 642, 651, 658, 663, 665, 676, 684, 687, 689, 694, 699, 705, 712, 717, 723, 726, 734, 738, 870, 875, 882, 884, 900, 905, 910, 912, 918, 995, 1000, 1049, 1053, 1058, 1063, 1068, 1070, 1074, 1076, 1163, 1167, 1172, 1315, 1317, 36, 5, 1, 0, 5, 4, 0, 5, 6, 0, 5, 2, 0, 5, 3, 0, 5, 8, 0, 5, 5, 0, 5, 9, 0, 5, 11, 0, 5, 13, 0, 0, 1, 0, 4, 0, 0, 7, 16, 0, 7, 65, 0, 5, 0, 0, 7, 24, 0, 7, 66, 0, 7, 104, 0, 7, 33, 0, 7, 31, 0, 7, 76, 0, 7, 25, 0, 7, 35, 0, 7, 47, 0, 7, 64, 0, 7, 80, 0, 5, 10, 0, 5, 7, 0, 7, 90, 0, 7, 89, 0, 7, 68, 0, 7, 67, 0, 7, 88, 0, 5, 12, 0, 5, 14, 0, 7, 28, 0] \ No newline at end of file +[4, 0, 119, 1484, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 2, 83, 7, 83, 2, 84, 7, 84, 2, 85, 7, 85, 2, 86, 7, 86, 2, 87, 7, 87, 2, 88, 7, 88, 2, 89, 7, 89, 2, 90, 7, 90, 2, 91, 7, 91, 2, 92, 7, 92, 2, 93, 7, 93, 2, 94, 7, 94, 2, 95, 7, 95, 2, 96, 7, 96, 2, 97, 7, 97, 2, 98, 7, 98, 2, 99, 7, 99, 2, 100, 7, 100, 2, 101, 7, 101, 2, 102, 7, 102, 2, 103, 7, 103, 2, 104, 7, 104, 2, 105, 7, 105, 2, 106, 7, 106, 2, 107, 7, 107, 2, 108, 7, 108, 2, 109, 7, 109, 2, 110, 7, 110, 2, 111, 7, 111, 2, 112, 7, 112, 2, 113, 7, 113, 2, 114, 7, 114, 2, 115, 7, 115, 2, 116, 7, 116, 2, 117, 7, 117, 2, 118, 7, 118, 2, 119, 7, 119, 2, 120, 7, 120, 2, 121, 7, 121, 2, 122, 7, 122, 2, 123, 7, 123, 2, 124, 7, 124, 2, 125, 7, 125, 2, 126, 7, 126, 2, 127, 7, 127, 2, 128, 7, 128, 2, 129, 7, 129, 2, 130, 7, 130, 2, 131, 7, 131, 2, 132, 7, 132, 2, 133, 7, 133, 2, 134, 7, 134, 2, 135, 7, 135, 2, 136, 7, 136, 2, 137, 7, 137, 2, 138, 7, 138, 2, 139, 7, 139, 2, 140, 7, 140, 2, 141, 7, 141, 2, 142, 7, 142, 2, 143, 7, 143, 2, 144, 7, 144, 2, 145, 7, 145, 2, 146, 7, 146, 2, 147, 7, 147, 2, 148, 7, 148, 2, 149, 7, 149, 2, 150, 7, 150, 2, 151, 7, 151, 2, 152, 7, 152, 2, 153, 7, 153, 2, 154, 7, 154, 2, 155, 7, 155, 2, 156, 7, 156, 2, 157, 7, 157, 2, 158, 7, 158, 2, 159, 7, 159, 2, 160, 7, 160, 2, 161, 7, 161, 2, 162, 7, 162, 2, 163, 7, 163, 2, 164, 7, 164, 2, 165, 7, 165, 2, 166, 7, 166, 2, 167, 7, 167, 2, 168, 7, 168, 2, 169, 7, 169, 2, 170, 7, 170, 2, 171, 7, 171, 2, 172, 7, 172, 2, 173, 7, 173, 2, 174, 7, 174, 2, 175, 7, 175, 2, 176, 7, 176, 2, 177, 7, 177, 2, 178, 7, 178, 2, 179, 7, 179, 2, 180, 7, 180, 2, 181, 7, 181, 2, 182, 7, 182, 2, 183, 7, 183, 2, 184, 7, 184, 2, 185, 7, 185, 2, 186, 7, 186, 2, 187, 7, 187, 2, 188, 7, 188, 2, 189, 7, 189, 2, 190, 7, 190, 2, 191, 7, 191, 2, 192, 7, 192, 2, 193, 7, 193, 2, 194, 7, 194, 2, 195, 7, 195, 2, 196, 7, 196, 2, 197, 7, 197, 2, 198, 7, 198, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 4, 19, 580, 8, 19, 11, 19, 12, 19, 581, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 5, 20, 590, 8, 20, 10, 20, 12, 20, 593, 9, 20, 1, 20, 3, 20, 596, 8, 20, 1, 20, 3, 20, 599, 8, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 5, 21, 608, 8, 21, 10, 21, 12, 21, 611, 9, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 22, 4, 22, 619, 8, 22, 11, 22, 12, 22, 620, 1, 22, 1, 22, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 29, 1, 29, 3, 29, 642, 8, 29, 1, 29, 4, 29, 645, 8, 29, 11, 29, 12, 29, 646, 1, 30, 1, 30, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 3, 32, 656, 8, 32, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 3, 34, 663, 8, 34, 1, 35, 1, 35, 1, 35, 5, 35, 668, 8, 35, 10, 35, 12, 35, 671, 9, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 5, 35, 679, 8, 35, 10, 35, 12, 35, 682, 9, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 3, 35, 689, 8, 35, 1, 35, 3, 35, 692, 8, 35, 3, 35, 694, 8, 35, 1, 36, 4, 36, 697, 8, 36, 11, 36, 12, 36, 698, 1, 37, 4, 37, 702, 8, 37, 11, 37, 12, 37, 703, 1, 37, 1, 37, 5, 37, 708, 8, 37, 10, 37, 12, 37, 711, 9, 37, 1, 37, 1, 37, 4, 37, 715, 8, 37, 11, 37, 12, 37, 716, 1, 37, 4, 37, 720, 8, 37, 11, 37, 12, 37, 721, 1, 37, 1, 37, 5, 37, 726, 8, 37, 10, 37, 12, 37, 729, 9, 37, 3, 37, 731, 8, 37, 1, 37, 1, 37, 1, 37, 1, 37, 4, 37, 737, 8, 37, 11, 37, 12, 37, 738, 1, 37, 1, 37, 3, 37, 743, 8, 37, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 62, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 69, 1, 69, 1, 70, 1, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 74, 1, 74, 1, 74, 1, 74, 1, 75, 1, 75, 1, 75, 3, 75, 874, 8, 75, 1, 75, 5, 75, 877, 8, 75, 10, 75, 12, 75, 880, 9, 75, 1, 75, 1, 75, 4, 75, 884, 8, 75, 11, 75, 12, 75, 885, 3, 75, 888, 8, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 1, 77, 1, 78, 1, 78, 5, 78, 902, 8, 78, 10, 78, 12, 78, 905, 9, 78, 1, 78, 1, 78, 3, 78, 909, 8, 78, 1, 78, 4, 78, 912, 8, 78, 11, 78, 12, 78, 913, 3, 78, 916, 8, 78, 1, 79, 1, 79, 4, 79, 920, 8, 79, 11, 79, 12, 79, 921, 1, 79, 1, 79, 1, 80, 1, 80, 1, 81, 1, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 83, 1, 83, 1, 83, 1, 83, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 86, 1, 86, 1, 86, 1, 86, 1, 87, 1, 87, 1, 87, 1, 87, 1, 88, 1, 88, 1, 88, 1, 88, 1, 89, 1, 89, 1, 89, 1, 89, 1, 89, 1, 90, 1, 90, 1, 90, 1, 90, 1, 91, 1, 91, 1, 91, 1, 91, 1, 92, 1, 92, 1, 92, 1, 92, 1, 93, 1, 93, 1, 93, 1, 93, 1, 94, 1, 94, 1, 94, 1, 94, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 96, 1, 96, 1, 96, 3, 96, 999, 8, 96, 1, 97, 4, 97, 1002, 8, 97, 11, 97, 12, 97, 1003, 1, 98, 1, 98, 1, 98, 1, 98, 1, 99, 1, 99, 1, 99, 1, 99, 1, 100, 1, 100, 1, 100, 1, 100, 1, 101, 1, 101, 1, 101, 1, 101, 1, 102, 1, 102, 1, 102, 1, 102, 1, 103, 1, 103, 1, 103, 1, 103, 1, 103, 1, 104, 1, 104, 1, 104, 1, 104, 1, 105, 1, 105, 1, 105, 1, 105, 1, 106, 1, 106, 1, 106, 1, 106, 1, 106, 1, 107, 1, 107, 1, 107, 1, 107, 1, 107, 1, 108, 1, 108, 1, 108, 1, 108, 3, 108, 1053, 8, 108, 1, 109, 1, 109, 3, 109, 1057, 8, 109, 1, 109, 5, 109, 1060, 8, 109, 10, 109, 12, 109, 1063, 9, 109, 1, 109, 1, 109, 3, 109, 1067, 8, 109, 1, 109, 4, 109, 1070, 8, 109, 11, 109, 12, 109, 1071, 3, 109, 1074, 8, 109, 1, 110, 1, 110, 4, 110, 1078, 8, 110, 11, 110, 12, 110, 1079, 1, 111, 1, 111, 1, 111, 1, 111, 1, 112, 1, 112, 1, 112, 1, 112, 1, 113, 1, 113, 1, 113, 1, 113, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 115, 1, 115, 1, 115, 1, 115, 1, 116, 1, 116, 1, 116, 1, 116, 1, 117, 1, 117, 1, 117, 1, 117, 1, 118, 1, 118, 1, 118, 1, 118, 1, 118, 1, 119, 1, 119, 1, 119, 1, 119, 1, 119, 1, 120, 1, 120, 1, 120, 1, 121, 1, 121, 1, 121, 1, 121, 1, 122, 1, 122, 1, 122, 1, 122, 1, 123, 1, 123, 1, 123, 1, 123, 1, 124, 1, 124, 1, 124, 1, 124, 1, 125, 1, 125, 1, 125, 1, 125, 1, 125, 1, 126, 1, 126, 1, 126, 1, 126, 1, 126, 1, 127, 1, 127, 1, 127, 1, 127, 1, 127, 1, 128, 1, 128, 1, 128, 1, 128, 1, 128, 1, 128, 1, 128, 1, 129, 1, 129, 1, 130, 4, 130, 1165, 8, 130, 11, 130, 12, 130, 1166, 1, 130, 1, 130, 3, 130, 1171, 8, 130, 1, 130, 4, 130, 1174, 8, 130, 11, 130, 12, 130, 1175, 1, 131, 1, 131, 1, 131, 1, 131, 1, 132, 1, 132, 1, 132, 1, 132, 1, 133, 1, 133, 1, 133, 1, 133, 1, 134, 1, 134, 1, 134, 1, 134, 1, 135, 1, 135, 1, 135, 1, 135, 1, 135, 1, 135, 1, 136, 1, 136, 1, 136, 1, 136, 1, 137, 1, 137, 1, 137, 1, 137, 1, 138, 1, 138, 1, 138, 1, 138, 1, 139, 1, 139, 1, 139, 1, 139, 1, 140, 1, 140, 1, 140, 1, 140, 1, 141, 1, 141, 1, 141, 1, 141, 1, 142, 1, 142, 1, 142, 1, 142, 1, 142, 1, 143, 1, 143, 1, 143, 1, 143, 1, 143, 1, 144, 1, 144, 1, 144, 1, 144, 1, 145, 1, 145, 1, 145, 1, 145, 1, 146, 1, 146, 1, 146, 1, 146, 1, 147, 1, 147, 1, 147, 1, 147, 1, 147, 1, 148, 1, 148, 1, 148, 1, 148, 1, 149, 1, 149, 1, 149, 1, 149, 1, 149, 1, 150, 1, 150, 1, 150, 1, 150, 1, 150, 1, 151, 1, 151, 1, 151, 1, 151, 1, 152, 1, 152, 1, 152, 1, 152, 1, 153, 1, 153, 1, 153, 1, 153, 1, 154, 1, 154, 1, 154, 1, 154, 1, 155, 1, 155, 1, 155, 1, 155, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 157, 1, 157, 1, 157, 1, 157, 1, 157, 1, 158, 1, 158, 1, 158, 1, 158, 1, 159, 1, 159, 1, 159, 1, 159, 1, 160, 1, 160, 1, 160, 1, 160, 1, 161, 1, 161, 1, 161, 1, 161, 1, 161, 1, 162, 1, 162, 1, 162, 1, 162, 1, 163, 1, 163, 1, 163, 1, 163, 1, 163, 4, 163, 1321, 8, 163, 11, 163, 12, 163, 1322, 1, 164, 1, 164, 1, 164, 1, 164, 1, 165, 1, 165, 1, 165, 1, 165, 1, 166, 1, 166, 1, 166, 1, 166, 1, 167, 1, 167, 1, 167, 1, 167, 1, 167, 1, 168, 1, 168, 1, 168, 1, 168, 1, 169, 1, 169, 1, 169, 1, 169, 1, 170, 1, 170, 1, 170, 1, 170, 1, 171, 1, 171, 1, 171, 1, 171, 1, 171, 1, 172, 1, 172, 1, 172, 1, 172, 1, 173, 1, 173, 1, 173, 1, 173, 1, 174, 1, 174, 1, 174, 1, 174, 1, 175, 1, 175, 1, 175, 1, 175, 1, 176, 1, 176, 1, 176, 1, 176, 1, 177, 1, 177, 1, 177, 1, 177, 1, 177, 1, 177, 1, 178, 1, 178, 1, 178, 1, 178, 1, 179, 1, 179, 1, 179, 1, 179, 1, 180, 1, 180, 1, 180, 1, 180, 1, 181, 1, 181, 1, 181, 1, 181, 1, 182, 1, 182, 1, 182, 1, 182, 1, 183, 1, 183, 1, 183, 1, 183, 1, 184, 1, 184, 1, 184, 1, 184, 1, 184, 1, 185, 1, 185, 1, 185, 1, 185, 1, 185, 1, 185, 1, 186, 1, 186, 1, 186, 1, 186, 1, 186, 1, 186, 1, 187, 1, 187, 1, 187, 1, 187, 1, 188, 1, 188, 1, 188, 1, 188, 1, 189, 1, 189, 1, 189, 1, 189, 1, 190, 1, 190, 1, 190, 1, 190, 1, 190, 1, 190, 1, 191, 1, 191, 1, 191, 1, 191, 1, 191, 1, 191, 1, 192, 1, 192, 1, 192, 1, 192, 1, 193, 1, 193, 1, 193, 1, 193, 1, 194, 1, 194, 1, 194, 1, 194, 1, 195, 1, 195, 1, 195, 1, 195, 1, 195, 1, 195, 1, 196, 1, 196, 1, 196, 1, 196, 1, 196, 1, 196, 1, 197, 1, 197, 1, 197, 1, 197, 1, 197, 1, 197, 1, 198, 1, 198, 1, 198, 1, 198, 1, 198, 2, 609, 680, 0, 199, 15, 1, 17, 2, 19, 3, 21, 4, 23, 5, 25, 6, 27, 7, 29, 8, 31, 9, 33, 10, 35, 11, 37, 12, 39, 13, 41, 14, 43, 15, 45, 16, 47, 17, 49, 18, 51, 19, 53, 20, 55, 21, 57, 22, 59, 23, 61, 24, 63, 25, 65, 0, 67, 0, 69, 0, 71, 0, 73, 0, 75, 0, 77, 0, 79, 0, 81, 0, 83, 0, 85, 26, 87, 27, 89, 28, 91, 29, 93, 30, 95, 31, 97, 32, 99, 33, 101, 34, 103, 35, 105, 36, 107, 37, 109, 38, 111, 39, 113, 40, 115, 41, 117, 42, 119, 43, 121, 44, 123, 45, 125, 46, 127, 47, 129, 48, 131, 49, 133, 50, 135, 51, 137, 52, 139, 53, 141, 54, 143, 55, 145, 56, 147, 57, 149, 58, 151, 59, 153, 60, 155, 61, 157, 62, 159, 63, 161, 0, 163, 0, 165, 64, 167, 65, 169, 66, 171, 67, 173, 0, 175, 68, 177, 69, 179, 70, 181, 71, 183, 0, 185, 0, 187, 72, 189, 73, 191, 74, 193, 0, 195, 0, 197, 0, 199, 0, 201, 0, 203, 0, 205, 75, 207, 0, 209, 76, 211, 0, 213, 0, 215, 77, 217, 78, 219, 79, 221, 0, 223, 0, 225, 0, 227, 0, 229, 0, 231, 0, 233, 0, 235, 80, 237, 81, 239, 82, 241, 83, 243, 0, 245, 0, 247, 0, 249, 0, 251, 0, 253, 0, 255, 84, 257, 0, 259, 85, 261, 86, 263, 87, 265, 0, 267, 0, 269, 88, 271, 89, 273, 0, 275, 90, 277, 0, 279, 91, 281, 92, 283, 93, 285, 0, 287, 0, 289, 0, 291, 0, 293, 0, 295, 0, 297, 0, 299, 0, 301, 0, 303, 94, 305, 95, 307, 96, 309, 0, 311, 0, 313, 0, 315, 0, 317, 0, 319, 0, 321, 97, 323, 98, 325, 99, 327, 0, 329, 100, 331, 101, 333, 102, 335, 103, 337, 0, 339, 0, 341, 104, 343, 105, 345, 106, 347, 107, 349, 0, 351, 0, 353, 0, 355, 0, 357, 0, 359, 0, 361, 0, 363, 108, 365, 109, 367, 110, 369, 0, 371, 0, 373, 0, 375, 0, 377, 111, 379, 112, 381, 113, 383, 0, 385, 0, 387, 0, 389, 114, 391, 115, 393, 116, 395, 0, 397, 0, 399, 117, 401, 118, 403, 119, 405, 0, 407, 0, 409, 0, 411, 0, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 35, 2, 0, 68, 68, 100, 100, 2, 0, 73, 73, 105, 105, 2, 0, 83, 83, 115, 115, 2, 0, 69, 69, 101, 101, 2, 0, 67, 67, 99, 99, 2, 0, 84, 84, 116, 116, 2, 0, 82, 82, 114, 114, 2, 0, 79, 79, 111, 111, 2, 0, 80, 80, 112, 112, 2, 0, 78, 78, 110, 110, 2, 0, 72, 72, 104, 104, 2, 0, 86, 86, 118, 118, 2, 0, 65, 65, 97, 97, 2, 0, 76, 76, 108, 108, 2, 0, 88, 88, 120, 120, 2, 0, 70, 70, 102, 102, 2, 0, 77, 77, 109, 109, 2, 0, 71, 71, 103, 103, 2, 0, 75, 75, 107, 107, 2, 0, 87, 87, 119, 119, 2, 0, 85, 85, 117, 117, 6, 0, 9, 10, 13, 13, 32, 32, 47, 47, 91, 91, 93, 93, 2, 0, 10, 10, 13, 13, 3, 0, 9, 10, 13, 13, 32, 32, 1, 0, 48, 57, 2, 0, 65, 90, 97, 122, 8, 0, 34, 34, 78, 78, 82, 82, 84, 84, 92, 92, 110, 110, 114, 114, 116, 116, 4, 0, 10, 10, 13, 13, 34, 34, 92, 92, 2, 0, 43, 43, 45, 45, 1, 0, 96, 96, 2, 0, 66, 66, 98, 98, 2, 0, 89, 89, 121, 121, 11, 0, 9, 10, 13, 13, 32, 32, 34, 34, 44, 44, 47, 47, 58, 58, 61, 61, 91, 91, 93, 93, 124, 124, 2, 0, 42, 42, 47, 47, 11, 0, 9, 10, 13, 13, 32, 32, 34, 35, 44, 44, 47, 47, 58, 58, 60, 60, 62, 63, 92, 92, 124, 124, 1512, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 61, 1, 0, 0, 0, 1, 63, 1, 0, 0, 0, 1, 85, 1, 0, 0, 0, 1, 87, 1, 0, 0, 0, 1, 89, 1, 0, 0, 0, 1, 91, 1, 0, 0, 0, 1, 93, 1, 0, 0, 0, 1, 95, 1, 0, 0, 0, 1, 97, 1, 0, 0, 0, 1, 99, 1, 0, 0, 0, 1, 101, 1, 0, 0, 0, 1, 103, 1, 0, 0, 0, 1, 105, 1, 0, 0, 0, 1, 107, 1, 0, 0, 0, 1, 109, 1, 0, 0, 0, 1, 111, 1, 0, 0, 0, 1, 113, 1, 0, 0, 0, 1, 115, 1, 0, 0, 0, 1, 117, 1, 0, 0, 0, 1, 119, 1, 0, 0, 0, 1, 121, 1, 0, 0, 0, 1, 123, 1, 0, 0, 0, 1, 125, 1, 0, 0, 0, 1, 127, 1, 0, 0, 0, 1, 129, 1, 0, 0, 0, 1, 131, 1, 0, 0, 0, 1, 133, 1, 0, 0, 0, 1, 135, 1, 0, 0, 0, 1, 137, 1, 0, 0, 0, 1, 139, 1, 0, 0, 0, 1, 141, 1, 0, 0, 0, 1, 143, 1, 0, 0, 0, 1, 145, 1, 0, 0, 0, 1, 147, 1, 0, 0, 0, 1, 149, 1, 0, 0, 0, 1, 151, 1, 0, 0, 0, 1, 153, 1, 0, 0, 0, 1, 155, 1, 0, 0, 0, 1, 157, 1, 0, 0, 0, 1, 159, 1, 0, 0, 0, 1, 161, 1, 0, 0, 0, 1, 163, 1, 0, 0, 0, 1, 165, 1, 0, 0, 0, 1, 167, 1, 0, 0, 0, 1, 169, 1, 0, 0, 0, 1, 171, 1, 0, 0, 0, 1, 175, 1, 0, 0, 0, 1, 177, 1, 0, 0, 0, 1, 179, 1, 0, 0, 0, 1, 181, 1, 0, 0, 0, 2, 183, 1, 0, 0, 0, 2, 185, 1, 0, 0, 0, 2, 187, 1, 0, 0, 0, 2, 189, 1, 0, 0, 0, 2, 191, 1, 0, 0, 0, 3, 193, 1, 0, 0, 0, 3, 195, 1, 0, 0, 0, 3, 197, 1, 0, 0, 0, 3, 199, 1, 0, 0, 0, 3, 201, 1, 0, 0, 0, 3, 203, 1, 0, 0, 0, 3, 205, 1, 0, 0, 0, 3, 209, 1, 0, 0, 0, 3, 211, 1, 0, 0, 0, 3, 213, 1, 0, 0, 0, 3, 215, 1, 0, 0, 0, 3, 217, 1, 0, 0, 0, 3, 219, 1, 0, 0, 0, 4, 221, 1, 0, 0, 0, 4, 223, 1, 0, 0, 0, 4, 225, 1, 0, 0, 0, 4, 227, 1, 0, 0, 0, 4, 229, 1, 0, 0, 0, 4, 235, 1, 0, 0, 0, 4, 237, 1, 0, 0, 0, 4, 239, 1, 0, 0, 0, 4, 241, 1, 0, 0, 0, 5, 243, 1, 0, 0, 0, 5, 245, 1, 0, 0, 0, 5, 247, 1, 0, 0, 0, 5, 249, 1, 0, 0, 0, 5, 251, 1, 0, 0, 0, 5, 253, 1, 0, 0, 0, 5, 255, 1, 0, 0, 0, 5, 257, 1, 0, 0, 0, 5, 259, 1, 0, 0, 0, 5, 261, 1, 0, 0, 0, 5, 263, 1, 0, 0, 0, 6, 265, 1, 0, 0, 0, 6, 267, 1, 0, 0, 0, 6, 269, 1, 0, 0, 0, 6, 271, 1, 0, 0, 0, 6, 275, 1, 0, 0, 0, 6, 277, 1, 0, 0, 0, 6, 279, 1, 0, 0, 0, 6, 281, 1, 0, 0, 0, 6, 283, 1, 0, 0, 0, 7, 285, 1, 0, 0, 0, 7, 287, 1, 0, 0, 0, 7, 289, 1, 0, 0, 0, 7, 291, 1, 0, 0, 0, 7, 293, 1, 0, 0, 0, 7, 295, 1, 0, 0, 0, 7, 297, 1, 0, 0, 0, 7, 299, 1, 0, 0, 0, 7, 301, 1, 0, 0, 0, 7, 303, 1, 0, 0, 0, 7, 305, 1, 0, 0, 0, 7, 307, 1, 0, 0, 0, 8, 309, 1, 0, 0, 0, 8, 311, 1, 0, 0, 0, 8, 313, 1, 0, 0, 0, 8, 315, 1, 0, 0, 0, 8, 317, 1, 0, 0, 0, 8, 319, 1, 0, 0, 0, 8, 321, 1, 0, 0, 0, 8, 323, 1, 0, 0, 0, 8, 325, 1, 0, 0, 0, 9, 327, 1, 0, 0, 0, 9, 329, 1, 0, 0, 0, 9, 331, 1, 0, 0, 0, 9, 333, 1, 0, 0, 0, 9, 335, 1, 0, 0, 0, 10, 337, 1, 0, 0, 0, 10, 339, 1, 0, 0, 0, 10, 341, 1, 0, 0, 0, 10, 343, 1, 0, 0, 0, 10, 345, 1, 0, 0, 0, 10, 347, 1, 0, 0, 0, 11, 349, 1, 0, 0, 0, 11, 351, 1, 0, 0, 0, 11, 353, 1, 0, 0, 0, 11, 355, 1, 0, 0, 0, 11, 357, 1, 0, 0, 0, 11, 359, 1, 0, 0, 0, 11, 361, 1, 0, 0, 0, 11, 363, 1, 0, 0, 0, 11, 365, 1, 0, 0, 0, 11, 367, 1, 0, 0, 0, 12, 369, 1, 0, 0, 0, 12, 371, 1, 0, 0, 0, 12, 373, 1, 0, 0, 0, 12, 375, 1, 0, 0, 0, 12, 377, 1, 0, 0, 0, 12, 379, 1, 0, 0, 0, 12, 381, 1, 0, 0, 0, 13, 383, 1, 0, 0, 0, 13, 385, 1, 0, 0, 0, 13, 387, 1, 0, 0, 0, 13, 389, 1, 0, 0, 0, 13, 391, 1, 0, 0, 0, 13, 393, 1, 0, 0, 0, 14, 395, 1, 0, 0, 0, 14, 397, 1, 0, 0, 0, 14, 399, 1, 0, 0, 0, 14, 401, 1, 0, 0, 0, 14, 403, 1, 0, 0, 0, 14, 405, 1, 0, 0, 0, 14, 407, 1, 0, 0, 0, 14, 409, 1, 0, 0, 0, 14, 411, 1, 0, 0, 0, 15, 413, 1, 0, 0, 0, 17, 423, 1, 0, 0, 0, 19, 430, 1, 0, 0, 0, 21, 439, 1, 0, 0, 0, 23, 446, 1, 0, 0, 0, 25, 456, 1, 0, 0, 0, 27, 463, 1, 0, 0, 0, 29, 470, 1, 0, 0, 0, 31, 477, 1, 0, 0, 0, 33, 485, 1, 0, 0, 0, 35, 497, 1, 0, 0, 0, 37, 506, 1, 0, 0, 0, 39, 512, 1, 0, 0, 0, 41, 519, 1, 0, 0, 0, 43, 526, 1, 0, 0, 0, 45, 534, 1, 0, 0, 0, 47, 542, 1, 0, 0, 0, 49, 557, 1, 0, 0, 0, 51, 567, 1, 0, 0, 0, 53, 579, 1, 0, 0, 0, 55, 585, 1, 0, 0, 0, 57, 602, 1, 0, 0, 0, 59, 618, 1, 0, 0, 0, 61, 624, 1, 0, 0, 0, 63, 626, 1, 0, 0, 0, 65, 630, 1, 0, 0, 0, 67, 632, 1, 0, 0, 0, 69, 634, 1, 0, 0, 0, 71, 637, 1, 0, 0, 0, 73, 639, 1, 0, 0, 0, 75, 648, 1, 0, 0, 0, 77, 650, 1, 0, 0, 0, 79, 655, 1, 0, 0, 0, 81, 657, 1, 0, 0, 0, 83, 662, 1, 0, 0, 0, 85, 693, 1, 0, 0, 0, 87, 696, 1, 0, 0, 0, 89, 742, 1, 0, 0, 0, 91, 744, 1, 0, 0, 0, 93, 747, 1, 0, 0, 0, 95, 751, 1, 0, 0, 0, 97, 755, 1, 0, 0, 0, 99, 757, 1, 0, 0, 0, 101, 760, 1, 0, 0, 0, 103, 762, 1, 0, 0, 0, 105, 767, 1, 0, 0, 0, 107, 769, 1, 0, 0, 0, 109, 775, 1, 0, 0, 0, 111, 781, 1, 0, 0, 0, 113, 784, 1, 0, 0, 0, 115, 787, 1, 0, 0, 0, 117, 792, 1, 0, 0, 0, 119, 797, 1, 0, 0, 0, 121, 799, 1, 0, 0, 0, 123, 803, 1, 0, 0, 0, 125, 808, 1, 0, 0, 0, 127, 814, 1, 0, 0, 0, 129, 817, 1, 0, 0, 0, 131, 819, 1, 0, 0, 0, 133, 825, 1, 0, 0, 0, 135, 827, 1, 0, 0, 0, 137, 832, 1, 0, 0, 0, 139, 835, 1, 0, 0, 0, 141, 838, 1, 0, 0, 0, 143, 841, 1, 0, 0, 0, 145, 843, 1, 0, 0, 0, 147, 846, 1, 0, 0, 0, 149, 848, 1, 0, 0, 0, 151, 851, 1, 0, 0, 0, 153, 853, 1, 0, 0, 0, 155, 855, 1, 0, 0, 0, 157, 857, 1, 0, 0, 0, 159, 859, 1, 0, 0, 0, 161, 861, 1, 0, 0, 0, 163, 866, 1, 0, 0, 0, 165, 887, 1, 0, 0, 0, 167, 889, 1, 0, 0, 0, 169, 894, 1, 0, 0, 0, 171, 915, 1, 0, 0, 0, 173, 917, 1, 0, 0, 0, 175, 925, 1, 0, 0, 0, 177, 927, 1, 0, 0, 0, 179, 931, 1, 0, 0, 0, 181, 935, 1, 0, 0, 0, 183, 939, 1, 0, 0, 0, 185, 944, 1, 0, 0, 0, 187, 949, 1, 0, 0, 0, 189, 953, 1, 0, 0, 0, 191, 957, 1, 0, 0, 0, 193, 961, 1, 0, 0, 0, 195, 966, 1, 0, 0, 0, 197, 970, 1, 0, 0, 0, 199, 974, 1, 0, 0, 0, 201, 978, 1, 0, 0, 0, 203, 982, 1, 0, 0, 0, 205, 986, 1, 0, 0, 0, 207, 998, 1, 0, 0, 0, 209, 1001, 1, 0, 0, 0, 211, 1005, 1, 0, 0, 0, 213, 1009, 1, 0, 0, 0, 215, 1013, 1, 0, 0, 0, 217, 1017, 1, 0, 0, 0, 219, 1021, 1, 0, 0, 0, 221, 1025, 1, 0, 0, 0, 223, 1030, 1, 0, 0, 0, 225, 1034, 1, 0, 0, 0, 227, 1038, 1, 0, 0, 0, 229, 1043, 1, 0, 0, 0, 231, 1052, 1, 0, 0, 0, 233, 1073, 1, 0, 0, 0, 235, 1077, 1, 0, 0, 0, 237, 1081, 1, 0, 0, 0, 239, 1085, 1, 0, 0, 0, 241, 1089, 1, 0, 0, 0, 243, 1093, 1, 0, 0, 0, 245, 1098, 1, 0, 0, 0, 247, 1102, 1, 0, 0, 0, 249, 1106, 1, 0, 0, 0, 251, 1110, 1, 0, 0, 0, 253, 1115, 1, 0, 0, 0, 255, 1120, 1, 0, 0, 0, 257, 1123, 1, 0, 0, 0, 259, 1127, 1, 0, 0, 0, 261, 1131, 1, 0, 0, 0, 263, 1135, 1, 0, 0, 0, 265, 1139, 1, 0, 0, 0, 267, 1144, 1, 0, 0, 0, 269, 1149, 1, 0, 0, 0, 271, 1154, 1, 0, 0, 0, 273, 1161, 1, 0, 0, 0, 275, 1170, 1, 0, 0, 0, 277, 1177, 1, 0, 0, 0, 279, 1181, 1, 0, 0, 0, 281, 1185, 1, 0, 0, 0, 283, 1189, 1, 0, 0, 0, 285, 1193, 1, 0, 0, 0, 287, 1199, 1, 0, 0, 0, 289, 1203, 1, 0, 0, 0, 291, 1207, 1, 0, 0, 0, 293, 1211, 1, 0, 0, 0, 295, 1215, 1, 0, 0, 0, 297, 1219, 1, 0, 0, 0, 299, 1223, 1, 0, 0, 0, 301, 1228, 1, 0, 0, 0, 303, 1233, 1, 0, 0, 0, 305, 1237, 1, 0, 0, 0, 307, 1241, 1, 0, 0, 0, 309, 1245, 1, 0, 0, 0, 311, 1250, 1, 0, 0, 0, 313, 1254, 1, 0, 0, 0, 315, 1259, 1, 0, 0, 0, 317, 1264, 1, 0, 0, 0, 319, 1268, 1, 0, 0, 0, 321, 1272, 1, 0, 0, 0, 323, 1276, 1, 0, 0, 0, 325, 1280, 1, 0, 0, 0, 327, 1284, 1, 0, 0, 0, 329, 1289, 1, 0, 0, 0, 331, 1294, 1, 0, 0, 0, 333, 1298, 1, 0, 0, 0, 335, 1302, 1, 0, 0, 0, 337, 1306, 1, 0, 0, 0, 339, 1311, 1, 0, 0, 0, 341, 1320, 1, 0, 0, 0, 343, 1324, 1, 0, 0, 0, 345, 1328, 1, 0, 0, 0, 347, 1332, 1, 0, 0, 0, 349, 1336, 1, 0, 0, 0, 351, 1341, 1, 0, 0, 0, 353, 1345, 1, 0, 0, 0, 355, 1349, 1, 0, 0, 0, 357, 1353, 1, 0, 0, 0, 359, 1358, 1, 0, 0, 0, 361, 1362, 1, 0, 0, 0, 363, 1366, 1, 0, 0, 0, 365, 1370, 1, 0, 0, 0, 367, 1374, 1, 0, 0, 0, 369, 1378, 1, 0, 0, 0, 371, 1384, 1, 0, 0, 0, 373, 1388, 1, 0, 0, 0, 375, 1392, 1, 0, 0, 0, 377, 1396, 1, 0, 0, 0, 379, 1400, 1, 0, 0, 0, 381, 1404, 1, 0, 0, 0, 383, 1408, 1, 0, 0, 0, 385, 1413, 1, 0, 0, 0, 387, 1419, 1, 0, 0, 0, 389, 1425, 1, 0, 0, 0, 391, 1429, 1, 0, 0, 0, 393, 1433, 1, 0, 0, 0, 395, 1437, 1, 0, 0, 0, 397, 1443, 1, 0, 0, 0, 399, 1449, 1, 0, 0, 0, 401, 1453, 1, 0, 0, 0, 403, 1457, 1, 0, 0, 0, 405, 1461, 1, 0, 0, 0, 407, 1467, 1, 0, 0, 0, 409, 1473, 1, 0, 0, 0, 411, 1479, 1, 0, 0, 0, 413, 414, 7, 0, 0, 0, 414, 415, 7, 1, 0, 0, 415, 416, 7, 2, 0, 0, 416, 417, 7, 2, 0, 0, 417, 418, 7, 3, 0, 0, 418, 419, 7, 4, 0, 0, 419, 420, 7, 5, 0, 0, 420, 421, 1, 0, 0, 0, 421, 422, 6, 0, 0, 0, 422, 16, 1, 0, 0, 0, 423, 424, 7, 0, 0, 0, 424, 425, 7, 6, 0, 0, 425, 426, 7, 7, 0, 0, 426, 427, 7, 8, 0, 0, 427, 428, 1, 0, 0, 0, 428, 429, 6, 1, 1, 0, 429, 18, 1, 0, 0, 0, 430, 431, 7, 3, 0, 0, 431, 432, 7, 9, 0, 0, 432, 433, 7, 6, 0, 0, 433, 434, 7, 1, 0, 0, 434, 435, 7, 4, 0, 0, 435, 436, 7, 10, 0, 0, 436, 437, 1, 0, 0, 0, 437, 438, 6, 2, 2, 0, 438, 20, 1, 0, 0, 0, 439, 440, 7, 3, 0, 0, 440, 441, 7, 11, 0, 0, 441, 442, 7, 12, 0, 0, 442, 443, 7, 13, 0, 0, 443, 444, 1, 0, 0, 0, 444, 445, 6, 3, 0, 0, 445, 22, 1, 0, 0, 0, 446, 447, 7, 3, 0, 0, 447, 448, 7, 14, 0, 0, 448, 449, 7, 8, 0, 0, 449, 450, 7, 13, 0, 0, 450, 451, 7, 12, 0, 0, 451, 452, 7, 1, 0, 0, 452, 453, 7, 9, 0, 0, 453, 454, 1, 0, 0, 0, 454, 455, 6, 4, 3, 0, 455, 24, 1, 0, 0, 0, 456, 457, 7, 15, 0, 0, 457, 458, 7, 6, 0, 0, 458, 459, 7, 7, 0, 0, 459, 460, 7, 16, 0, 0, 460, 461, 1, 0, 0, 0, 461, 462, 6, 5, 4, 0, 462, 26, 1, 0, 0, 0, 463, 464, 7, 17, 0, 0, 464, 465, 7, 6, 0, 0, 465, 466, 7, 7, 0, 0, 466, 467, 7, 18, 0, 0, 467, 468, 1, 0, 0, 0, 468, 469, 6, 6, 0, 0, 469, 28, 1, 0, 0, 0, 470, 471, 7, 18, 0, 0, 471, 472, 7, 3, 0, 0, 472, 473, 7, 3, 0, 0, 473, 474, 7, 8, 0, 0, 474, 475, 1, 0, 0, 0, 475, 476, 6, 7, 1, 0, 476, 30, 1, 0, 0, 0, 477, 478, 7, 13, 0, 0, 478, 479, 7, 1, 0, 0, 479, 480, 7, 16, 0, 0, 480, 481, 7, 1, 0, 0, 481, 482, 7, 5, 0, 0, 482, 483, 1, 0, 0, 0, 483, 484, 6, 8, 0, 0, 484, 32, 1, 0, 0, 0, 485, 486, 7, 16, 0, 0, 486, 487, 7, 11, 0, 0, 487, 488, 5, 95, 0, 0, 488, 489, 7, 3, 0, 0, 489, 490, 7, 14, 0, 0, 490, 491, 7, 8, 0, 0, 491, 492, 7, 12, 0, 0, 492, 493, 7, 9, 0, 0, 493, 494, 7, 0, 0, 0, 494, 495, 1, 0, 0, 0, 495, 496, 6, 9, 5, 0, 496, 34, 1, 0, 0, 0, 497, 498, 7, 6, 0, 0, 498, 499, 7, 3, 0, 0, 499, 500, 7, 9, 0, 0, 500, 501, 7, 12, 0, 0, 501, 502, 7, 16, 0, 0, 502, 503, 7, 3, 0, 0, 503, 504, 1, 0, 0, 0, 504, 505, 6, 10, 6, 0, 505, 36, 1, 0, 0, 0, 506, 507, 7, 6, 0, 0, 507, 508, 7, 7, 0, 0, 508, 509, 7, 19, 0, 0, 509, 510, 1, 0, 0, 0, 510, 511, 6, 11, 0, 0, 511, 38, 1, 0, 0, 0, 512, 513, 7, 2, 0, 0, 513, 514, 7, 10, 0, 0, 514, 515, 7, 7, 0, 0, 515, 516, 7, 19, 0, 0, 516, 517, 1, 0, 0, 0, 517, 518, 6, 12, 7, 0, 518, 40, 1, 0, 0, 0, 519, 520, 7, 2, 0, 0, 520, 521, 7, 7, 0, 0, 521, 522, 7, 6, 0, 0, 522, 523, 7, 5, 0, 0, 523, 524, 1, 0, 0, 0, 524, 525, 6, 13, 0, 0, 525, 42, 1, 0, 0, 0, 526, 527, 7, 2, 0, 0, 527, 528, 7, 5, 0, 0, 528, 529, 7, 12, 0, 0, 529, 530, 7, 5, 0, 0, 530, 531, 7, 2, 0, 0, 531, 532, 1, 0, 0, 0, 532, 533, 6, 14, 0, 0, 533, 44, 1, 0, 0, 0, 534, 535, 7, 19, 0, 0, 535, 536, 7, 10, 0, 0, 536, 537, 7, 3, 0, 0, 537, 538, 7, 6, 0, 0, 538, 539, 7, 3, 0, 0, 539, 540, 1, 0, 0, 0, 540, 541, 6, 15, 0, 0, 541, 46, 1, 0, 0, 0, 542, 543, 4, 16, 0, 0, 543, 544, 7, 1, 0, 0, 544, 545, 7, 9, 0, 0, 545, 546, 7, 13, 0, 0, 546, 547, 7, 1, 0, 0, 547, 548, 7, 9, 0, 0, 548, 549, 7, 3, 0, 0, 549, 550, 7, 2, 0, 0, 550, 551, 7, 5, 0, 0, 551, 552, 7, 12, 0, 0, 552, 553, 7, 5, 0, 0, 553, 554, 7, 2, 0, 0, 554, 555, 1, 0, 0, 0, 555, 556, 6, 16, 0, 0, 556, 48, 1, 0, 0, 0, 557, 558, 4, 17, 1, 0, 558, 559, 7, 13, 0, 0, 559, 560, 7, 7, 0, 0, 560, 561, 7, 7, 0, 0, 561, 562, 7, 18, 0, 0, 562, 563, 7, 20, 0, 0, 563, 564, 7, 8, 0, 0, 564, 565, 1, 0, 0, 0, 565, 566, 6, 17, 8, 0, 566, 50, 1, 0, 0, 0, 567, 568, 4, 18, 2, 0, 568, 569, 7, 16, 0, 0, 569, 570, 7, 3, 0, 0, 570, 571, 7, 5, 0, 0, 571, 572, 7, 6, 0, 0, 572, 573, 7, 1, 0, 0, 573, 574, 7, 4, 0, 0, 574, 575, 7, 2, 0, 0, 575, 576, 1, 0, 0, 0, 576, 577, 6, 18, 9, 0, 577, 52, 1, 0, 0, 0, 578, 580, 8, 21, 0, 0, 579, 578, 1, 0, 0, 0, 580, 581, 1, 0, 0, 0, 581, 579, 1, 0, 0, 0, 581, 582, 1, 0, 0, 0, 582, 583, 1, 0, 0, 0, 583, 584, 6, 19, 0, 0, 584, 54, 1, 0, 0, 0, 585, 586, 5, 47, 0, 0, 586, 587, 5, 47, 0, 0, 587, 591, 1, 0, 0, 0, 588, 590, 8, 22, 0, 0, 589, 588, 1, 0, 0, 0, 590, 593, 1, 0, 0, 0, 591, 589, 1, 0, 0, 0, 591, 592, 1, 0, 0, 0, 592, 595, 1, 0, 0, 0, 593, 591, 1, 0, 0, 0, 594, 596, 5, 13, 0, 0, 595, 594, 1, 0, 0, 0, 595, 596, 1, 0, 0, 0, 596, 598, 1, 0, 0, 0, 597, 599, 5, 10, 0, 0, 598, 597, 1, 0, 0, 0, 598, 599, 1, 0, 0, 0, 599, 600, 1, 0, 0, 0, 600, 601, 6, 20, 10, 0, 601, 56, 1, 0, 0, 0, 602, 603, 5, 47, 0, 0, 603, 604, 5, 42, 0, 0, 604, 609, 1, 0, 0, 0, 605, 608, 3, 57, 21, 0, 606, 608, 9, 0, 0, 0, 607, 605, 1, 0, 0, 0, 607, 606, 1, 0, 0, 0, 608, 611, 1, 0, 0, 0, 609, 610, 1, 0, 0, 0, 609, 607, 1, 0, 0, 0, 610, 612, 1, 0, 0, 0, 611, 609, 1, 0, 0, 0, 612, 613, 5, 42, 0, 0, 613, 614, 5, 47, 0, 0, 614, 615, 1, 0, 0, 0, 615, 616, 6, 21, 10, 0, 616, 58, 1, 0, 0, 0, 617, 619, 7, 23, 0, 0, 618, 617, 1, 0, 0, 0, 619, 620, 1, 0, 0, 0, 620, 618, 1, 0, 0, 0, 620, 621, 1, 0, 0, 0, 621, 622, 1, 0, 0, 0, 622, 623, 6, 22, 10, 0, 623, 60, 1, 0, 0, 0, 624, 625, 5, 58, 0, 0, 625, 62, 1, 0, 0, 0, 626, 627, 5, 124, 0, 0, 627, 628, 1, 0, 0, 0, 628, 629, 6, 24, 11, 0, 629, 64, 1, 0, 0, 0, 630, 631, 7, 24, 0, 0, 631, 66, 1, 0, 0, 0, 632, 633, 7, 25, 0, 0, 633, 68, 1, 0, 0, 0, 634, 635, 5, 92, 0, 0, 635, 636, 7, 26, 0, 0, 636, 70, 1, 0, 0, 0, 637, 638, 8, 27, 0, 0, 638, 72, 1, 0, 0, 0, 639, 641, 7, 3, 0, 0, 640, 642, 7, 28, 0, 0, 641, 640, 1, 0, 0, 0, 641, 642, 1, 0, 0, 0, 642, 644, 1, 0, 0, 0, 643, 645, 3, 65, 25, 0, 644, 643, 1, 0, 0, 0, 645, 646, 1, 0, 0, 0, 646, 644, 1, 0, 0, 0, 646, 647, 1, 0, 0, 0, 647, 74, 1, 0, 0, 0, 648, 649, 5, 64, 0, 0, 649, 76, 1, 0, 0, 0, 650, 651, 5, 96, 0, 0, 651, 78, 1, 0, 0, 0, 652, 656, 8, 29, 0, 0, 653, 654, 5, 96, 0, 0, 654, 656, 5, 96, 0, 0, 655, 652, 1, 0, 0, 0, 655, 653, 1, 0, 0, 0, 656, 80, 1, 0, 0, 0, 657, 658, 5, 95, 0, 0, 658, 82, 1, 0, 0, 0, 659, 663, 3, 67, 26, 0, 660, 663, 3, 65, 25, 0, 661, 663, 3, 81, 33, 0, 662, 659, 1, 0, 0, 0, 662, 660, 1, 0, 0, 0, 662, 661, 1, 0, 0, 0, 663, 84, 1, 0, 0, 0, 664, 669, 5, 34, 0, 0, 665, 668, 3, 69, 27, 0, 666, 668, 3, 71, 28, 0, 667, 665, 1, 0, 0, 0, 667, 666, 1, 0, 0, 0, 668, 671, 1, 0, 0, 0, 669, 667, 1, 0, 0, 0, 669, 670, 1, 0, 0, 0, 670, 672, 1, 0, 0, 0, 671, 669, 1, 0, 0, 0, 672, 694, 5, 34, 0, 0, 673, 674, 5, 34, 0, 0, 674, 675, 5, 34, 0, 0, 675, 676, 5, 34, 0, 0, 676, 680, 1, 0, 0, 0, 677, 679, 8, 22, 0, 0, 678, 677, 1, 0, 0, 0, 679, 682, 1, 0, 0, 0, 680, 681, 1, 0, 0, 0, 680, 678, 1, 0, 0, 0, 681, 683, 1, 0, 0, 0, 682, 680, 1, 0, 0, 0, 683, 684, 5, 34, 0, 0, 684, 685, 5, 34, 0, 0, 685, 686, 5, 34, 0, 0, 686, 688, 1, 0, 0, 0, 687, 689, 5, 34, 0, 0, 688, 687, 1, 0, 0, 0, 688, 689, 1, 0, 0, 0, 689, 691, 1, 0, 0, 0, 690, 692, 5, 34, 0, 0, 691, 690, 1, 0, 0, 0, 691, 692, 1, 0, 0, 0, 692, 694, 1, 0, 0, 0, 693, 664, 1, 0, 0, 0, 693, 673, 1, 0, 0, 0, 694, 86, 1, 0, 0, 0, 695, 697, 3, 65, 25, 0, 696, 695, 1, 0, 0, 0, 697, 698, 1, 0, 0, 0, 698, 696, 1, 0, 0, 0, 698, 699, 1, 0, 0, 0, 699, 88, 1, 0, 0, 0, 700, 702, 3, 65, 25, 0, 701, 700, 1, 0, 0, 0, 702, 703, 1, 0, 0, 0, 703, 701, 1, 0, 0, 0, 703, 704, 1, 0, 0, 0, 704, 705, 1, 0, 0, 0, 705, 709, 3, 105, 45, 0, 706, 708, 3, 65, 25, 0, 707, 706, 1, 0, 0, 0, 708, 711, 1, 0, 0, 0, 709, 707, 1, 0, 0, 0, 709, 710, 1, 0, 0, 0, 710, 743, 1, 0, 0, 0, 711, 709, 1, 0, 0, 0, 712, 714, 3, 105, 45, 0, 713, 715, 3, 65, 25, 0, 714, 713, 1, 0, 0, 0, 715, 716, 1, 0, 0, 0, 716, 714, 1, 0, 0, 0, 716, 717, 1, 0, 0, 0, 717, 743, 1, 0, 0, 0, 718, 720, 3, 65, 25, 0, 719, 718, 1, 0, 0, 0, 720, 721, 1, 0, 0, 0, 721, 719, 1, 0, 0, 0, 721, 722, 1, 0, 0, 0, 722, 730, 1, 0, 0, 0, 723, 727, 3, 105, 45, 0, 724, 726, 3, 65, 25, 0, 725, 724, 1, 0, 0, 0, 726, 729, 1, 0, 0, 0, 727, 725, 1, 0, 0, 0, 727, 728, 1, 0, 0, 0, 728, 731, 1, 0, 0, 0, 729, 727, 1, 0, 0, 0, 730, 723, 1, 0, 0, 0, 730, 731, 1, 0, 0, 0, 731, 732, 1, 0, 0, 0, 732, 733, 3, 73, 29, 0, 733, 743, 1, 0, 0, 0, 734, 736, 3, 105, 45, 0, 735, 737, 3, 65, 25, 0, 736, 735, 1, 0, 0, 0, 737, 738, 1, 0, 0, 0, 738, 736, 1, 0, 0, 0, 738, 739, 1, 0, 0, 0, 739, 740, 1, 0, 0, 0, 740, 741, 3, 73, 29, 0, 741, 743, 1, 0, 0, 0, 742, 701, 1, 0, 0, 0, 742, 712, 1, 0, 0, 0, 742, 719, 1, 0, 0, 0, 742, 734, 1, 0, 0, 0, 743, 90, 1, 0, 0, 0, 744, 745, 7, 30, 0, 0, 745, 746, 7, 31, 0, 0, 746, 92, 1, 0, 0, 0, 747, 748, 7, 12, 0, 0, 748, 749, 7, 9, 0, 0, 749, 750, 7, 0, 0, 0, 750, 94, 1, 0, 0, 0, 751, 752, 7, 12, 0, 0, 752, 753, 7, 2, 0, 0, 753, 754, 7, 4, 0, 0, 754, 96, 1, 0, 0, 0, 755, 756, 5, 61, 0, 0, 756, 98, 1, 0, 0, 0, 757, 758, 5, 58, 0, 0, 758, 759, 5, 58, 0, 0, 759, 100, 1, 0, 0, 0, 760, 761, 5, 44, 0, 0, 761, 102, 1, 0, 0, 0, 762, 763, 7, 0, 0, 0, 763, 764, 7, 3, 0, 0, 764, 765, 7, 2, 0, 0, 765, 766, 7, 4, 0, 0, 766, 104, 1, 0, 0, 0, 767, 768, 5, 46, 0, 0, 768, 106, 1, 0, 0, 0, 769, 770, 7, 15, 0, 0, 770, 771, 7, 12, 0, 0, 771, 772, 7, 13, 0, 0, 772, 773, 7, 2, 0, 0, 773, 774, 7, 3, 0, 0, 774, 108, 1, 0, 0, 0, 775, 776, 7, 15, 0, 0, 776, 777, 7, 1, 0, 0, 777, 778, 7, 6, 0, 0, 778, 779, 7, 2, 0, 0, 779, 780, 7, 5, 0, 0, 780, 110, 1, 0, 0, 0, 781, 782, 7, 1, 0, 0, 782, 783, 7, 9, 0, 0, 783, 112, 1, 0, 0, 0, 784, 785, 7, 1, 0, 0, 785, 786, 7, 2, 0, 0, 786, 114, 1, 0, 0, 0, 787, 788, 7, 13, 0, 0, 788, 789, 7, 12, 0, 0, 789, 790, 7, 2, 0, 0, 790, 791, 7, 5, 0, 0, 791, 116, 1, 0, 0, 0, 792, 793, 7, 13, 0, 0, 793, 794, 7, 1, 0, 0, 794, 795, 7, 18, 0, 0, 795, 796, 7, 3, 0, 0, 796, 118, 1, 0, 0, 0, 797, 798, 5, 40, 0, 0, 798, 120, 1, 0, 0, 0, 799, 800, 7, 9, 0, 0, 800, 801, 7, 7, 0, 0, 801, 802, 7, 5, 0, 0, 802, 122, 1, 0, 0, 0, 803, 804, 7, 9, 0, 0, 804, 805, 7, 20, 0, 0, 805, 806, 7, 13, 0, 0, 806, 807, 7, 13, 0, 0, 807, 124, 1, 0, 0, 0, 808, 809, 7, 9, 0, 0, 809, 810, 7, 20, 0, 0, 810, 811, 7, 13, 0, 0, 811, 812, 7, 13, 0, 0, 812, 813, 7, 2, 0, 0, 813, 126, 1, 0, 0, 0, 814, 815, 7, 7, 0, 0, 815, 816, 7, 6, 0, 0, 816, 128, 1, 0, 0, 0, 817, 818, 5, 63, 0, 0, 818, 130, 1, 0, 0, 0, 819, 820, 7, 6, 0, 0, 820, 821, 7, 13, 0, 0, 821, 822, 7, 1, 0, 0, 822, 823, 7, 18, 0, 0, 823, 824, 7, 3, 0, 0, 824, 132, 1, 0, 0, 0, 825, 826, 5, 41, 0, 0, 826, 134, 1, 0, 0, 0, 827, 828, 7, 5, 0, 0, 828, 829, 7, 6, 0, 0, 829, 830, 7, 20, 0, 0, 830, 831, 7, 3, 0, 0, 831, 136, 1, 0, 0, 0, 832, 833, 5, 61, 0, 0, 833, 834, 5, 61, 0, 0, 834, 138, 1, 0, 0, 0, 835, 836, 5, 61, 0, 0, 836, 837, 5, 126, 0, 0, 837, 140, 1, 0, 0, 0, 838, 839, 5, 33, 0, 0, 839, 840, 5, 61, 0, 0, 840, 142, 1, 0, 0, 0, 841, 842, 5, 60, 0, 0, 842, 144, 1, 0, 0, 0, 843, 844, 5, 60, 0, 0, 844, 845, 5, 61, 0, 0, 845, 146, 1, 0, 0, 0, 846, 847, 5, 62, 0, 0, 847, 148, 1, 0, 0, 0, 848, 849, 5, 62, 0, 0, 849, 850, 5, 61, 0, 0, 850, 150, 1, 0, 0, 0, 851, 852, 5, 43, 0, 0, 852, 152, 1, 0, 0, 0, 853, 854, 5, 45, 0, 0, 854, 154, 1, 0, 0, 0, 855, 856, 5, 42, 0, 0, 856, 156, 1, 0, 0, 0, 857, 858, 5, 47, 0, 0, 858, 158, 1, 0, 0, 0, 859, 860, 5, 37, 0, 0, 860, 160, 1, 0, 0, 0, 861, 862, 4, 73, 3, 0, 862, 863, 3, 61, 23, 0, 863, 864, 1, 0, 0, 0, 864, 865, 6, 73, 12, 0, 865, 162, 1, 0, 0, 0, 866, 867, 3, 45, 15, 0, 867, 868, 1, 0, 0, 0, 868, 869, 6, 74, 13, 0, 869, 164, 1, 0, 0, 0, 870, 873, 3, 129, 57, 0, 871, 874, 3, 67, 26, 0, 872, 874, 3, 81, 33, 0, 873, 871, 1, 0, 0, 0, 873, 872, 1, 0, 0, 0, 874, 878, 1, 0, 0, 0, 875, 877, 3, 83, 34, 0, 876, 875, 1, 0, 0, 0, 877, 880, 1, 0, 0, 0, 878, 876, 1, 0, 0, 0, 878, 879, 1, 0, 0, 0, 879, 888, 1, 0, 0, 0, 880, 878, 1, 0, 0, 0, 881, 883, 3, 129, 57, 0, 882, 884, 3, 65, 25, 0, 883, 882, 1, 0, 0, 0, 884, 885, 1, 0, 0, 0, 885, 883, 1, 0, 0, 0, 885, 886, 1, 0, 0, 0, 886, 888, 1, 0, 0, 0, 887, 870, 1, 0, 0, 0, 887, 881, 1, 0, 0, 0, 888, 166, 1, 0, 0, 0, 889, 890, 5, 91, 0, 0, 890, 891, 1, 0, 0, 0, 891, 892, 6, 76, 0, 0, 892, 893, 6, 76, 0, 0, 893, 168, 1, 0, 0, 0, 894, 895, 5, 93, 0, 0, 895, 896, 1, 0, 0, 0, 896, 897, 6, 77, 11, 0, 897, 898, 6, 77, 11, 0, 898, 170, 1, 0, 0, 0, 899, 903, 3, 67, 26, 0, 900, 902, 3, 83, 34, 0, 901, 900, 1, 0, 0, 0, 902, 905, 1, 0, 0, 0, 903, 901, 1, 0, 0, 0, 903, 904, 1, 0, 0, 0, 904, 916, 1, 0, 0, 0, 905, 903, 1, 0, 0, 0, 906, 909, 3, 81, 33, 0, 907, 909, 3, 75, 30, 0, 908, 906, 1, 0, 0, 0, 908, 907, 1, 0, 0, 0, 909, 911, 1, 0, 0, 0, 910, 912, 3, 83, 34, 0, 911, 910, 1, 0, 0, 0, 912, 913, 1, 0, 0, 0, 913, 911, 1, 0, 0, 0, 913, 914, 1, 0, 0, 0, 914, 916, 1, 0, 0, 0, 915, 899, 1, 0, 0, 0, 915, 908, 1, 0, 0, 0, 916, 172, 1, 0, 0, 0, 917, 919, 3, 77, 31, 0, 918, 920, 3, 79, 32, 0, 919, 918, 1, 0, 0, 0, 920, 921, 1, 0, 0, 0, 921, 919, 1, 0, 0, 0, 921, 922, 1, 0, 0, 0, 922, 923, 1, 0, 0, 0, 923, 924, 3, 77, 31, 0, 924, 174, 1, 0, 0, 0, 925, 926, 3, 173, 79, 0, 926, 176, 1, 0, 0, 0, 927, 928, 3, 55, 20, 0, 928, 929, 1, 0, 0, 0, 929, 930, 6, 81, 10, 0, 930, 178, 1, 0, 0, 0, 931, 932, 3, 57, 21, 0, 932, 933, 1, 0, 0, 0, 933, 934, 6, 82, 10, 0, 934, 180, 1, 0, 0, 0, 935, 936, 3, 59, 22, 0, 936, 937, 1, 0, 0, 0, 937, 938, 6, 83, 10, 0, 938, 182, 1, 0, 0, 0, 939, 940, 3, 167, 76, 0, 940, 941, 1, 0, 0, 0, 941, 942, 6, 84, 14, 0, 942, 943, 6, 84, 15, 0, 943, 184, 1, 0, 0, 0, 944, 945, 3, 63, 24, 0, 945, 946, 1, 0, 0, 0, 946, 947, 6, 85, 16, 0, 947, 948, 6, 85, 11, 0, 948, 186, 1, 0, 0, 0, 949, 950, 3, 59, 22, 0, 950, 951, 1, 0, 0, 0, 951, 952, 6, 86, 10, 0, 952, 188, 1, 0, 0, 0, 953, 954, 3, 55, 20, 0, 954, 955, 1, 0, 0, 0, 955, 956, 6, 87, 10, 0, 956, 190, 1, 0, 0, 0, 957, 958, 3, 57, 21, 0, 958, 959, 1, 0, 0, 0, 959, 960, 6, 88, 10, 0, 960, 192, 1, 0, 0, 0, 961, 962, 3, 63, 24, 0, 962, 963, 1, 0, 0, 0, 963, 964, 6, 89, 16, 0, 964, 965, 6, 89, 11, 0, 965, 194, 1, 0, 0, 0, 966, 967, 3, 167, 76, 0, 967, 968, 1, 0, 0, 0, 968, 969, 6, 90, 14, 0, 969, 196, 1, 0, 0, 0, 970, 971, 3, 169, 77, 0, 971, 972, 1, 0, 0, 0, 972, 973, 6, 91, 17, 0, 973, 198, 1, 0, 0, 0, 974, 975, 3, 61, 23, 0, 975, 976, 1, 0, 0, 0, 976, 977, 6, 92, 12, 0, 977, 200, 1, 0, 0, 0, 978, 979, 3, 101, 43, 0, 979, 980, 1, 0, 0, 0, 980, 981, 6, 93, 18, 0, 981, 202, 1, 0, 0, 0, 982, 983, 3, 97, 41, 0, 983, 984, 1, 0, 0, 0, 984, 985, 6, 94, 19, 0, 985, 204, 1, 0, 0, 0, 986, 987, 7, 16, 0, 0, 987, 988, 7, 3, 0, 0, 988, 989, 7, 5, 0, 0, 989, 990, 7, 12, 0, 0, 990, 991, 7, 0, 0, 0, 991, 992, 7, 12, 0, 0, 992, 993, 7, 5, 0, 0, 993, 994, 7, 12, 0, 0, 994, 206, 1, 0, 0, 0, 995, 999, 8, 32, 0, 0, 996, 997, 5, 47, 0, 0, 997, 999, 8, 33, 0, 0, 998, 995, 1, 0, 0, 0, 998, 996, 1, 0, 0, 0, 999, 208, 1, 0, 0, 0, 1000, 1002, 3, 207, 96, 0, 1001, 1000, 1, 0, 0, 0, 1002, 1003, 1, 0, 0, 0, 1003, 1001, 1, 0, 0, 0, 1003, 1004, 1, 0, 0, 0, 1004, 210, 1, 0, 0, 0, 1005, 1006, 3, 209, 97, 0, 1006, 1007, 1, 0, 0, 0, 1007, 1008, 6, 98, 20, 0, 1008, 212, 1, 0, 0, 0, 1009, 1010, 3, 85, 35, 0, 1010, 1011, 1, 0, 0, 0, 1011, 1012, 6, 99, 21, 0, 1012, 214, 1, 0, 0, 0, 1013, 1014, 3, 55, 20, 0, 1014, 1015, 1, 0, 0, 0, 1015, 1016, 6, 100, 10, 0, 1016, 216, 1, 0, 0, 0, 1017, 1018, 3, 57, 21, 0, 1018, 1019, 1, 0, 0, 0, 1019, 1020, 6, 101, 10, 0, 1020, 218, 1, 0, 0, 0, 1021, 1022, 3, 59, 22, 0, 1022, 1023, 1, 0, 0, 0, 1023, 1024, 6, 102, 10, 0, 1024, 220, 1, 0, 0, 0, 1025, 1026, 3, 63, 24, 0, 1026, 1027, 1, 0, 0, 0, 1027, 1028, 6, 103, 16, 0, 1028, 1029, 6, 103, 11, 0, 1029, 222, 1, 0, 0, 0, 1030, 1031, 3, 105, 45, 0, 1031, 1032, 1, 0, 0, 0, 1032, 1033, 6, 104, 22, 0, 1033, 224, 1, 0, 0, 0, 1034, 1035, 3, 101, 43, 0, 1035, 1036, 1, 0, 0, 0, 1036, 1037, 6, 105, 18, 0, 1037, 226, 1, 0, 0, 0, 1038, 1039, 4, 106, 4, 0, 1039, 1040, 3, 129, 57, 0, 1040, 1041, 1, 0, 0, 0, 1041, 1042, 6, 106, 23, 0, 1042, 228, 1, 0, 0, 0, 1043, 1044, 4, 107, 5, 0, 1044, 1045, 3, 165, 75, 0, 1045, 1046, 1, 0, 0, 0, 1046, 1047, 6, 107, 24, 0, 1047, 230, 1, 0, 0, 0, 1048, 1053, 3, 67, 26, 0, 1049, 1053, 3, 65, 25, 0, 1050, 1053, 3, 81, 33, 0, 1051, 1053, 3, 155, 70, 0, 1052, 1048, 1, 0, 0, 0, 1052, 1049, 1, 0, 0, 0, 1052, 1050, 1, 0, 0, 0, 1052, 1051, 1, 0, 0, 0, 1053, 232, 1, 0, 0, 0, 1054, 1057, 3, 67, 26, 0, 1055, 1057, 3, 155, 70, 0, 1056, 1054, 1, 0, 0, 0, 1056, 1055, 1, 0, 0, 0, 1057, 1061, 1, 0, 0, 0, 1058, 1060, 3, 231, 108, 0, 1059, 1058, 1, 0, 0, 0, 1060, 1063, 1, 0, 0, 0, 1061, 1059, 1, 0, 0, 0, 1061, 1062, 1, 0, 0, 0, 1062, 1074, 1, 0, 0, 0, 1063, 1061, 1, 0, 0, 0, 1064, 1067, 3, 81, 33, 0, 1065, 1067, 3, 75, 30, 0, 1066, 1064, 1, 0, 0, 0, 1066, 1065, 1, 0, 0, 0, 1067, 1069, 1, 0, 0, 0, 1068, 1070, 3, 231, 108, 0, 1069, 1068, 1, 0, 0, 0, 1070, 1071, 1, 0, 0, 0, 1071, 1069, 1, 0, 0, 0, 1071, 1072, 1, 0, 0, 0, 1072, 1074, 1, 0, 0, 0, 1073, 1056, 1, 0, 0, 0, 1073, 1066, 1, 0, 0, 0, 1074, 234, 1, 0, 0, 0, 1075, 1078, 3, 233, 109, 0, 1076, 1078, 3, 173, 79, 0, 1077, 1075, 1, 0, 0, 0, 1077, 1076, 1, 0, 0, 0, 1078, 1079, 1, 0, 0, 0, 1079, 1077, 1, 0, 0, 0, 1079, 1080, 1, 0, 0, 0, 1080, 236, 1, 0, 0, 0, 1081, 1082, 3, 55, 20, 0, 1082, 1083, 1, 0, 0, 0, 1083, 1084, 6, 111, 10, 0, 1084, 238, 1, 0, 0, 0, 1085, 1086, 3, 57, 21, 0, 1086, 1087, 1, 0, 0, 0, 1087, 1088, 6, 112, 10, 0, 1088, 240, 1, 0, 0, 0, 1089, 1090, 3, 59, 22, 0, 1090, 1091, 1, 0, 0, 0, 1091, 1092, 6, 113, 10, 0, 1092, 242, 1, 0, 0, 0, 1093, 1094, 3, 63, 24, 0, 1094, 1095, 1, 0, 0, 0, 1095, 1096, 6, 114, 16, 0, 1096, 1097, 6, 114, 11, 0, 1097, 244, 1, 0, 0, 0, 1098, 1099, 3, 97, 41, 0, 1099, 1100, 1, 0, 0, 0, 1100, 1101, 6, 115, 19, 0, 1101, 246, 1, 0, 0, 0, 1102, 1103, 3, 101, 43, 0, 1103, 1104, 1, 0, 0, 0, 1104, 1105, 6, 116, 18, 0, 1105, 248, 1, 0, 0, 0, 1106, 1107, 3, 105, 45, 0, 1107, 1108, 1, 0, 0, 0, 1108, 1109, 6, 117, 22, 0, 1109, 250, 1, 0, 0, 0, 1110, 1111, 4, 118, 6, 0, 1111, 1112, 3, 129, 57, 0, 1112, 1113, 1, 0, 0, 0, 1113, 1114, 6, 118, 23, 0, 1114, 252, 1, 0, 0, 0, 1115, 1116, 4, 119, 7, 0, 1116, 1117, 3, 165, 75, 0, 1117, 1118, 1, 0, 0, 0, 1118, 1119, 6, 119, 24, 0, 1119, 254, 1, 0, 0, 0, 1120, 1121, 7, 12, 0, 0, 1121, 1122, 7, 2, 0, 0, 1122, 256, 1, 0, 0, 0, 1123, 1124, 3, 235, 110, 0, 1124, 1125, 1, 0, 0, 0, 1125, 1126, 6, 121, 25, 0, 1126, 258, 1, 0, 0, 0, 1127, 1128, 3, 55, 20, 0, 1128, 1129, 1, 0, 0, 0, 1129, 1130, 6, 122, 10, 0, 1130, 260, 1, 0, 0, 0, 1131, 1132, 3, 57, 21, 0, 1132, 1133, 1, 0, 0, 0, 1133, 1134, 6, 123, 10, 0, 1134, 262, 1, 0, 0, 0, 1135, 1136, 3, 59, 22, 0, 1136, 1137, 1, 0, 0, 0, 1137, 1138, 6, 124, 10, 0, 1138, 264, 1, 0, 0, 0, 1139, 1140, 3, 63, 24, 0, 1140, 1141, 1, 0, 0, 0, 1141, 1142, 6, 125, 16, 0, 1142, 1143, 6, 125, 11, 0, 1143, 266, 1, 0, 0, 0, 1144, 1145, 3, 167, 76, 0, 1145, 1146, 1, 0, 0, 0, 1146, 1147, 6, 126, 14, 0, 1147, 1148, 6, 126, 26, 0, 1148, 268, 1, 0, 0, 0, 1149, 1150, 7, 7, 0, 0, 1150, 1151, 7, 9, 0, 0, 1151, 1152, 1, 0, 0, 0, 1152, 1153, 6, 127, 27, 0, 1153, 270, 1, 0, 0, 0, 1154, 1155, 7, 19, 0, 0, 1155, 1156, 7, 1, 0, 0, 1156, 1157, 7, 5, 0, 0, 1157, 1158, 7, 10, 0, 0, 1158, 1159, 1, 0, 0, 0, 1159, 1160, 6, 128, 27, 0, 1160, 272, 1, 0, 0, 0, 1161, 1162, 8, 34, 0, 0, 1162, 274, 1, 0, 0, 0, 1163, 1165, 3, 273, 129, 0, 1164, 1163, 1, 0, 0, 0, 1165, 1166, 1, 0, 0, 0, 1166, 1164, 1, 0, 0, 0, 1166, 1167, 1, 0, 0, 0, 1167, 1168, 1, 0, 0, 0, 1168, 1169, 3, 61, 23, 0, 1169, 1171, 1, 0, 0, 0, 1170, 1164, 1, 0, 0, 0, 1170, 1171, 1, 0, 0, 0, 1171, 1173, 1, 0, 0, 0, 1172, 1174, 3, 273, 129, 0, 1173, 1172, 1, 0, 0, 0, 1174, 1175, 1, 0, 0, 0, 1175, 1173, 1, 0, 0, 0, 1175, 1176, 1, 0, 0, 0, 1176, 276, 1, 0, 0, 0, 1177, 1178, 3, 275, 130, 0, 1178, 1179, 1, 0, 0, 0, 1179, 1180, 6, 131, 28, 0, 1180, 278, 1, 0, 0, 0, 1181, 1182, 3, 55, 20, 0, 1182, 1183, 1, 0, 0, 0, 1183, 1184, 6, 132, 10, 0, 1184, 280, 1, 0, 0, 0, 1185, 1186, 3, 57, 21, 0, 1186, 1187, 1, 0, 0, 0, 1187, 1188, 6, 133, 10, 0, 1188, 282, 1, 0, 0, 0, 1189, 1190, 3, 59, 22, 0, 1190, 1191, 1, 0, 0, 0, 1191, 1192, 6, 134, 10, 0, 1192, 284, 1, 0, 0, 0, 1193, 1194, 3, 63, 24, 0, 1194, 1195, 1, 0, 0, 0, 1195, 1196, 6, 135, 16, 0, 1196, 1197, 6, 135, 11, 0, 1197, 1198, 6, 135, 11, 0, 1198, 286, 1, 0, 0, 0, 1199, 1200, 3, 97, 41, 0, 1200, 1201, 1, 0, 0, 0, 1201, 1202, 6, 136, 19, 0, 1202, 288, 1, 0, 0, 0, 1203, 1204, 3, 101, 43, 0, 1204, 1205, 1, 0, 0, 0, 1205, 1206, 6, 137, 18, 0, 1206, 290, 1, 0, 0, 0, 1207, 1208, 3, 105, 45, 0, 1208, 1209, 1, 0, 0, 0, 1209, 1210, 6, 138, 22, 0, 1210, 292, 1, 0, 0, 0, 1211, 1212, 3, 271, 128, 0, 1212, 1213, 1, 0, 0, 0, 1213, 1214, 6, 139, 29, 0, 1214, 294, 1, 0, 0, 0, 1215, 1216, 3, 235, 110, 0, 1216, 1217, 1, 0, 0, 0, 1217, 1218, 6, 140, 25, 0, 1218, 296, 1, 0, 0, 0, 1219, 1220, 3, 175, 80, 0, 1220, 1221, 1, 0, 0, 0, 1221, 1222, 6, 141, 30, 0, 1222, 298, 1, 0, 0, 0, 1223, 1224, 4, 142, 8, 0, 1224, 1225, 3, 129, 57, 0, 1225, 1226, 1, 0, 0, 0, 1226, 1227, 6, 142, 23, 0, 1227, 300, 1, 0, 0, 0, 1228, 1229, 4, 143, 9, 0, 1229, 1230, 3, 165, 75, 0, 1230, 1231, 1, 0, 0, 0, 1231, 1232, 6, 143, 24, 0, 1232, 302, 1, 0, 0, 0, 1233, 1234, 3, 55, 20, 0, 1234, 1235, 1, 0, 0, 0, 1235, 1236, 6, 144, 10, 0, 1236, 304, 1, 0, 0, 0, 1237, 1238, 3, 57, 21, 0, 1238, 1239, 1, 0, 0, 0, 1239, 1240, 6, 145, 10, 0, 1240, 306, 1, 0, 0, 0, 1241, 1242, 3, 59, 22, 0, 1242, 1243, 1, 0, 0, 0, 1243, 1244, 6, 146, 10, 0, 1244, 308, 1, 0, 0, 0, 1245, 1246, 3, 63, 24, 0, 1246, 1247, 1, 0, 0, 0, 1247, 1248, 6, 147, 16, 0, 1248, 1249, 6, 147, 11, 0, 1249, 310, 1, 0, 0, 0, 1250, 1251, 3, 105, 45, 0, 1251, 1252, 1, 0, 0, 0, 1252, 1253, 6, 148, 22, 0, 1253, 312, 1, 0, 0, 0, 1254, 1255, 4, 149, 10, 0, 1255, 1256, 3, 129, 57, 0, 1256, 1257, 1, 0, 0, 0, 1257, 1258, 6, 149, 23, 0, 1258, 314, 1, 0, 0, 0, 1259, 1260, 4, 150, 11, 0, 1260, 1261, 3, 165, 75, 0, 1261, 1262, 1, 0, 0, 0, 1262, 1263, 6, 150, 24, 0, 1263, 316, 1, 0, 0, 0, 1264, 1265, 3, 175, 80, 0, 1265, 1266, 1, 0, 0, 0, 1266, 1267, 6, 151, 30, 0, 1267, 318, 1, 0, 0, 0, 1268, 1269, 3, 171, 78, 0, 1269, 1270, 1, 0, 0, 0, 1270, 1271, 6, 152, 31, 0, 1271, 320, 1, 0, 0, 0, 1272, 1273, 3, 55, 20, 0, 1273, 1274, 1, 0, 0, 0, 1274, 1275, 6, 153, 10, 0, 1275, 322, 1, 0, 0, 0, 1276, 1277, 3, 57, 21, 0, 1277, 1278, 1, 0, 0, 0, 1278, 1279, 6, 154, 10, 0, 1279, 324, 1, 0, 0, 0, 1280, 1281, 3, 59, 22, 0, 1281, 1282, 1, 0, 0, 0, 1282, 1283, 6, 155, 10, 0, 1283, 326, 1, 0, 0, 0, 1284, 1285, 3, 63, 24, 0, 1285, 1286, 1, 0, 0, 0, 1286, 1287, 6, 156, 16, 0, 1287, 1288, 6, 156, 11, 0, 1288, 328, 1, 0, 0, 0, 1289, 1290, 7, 1, 0, 0, 1290, 1291, 7, 9, 0, 0, 1291, 1292, 7, 15, 0, 0, 1292, 1293, 7, 7, 0, 0, 1293, 330, 1, 0, 0, 0, 1294, 1295, 3, 55, 20, 0, 1295, 1296, 1, 0, 0, 0, 1296, 1297, 6, 158, 10, 0, 1297, 332, 1, 0, 0, 0, 1298, 1299, 3, 57, 21, 0, 1299, 1300, 1, 0, 0, 0, 1300, 1301, 6, 159, 10, 0, 1301, 334, 1, 0, 0, 0, 1302, 1303, 3, 59, 22, 0, 1303, 1304, 1, 0, 0, 0, 1304, 1305, 6, 160, 10, 0, 1305, 336, 1, 0, 0, 0, 1306, 1307, 3, 169, 77, 0, 1307, 1308, 1, 0, 0, 0, 1308, 1309, 6, 161, 17, 0, 1309, 1310, 6, 161, 11, 0, 1310, 338, 1, 0, 0, 0, 1311, 1312, 3, 61, 23, 0, 1312, 1313, 1, 0, 0, 0, 1313, 1314, 6, 162, 12, 0, 1314, 340, 1, 0, 0, 0, 1315, 1321, 3, 75, 30, 0, 1316, 1321, 3, 65, 25, 0, 1317, 1321, 3, 105, 45, 0, 1318, 1321, 3, 67, 26, 0, 1319, 1321, 3, 81, 33, 0, 1320, 1315, 1, 0, 0, 0, 1320, 1316, 1, 0, 0, 0, 1320, 1317, 1, 0, 0, 0, 1320, 1318, 1, 0, 0, 0, 1320, 1319, 1, 0, 0, 0, 1321, 1322, 1, 0, 0, 0, 1322, 1320, 1, 0, 0, 0, 1322, 1323, 1, 0, 0, 0, 1323, 342, 1, 0, 0, 0, 1324, 1325, 3, 55, 20, 0, 1325, 1326, 1, 0, 0, 0, 1326, 1327, 6, 164, 10, 0, 1327, 344, 1, 0, 0, 0, 1328, 1329, 3, 57, 21, 0, 1329, 1330, 1, 0, 0, 0, 1330, 1331, 6, 165, 10, 0, 1331, 346, 1, 0, 0, 0, 1332, 1333, 3, 59, 22, 0, 1333, 1334, 1, 0, 0, 0, 1334, 1335, 6, 166, 10, 0, 1335, 348, 1, 0, 0, 0, 1336, 1337, 3, 63, 24, 0, 1337, 1338, 1, 0, 0, 0, 1338, 1339, 6, 167, 16, 0, 1339, 1340, 6, 167, 11, 0, 1340, 350, 1, 0, 0, 0, 1341, 1342, 3, 61, 23, 0, 1342, 1343, 1, 0, 0, 0, 1343, 1344, 6, 168, 12, 0, 1344, 352, 1, 0, 0, 0, 1345, 1346, 3, 101, 43, 0, 1346, 1347, 1, 0, 0, 0, 1347, 1348, 6, 169, 18, 0, 1348, 354, 1, 0, 0, 0, 1349, 1350, 3, 105, 45, 0, 1350, 1351, 1, 0, 0, 0, 1351, 1352, 6, 170, 22, 0, 1352, 356, 1, 0, 0, 0, 1353, 1354, 3, 269, 127, 0, 1354, 1355, 1, 0, 0, 0, 1355, 1356, 6, 171, 32, 0, 1356, 1357, 6, 171, 33, 0, 1357, 358, 1, 0, 0, 0, 1358, 1359, 3, 209, 97, 0, 1359, 1360, 1, 0, 0, 0, 1360, 1361, 6, 172, 20, 0, 1361, 360, 1, 0, 0, 0, 1362, 1363, 3, 85, 35, 0, 1363, 1364, 1, 0, 0, 0, 1364, 1365, 6, 173, 21, 0, 1365, 362, 1, 0, 0, 0, 1366, 1367, 3, 55, 20, 0, 1367, 1368, 1, 0, 0, 0, 1368, 1369, 6, 174, 10, 0, 1369, 364, 1, 0, 0, 0, 1370, 1371, 3, 57, 21, 0, 1371, 1372, 1, 0, 0, 0, 1372, 1373, 6, 175, 10, 0, 1373, 366, 1, 0, 0, 0, 1374, 1375, 3, 59, 22, 0, 1375, 1376, 1, 0, 0, 0, 1376, 1377, 6, 176, 10, 0, 1377, 368, 1, 0, 0, 0, 1378, 1379, 3, 63, 24, 0, 1379, 1380, 1, 0, 0, 0, 1380, 1381, 6, 177, 16, 0, 1381, 1382, 6, 177, 11, 0, 1382, 1383, 6, 177, 11, 0, 1383, 370, 1, 0, 0, 0, 1384, 1385, 3, 101, 43, 0, 1385, 1386, 1, 0, 0, 0, 1386, 1387, 6, 178, 18, 0, 1387, 372, 1, 0, 0, 0, 1388, 1389, 3, 105, 45, 0, 1389, 1390, 1, 0, 0, 0, 1390, 1391, 6, 179, 22, 0, 1391, 374, 1, 0, 0, 0, 1392, 1393, 3, 235, 110, 0, 1393, 1394, 1, 0, 0, 0, 1394, 1395, 6, 180, 25, 0, 1395, 376, 1, 0, 0, 0, 1396, 1397, 3, 55, 20, 0, 1397, 1398, 1, 0, 0, 0, 1398, 1399, 6, 181, 10, 0, 1399, 378, 1, 0, 0, 0, 1400, 1401, 3, 57, 21, 0, 1401, 1402, 1, 0, 0, 0, 1402, 1403, 6, 182, 10, 0, 1403, 380, 1, 0, 0, 0, 1404, 1405, 3, 59, 22, 0, 1405, 1406, 1, 0, 0, 0, 1406, 1407, 6, 183, 10, 0, 1407, 382, 1, 0, 0, 0, 1408, 1409, 3, 63, 24, 0, 1409, 1410, 1, 0, 0, 0, 1410, 1411, 6, 184, 16, 0, 1411, 1412, 6, 184, 11, 0, 1412, 384, 1, 0, 0, 0, 1413, 1414, 3, 209, 97, 0, 1414, 1415, 1, 0, 0, 0, 1415, 1416, 6, 185, 20, 0, 1416, 1417, 6, 185, 11, 0, 1417, 1418, 6, 185, 34, 0, 1418, 386, 1, 0, 0, 0, 1419, 1420, 3, 85, 35, 0, 1420, 1421, 1, 0, 0, 0, 1421, 1422, 6, 186, 21, 0, 1422, 1423, 6, 186, 11, 0, 1423, 1424, 6, 186, 34, 0, 1424, 388, 1, 0, 0, 0, 1425, 1426, 3, 55, 20, 0, 1426, 1427, 1, 0, 0, 0, 1427, 1428, 6, 187, 10, 0, 1428, 390, 1, 0, 0, 0, 1429, 1430, 3, 57, 21, 0, 1430, 1431, 1, 0, 0, 0, 1431, 1432, 6, 188, 10, 0, 1432, 392, 1, 0, 0, 0, 1433, 1434, 3, 59, 22, 0, 1434, 1435, 1, 0, 0, 0, 1435, 1436, 6, 189, 10, 0, 1436, 394, 1, 0, 0, 0, 1437, 1438, 3, 61, 23, 0, 1438, 1439, 1, 0, 0, 0, 1439, 1440, 6, 190, 12, 0, 1440, 1441, 6, 190, 11, 0, 1441, 1442, 6, 190, 9, 0, 1442, 396, 1, 0, 0, 0, 1443, 1444, 3, 101, 43, 0, 1444, 1445, 1, 0, 0, 0, 1445, 1446, 6, 191, 18, 0, 1446, 1447, 6, 191, 11, 0, 1447, 1448, 6, 191, 9, 0, 1448, 398, 1, 0, 0, 0, 1449, 1450, 3, 55, 20, 0, 1450, 1451, 1, 0, 0, 0, 1451, 1452, 6, 192, 10, 0, 1452, 400, 1, 0, 0, 0, 1453, 1454, 3, 57, 21, 0, 1454, 1455, 1, 0, 0, 0, 1455, 1456, 6, 193, 10, 0, 1456, 402, 1, 0, 0, 0, 1457, 1458, 3, 59, 22, 0, 1458, 1459, 1, 0, 0, 0, 1459, 1460, 6, 194, 10, 0, 1460, 404, 1, 0, 0, 0, 1461, 1462, 3, 175, 80, 0, 1462, 1463, 1, 0, 0, 0, 1463, 1464, 6, 195, 11, 0, 1464, 1465, 6, 195, 0, 0, 1465, 1466, 6, 195, 30, 0, 1466, 406, 1, 0, 0, 0, 1467, 1468, 3, 171, 78, 0, 1468, 1469, 1, 0, 0, 0, 1469, 1470, 6, 196, 11, 0, 1470, 1471, 6, 196, 0, 0, 1471, 1472, 6, 196, 31, 0, 1472, 408, 1, 0, 0, 0, 1473, 1474, 3, 91, 38, 0, 1474, 1475, 1, 0, 0, 0, 1475, 1476, 6, 197, 11, 0, 1476, 1477, 6, 197, 0, 0, 1477, 1478, 6, 197, 35, 0, 1478, 410, 1, 0, 0, 0, 1479, 1480, 3, 63, 24, 0, 1480, 1481, 1, 0, 0, 0, 1481, 1482, 6, 198, 16, 0, 1482, 1483, 6, 198, 11, 0, 1483, 412, 1, 0, 0, 0, 65, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 581, 591, 595, 598, 607, 609, 620, 641, 646, 655, 662, 667, 669, 680, 688, 691, 693, 698, 703, 709, 716, 721, 727, 730, 738, 742, 873, 878, 885, 887, 903, 908, 913, 915, 921, 998, 1003, 1052, 1056, 1061, 1066, 1071, 1073, 1077, 1079, 1166, 1170, 1175, 1320, 1322, 36, 5, 1, 0, 5, 4, 0, 5, 6, 0, 5, 2, 0, 5, 3, 0, 5, 8, 0, 5, 5, 0, 5, 9, 0, 5, 11, 0, 5, 13, 0, 0, 1, 0, 4, 0, 0, 7, 24, 0, 7, 16, 0, 7, 65, 0, 5, 0, 0, 7, 25, 0, 7, 66, 0, 7, 34, 0, 7, 32, 0, 7, 76, 0, 7, 26, 0, 7, 36, 0, 7, 48, 0, 7, 64, 0, 7, 80, 0, 5, 10, 0, 5, 7, 0, 7, 90, 0, 7, 89, 0, 7, 68, 0, 7, 67, 0, 7, 88, 0, 5, 12, 0, 5, 14, 0, 7, 29, 0] \ No newline at end of file diff --git a/packages/kbn-esql-ast/src/antlr/esql_lexer.tokens b/packages/kbn-esql-ast/src/antlr/esql_lexer.tokens index 4d1f426289149..3dd1a2c754038 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_lexer.tokens +++ b/packages/kbn-esql-ast/src/antlr/esql_lexer.tokens @@ -21,46 +21,46 @@ UNKNOWN_CMD=20 LINE_COMMENT=21 MULTILINE_COMMENT=22 WS=23 -PIPE=24 -QUOTED_STRING=25 -INTEGER_LITERAL=26 -DECIMAL_LITERAL=27 -BY=28 -AND=29 -ASC=30 -ASSIGN=31 -CAST_OP=32 -COMMA=33 -DESC=34 -DOT=35 -FALSE=36 -FIRST=37 -IN=38 -IS=39 -LAST=40 -LIKE=41 -LP=42 -NOT=43 -NULL=44 -NULLS=45 -OR=46 -PARAM=47 -RLIKE=48 -RP=49 -TRUE=50 -EQ=51 -CIEQ=52 -NEQ=53 -LT=54 -LTE=55 -GT=56 -GTE=57 -PLUS=58 -MINUS=59 -ASTERISK=60 -SLASH=61 -PERCENT=62 -MATCH=63 +COLON=24 +PIPE=25 +QUOTED_STRING=26 +INTEGER_LITERAL=27 +DECIMAL_LITERAL=28 +BY=29 +AND=30 +ASC=31 +ASSIGN=32 +CAST_OP=33 +COMMA=34 +DESC=35 +DOT=36 +FALSE=37 +FIRST=38 +IN=39 +IS=40 +LAST=41 +LIKE=42 +LP=43 +NOT=44 +NULL=45 +NULLS=46 +OR=47 +PARAM=48 +RLIKE=49 +RP=50 +TRUE=51 +EQ=52 +CIEQ=53 +NEQ=54 +LT=55 +LTE=56 +GT=57 +GTE=58 +PLUS=59 +MINUS=60 +ASTERISK=61 +SLASH=62 +PERCENT=63 NAMED_OR_POSITIONAL_PARAM=64 OPENING_BRACKET=65 CLOSING_BRACKET=66 @@ -101,23 +101,22 @@ INFO=100 SHOW_LINE_COMMENT=101 SHOW_MULTILINE_COMMENT=102 SHOW_WS=103 -COLON=104 -SETTING=105 -SETTING_LINE_COMMENT=106 -SETTTING_MULTILINE_COMMENT=107 -SETTING_WS=108 -LOOKUP_LINE_COMMENT=109 -LOOKUP_MULTILINE_COMMENT=110 -LOOKUP_WS=111 -LOOKUP_FIELD_LINE_COMMENT=112 -LOOKUP_FIELD_MULTILINE_COMMENT=113 -LOOKUP_FIELD_WS=114 -METRICS_LINE_COMMENT=115 -METRICS_MULTILINE_COMMENT=116 -METRICS_WS=117 -CLOSING_METRICS_LINE_COMMENT=118 -CLOSING_METRICS_MULTILINE_COMMENT=119 -CLOSING_METRICS_WS=120 +SETTING=104 +SETTING_LINE_COMMENT=105 +SETTTING_MULTILINE_COMMENT=106 +SETTING_WS=107 +LOOKUP_LINE_COMMENT=108 +LOOKUP_MULTILINE_COMMENT=109 +LOOKUP_WS=110 +LOOKUP_FIELD_LINE_COMMENT=111 +LOOKUP_FIELD_MULTILINE_COMMENT=112 +LOOKUP_FIELD_WS=113 +METRICS_LINE_COMMENT=114 +METRICS_MULTILINE_COMMENT=115 +METRICS_WS=116 +CLOSING_METRICS_LINE_COMMENT=117 +CLOSING_METRICS_MULTILINE_COMMENT=118 +CLOSING_METRICS_WS=119 'dissect'=1 'drop'=2 'enrich'=3 @@ -134,47 +133,46 @@ CLOSING_METRICS_WS=120 'sort'=14 'stats'=15 'where'=16 -'|'=24 -'by'=28 -'and'=29 -'asc'=30 -'='=31 -'::'=32 -','=33 -'desc'=34 -'.'=35 -'false'=36 -'first'=37 -'in'=38 -'is'=39 -'last'=40 -'like'=41 -'('=42 -'not'=43 -'null'=44 -'nulls'=45 -'or'=46 -'?'=47 -'rlike'=48 -')'=49 -'true'=50 -'=='=51 -'=~'=52 -'!='=53 -'<'=54 -'<='=55 -'>'=56 -'>='=57 -'+'=58 -'-'=59 -'*'=60 -'/'=61 -'%'=62 -'match'=63 +':'=24 +'|'=25 +'by'=29 +'and'=30 +'asc'=31 +'='=32 +'::'=33 +','=34 +'desc'=35 +'.'=36 +'false'=37 +'first'=38 +'in'=39 +'is'=40 +'last'=41 +'like'=42 +'('=43 +'not'=44 +'null'=45 +'nulls'=46 +'or'=47 +'?'=48 +'rlike'=49 +')'=50 +'true'=51 +'=='=52 +'=~'=53 +'!='=54 +'<'=55 +'<='=56 +'>'=57 +'>='=58 +'+'=59 +'-'=60 +'*'=61 +'/'=62 +'%'=63 ']'=66 'metadata'=75 'as'=84 'on'=88 'with'=89 'info'=100 -':'=104 diff --git a/packages/kbn-esql-ast/src/antlr/esql_lexer.ts b/packages/kbn-esql-ast/src/antlr/esql_lexer.ts index 589148bf08c7c..54546fef85904 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_lexer.ts +++ b/packages/kbn-esql-ast/src/antlr/esql_lexer.ts @@ -46,46 +46,46 @@ export default class esql_lexer extends lexer_config { public static readonly LINE_COMMENT = 21; public static readonly MULTILINE_COMMENT = 22; public static readonly WS = 23; - public static readonly PIPE = 24; - public static readonly QUOTED_STRING = 25; - public static readonly INTEGER_LITERAL = 26; - public static readonly DECIMAL_LITERAL = 27; - public static readonly BY = 28; - public static readonly AND = 29; - public static readonly ASC = 30; - public static readonly ASSIGN = 31; - public static readonly CAST_OP = 32; - public static readonly COMMA = 33; - public static readonly DESC = 34; - public static readonly DOT = 35; - public static readonly FALSE = 36; - public static readonly FIRST = 37; - public static readonly IN = 38; - public static readonly IS = 39; - public static readonly LAST = 40; - public static readonly LIKE = 41; - public static readonly LP = 42; - public static readonly NOT = 43; - public static readonly NULL = 44; - public static readonly NULLS = 45; - public static readonly OR = 46; - public static readonly PARAM = 47; - public static readonly RLIKE = 48; - public static readonly RP = 49; - public static readonly TRUE = 50; - public static readonly EQ = 51; - public static readonly CIEQ = 52; - public static readonly NEQ = 53; - public static readonly LT = 54; - public static readonly LTE = 55; - public static readonly GT = 56; - public static readonly GTE = 57; - public static readonly PLUS = 58; - public static readonly MINUS = 59; - public static readonly ASTERISK = 60; - public static readonly SLASH = 61; - public static readonly PERCENT = 62; - public static readonly MATCH = 63; + public static readonly COLON = 24; + public static readonly PIPE = 25; + public static readonly QUOTED_STRING = 26; + public static readonly INTEGER_LITERAL = 27; + public static readonly DECIMAL_LITERAL = 28; + public static readonly BY = 29; + public static readonly AND = 30; + public static readonly ASC = 31; + public static readonly ASSIGN = 32; + public static readonly CAST_OP = 33; + public static readonly COMMA = 34; + public static readonly DESC = 35; + public static readonly DOT = 36; + public static readonly FALSE = 37; + public static readonly FIRST = 38; + public static readonly IN = 39; + public static readonly IS = 40; + public static readonly LAST = 41; + public static readonly LIKE = 42; + public static readonly LP = 43; + public static readonly NOT = 44; + public static readonly NULL = 45; + public static readonly NULLS = 46; + public static readonly OR = 47; + public static readonly PARAM = 48; + public static readonly RLIKE = 49; + public static readonly RP = 50; + public static readonly TRUE = 51; + public static readonly EQ = 52; + public static readonly CIEQ = 53; + public static readonly NEQ = 54; + public static readonly LT = 55; + public static readonly LTE = 56; + public static readonly GT = 57; + public static readonly GTE = 58; + public static readonly PLUS = 59; + public static readonly MINUS = 60; + public static readonly ASTERISK = 61; + public static readonly SLASH = 62; + public static readonly PERCENT = 63; public static readonly NAMED_OR_POSITIONAL_PARAM = 64; public static readonly OPENING_BRACKET = 65; public static readonly CLOSING_BRACKET = 66; @@ -126,23 +126,22 @@ export default class esql_lexer extends lexer_config { public static readonly SHOW_LINE_COMMENT = 101; public static readonly SHOW_MULTILINE_COMMENT = 102; public static readonly SHOW_WS = 103; - public static readonly COLON = 104; - public static readonly SETTING = 105; - public static readonly SETTING_LINE_COMMENT = 106; - public static readonly SETTTING_MULTILINE_COMMENT = 107; - public static readonly SETTING_WS = 108; - public static readonly LOOKUP_LINE_COMMENT = 109; - public static readonly LOOKUP_MULTILINE_COMMENT = 110; - public static readonly LOOKUP_WS = 111; - public static readonly LOOKUP_FIELD_LINE_COMMENT = 112; - public static readonly LOOKUP_FIELD_MULTILINE_COMMENT = 113; - public static readonly LOOKUP_FIELD_WS = 114; - public static readonly METRICS_LINE_COMMENT = 115; - public static readonly METRICS_MULTILINE_COMMENT = 116; - public static readonly METRICS_WS = 117; - public static readonly CLOSING_METRICS_LINE_COMMENT = 118; - public static readonly CLOSING_METRICS_MULTILINE_COMMENT = 119; - public static readonly CLOSING_METRICS_WS = 120; + public static readonly SETTING = 104; + public static readonly SETTING_LINE_COMMENT = 105; + public static readonly SETTTING_MULTILINE_COMMENT = 106; + public static readonly SETTING_WS = 107; + public static readonly LOOKUP_LINE_COMMENT = 108; + public static readonly LOOKUP_MULTILINE_COMMENT = 109; + public static readonly LOOKUP_WS = 110; + public static readonly LOOKUP_FIELD_LINE_COMMENT = 111; + public static readonly LOOKUP_FIELD_MULTILINE_COMMENT = 112; + public static readonly LOOKUP_FIELD_WS = 113; + public static readonly METRICS_LINE_COMMENT = 114; + public static readonly METRICS_MULTILINE_COMMENT = 115; + public static readonly METRICS_WS = 116; + public static readonly CLOSING_METRICS_LINE_COMMENT = 117; + public static readonly CLOSING_METRICS_MULTILINE_COMMENT = 118; + public static readonly CLOSING_METRICS_WS = 119; public static readonly EOF = Token.EOF; public static readonly EXPRESSION_MODE = 1; public static readonly EXPLAIN_MODE = 2; @@ -173,26 +172,26 @@ export default class esql_lexer extends lexer_config { null, null, null, null, null, null, - "'|'", null, + "':'", "'|'", null, null, - "'by'", "'and'", - "'asc'", "'='", - "'::'", "','", - "'desc'", "'.'", - "'false'", "'first'", - "'in'", "'is'", - "'last'", "'like'", - "'('", "'not'", - "'null'", "'nulls'", - "'or'", "'?'", - "'rlike'", "')'", - "'true'", "'=='", - "'=~'", "'!='", - "'<'", "'<='", - "'>'", "'>='", - "'+'", "'-'", - "'*'", "'/'", - "'%'", "'match'", + null, "'by'", + "'and'", "'asc'", + "'='", "'::'", + "','", "'desc'", + "'.'", "'false'", + "'first'", "'in'", + "'is'", "'last'", + "'like'", "'('", + "'not'", "'null'", + "'nulls'", "'or'", + "'?'", "'rlike'", + "')'", "'true'", + "'=='", "'=~'", + "'!='", "'<'", + "'<='", "'>'", + "'>='", "'+'", + "'-'", "'*'", + "'/'", "'%'", null, null, "']'", null, null, null, @@ -211,9 +210,7 @@ export default class esql_lexer extends lexer_config { null, null, null, null, null, null, - "'info'", null, - null, null, - "':'" ]; + "'info'" ]; public static readonly symbolicNames: (string | null)[] = [ null, "DISSECT", "DROP", "ENRICH", "EVAL", "EXPLAIN", @@ -229,8 +226,8 @@ export default class esql_lexer extends lexer_config { "UNKNOWN_CMD", "LINE_COMMENT", "MULTILINE_COMMENT", - "WS", "PIPE", - "QUOTED_STRING", + "WS", "COLON", + "PIPE", "QUOTED_STRING", "INTEGER_LITERAL", "DECIMAL_LITERAL", "BY", "AND", @@ -251,7 +248,7 @@ export default class esql_lexer extends lexer_config { "GTE", "PLUS", "MINUS", "ASTERISK", "SLASH", "PERCENT", - "MATCH", "NAMED_OR_POSITIONAL_PARAM", + "NAMED_OR_POSITIONAL_PARAM", "OPENING_BRACKET", "CLOSING_BRACKET", "UNQUOTED_IDENTIFIER", @@ -288,7 +285,7 @@ export default class esql_lexer extends lexer_config { "INFO", "SHOW_LINE_COMMENT", "SHOW_MULTILINE_COMMENT", "SHOW_WS", - "COLON", "SETTING", + "SETTING", "SETTING_LINE_COMMENT", "SETTTING_MULTILINE_COMMENT", "SETTING_WS", @@ -317,13 +314,13 @@ export default class esql_lexer extends lexer_config { "DISSECT", "DROP", "ENRICH", "EVAL", "EXPLAIN", "FROM", "GROK", "KEEP", "LIMIT", "MV_EXPAND", "RENAME", "ROW", "SHOW", "SORT", "STATS", "WHERE", "DEV_INLINESTATS", "DEV_LOOKUP", "DEV_METRICS", "UNKNOWN_CMD", "LINE_COMMENT", - "MULTILINE_COMMENT", "WS", "PIPE", "DIGIT", "LETTER", "ESCAPE_SEQUENCE", + "MULTILINE_COMMENT", "WS", "COLON", "PIPE", "DIGIT", "LETTER", "ESCAPE_SEQUENCE", "UNESCAPED_CHARS", "EXPONENT", "ASPERAND", "BACKQUOTE", "BACKQUOTE_BLOCK", "UNDERSCORE", "UNQUOTED_ID_BODY", "QUOTED_STRING", "INTEGER_LITERAL", "DECIMAL_LITERAL", "BY", "AND", "ASC", "ASSIGN", "CAST_OP", "COMMA", "DESC", "DOT", "FALSE", "FIRST", "IN", "IS", "LAST", "LIKE", "LP", "NOT", "NULL", "NULLS", "OR", "PARAM", "RLIKE", "RP", "TRUE", "EQ", "CIEQ", "NEQ", "LT", - "LTE", "GT", "GTE", "PLUS", "MINUS", "ASTERISK", "SLASH", "PERCENT", "MATCH", + "LTE", "GT", "GTE", "PLUS", "MINUS", "ASTERISK", "SLASH", "PERCENT", "EXPRESSION_COLON", "NESTED_WHERE", "NAMED_OR_POSITIONAL_PARAM", "OPENING_BRACKET", "CLOSING_BRACKET", "UNQUOTED_IDENTIFIER", "QUOTED_ID", "QUOTED_IDENTIFIER", "EXPR_LINE_COMMENT", "EXPR_MULTILINE_COMMENT", "EXPR_WS", "EXPLAIN_OPENING_BRACKET", "EXPLAIN_PIPE", @@ -346,7 +343,7 @@ export default class esql_lexer extends lexer_config { "MVEXPAND_DOT", "MVEXPAND_PARAM", "MVEXPAND_NAMED_OR_POSITIONAL_PARAM", "MVEXPAND_QUOTED_IDENTIFIER", "MVEXPAND_UNQUOTED_IDENTIFIER", "MVEXPAND_LINE_COMMENT", "MVEXPAND_MULTILINE_COMMENT", "MVEXPAND_WS", "SHOW_PIPE", "INFO", "SHOW_LINE_COMMENT", - "SHOW_MULTILINE_COMMENT", "SHOW_WS", "SETTING_CLOSING_BRACKET", "COLON", + "SHOW_MULTILINE_COMMENT", "SHOW_WS", "SETTING_CLOSING_BRACKET", "SETTING_COLON", "SETTING", "SETTING_LINE_COMMENT", "SETTTING_MULTILINE_COMMENT", "SETTING_WS", "LOOKUP_PIPE", "LOOKUP_COLON", "LOOKUP_COMMA", "LOOKUP_DOT", "LOOKUP_ON", "LOOKUP_UNQUOTED_SOURCE", "LOOKUP_QUOTED_SOURCE", "LOOKUP_LINE_COMMENT", @@ -386,21 +383,23 @@ export default class esql_lexer extends lexer_config { return this.DEV_LOOKUP_sempred(localctx, predIndex); case 18: return this.DEV_METRICS_sempred(localctx, predIndex); - case 105: - return this.PROJECT_PARAM_sempred(localctx, predIndex); + case 73: + return this.EXPRESSION_COLON_sempred(localctx, predIndex); case 106: + return this.PROJECT_PARAM_sempred(localctx, predIndex); + case 107: return this.PROJECT_NAMED_OR_POSITIONAL_PARAM_sempred(localctx, predIndex); - case 117: - return this.RENAME_PARAM_sempred(localctx, predIndex); case 118: + return this.RENAME_PARAM_sempred(localctx, predIndex); + case 119: return this.RENAME_NAMED_OR_POSITIONAL_PARAM_sempred(localctx, predIndex); - case 141: - return this.ENRICH_FIELD_PARAM_sempred(localctx, predIndex); case 142: + return this.ENRICH_FIELD_PARAM_sempred(localctx, predIndex); + case 143: return this.ENRICH_FIELD_NAMED_OR_POSITIONAL_PARAM_sempred(localctx, predIndex); - case 148: - return this.MVEXPAND_PARAM_sempred(localctx, predIndex); case 149: + return this.MVEXPAND_PARAM_sempred(localctx, predIndex); + case 150: return this.MVEXPAND_NAMED_OR_POSITIONAL_PARAM_sempred(localctx, predIndex); } return true; @@ -426,64 +425,71 @@ export default class esql_lexer extends lexer_config { } return true; } - private PROJECT_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { + private EXPRESSION_COLON_sempred(localctx: RuleContext, predIndex: number): boolean { switch (predIndex) { case 3: return this.isDevVersion(); } return true; } - private PROJECT_NAMED_OR_POSITIONAL_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { + private PROJECT_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { switch (predIndex) { case 4: return this.isDevVersion(); } return true; } - private RENAME_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { + private PROJECT_NAMED_OR_POSITIONAL_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { switch (predIndex) { case 5: return this.isDevVersion(); } return true; } - private RENAME_NAMED_OR_POSITIONAL_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { + private RENAME_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { switch (predIndex) { case 6: return this.isDevVersion(); } return true; } - private ENRICH_FIELD_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { + private RENAME_NAMED_OR_POSITIONAL_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { switch (predIndex) { case 7: return this.isDevVersion(); } return true; } - private ENRICH_FIELD_NAMED_OR_POSITIONAL_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { + private ENRICH_FIELD_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { switch (predIndex) { case 8: return this.isDevVersion(); } return true; } - private MVEXPAND_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { + private ENRICH_FIELD_NAMED_OR_POSITIONAL_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { switch (predIndex) { case 9: return this.isDevVersion(); } return true; } - private MVEXPAND_NAMED_OR_POSITIONAL_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { + private MVEXPAND_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { switch (predIndex) { case 10: return this.isDevVersion(); } return true; } + private MVEXPAND_NAMED_OR_POSITIONAL_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { + switch (predIndex) { + case 11: + return this.isDevVersion(); + } + return true; + } - public static readonly _serializedATN: number[] = [4,0,120,1479,6,-1,6, + public static readonly _serializedATN: number[] = [4,0,119,1484,6,-1,6, -1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,2,0, 7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6,2,7,7,7,2,8,7,8,2,9, 7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14,7,14,2,15,7,15,2,16,7, @@ -514,483 +520,485 @@ export default class esql_lexer extends lexer_config { 2,175,7,175,2,176,7,176,2,177,7,177,2,178,7,178,2,179,7,179,2,180,7,180, 2,181,7,181,2,182,7,182,2,183,7,183,2,184,7,184,2,185,7,185,2,186,7,186, 2,187,7,187,2,188,7,188,2,189,7,189,2,190,7,190,2,191,7,191,2,192,7,192, - 2,193,7,193,2,194,7,194,2,195,7,195,2,196,7,196,2,197,7,197,1,0,1,0,1,0, - 1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,2,1,2,1,2, - 1,2,1,2,1,2,1,2,1,2,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,4,1,4,1,4,1,4,1,4,1,4, - 1,4,1,4,1,4,1,4,1,5,1,5,1,5,1,5,1,5,1,5,1,5,1,6,1,6,1,6,1,6,1,6,1,6,1,6, - 1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,9,1,9,1,9, - 1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,10,1,10,1,10,1,10,1,10,1,10,1,10, - 1,10,1,10,1,11,1,11,1,11,1,11,1,11,1,11,1,12,1,12,1,12,1,12,1,12,1,12,1, - 12,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,14,1,14,1,14,1,14,1,14,1,14,1,14, - 1,14,1,15,1,15,1,15,1,15,1,15,1,15,1,15,1,15,1,16,1,16,1,16,1,16,1,16,1, - 16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,17,1,17,1,17,1,17,1,17, - 1,17,1,17,1,17,1,17,1,17,1,18,1,18,1,18,1,18,1,18,1,18,1,18,1,18,1,18,1, - 18,1,18,1,19,4,19,578,8,19,11,19,12,19,579,1,19,1,19,1,20,1,20,1,20,1,20, - 5,20,588,8,20,10,20,12,20,591,9,20,1,20,3,20,594,8,20,1,20,3,20,597,8,20, - 1,20,1,20,1,21,1,21,1,21,1,21,1,21,5,21,606,8,21,10,21,12,21,609,9,21,1, - 21,1,21,1,21,1,21,1,21,1,22,4,22,617,8,22,11,22,12,22,618,1,22,1,22,1,23, - 1,23,1,23,1,23,1,24,1,24,1,25,1,25,1,26,1,26,1,26,1,27,1,27,1,28,1,28,3, - 28,638,8,28,1,28,4,28,641,8,28,11,28,12,28,642,1,29,1,29,1,30,1,30,1,31, - 1,31,1,31,3,31,652,8,31,1,32,1,32,1,33,1,33,1,33,3,33,659,8,33,1,34,1,34, - 1,34,5,34,664,8,34,10,34,12,34,667,9,34,1,34,1,34,1,34,1,34,1,34,1,34,5, - 34,675,8,34,10,34,12,34,678,9,34,1,34,1,34,1,34,1,34,1,34,3,34,685,8,34, - 1,34,3,34,688,8,34,3,34,690,8,34,1,35,4,35,693,8,35,11,35,12,35,694,1,36, - 4,36,698,8,36,11,36,12,36,699,1,36,1,36,5,36,704,8,36,10,36,12,36,707,9, - 36,1,36,1,36,4,36,711,8,36,11,36,12,36,712,1,36,4,36,716,8,36,11,36,12, - 36,717,1,36,1,36,5,36,722,8,36,10,36,12,36,725,9,36,3,36,727,8,36,1,36, - 1,36,1,36,1,36,4,36,733,8,36,11,36,12,36,734,1,36,1,36,3,36,739,8,36,1, - 37,1,37,1,37,1,38,1,38,1,38,1,38,1,39,1,39,1,39,1,39,1,40,1,40,1,41,1,41, - 1,41,1,42,1,42,1,43,1,43,1,43,1,43,1,43,1,44,1,44,1,45,1,45,1,45,1,45,1, - 45,1,45,1,46,1,46,1,46,1,46,1,46,1,46,1,47,1,47,1,47,1,48,1,48,1,48,1,49, - 1,49,1,49,1,49,1,49,1,50,1,50,1,50,1,50,1,50,1,51,1,51,1,52,1,52,1,52,1, - 52,1,53,1,53,1,53,1,53,1,53,1,54,1,54,1,54,1,54,1,54,1,54,1,55,1,55,1,55, - 1,56,1,56,1,57,1,57,1,57,1,57,1,57,1,57,1,58,1,58,1,59,1,59,1,59,1,59,1, - 59,1,60,1,60,1,60,1,61,1,61,1,61,1,62,1,62,1,62,1,63,1,63,1,64,1,64,1,64, - 1,65,1,65,1,66,1,66,1,66,1,67,1,67,1,68,1,68,1,69,1,69,1,70,1,70,1,71,1, - 71,1,72,1,72,1,72,1,72,1,72,1,72,1,73,1,73,1,73,1,73,1,74,1,74,1,74,3,74, - 871,8,74,1,74,5,74,874,8,74,10,74,12,74,877,9,74,1,74,1,74,4,74,881,8,74, - 11,74,12,74,882,3,74,885,8,74,1,75,1,75,1,75,1,75,1,75,1,76,1,76,1,76,1, - 76,1,76,1,77,1,77,5,77,899,8,77,10,77,12,77,902,9,77,1,77,1,77,3,77,906, - 8,77,1,77,4,77,909,8,77,11,77,12,77,910,3,77,913,8,77,1,78,1,78,4,78,917, - 8,78,11,78,12,78,918,1,78,1,78,1,79,1,79,1,80,1,80,1,80,1,80,1,81,1,81, - 1,81,1,81,1,82,1,82,1,82,1,82,1,83,1,83,1,83,1,83,1,83,1,84,1,84,1,84,1, - 84,1,84,1,85,1,85,1,85,1,85,1,86,1,86,1,86,1,86,1,87,1,87,1,87,1,87,1,88, - 1,88,1,88,1,88,1,88,1,89,1,89,1,89,1,89,1,90,1,90,1,90,1,90,1,91,1,91,1, - 91,1,91,1,92,1,92,1,92,1,92,1,93,1,93,1,93,1,93,1,94,1,94,1,94,1,94,1,94, - 1,94,1,94,1,94,1,94,1,95,1,95,1,95,3,95,996,8,95,1,96,4,96,999,8,96,11, - 96,12,96,1000,1,97,1,97,1,97,1,97,1,98,1,98,1,98,1,98,1,99,1,99,1,99,1, - 99,1,100,1,100,1,100,1,100,1,101,1,101,1,101,1,101,1,102,1,102,1,102,1, - 102,1,102,1,103,1,103,1,103,1,103,1,104,1,104,1,104,1,104,1,105,1,105,1, - 105,1,105,1,105,1,106,1,106,1,106,1,106,1,106,1,107,1,107,1,107,1,107,3, - 107,1050,8,107,1,108,1,108,3,108,1054,8,108,1,108,5,108,1057,8,108,10,108, - 12,108,1060,9,108,1,108,1,108,3,108,1064,8,108,1,108,4,108,1067,8,108,11, - 108,12,108,1068,3,108,1071,8,108,1,109,1,109,4,109,1075,8,109,11,109,12, - 109,1076,1,110,1,110,1,110,1,110,1,111,1,111,1,111,1,111,1,112,1,112,1, - 112,1,112,1,113,1,113,1,113,1,113,1,113,1,114,1,114,1,114,1,114,1,115,1, - 115,1,115,1,115,1,116,1,116,1,116,1,116,1,117,1,117,1,117,1,117,1,117,1, - 118,1,118,1,118,1,118,1,118,1,119,1,119,1,119,1,120,1,120,1,120,1,120,1, - 121,1,121,1,121,1,121,1,122,1,122,1,122,1,122,1,123,1,123,1,123,1,123,1, - 124,1,124,1,124,1,124,1,124,1,125,1,125,1,125,1,125,1,125,1,126,1,126,1, - 126,1,126,1,126,1,127,1,127,1,127,1,127,1,127,1,127,1,127,1,128,1,128,1, - 129,4,129,1162,8,129,11,129,12,129,1163,1,129,1,129,3,129,1168,8,129,1, - 129,4,129,1171,8,129,11,129,12,129,1172,1,130,1,130,1,130,1,130,1,131,1, - 131,1,131,1,131,1,132,1,132,1,132,1,132,1,133,1,133,1,133,1,133,1,134,1, - 134,1,134,1,134,1,134,1,134,1,135,1,135,1,135,1,135,1,136,1,136,1,136,1, - 136,1,137,1,137,1,137,1,137,1,138,1,138,1,138,1,138,1,139,1,139,1,139,1, - 139,1,140,1,140,1,140,1,140,1,141,1,141,1,141,1,141,1,141,1,142,1,142,1, - 142,1,142,1,142,1,143,1,143,1,143,1,143,1,144,1,144,1,144,1,144,1,145,1, - 145,1,145,1,145,1,146,1,146,1,146,1,146,1,146,1,147,1,147,1,147,1,147,1, - 148,1,148,1,148,1,148,1,148,1,149,1,149,1,149,1,149,1,149,1,150,1,150,1, - 150,1,150,1,151,1,151,1,151,1,151,1,152,1,152,1,152,1,152,1,153,1,153,1, - 153,1,153,1,154,1,154,1,154,1,154,1,155,1,155,1,155,1,155,1,155,1,156,1, - 156,1,156,1,156,1,156,1,157,1,157,1,157,1,157,1,158,1,158,1,158,1,158,1, - 159,1,159,1,159,1,159,1,160,1,160,1,160,1,160,1,160,1,161,1,161,1,162,1, - 162,1,162,1,162,1,162,4,162,1316,8,162,11,162,12,162,1317,1,163,1,163,1, - 163,1,163,1,164,1,164,1,164,1,164,1,165,1,165,1,165,1,165,1,166,1,166,1, - 166,1,166,1,166,1,167,1,167,1,167,1,167,1,168,1,168,1,168,1,168,1,169,1, - 169,1,169,1,169,1,170,1,170,1,170,1,170,1,170,1,171,1,171,1,171,1,171,1, - 172,1,172,1,172,1,172,1,173,1,173,1,173,1,173,1,174,1,174,1,174,1,174,1, - 175,1,175,1,175,1,175,1,176,1,176,1,176,1,176,1,176,1,176,1,177,1,177,1, - 177,1,177,1,178,1,178,1,178,1,178,1,179,1,179,1,179,1,179,1,180,1,180,1, - 180,1,180,1,181,1,181,1,181,1,181,1,182,1,182,1,182,1,182,1,183,1,183,1, - 183,1,183,1,183,1,184,1,184,1,184,1,184,1,184,1,184,1,185,1,185,1,185,1, - 185,1,185,1,185,1,186,1,186,1,186,1,186,1,187,1,187,1,187,1,187,1,188,1, - 188,1,188,1,188,1,189,1,189,1,189,1,189,1,189,1,189,1,190,1,190,1,190,1, - 190,1,190,1,190,1,191,1,191,1,191,1,191,1,192,1,192,1,192,1,192,1,193,1, - 193,1,193,1,193,1,194,1,194,1,194,1,194,1,194,1,194,1,195,1,195,1,195,1, - 195,1,195,1,195,1,196,1,196,1,196,1,196,1,196,1,196,1,197,1,197,1,197,1, - 197,1,197,2,607,676,0,198,15,1,17,2,19,3,21,4,23,5,25,6,27,7,29,8,31,9, - 33,10,35,11,37,12,39,13,41,14,43,15,45,16,47,17,49,18,51,19,53,20,55,21, - 57,22,59,23,61,24,63,0,65,0,67,0,69,0,71,0,73,0,75,0,77,0,79,0,81,0,83, - 25,85,26,87,27,89,28,91,29,93,30,95,31,97,32,99,33,101,34,103,35,105,36, - 107,37,109,38,111,39,113,40,115,41,117,42,119,43,121,44,123,45,125,46,127, - 47,129,48,131,49,133,50,135,51,137,52,139,53,141,54,143,55,145,56,147,57, - 149,58,151,59,153,60,155,61,157,62,159,63,161,0,163,64,165,65,167,66,169, - 67,171,0,173,68,175,69,177,70,179,71,181,0,183,0,185,72,187,73,189,74,191, - 0,193,0,195,0,197,0,199,0,201,0,203,75,205,0,207,76,209,0,211,0,213,77, - 215,78,217,79,219,0,221,0,223,0,225,0,227,0,229,0,231,0,233,80,235,81,237, - 82,239,83,241,0,243,0,245,0,247,0,249,0,251,0,253,84,255,0,257,85,259,86, - 261,87,263,0,265,0,267,88,269,89,271,0,273,90,275,0,277,91,279,92,281,93, - 283,0,285,0,287,0,289,0,291,0,293,0,295,0,297,0,299,0,301,94,303,95,305, - 96,307,0,309,0,311,0,313,0,315,0,317,0,319,97,321,98,323,99,325,0,327,100, - 329,101,331,102,333,103,335,0,337,104,339,105,341,106,343,107,345,108,347, - 0,349,0,351,0,353,0,355,0,357,0,359,0,361,109,363,110,365,111,367,0,369, - 0,371,0,373,0,375,112,377,113,379,114,381,0,383,0,385,0,387,115,389,116, - 391,117,393,0,395,0,397,118,399,119,401,120,403,0,405,0,407,0,409,0,15, - 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,35,2,0,68,68,100,100,2,0,73,73,105,105, - 2,0,83,83,115,115,2,0,69,69,101,101,2,0,67,67,99,99,2,0,84,84,116,116,2, - 0,82,82,114,114,2,0,79,79,111,111,2,0,80,80,112,112,2,0,78,78,110,110,2, - 0,72,72,104,104,2,0,86,86,118,118,2,0,65,65,97,97,2,0,76,76,108,108,2,0, - 88,88,120,120,2,0,70,70,102,102,2,0,77,77,109,109,2,0,71,71,103,103,2,0, - 75,75,107,107,2,0,87,87,119,119,2,0,85,85,117,117,6,0,9,10,13,13,32,32, - 47,47,91,91,93,93,2,0,10,10,13,13,3,0,9,10,13,13,32,32,1,0,48,57,2,0,65, - 90,97,122,8,0,34,34,78,78,82,82,84,84,92,92,110,110,114,114,116,116,4,0, - 10,10,13,13,34,34,92,92,2,0,43,43,45,45,1,0,96,96,2,0,66,66,98,98,2,0,89, - 89,121,121,11,0,9,10,13,13,32,32,34,34,44,44,47,47,58,58,61,61,91,91,93, - 93,124,124,2,0,42,42,47,47,11,0,9,10,13,13,32,32,34,35,44,44,47,47,58,58, - 60,60,62,63,92,92,124,124,1507,0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0,0,0,0, - 21,1,0,0,0,0,23,1,0,0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,29,1,0,0,0,0,31,1,0, - 0,0,0,33,1,0,0,0,0,35,1,0,0,0,0,37,1,0,0,0,0,39,1,0,0,0,0,41,1,0,0,0,0, - 43,1,0,0,0,0,45,1,0,0,0,0,47,1,0,0,0,0,49,1,0,0,0,0,51,1,0,0,0,0,53,1,0, - 0,0,0,55,1,0,0,0,0,57,1,0,0,0,0,59,1,0,0,0,1,61,1,0,0,0,1,83,1,0,0,0,1, - 85,1,0,0,0,1,87,1,0,0,0,1,89,1,0,0,0,1,91,1,0,0,0,1,93,1,0,0,0,1,95,1,0, - 0,0,1,97,1,0,0,0,1,99,1,0,0,0,1,101,1,0,0,0,1,103,1,0,0,0,1,105,1,0,0,0, - 1,107,1,0,0,0,1,109,1,0,0,0,1,111,1,0,0,0,1,113,1,0,0,0,1,115,1,0,0,0,1, - 117,1,0,0,0,1,119,1,0,0,0,1,121,1,0,0,0,1,123,1,0,0,0,1,125,1,0,0,0,1,127, - 1,0,0,0,1,129,1,0,0,0,1,131,1,0,0,0,1,133,1,0,0,0,1,135,1,0,0,0,1,137,1, - 0,0,0,1,139,1,0,0,0,1,141,1,0,0,0,1,143,1,0,0,0,1,145,1,0,0,0,1,147,1,0, - 0,0,1,149,1,0,0,0,1,151,1,0,0,0,1,153,1,0,0,0,1,155,1,0,0,0,1,157,1,0,0, - 0,1,159,1,0,0,0,1,161,1,0,0,0,1,163,1,0,0,0,1,165,1,0,0,0,1,167,1,0,0,0, - 1,169,1,0,0,0,1,173,1,0,0,0,1,175,1,0,0,0,1,177,1,0,0,0,1,179,1,0,0,0,2, - 181,1,0,0,0,2,183,1,0,0,0,2,185,1,0,0,0,2,187,1,0,0,0,2,189,1,0,0,0,3,191, - 1,0,0,0,3,193,1,0,0,0,3,195,1,0,0,0,3,197,1,0,0,0,3,199,1,0,0,0,3,201,1, - 0,0,0,3,203,1,0,0,0,3,207,1,0,0,0,3,209,1,0,0,0,3,211,1,0,0,0,3,213,1,0, - 0,0,3,215,1,0,0,0,3,217,1,0,0,0,4,219,1,0,0,0,4,221,1,0,0,0,4,223,1,0,0, - 0,4,225,1,0,0,0,4,227,1,0,0,0,4,233,1,0,0,0,4,235,1,0,0,0,4,237,1,0,0,0, - 4,239,1,0,0,0,5,241,1,0,0,0,5,243,1,0,0,0,5,245,1,0,0,0,5,247,1,0,0,0,5, - 249,1,0,0,0,5,251,1,0,0,0,5,253,1,0,0,0,5,255,1,0,0,0,5,257,1,0,0,0,5,259, - 1,0,0,0,5,261,1,0,0,0,6,263,1,0,0,0,6,265,1,0,0,0,6,267,1,0,0,0,6,269,1, - 0,0,0,6,273,1,0,0,0,6,275,1,0,0,0,6,277,1,0,0,0,6,279,1,0,0,0,6,281,1,0, - 0,0,7,283,1,0,0,0,7,285,1,0,0,0,7,287,1,0,0,0,7,289,1,0,0,0,7,291,1,0,0, - 0,7,293,1,0,0,0,7,295,1,0,0,0,7,297,1,0,0,0,7,299,1,0,0,0,7,301,1,0,0,0, - 7,303,1,0,0,0,7,305,1,0,0,0,8,307,1,0,0,0,8,309,1,0,0,0,8,311,1,0,0,0,8, - 313,1,0,0,0,8,315,1,0,0,0,8,317,1,0,0,0,8,319,1,0,0,0,8,321,1,0,0,0,8,323, - 1,0,0,0,9,325,1,0,0,0,9,327,1,0,0,0,9,329,1,0,0,0,9,331,1,0,0,0,9,333,1, - 0,0,0,10,335,1,0,0,0,10,337,1,0,0,0,10,339,1,0,0,0,10,341,1,0,0,0,10,343, - 1,0,0,0,10,345,1,0,0,0,11,347,1,0,0,0,11,349,1,0,0,0,11,351,1,0,0,0,11, - 353,1,0,0,0,11,355,1,0,0,0,11,357,1,0,0,0,11,359,1,0,0,0,11,361,1,0,0,0, - 11,363,1,0,0,0,11,365,1,0,0,0,12,367,1,0,0,0,12,369,1,0,0,0,12,371,1,0, - 0,0,12,373,1,0,0,0,12,375,1,0,0,0,12,377,1,0,0,0,12,379,1,0,0,0,13,381, - 1,0,0,0,13,383,1,0,0,0,13,385,1,0,0,0,13,387,1,0,0,0,13,389,1,0,0,0,13, - 391,1,0,0,0,14,393,1,0,0,0,14,395,1,0,0,0,14,397,1,0,0,0,14,399,1,0,0,0, - 14,401,1,0,0,0,14,403,1,0,0,0,14,405,1,0,0,0,14,407,1,0,0,0,14,409,1,0, - 0,0,15,411,1,0,0,0,17,421,1,0,0,0,19,428,1,0,0,0,21,437,1,0,0,0,23,444, - 1,0,0,0,25,454,1,0,0,0,27,461,1,0,0,0,29,468,1,0,0,0,31,475,1,0,0,0,33, - 483,1,0,0,0,35,495,1,0,0,0,37,504,1,0,0,0,39,510,1,0,0,0,41,517,1,0,0,0, - 43,524,1,0,0,0,45,532,1,0,0,0,47,540,1,0,0,0,49,555,1,0,0,0,51,565,1,0, - 0,0,53,577,1,0,0,0,55,583,1,0,0,0,57,600,1,0,0,0,59,616,1,0,0,0,61,622, - 1,0,0,0,63,626,1,0,0,0,65,628,1,0,0,0,67,630,1,0,0,0,69,633,1,0,0,0,71, - 635,1,0,0,0,73,644,1,0,0,0,75,646,1,0,0,0,77,651,1,0,0,0,79,653,1,0,0,0, - 81,658,1,0,0,0,83,689,1,0,0,0,85,692,1,0,0,0,87,738,1,0,0,0,89,740,1,0, - 0,0,91,743,1,0,0,0,93,747,1,0,0,0,95,751,1,0,0,0,97,753,1,0,0,0,99,756, - 1,0,0,0,101,758,1,0,0,0,103,763,1,0,0,0,105,765,1,0,0,0,107,771,1,0,0,0, - 109,777,1,0,0,0,111,780,1,0,0,0,113,783,1,0,0,0,115,788,1,0,0,0,117,793, - 1,0,0,0,119,795,1,0,0,0,121,799,1,0,0,0,123,804,1,0,0,0,125,810,1,0,0,0, - 127,813,1,0,0,0,129,815,1,0,0,0,131,821,1,0,0,0,133,823,1,0,0,0,135,828, - 1,0,0,0,137,831,1,0,0,0,139,834,1,0,0,0,141,837,1,0,0,0,143,839,1,0,0,0, - 145,842,1,0,0,0,147,844,1,0,0,0,149,847,1,0,0,0,151,849,1,0,0,0,153,851, - 1,0,0,0,155,853,1,0,0,0,157,855,1,0,0,0,159,857,1,0,0,0,161,863,1,0,0,0, - 163,884,1,0,0,0,165,886,1,0,0,0,167,891,1,0,0,0,169,912,1,0,0,0,171,914, - 1,0,0,0,173,922,1,0,0,0,175,924,1,0,0,0,177,928,1,0,0,0,179,932,1,0,0,0, - 181,936,1,0,0,0,183,941,1,0,0,0,185,946,1,0,0,0,187,950,1,0,0,0,189,954, - 1,0,0,0,191,958,1,0,0,0,193,963,1,0,0,0,195,967,1,0,0,0,197,971,1,0,0,0, - 199,975,1,0,0,0,201,979,1,0,0,0,203,983,1,0,0,0,205,995,1,0,0,0,207,998, - 1,0,0,0,209,1002,1,0,0,0,211,1006,1,0,0,0,213,1010,1,0,0,0,215,1014,1,0, - 0,0,217,1018,1,0,0,0,219,1022,1,0,0,0,221,1027,1,0,0,0,223,1031,1,0,0,0, - 225,1035,1,0,0,0,227,1040,1,0,0,0,229,1049,1,0,0,0,231,1070,1,0,0,0,233, - 1074,1,0,0,0,235,1078,1,0,0,0,237,1082,1,0,0,0,239,1086,1,0,0,0,241,1090, - 1,0,0,0,243,1095,1,0,0,0,245,1099,1,0,0,0,247,1103,1,0,0,0,249,1107,1,0, - 0,0,251,1112,1,0,0,0,253,1117,1,0,0,0,255,1120,1,0,0,0,257,1124,1,0,0,0, - 259,1128,1,0,0,0,261,1132,1,0,0,0,263,1136,1,0,0,0,265,1141,1,0,0,0,267, - 1146,1,0,0,0,269,1151,1,0,0,0,271,1158,1,0,0,0,273,1167,1,0,0,0,275,1174, - 1,0,0,0,277,1178,1,0,0,0,279,1182,1,0,0,0,281,1186,1,0,0,0,283,1190,1,0, - 0,0,285,1196,1,0,0,0,287,1200,1,0,0,0,289,1204,1,0,0,0,291,1208,1,0,0,0, - 293,1212,1,0,0,0,295,1216,1,0,0,0,297,1220,1,0,0,0,299,1225,1,0,0,0,301, - 1230,1,0,0,0,303,1234,1,0,0,0,305,1238,1,0,0,0,307,1242,1,0,0,0,309,1247, - 1,0,0,0,311,1251,1,0,0,0,313,1256,1,0,0,0,315,1261,1,0,0,0,317,1265,1,0, - 0,0,319,1269,1,0,0,0,321,1273,1,0,0,0,323,1277,1,0,0,0,325,1281,1,0,0,0, - 327,1286,1,0,0,0,329,1291,1,0,0,0,331,1295,1,0,0,0,333,1299,1,0,0,0,335, - 1303,1,0,0,0,337,1308,1,0,0,0,339,1315,1,0,0,0,341,1319,1,0,0,0,343,1323, - 1,0,0,0,345,1327,1,0,0,0,347,1331,1,0,0,0,349,1336,1,0,0,0,351,1340,1,0, - 0,0,353,1344,1,0,0,0,355,1348,1,0,0,0,357,1353,1,0,0,0,359,1357,1,0,0,0, - 361,1361,1,0,0,0,363,1365,1,0,0,0,365,1369,1,0,0,0,367,1373,1,0,0,0,369, - 1379,1,0,0,0,371,1383,1,0,0,0,373,1387,1,0,0,0,375,1391,1,0,0,0,377,1395, - 1,0,0,0,379,1399,1,0,0,0,381,1403,1,0,0,0,383,1408,1,0,0,0,385,1414,1,0, - 0,0,387,1420,1,0,0,0,389,1424,1,0,0,0,391,1428,1,0,0,0,393,1432,1,0,0,0, - 395,1438,1,0,0,0,397,1444,1,0,0,0,399,1448,1,0,0,0,401,1452,1,0,0,0,403, - 1456,1,0,0,0,405,1462,1,0,0,0,407,1468,1,0,0,0,409,1474,1,0,0,0,411,412, - 7,0,0,0,412,413,7,1,0,0,413,414,7,2,0,0,414,415,7,2,0,0,415,416,7,3,0,0, - 416,417,7,4,0,0,417,418,7,5,0,0,418,419,1,0,0,0,419,420,6,0,0,0,420,16, - 1,0,0,0,421,422,7,0,0,0,422,423,7,6,0,0,423,424,7,7,0,0,424,425,7,8,0,0, - 425,426,1,0,0,0,426,427,6,1,1,0,427,18,1,0,0,0,428,429,7,3,0,0,429,430, - 7,9,0,0,430,431,7,6,0,0,431,432,7,1,0,0,432,433,7,4,0,0,433,434,7,10,0, - 0,434,435,1,0,0,0,435,436,6,2,2,0,436,20,1,0,0,0,437,438,7,3,0,0,438,439, - 7,11,0,0,439,440,7,12,0,0,440,441,7,13,0,0,441,442,1,0,0,0,442,443,6,3, - 0,0,443,22,1,0,0,0,444,445,7,3,0,0,445,446,7,14,0,0,446,447,7,8,0,0,447, - 448,7,13,0,0,448,449,7,12,0,0,449,450,7,1,0,0,450,451,7,9,0,0,451,452,1, - 0,0,0,452,453,6,4,3,0,453,24,1,0,0,0,454,455,7,15,0,0,455,456,7,6,0,0,456, - 457,7,7,0,0,457,458,7,16,0,0,458,459,1,0,0,0,459,460,6,5,4,0,460,26,1,0, - 0,0,461,462,7,17,0,0,462,463,7,6,0,0,463,464,7,7,0,0,464,465,7,18,0,0,465, - 466,1,0,0,0,466,467,6,6,0,0,467,28,1,0,0,0,468,469,7,18,0,0,469,470,7,3, - 0,0,470,471,7,3,0,0,471,472,7,8,0,0,472,473,1,0,0,0,473,474,6,7,1,0,474, - 30,1,0,0,0,475,476,7,13,0,0,476,477,7,1,0,0,477,478,7,16,0,0,478,479,7, - 1,0,0,479,480,7,5,0,0,480,481,1,0,0,0,481,482,6,8,0,0,482,32,1,0,0,0,483, - 484,7,16,0,0,484,485,7,11,0,0,485,486,5,95,0,0,486,487,7,3,0,0,487,488, - 7,14,0,0,488,489,7,8,0,0,489,490,7,12,0,0,490,491,7,9,0,0,491,492,7,0,0, - 0,492,493,1,0,0,0,493,494,6,9,5,0,494,34,1,0,0,0,495,496,7,6,0,0,496,497, - 7,3,0,0,497,498,7,9,0,0,498,499,7,12,0,0,499,500,7,16,0,0,500,501,7,3,0, - 0,501,502,1,0,0,0,502,503,6,10,6,0,503,36,1,0,0,0,504,505,7,6,0,0,505,506, - 7,7,0,0,506,507,7,19,0,0,507,508,1,0,0,0,508,509,6,11,0,0,509,38,1,0,0, - 0,510,511,7,2,0,0,511,512,7,10,0,0,512,513,7,7,0,0,513,514,7,19,0,0,514, - 515,1,0,0,0,515,516,6,12,7,0,516,40,1,0,0,0,517,518,7,2,0,0,518,519,7,7, - 0,0,519,520,7,6,0,0,520,521,7,5,0,0,521,522,1,0,0,0,522,523,6,13,0,0,523, - 42,1,0,0,0,524,525,7,2,0,0,525,526,7,5,0,0,526,527,7,12,0,0,527,528,7,5, - 0,0,528,529,7,2,0,0,529,530,1,0,0,0,530,531,6,14,0,0,531,44,1,0,0,0,532, - 533,7,19,0,0,533,534,7,10,0,0,534,535,7,3,0,0,535,536,7,6,0,0,536,537,7, - 3,0,0,537,538,1,0,0,0,538,539,6,15,0,0,539,46,1,0,0,0,540,541,4,16,0,0, - 541,542,7,1,0,0,542,543,7,9,0,0,543,544,7,13,0,0,544,545,7,1,0,0,545,546, - 7,9,0,0,546,547,7,3,0,0,547,548,7,2,0,0,548,549,7,5,0,0,549,550,7,12,0, - 0,550,551,7,5,0,0,551,552,7,2,0,0,552,553,1,0,0,0,553,554,6,16,0,0,554, - 48,1,0,0,0,555,556,4,17,1,0,556,557,7,13,0,0,557,558,7,7,0,0,558,559,7, - 7,0,0,559,560,7,18,0,0,560,561,7,20,0,0,561,562,7,8,0,0,562,563,1,0,0,0, - 563,564,6,17,8,0,564,50,1,0,0,0,565,566,4,18,2,0,566,567,7,16,0,0,567,568, - 7,3,0,0,568,569,7,5,0,0,569,570,7,6,0,0,570,571,7,1,0,0,571,572,7,4,0,0, - 572,573,7,2,0,0,573,574,1,0,0,0,574,575,6,18,9,0,575,52,1,0,0,0,576,578, - 8,21,0,0,577,576,1,0,0,0,578,579,1,0,0,0,579,577,1,0,0,0,579,580,1,0,0, - 0,580,581,1,0,0,0,581,582,6,19,0,0,582,54,1,0,0,0,583,584,5,47,0,0,584, - 585,5,47,0,0,585,589,1,0,0,0,586,588,8,22,0,0,587,586,1,0,0,0,588,591,1, - 0,0,0,589,587,1,0,0,0,589,590,1,0,0,0,590,593,1,0,0,0,591,589,1,0,0,0,592, - 594,5,13,0,0,593,592,1,0,0,0,593,594,1,0,0,0,594,596,1,0,0,0,595,597,5, - 10,0,0,596,595,1,0,0,0,596,597,1,0,0,0,597,598,1,0,0,0,598,599,6,20,10, - 0,599,56,1,0,0,0,600,601,5,47,0,0,601,602,5,42,0,0,602,607,1,0,0,0,603, - 606,3,57,21,0,604,606,9,0,0,0,605,603,1,0,0,0,605,604,1,0,0,0,606,609,1, - 0,0,0,607,608,1,0,0,0,607,605,1,0,0,0,608,610,1,0,0,0,609,607,1,0,0,0,610, - 611,5,42,0,0,611,612,5,47,0,0,612,613,1,0,0,0,613,614,6,21,10,0,614,58, - 1,0,0,0,615,617,7,23,0,0,616,615,1,0,0,0,617,618,1,0,0,0,618,616,1,0,0, - 0,618,619,1,0,0,0,619,620,1,0,0,0,620,621,6,22,10,0,621,60,1,0,0,0,622, - 623,5,124,0,0,623,624,1,0,0,0,624,625,6,23,11,0,625,62,1,0,0,0,626,627, - 7,24,0,0,627,64,1,0,0,0,628,629,7,25,0,0,629,66,1,0,0,0,630,631,5,92,0, - 0,631,632,7,26,0,0,632,68,1,0,0,0,633,634,8,27,0,0,634,70,1,0,0,0,635,637, - 7,3,0,0,636,638,7,28,0,0,637,636,1,0,0,0,637,638,1,0,0,0,638,640,1,0,0, - 0,639,641,3,63,24,0,640,639,1,0,0,0,641,642,1,0,0,0,642,640,1,0,0,0,642, - 643,1,0,0,0,643,72,1,0,0,0,644,645,5,64,0,0,645,74,1,0,0,0,646,647,5,96, - 0,0,647,76,1,0,0,0,648,652,8,29,0,0,649,650,5,96,0,0,650,652,5,96,0,0,651, - 648,1,0,0,0,651,649,1,0,0,0,652,78,1,0,0,0,653,654,5,95,0,0,654,80,1,0, - 0,0,655,659,3,65,25,0,656,659,3,63,24,0,657,659,3,79,32,0,658,655,1,0,0, - 0,658,656,1,0,0,0,658,657,1,0,0,0,659,82,1,0,0,0,660,665,5,34,0,0,661,664, - 3,67,26,0,662,664,3,69,27,0,663,661,1,0,0,0,663,662,1,0,0,0,664,667,1,0, - 0,0,665,663,1,0,0,0,665,666,1,0,0,0,666,668,1,0,0,0,667,665,1,0,0,0,668, - 690,5,34,0,0,669,670,5,34,0,0,670,671,5,34,0,0,671,672,5,34,0,0,672,676, - 1,0,0,0,673,675,8,22,0,0,674,673,1,0,0,0,675,678,1,0,0,0,676,677,1,0,0, - 0,676,674,1,0,0,0,677,679,1,0,0,0,678,676,1,0,0,0,679,680,5,34,0,0,680, - 681,5,34,0,0,681,682,5,34,0,0,682,684,1,0,0,0,683,685,5,34,0,0,684,683, - 1,0,0,0,684,685,1,0,0,0,685,687,1,0,0,0,686,688,5,34,0,0,687,686,1,0,0, - 0,687,688,1,0,0,0,688,690,1,0,0,0,689,660,1,0,0,0,689,669,1,0,0,0,690,84, - 1,0,0,0,691,693,3,63,24,0,692,691,1,0,0,0,693,694,1,0,0,0,694,692,1,0,0, - 0,694,695,1,0,0,0,695,86,1,0,0,0,696,698,3,63,24,0,697,696,1,0,0,0,698, - 699,1,0,0,0,699,697,1,0,0,0,699,700,1,0,0,0,700,701,1,0,0,0,701,705,3,103, - 44,0,702,704,3,63,24,0,703,702,1,0,0,0,704,707,1,0,0,0,705,703,1,0,0,0, - 705,706,1,0,0,0,706,739,1,0,0,0,707,705,1,0,0,0,708,710,3,103,44,0,709, - 711,3,63,24,0,710,709,1,0,0,0,711,712,1,0,0,0,712,710,1,0,0,0,712,713,1, - 0,0,0,713,739,1,0,0,0,714,716,3,63,24,0,715,714,1,0,0,0,716,717,1,0,0,0, - 717,715,1,0,0,0,717,718,1,0,0,0,718,726,1,0,0,0,719,723,3,103,44,0,720, - 722,3,63,24,0,721,720,1,0,0,0,722,725,1,0,0,0,723,721,1,0,0,0,723,724,1, - 0,0,0,724,727,1,0,0,0,725,723,1,0,0,0,726,719,1,0,0,0,726,727,1,0,0,0,727, - 728,1,0,0,0,728,729,3,71,28,0,729,739,1,0,0,0,730,732,3,103,44,0,731,733, - 3,63,24,0,732,731,1,0,0,0,733,734,1,0,0,0,734,732,1,0,0,0,734,735,1,0,0, - 0,735,736,1,0,0,0,736,737,3,71,28,0,737,739,1,0,0,0,738,697,1,0,0,0,738, - 708,1,0,0,0,738,715,1,0,0,0,738,730,1,0,0,0,739,88,1,0,0,0,740,741,7,30, - 0,0,741,742,7,31,0,0,742,90,1,0,0,0,743,744,7,12,0,0,744,745,7,9,0,0,745, - 746,7,0,0,0,746,92,1,0,0,0,747,748,7,12,0,0,748,749,7,2,0,0,749,750,7,4, - 0,0,750,94,1,0,0,0,751,752,5,61,0,0,752,96,1,0,0,0,753,754,5,58,0,0,754, - 755,5,58,0,0,755,98,1,0,0,0,756,757,5,44,0,0,757,100,1,0,0,0,758,759,7, - 0,0,0,759,760,7,3,0,0,760,761,7,2,0,0,761,762,7,4,0,0,762,102,1,0,0,0,763, - 764,5,46,0,0,764,104,1,0,0,0,765,766,7,15,0,0,766,767,7,12,0,0,767,768, - 7,13,0,0,768,769,7,2,0,0,769,770,7,3,0,0,770,106,1,0,0,0,771,772,7,15,0, - 0,772,773,7,1,0,0,773,774,7,6,0,0,774,775,7,2,0,0,775,776,7,5,0,0,776,108, - 1,0,0,0,777,778,7,1,0,0,778,779,7,9,0,0,779,110,1,0,0,0,780,781,7,1,0,0, - 781,782,7,2,0,0,782,112,1,0,0,0,783,784,7,13,0,0,784,785,7,12,0,0,785,786, - 7,2,0,0,786,787,7,5,0,0,787,114,1,0,0,0,788,789,7,13,0,0,789,790,7,1,0, - 0,790,791,7,18,0,0,791,792,7,3,0,0,792,116,1,0,0,0,793,794,5,40,0,0,794, - 118,1,0,0,0,795,796,7,9,0,0,796,797,7,7,0,0,797,798,7,5,0,0,798,120,1,0, - 0,0,799,800,7,9,0,0,800,801,7,20,0,0,801,802,7,13,0,0,802,803,7,13,0,0, - 803,122,1,0,0,0,804,805,7,9,0,0,805,806,7,20,0,0,806,807,7,13,0,0,807,808, - 7,13,0,0,808,809,7,2,0,0,809,124,1,0,0,0,810,811,7,7,0,0,811,812,7,6,0, - 0,812,126,1,0,0,0,813,814,5,63,0,0,814,128,1,0,0,0,815,816,7,6,0,0,816, - 817,7,13,0,0,817,818,7,1,0,0,818,819,7,18,0,0,819,820,7,3,0,0,820,130,1, - 0,0,0,821,822,5,41,0,0,822,132,1,0,0,0,823,824,7,5,0,0,824,825,7,6,0,0, - 825,826,7,20,0,0,826,827,7,3,0,0,827,134,1,0,0,0,828,829,5,61,0,0,829,830, - 5,61,0,0,830,136,1,0,0,0,831,832,5,61,0,0,832,833,5,126,0,0,833,138,1,0, - 0,0,834,835,5,33,0,0,835,836,5,61,0,0,836,140,1,0,0,0,837,838,5,60,0,0, - 838,142,1,0,0,0,839,840,5,60,0,0,840,841,5,61,0,0,841,144,1,0,0,0,842,843, - 5,62,0,0,843,146,1,0,0,0,844,845,5,62,0,0,845,846,5,61,0,0,846,148,1,0, - 0,0,847,848,5,43,0,0,848,150,1,0,0,0,849,850,5,45,0,0,850,152,1,0,0,0,851, - 852,5,42,0,0,852,154,1,0,0,0,853,854,5,47,0,0,854,156,1,0,0,0,855,856,5, - 37,0,0,856,158,1,0,0,0,857,858,7,16,0,0,858,859,7,12,0,0,859,860,7,5,0, - 0,860,861,7,4,0,0,861,862,7,10,0,0,862,160,1,0,0,0,863,864,3,45,15,0,864, - 865,1,0,0,0,865,866,6,73,12,0,866,162,1,0,0,0,867,870,3,127,56,0,868,871, - 3,65,25,0,869,871,3,79,32,0,870,868,1,0,0,0,870,869,1,0,0,0,871,875,1,0, - 0,0,872,874,3,81,33,0,873,872,1,0,0,0,874,877,1,0,0,0,875,873,1,0,0,0,875, - 876,1,0,0,0,876,885,1,0,0,0,877,875,1,0,0,0,878,880,3,127,56,0,879,881, - 3,63,24,0,880,879,1,0,0,0,881,882,1,0,0,0,882,880,1,0,0,0,882,883,1,0,0, - 0,883,885,1,0,0,0,884,867,1,0,0,0,884,878,1,0,0,0,885,164,1,0,0,0,886,887, - 5,91,0,0,887,888,1,0,0,0,888,889,6,75,0,0,889,890,6,75,0,0,890,166,1,0, - 0,0,891,892,5,93,0,0,892,893,1,0,0,0,893,894,6,76,11,0,894,895,6,76,11, - 0,895,168,1,0,0,0,896,900,3,65,25,0,897,899,3,81,33,0,898,897,1,0,0,0,899, - 902,1,0,0,0,900,898,1,0,0,0,900,901,1,0,0,0,901,913,1,0,0,0,902,900,1,0, - 0,0,903,906,3,79,32,0,904,906,3,73,29,0,905,903,1,0,0,0,905,904,1,0,0,0, - 906,908,1,0,0,0,907,909,3,81,33,0,908,907,1,0,0,0,909,910,1,0,0,0,910,908, - 1,0,0,0,910,911,1,0,0,0,911,913,1,0,0,0,912,896,1,0,0,0,912,905,1,0,0,0, - 913,170,1,0,0,0,914,916,3,75,30,0,915,917,3,77,31,0,916,915,1,0,0,0,917, - 918,1,0,0,0,918,916,1,0,0,0,918,919,1,0,0,0,919,920,1,0,0,0,920,921,3,75, - 30,0,921,172,1,0,0,0,922,923,3,171,78,0,923,174,1,0,0,0,924,925,3,55,20, - 0,925,926,1,0,0,0,926,927,6,80,10,0,927,176,1,0,0,0,928,929,3,57,21,0,929, - 930,1,0,0,0,930,931,6,81,10,0,931,178,1,0,0,0,932,933,3,59,22,0,933,934, - 1,0,0,0,934,935,6,82,10,0,935,180,1,0,0,0,936,937,3,165,75,0,937,938,1, - 0,0,0,938,939,6,83,13,0,939,940,6,83,14,0,940,182,1,0,0,0,941,942,3,61, - 23,0,942,943,1,0,0,0,943,944,6,84,15,0,944,945,6,84,11,0,945,184,1,0,0, - 0,946,947,3,59,22,0,947,948,1,0,0,0,948,949,6,85,10,0,949,186,1,0,0,0,950, - 951,3,55,20,0,951,952,1,0,0,0,952,953,6,86,10,0,953,188,1,0,0,0,954,955, - 3,57,21,0,955,956,1,0,0,0,956,957,6,87,10,0,957,190,1,0,0,0,958,959,3,61, - 23,0,959,960,1,0,0,0,960,961,6,88,15,0,961,962,6,88,11,0,962,192,1,0,0, - 0,963,964,3,165,75,0,964,965,1,0,0,0,965,966,6,89,13,0,966,194,1,0,0,0, - 967,968,3,167,76,0,968,969,1,0,0,0,969,970,6,90,16,0,970,196,1,0,0,0,971, - 972,3,337,161,0,972,973,1,0,0,0,973,974,6,91,17,0,974,198,1,0,0,0,975,976, - 3,99,42,0,976,977,1,0,0,0,977,978,6,92,18,0,978,200,1,0,0,0,979,980,3,95, - 40,0,980,981,1,0,0,0,981,982,6,93,19,0,982,202,1,0,0,0,983,984,7,16,0,0, - 984,985,7,3,0,0,985,986,7,5,0,0,986,987,7,12,0,0,987,988,7,0,0,0,988,989, - 7,12,0,0,989,990,7,5,0,0,990,991,7,12,0,0,991,204,1,0,0,0,992,996,8,32, - 0,0,993,994,5,47,0,0,994,996,8,33,0,0,995,992,1,0,0,0,995,993,1,0,0,0,996, - 206,1,0,0,0,997,999,3,205,95,0,998,997,1,0,0,0,999,1000,1,0,0,0,1000,998, - 1,0,0,0,1000,1001,1,0,0,0,1001,208,1,0,0,0,1002,1003,3,207,96,0,1003,1004, - 1,0,0,0,1004,1005,6,97,20,0,1005,210,1,0,0,0,1006,1007,3,83,34,0,1007,1008, - 1,0,0,0,1008,1009,6,98,21,0,1009,212,1,0,0,0,1010,1011,3,55,20,0,1011,1012, - 1,0,0,0,1012,1013,6,99,10,0,1013,214,1,0,0,0,1014,1015,3,57,21,0,1015,1016, - 1,0,0,0,1016,1017,6,100,10,0,1017,216,1,0,0,0,1018,1019,3,59,22,0,1019, - 1020,1,0,0,0,1020,1021,6,101,10,0,1021,218,1,0,0,0,1022,1023,3,61,23,0, - 1023,1024,1,0,0,0,1024,1025,6,102,15,0,1025,1026,6,102,11,0,1026,220,1, - 0,0,0,1027,1028,3,103,44,0,1028,1029,1,0,0,0,1029,1030,6,103,22,0,1030, - 222,1,0,0,0,1031,1032,3,99,42,0,1032,1033,1,0,0,0,1033,1034,6,104,18,0, - 1034,224,1,0,0,0,1035,1036,4,105,3,0,1036,1037,3,127,56,0,1037,1038,1,0, - 0,0,1038,1039,6,105,23,0,1039,226,1,0,0,0,1040,1041,4,106,4,0,1041,1042, - 3,163,74,0,1042,1043,1,0,0,0,1043,1044,6,106,24,0,1044,228,1,0,0,0,1045, - 1050,3,65,25,0,1046,1050,3,63,24,0,1047,1050,3,79,32,0,1048,1050,3,153, - 69,0,1049,1045,1,0,0,0,1049,1046,1,0,0,0,1049,1047,1,0,0,0,1049,1048,1, - 0,0,0,1050,230,1,0,0,0,1051,1054,3,65,25,0,1052,1054,3,153,69,0,1053,1051, - 1,0,0,0,1053,1052,1,0,0,0,1054,1058,1,0,0,0,1055,1057,3,229,107,0,1056, - 1055,1,0,0,0,1057,1060,1,0,0,0,1058,1056,1,0,0,0,1058,1059,1,0,0,0,1059, - 1071,1,0,0,0,1060,1058,1,0,0,0,1061,1064,3,79,32,0,1062,1064,3,73,29,0, - 1063,1061,1,0,0,0,1063,1062,1,0,0,0,1064,1066,1,0,0,0,1065,1067,3,229,107, - 0,1066,1065,1,0,0,0,1067,1068,1,0,0,0,1068,1066,1,0,0,0,1068,1069,1,0,0, - 0,1069,1071,1,0,0,0,1070,1053,1,0,0,0,1070,1063,1,0,0,0,1071,232,1,0,0, - 0,1072,1075,3,231,108,0,1073,1075,3,171,78,0,1074,1072,1,0,0,0,1074,1073, - 1,0,0,0,1075,1076,1,0,0,0,1076,1074,1,0,0,0,1076,1077,1,0,0,0,1077,234, - 1,0,0,0,1078,1079,3,55,20,0,1079,1080,1,0,0,0,1080,1081,6,110,10,0,1081, - 236,1,0,0,0,1082,1083,3,57,21,0,1083,1084,1,0,0,0,1084,1085,6,111,10,0, - 1085,238,1,0,0,0,1086,1087,3,59,22,0,1087,1088,1,0,0,0,1088,1089,6,112, - 10,0,1089,240,1,0,0,0,1090,1091,3,61,23,0,1091,1092,1,0,0,0,1092,1093,6, - 113,15,0,1093,1094,6,113,11,0,1094,242,1,0,0,0,1095,1096,3,95,40,0,1096, - 1097,1,0,0,0,1097,1098,6,114,19,0,1098,244,1,0,0,0,1099,1100,3,99,42,0, - 1100,1101,1,0,0,0,1101,1102,6,115,18,0,1102,246,1,0,0,0,1103,1104,3,103, - 44,0,1104,1105,1,0,0,0,1105,1106,6,116,22,0,1106,248,1,0,0,0,1107,1108, - 4,117,5,0,1108,1109,3,127,56,0,1109,1110,1,0,0,0,1110,1111,6,117,23,0,1111, - 250,1,0,0,0,1112,1113,4,118,6,0,1113,1114,3,163,74,0,1114,1115,1,0,0,0, - 1115,1116,6,118,24,0,1116,252,1,0,0,0,1117,1118,7,12,0,0,1118,1119,7,2, - 0,0,1119,254,1,0,0,0,1120,1121,3,233,109,0,1121,1122,1,0,0,0,1122,1123, - 6,120,25,0,1123,256,1,0,0,0,1124,1125,3,55,20,0,1125,1126,1,0,0,0,1126, - 1127,6,121,10,0,1127,258,1,0,0,0,1128,1129,3,57,21,0,1129,1130,1,0,0,0, - 1130,1131,6,122,10,0,1131,260,1,0,0,0,1132,1133,3,59,22,0,1133,1134,1,0, - 0,0,1134,1135,6,123,10,0,1135,262,1,0,0,0,1136,1137,3,61,23,0,1137,1138, - 1,0,0,0,1138,1139,6,124,15,0,1139,1140,6,124,11,0,1140,264,1,0,0,0,1141, - 1142,3,165,75,0,1142,1143,1,0,0,0,1143,1144,6,125,13,0,1144,1145,6,125, - 26,0,1145,266,1,0,0,0,1146,1147,7,7,0,0,1147,1148,7,9,0,0,1148,1149,1,0, - 0,0,1149,1150,6,126,27,0,1150,268,1,0,0,0,1151,1152,7,19,0,0,1152,1153, - 7,1,0,0,1153,1154,7,5,0,0,1154,1155,7,10,0,0,1155,1156,1,0,0,0,1156,1157, - 6,127,27,0,1157,270,1,0,0,0,1158,1159,8,34,0,0,1159,272,1,0,0,0,1160,1162, - 3,271,128,0,1161,1160,1,0,0,0,1162,1163,1,0,0,0,1163,1161,1,0,0,0,1163, - 1164,1,0,0,0,1164,1165,1,0,0,0,1165,1166,3,337,161,0,1166,1168,1,0,0,0, - 1167,1161,1,0,0,0,1167,1168,1,0,0,0,1168,1170,1,0,0,0,1169,1171,3,271,128, - 0,1170,1169,1,0,0,0,1171,1172,1,0,0,0,1172,1170,1,0,0,0,1172,1173,1,0,0, - 0,1173,274,1,0,0,0,1174,1175,3,273,129,0,1175,1176,1,0,0,0,1176,1177,6, - 130,28,0,1177,276,1,0,0,0,1178,1179,3,55,20,0,1179,1180,1,0,0,0,1180,1181, - 6,131,10,0,1181,278,1,0,0,0,1182,1183,3,57,21,0,1183,1184,1,0,0,0,1184, - 1185,6,132,10,0,1185,280,1,0,0,0,1186,1187,3,59,22,0,1187,1188,1,0,0,0, - 1188,1189,6,133,10,0,1189,282,1,0,0,0,1190,1191,3,61,23,0,1191,1192,1,0, - 0,0,1192,1193,6,134,15,0,1193,1194,6,134,11,0,1194,1195,6,134,11,0,1195, - 284,1,0,0,0,1196,1197,3,95,40,0,1197,1198,1,0,0,0,1198,1199,6,135,19,0, - 1199,286,1,0,0,0,1200,1201,3,99,42,0,1201,1202,1,0,0,0,1202,1203,6,136, - 18,0,1203,288,1,0,0,0,1204,1205,3,103,44,0,1205,1206,1,0,0,0,1206,1207, - 6,137,22,0,1207,290,1,0,0,0,1208,1209,3,269,127,0,1209,1210,1,0,0,0,1210, - 1211,6,138,29,0,1211,292,1,0,0,0,1212,1213,3,233,109,0,1213,1214,1,0,0, - 0,1214,1215,6,139,25,0,1215,294,1,0,0,0,1216,1217,3,173,79,0,1217,1218, - 1,0,0,0,1218,1219,6,140,30,0,1219,296,1,0,0,0,1220,1221,4,141,7,0,1221, - 1222,3,127,56,0,1222,1223,1,0,0,0,1223,1224,6,141,23,0,1224,298,1,0,0,0, - 1225,1226,4,142,8,0,1226,1227,3,163,74,0,1227,1228,1,0,0,0,1228,1229,6, - 142,24,0,1229,300,1,0,0,0,1230,1231,3,55,20,0,1231,1232,1,0,0,0,1232,1233, - 6,143,10,0,1233,302,1,0,0,0,1234,1235,3,57,21,0,1235,1236,1,0,0,0,1236, - 1237,6,144,10,0,1237,304,1,0,0,0,1238,1239,3,59,22,0,1239,1240,1,0,0,0, - 1240,1241,6,145,10,0,1241,306,1,0,0,0,1242,1243,3,61,23,0,1243,1244,1,0, - 0,0,1244,1245,6,146,15,0,1245,1246,6,146,11,0,1246,308,1,0,0,0,1247,1248, - 3,103,44,0,1248,1249,1,0,0,0,1249,1250,6,147,22,0,1250,310,1,0,0,0,1251, - 1252,4,148,9,0,1252,1253,3,127,56,0,1253,1254,1,0,0,0,1254,1255,6,148,23, - 0,1255,312,1,0,0,0,1256,1257,4,149,10,0,1257,1258,3,163,74,0,1258,1259, - 1,0,0,0,1259,1260,6,149,24,0,1260,314,1,0,0,0,1261,1262,3,173,79,0,1262, - 1263,1,0,0,0,1263,1264,6,150,30,0,1264,316,1,0,0,0,1265,1266,3,169,77,0, - 1266,1267,1,0,0,0,1267,1268,6,151,31,0,1268,318,1,0,0,0,1269,1270,3,55, - 20,0,1270,1271,1,0,0,0,1271,1272,6,152,10,0,1272,320,1,0,0,0,1273,1274, - 3,57,21,0,1274,1275,1,0,0,0,1275,1276,6,153,10,0,1276,322,1,0,0,0,1277, - 1278,3,59,22,0,1278,1279,1,0,0,0,1279,1280,6,154,10,0,1280,324,1,0,0,0, - 1281,1282,3,61,23,0,1282,1283,1,0,0,0,1283,1284,6,155,15,0,1284,1285,6, - 155,11,0,1285,326,1,0,0,0,1286,1287,7,1,0,0,1287,1288,7,9,0,0,1288,1289, - 7,15,0,0,1289,1290,7,7,0,0,1290,328,1,0,0,0,1291,1292,3,55,20,0,1292,1293, - 1,0,0,0,1293,1294,6,157,10,0,1294,330,1,0,0,0,1295,1296,3,57,21,0,1296, - 1297,1,0,0,0,1297,1298,6,158,10,0,1298,332,1,0,0,0,1299,1300,3,59,22,0, - 1300,1301,1,0,0,0,1301,1302,6,159,10,0,1302,334,1,0,0,0,1303,1304,3,167, - 76,0,1304,1305,1,0,0,0,1305,1306,6,160,16,0,1306,1307,6,160,11,0,1307,336, - 1,0,0,0,1308,1309,5,58,0,0,1309,338,1,0,0,0,1310,1316,3,73,29,0,1311,1316, - 3,63,24,0,1312,1316,3,103,44,0,1313,1316,3,65,25,0,1314,1316,3,79,32,0, - 1315,1310,1,0,0,0,1315,1311,1,0,0,0,1315,1312,1,0,0,0,1315,1313,1,0,0,0, - 1315,1314,1,0,0,0,1316,1317,1,0,0,0,1317,1315,1,0,0,0,1317,1318,1,0,0,0, - 1318,340,1,0,0,0,1319,1320,3,55,20,0,1320,1321,1,0,0,0,1321,1322,6,163, - 10,0,1322,342,1,0,0,0,1323,1324,3,57,21,0,1324,1325,1,0,0,0,1325,1326,6, - 164,10,0,1326,344,1,0,0,0,1327,1328,3,59,22,0,1328,1329,1,0,0,0,1329,1330, - 6,165,10,0,1330,346,1,0,0,0,1331,1332,3,61,23,0,1332,1333,1,0,0,0,1333, - 1334,6,166,15,0,1334,1335,6,166,11,0,1335,348,1,0,0,0,1336,1337,3,337,161, - 0,1337,1338,1,0,0,0,1338,1339,6,167,17,0,1339,350,1,0,0,0,1340,1341,3,99, - 42,0,1341,1342,1,0,0,0,1342,1343,6,168,18,0,1343,352,1,0,0,0,1344,1345, - 3,103,44,0,1345,1346,1,0,0,0,1346,1347,6,169,22,0,1347,354,1,0,0,0,1348, - 1349,3,267,126,0,1349,1350,1,0,0,0,1350,1351,6,170,32,0,1351,1352,6,170, - 33,0,1352,356,1,0,0,0,1353,1354,3,207,96,0,1354,1355,1,0,0,0,1355,1356, - 6,171,20,0,1356,358,1,0,0,0,1357,1358,3,83,34,0,1358,1359,1,0,0,0,1359, - 1360,6,172,21,0,1360,360,1,0,0,0,1361,1362,3,55,20,0,1362,1363,1,0,0,0, - 1363,1364,6,173,10,0,1364,362,1,0,0,0,1365,1366,3,57,21,0,1366,1367,1,0, - 0,0,1367,1368,6,174,10,0,1368,364,1,0,0,0,1369,1370,3,59,22,0,1370,1371, - 1,0,0,0,1371,1372,6,175,10,0,1372,366,1,0,0,0,1373,1374,3,61,23,0,1374, - 1375,1,0,0,0,1375,1376,6,176,15,0,1376,1377,6,176,11,0,1377,1378,6,176, - 11,0,1378,368,1,0,0,0,1379,1380,3,99,42,0,1380,1381,1,0,0,0,1381,1382,6, - 177,18,0,1382,370,1,0,0,0,1383,1384,3,103,44,0,1384,1385,1,0,0,0,1385,1386, - 6,178,22,0,1386,372,1,0,0,0,1387,1388,3,233,109,0,1388,1389,1,0,0,0,1389, - 1390,6,179,25,0,1390,374,1,0,0,0,1391,1392,3,55,20,0,1392,1393,1,0,0,0, - 1393,1394,6,180,10,0,1394,376,1,0,0,0,1395,1396,3,57,21,0,1396,1397,1,0, - 0,0,1397,1398,6,181,10,0,1398,378,1,0,0,0,1399,1400,3,59,22,0,1400,1401, - 1,0,0,0,1401,1402,6,182,10,0,1402,380,1,0,0,0,1403,1404,3,61,23,0,1404, - 1405,1,0,0,0,1405,1406,6,183,15,0,1406,1407,6,183,11,0,1407,382,1,0,0,0, - 1408,1409,3,207,96,0,1409,1410,1,0,0,0,1410,1411,6,184,20,0,1411,1412,6, - 184,11,0,1412,1413,6,184,34,0,1413,384,1,0,0,0,1414,1415,3,83,34,0,1415, - 1416,1,0,0,0,1416,1417,6,185,21,0,1417,1418,6,185,11,0,1418,1419,6,185, - 34,0,1419,386,1,0,0,0,1420,1421,3,55,20,0,1421,1422,1,0,0,0,1422,1423,6, - 186,10,0,1423,388,1,0,0,0,1424,1425,3,57,21,0,1425,1426,1,0,0,0,1426,1427, - 6,187,10,0,1427,390,1,0,0,0,1428,1429,3,59,22,0,1429,1430,1,0,0,0,1430, - 1431,6,188,10,0,1431,392,1,0,0,0,1432,1433,3,337,161,0,1433,1434,1,0,0, - 0,1434,1435,6,189,17,0,1435,1436,6,189,11,0,1436,1437,6,189,9,0,1437,394, - 1,0,0,0,1438,1439,3,99,42,0,1439,1440,1,0,0,0,1440,1441,6,190,18,0,1441, - 1442,6,190,11,0,1442,1443,6,190,9,0,1443,396,1,0,0,0,1444,1445,3,55,20, - 0,1445,1446,1,0,0,0,1446,1447,6,191,10,0,1447,398,1,0,0,0,1448,1449,3,57, - 21,0,1449,1450,1,0,0,0,1450,1451,6,192,10,0,1451,400,1,0,0,0,1452,1453, - 3,59,22,0,1453,1454,1,0,0,0,1454,1455,6,193,10,0,1455,402,1,0,0,0,1456, - 1457,3,173,79,0,1457,1458,1,0,0,0,1458,1459,6,194,11,0,1459,1460,6,194, - 0,0,1460,1461,6,194,30,0,1461,404,1,0,0,0,1462,1463,3,169,77,0,1463,1464, - 1,0,0,0,1464,1465,6,195,11,0,1465,1466,6,195,0,0,1466,1467,6,195,31,0,1467, - 406,1,0,0,0,1468,1469,3,89,37,0,1469,1470,1,0,0,0,1470,1471,6,196,11,0, - 1471,1472,6,196,0,0,1472,1473,6,196,35,0,1473,408,1,0,0,0,1474,1475,3,61, - 23,0,1475,1476,1,0,0,0,1476,1477,6,197,15,0,1477,1478,6,197,11,0,1478,410, - 1,0,0,0,65,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,579,589,593,596,605,607,618, - 637,642,651,658,663,665,676,684,687,689,694,699,705,712,717,723,726,734, - 738,870,875,882,884,900,905,910,912,918,995,1000,1049,1053,1058,1063,1068, - 1070,1074,1076,1163,1167,1172,1315,1317,36,5,1,0,5,4,0,5,6,0,5,2,0,5,3, - 0,5,8,0,5,5,0,5,9,0,5,11,0,5,13,0,0,1,0,4,0,0,7,16,0,7,65,0,5,0,0,7,24, - 0,7,66,0,7,104,0,7,33,0,7,31,0,7,76,0,7,25,0,7,35,0,7,47,0,7,64,0,7,80, - 0,5,10,0,5,7,0,7,90,0,7,89,0,7,68,0,7,67,0,7,88,0,5,12,0,5,14,0,7,28,0]; + 2,193,7,193,2,194,7,194,2,195,7,195,2,196,7,196,2,197,7,197,2,198,7,198, + 1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2, + 1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,4,1,4,1,4, + 1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,5,1,5,1,5,1,5,1,5,1,5,1,5,1,6,1,6,1,6,1,6, + 1,6,1,6,1,6,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8, + 1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,10,1,10,1,10,1,10,1,10, + 1,10,1,10,1,10,1,10,1,11,1,11,1,11,1,11,1,11,1,11,1,12,1,12,1,12,1,12,1, + 12,1,12,1,12,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,14,1,14,1,14,1,14,1,14, + 1,14,1,14,1,14,1,15,1,15,1,15,1,15,1,15,1,15,1,15,1,15,1,16,1,16,1,16,1, + 16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,17,1,17,1,17, + 1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,18,1,18,1,18,1,18,1,18,1,18,1,18,1, + 18,1,18,1,18,1,18,1,19,4,19,580,8,19,11,19,12,19,581,1,19,1,19,1,20,1,20, + 1,20,1,20,5,20,590,8,20,10,20,12,20,593,9,20,1,20,3,20,596,8,20,1,20,3, + 20,599,8,20,1,20,1,20,1,21,1,21,1,21,1,21,1,21,5,21,608,8,21,10,21,12,21, + 611,9,21,1,21,1,21,1,21,1,21,1,21,1,22,4,22,619,8,22,11,22,12,22,620,1, + 22,1,22,1,23,1,23,1,24,1,24,1,24,1,24,1,25,1,25,1,26,1,26,1,27,1,27,1,27, + 1,28,1,28,1,29,1,29,3,29,642,8,29,1,29,4,29,645,8,29,11,29,12,29,646,1, + 30,1,30,1,31,1,31,1,32,1,32,1,32,3,32,656,8,32,1,33,1,33,1,34,1,34,1,34, + 3,34,663,8,34,1,35,1,35,1,35,5,35,668,8,35,10,35,12,35,671,9,35,1,35,1, + 35,1,35,1,35,1,35,1,35,5,35,679,8,35,10,35,12,35,682,9,35,1,35,1,35,1,35, + 1,35,1,35,3,35,689,8,35,1,35,3,35,692,8,35,3,35,694,8,35,1,36,4,36,697, + 8,36,11,36,12,36,698,1,37,4,37,702,8,37,11,37,12,37,703,1,37,1,37,5,37, + 708,8,37,10,37,12,37,711,9,37,1,37,1,37,4,37,715,8,37,11,37,12,37,716,1, + 37,4,37,720,8,37,11,37,12,37,721,1,37,1,37,5,37,726,8,37,10,37,12,37,729, + 9,37,3,37,731,8,37,1,37,1,37,1,37,1,37,4,37,737,8,37,11,37,12,37,738,1, + 37,1,37,3,37,743,8,37,1,38,1,38,1,38,1,39,1,39,1,39,1,39,1,40,1,40,1,40, + 1,40,1,41,1,41,1,42,1,42,1,42,1,43,1,43,1,44,1,44,1,44,1,44,1,44,1,45,1, + 45,1,46,1,46,1,46,1,46,1,46,1,46,1,47,1,47,1,47,1,47,1,47,1,47,1,48,1,48, + 1,48,1,49,1,49,1,49,1,50,1,50,1,50,1,50,1,50,1,51,1,51,1,51,1,51,1,51,1, + 52,1,52,1,53,1,53,1,53,1,53,1,54,1,54,1,54,1,54,1,54,1,55,1,55,1,55,1,55, + 1,55,1,55,1,56,1,56,1,56,1,57,1,57,1,58,1,58,1,58,1,58,1,58,1,58,1,59,1, + 59,1,60,1,60,1,60,1,60,1,60,1,61,1,61,1,61,1,62,1,62,1,62,1,63,1,63,1,63, + 1,64,1,64,1,65,1,65,1,65,1,66,1,66,1,67,1,67,1,67,1,68,1,68,1,69,1,69,1, + 70,1,70,1,71,1,71,1,72,1,72,1,73,1,73,1,73,1,73,1,73,1,74,1,74,1,74,1,74, + 1,75,1,75,1,75,3,75,874,8,75,1,75,5,75,877,8,75,10,75,12,75,880,9,75,1, + 75,1,75,4,75,884,8,75,11,75,12,75,885,3,75,888,8,75,1,76,1,76,1,76,1,76, + 1,76,1,77,1,77,1,77,1,77,1,77,1,78,1,78,5,78,902,8,78,10,78,12,78,905,9, + 78,1,78,1,78,3,78,909,8,78,1,78,4,78,912,8,78,11,78,12,78,913,3,78,916, + 8,78,1,79,1,79,4,79,920,8,79,11,79,12,79,921,1,79,1,79,1,80,1,80,1,81,1, + 81,1,81,1,81,1,82,1,82,1,82,1,82,1,83,1,83,1,83,1,83,1,84,1,84,1,84,1,84, + 1,84,1,85,1,85,1,85,1,85,1,85,1,86,1,86,1,86,1,86,1,87,1,87,1,87,1,87,1, + 88,1,88,1,88,1,88,1,89,1,89,1,89,1,89,1,89,1,90,1,90,1,90,1,90,1,91,1,91, + 1,91,1,91,1,92,1,92,1,92,1,92,1,93,1,93,1,93,1,93,1,94,1,94,1,94,1,94,1, + 95,1,95,1,95,1,95,1,95,1,95,1,95,1,95,1,95,1,96,1,96,1,96,3,96,999,8,96, + 1,97,4,97,1002,8,97,11,97,12,97,1003,1,98,1,98,1,98,1,98,1,99,1,99,1,99, + 1,99,1,100,1,100,1,100,1,100,1,101,1,101,1,101,1,101,1,102,1,102,1,102, + 1,102,1,103,1,103,1,103,1,103,1,103,1,104,1,104,1,104,1,104,1,105,1,105, + 1,105,1,105,1,106,1,106,1,106,1,106,1,106,1,107,1,107,1,107,1,107,1,107, + 1,108,1,108,1,108,1,108,3,108,1053,8,108,1,109,1,109,3,109,1057,8,109,1, + 109,5,109,1060,8,109,10,109,12,109,1063,9,109,1,109,1,109,3,109,1067,8, + 109,1,109,4,109,1070,8,109,11,109,12,109,1071,3,109,1074,8,109,1,110,1, + 110,4,110,1078,8,110,11,110,12,110,1079,1,111,1,111,1,111,1,111,1,112,1, + 112,1,112,1,112,1,113,1,113,1,113,1,113,1,114,1,114,1,114,1,114,1,114,1, + 115,1,115,1,115,1,115,1,116,1,116,1,116,1,116,1,117,1,117,1,117,1,117,1, + 118,1,118,1,118,1,118,1,118,1,119,1,119,1,119,1,119,1,119,1,120,1,120,1, + 120,1,121,1,121,1,121,1,121,1,122,1,122,1,122,1,122,1,123,1,123,1,123,1, + 123,1,124,1,124,1,124,1,124,1,125,1,125,1,125,1,125,1,125,1,126,1,126,1, + 126,1,126,1,126,1,127,1,127,1,127,1,127,1,127,1,128,1,128,1,128,1,128,1, + 128,1,128,1,128,1,129,1,129,1,130,4,130,1165,8,130,11,130,12,130,1166,1, + 130,1,130,3,130,1171,8,130,1,130,4,130,1174,8,130,11,130,12,130,1175,1, + 131,1,131,1,131,1,131,1,132,1,132,1,132,1,132,1,133,1,133,1,133,1,133,1, + 134,1,134,1,134,1,134,1,135,1,135,1,135,1,135,1,135,1,135,1,136,1,136,1, + 136,1,136,1,137,1,137,1,137,1,137,1,138,1,138,1,138,1,138,1,139,1,139,1, + 139,1,139,1,140,1,140,1,140,1,140,1,141,1,141,1,141,1,141,1,142,1,142,1, + 142,1,142,1,142,1,143,1,143,1,143,1,143,1,143,1,144,1,144,1,144,1,144,1, + 145,1,145,1,145,1,145,1,146,1,146,1,146,1,146,1,147,1,147,1,147,1,147,1, + 147,1,148,1,148,1,148,1,148,1,149,1,149,1,149,1,149,1,149,1,150,1,150,1, + 150,1,150,1,150,1,151,1,151,1,151,1,151,1,152,1,152,1,152,1,152,1,153,1, + 153,1,153,1,153,1,154,1,154,1,154,1,154,1,155,1,155,1,155,1,155,1,156,1, + 156,1,156,1,156,1,156,1,157,1,157,1,157,1,157,1,157,1,158,1,158,1,158,1, + 158,1,159,1,159,1,159,1,159,1,160,1,160,1,160,1,160,1,161,1,161,1,161,1, + 161,1,161,1,162,1,162,1,162,1,162,1,163,1,163,1,163,1,163,1,163,4,163,1321, + 8,163,11,163,12,163,1322,1,164,1,164,1,164,1,164,1,165,1,165,1,165,1,165, + 1,166,1,166,1,166,1,166,1,167,1,167,1,167,1,167,1,167,1,168,1,168,1,168, + 1,168,1,169,1,169,1,169,1,169,1,170,1,170,1,170,1,170,1,171,1,171,1,171, + 1,171,1,171,1,172,1,172,1,172,1,172,1,173,1,173,1,173,1,173,1,174,1,174, + 1,174,1,174,1,175,1,175,1,175,1,175,1,176,1,176,1,176,1,176,1,177,1,177, + 1,177,1,177,1,177,1,177,1,178,1,178,1,178,1,178,1,179,1,179,1,179,1,179, + 1,180,1,180,1,180,1,180,1,181,1,181,1,181,1,181,1,182,1,182,1,182,1,182, + 1,183,1,183,1,183,1,183,1,184,1,184,1,184,1,184,1,184,1,185,1,185,1,185, + 1,185,1,185,1,185,1,186,1,186,1,186,1,186,1,186,1,186,1,187,1,187,1,187, + 1,187,1,188,1,188,1,188,1,188,1,189,1,189,1,189,1,189,1,190,1,190,1,190, + 1,190,1,190,1,190,1,191,1,191,1,191,1,191,1,191,1,191,1,192,1,192,1,192, + 1,192,1,193,1,193,1,193,1,193,1,194,1,194,1,194,1,194,1,195,1,195,1,195, + 1,195,1,195,1,195,1,196,1,196,1,196,1,196,1,196,1,196,1,197,1,197,1,197, + 1,197,1,197,1,197,1,198,1,198,1,198,1,198,1,198,2,609,680,0,199,15,1,17, + 2,19,3,21,4,23,5,25,6,27,7,29,8,31,9,33,10,35,11,37,12,39,13,41,14,43,15, + 45,16,47,17,49,18,51,19,53,20,55,21,57,22,59,23,61,24,63,25,65,0,67,0,69, + 0,71,0,73,0,75,0,77,0,79,0,81,0,83,0,85,26,87,27,89,28,91,29,93,30,95,31, + 97,32,99,33,101,34,103,35,105,36,107,37,109,38,111,39,113,40,115,41,117, + 42,119,43,121,44,123,45,125,46,127,47,129,48,131,49,133,50,135,51,137,52, + 139,53,141,54,143,55,145,56,147,57,149,58,151,59,153,60,155,61,157,62,159, + 63,161,0,163,0,165,64,167,65,169,66,171,67,173,0,175,68,177,69,179,70,181, + 71,183,0,185,0,187,72,189,73,191,74,193,0,195,0,197,0,199,0,201,0,203,0, + 205,75,207,0,209,76,211,0,213,0,215,77,217,78,219,79,221,0,223,0,225,0, + 227,0,229,0,231,0,233,0,235,80,237,81,239,82,241,83,243,0,245,0,247,0,249, + 0,251,0,253,0,255,84,257,0,259,85,261,86,263,87,265,0,267,0,269,88,271, + 89,273,0,275,90,277,0,279,91,281,92,283,93,285,0,287,0,289,0,291,0,293, + 0,295,0,297,0,299,0,301,0,303,94,305,95,307,96,309,0,311,0,313,0,315,0, + 317,0,319,0,321,97,323,98,325,99,327,0,329,100,331,101,333,102,335,103, + 337,0,339,0,341,104,343,105,345,106,347,107,349,0,351,0,353,0,355,0,357, + 0,359,0,361,0,363,108,365,109,367,110,369,0,371,0,373,0,375,0,377,111,379, + 112,381,113,383,0,385,0,387,0,389,114,391,115,393,116,395,0,397,0,399,117, + 401,118,403,119,405,0,407,0,409,0,411,0,15,0,1,2,3,4,5,6,7,8,9,10,11,12, + 13,14,35,2,0,68,68,100,100,2,0,73,73,105,105,2,0,83,83,115,115,2,0,69,69, + 101,101,2,0,67,67,99,99,2,0,84,84,116,116,2,0,82,82,114,114,2,0,79,79,111, + 111,2,0,80,80,112,112,2,0,78,78,110,110,2,0,72,72,104,104,2,0,86,86,118, + 118,2,0,65,65,97,97,2,0,76,76,108,108,2,0,88,88,120,120,2,0,70,70,102,102, + 2,0,77,77,109,109,2,0,71,71,103,103,2,0,75,75,107,107,2,0,87,87,119,119, + 2,0,85,85,117,117,6,0,9,10,13,13,32,32,47,47,91,91,93,93,2,0,10,10,13,13, + 3,0,9,10,13,13,32,32,1,0,48,57,2,0,65,90,97,122,8,0,34,34,78,78,82,82,84, + 84,92,92,110,110,114,114,116,116,4,0,10,10,13,13,34,34,92,92,2,0,43,43, + 45,45,1,0,96,96,2,0,66,66,98,98,2,0,89,89,121,121,11,0,9,10,13,13,32,32, + 34,34,44,44,47,47,58,58,61,61,91,91,93,93,124,124,2,0,42,42,47,47,11,0, + 9,10,13,13,32,32,34,35,44,44,47,47,58,58,60,60,62,63,92,92,124,124,1512, + 0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0,23,1,0,0,0,0,25,1, + 0,0,0,0,27,1,0,0,0,0,29,1,0,0,0,0,31,1,0,0,0,0,33,1,0,0,0,0,35,1,0,0,0, + 0,37,1,0,0,0,0,39,1,0,0,0,0,41,1,0,0,0,0,43,1,0,0,0,0,45,1,0,0,0,0,47,1, + 0,0,0,0,49,1,0,0,0,0,51,1,0,0,0,0,53,1,0,0,0,0,55,1,0,0,0,0,57,1,0,0,0, + 0,59,1,0,0,0,0,61,1,0,0,0,1,63,1,0,0,0,1,85,1,0,0,0,1,87,1,0,0,0,1,89,1, + 0,0,0,1,91,1,0,0,0,1,93,1,0,0,0,1,95,1,0,0,0,1,97,1,0,0,0,1,99,1,0,0,0, + 1,101,1,0,0,0,1,103,1,0,0,0,1,105,1,0,0,0,1,107,1,0,0,0,1,109,1,0,0,0,1, + 111,1,0,0,0,1,113,1,0,0,0,1,115,1,0,0,0,1,117,1,0,0,0,1,119,1,0,0,0,1,121, + 1,0,0,0,1,123,1,0,0,0,1,125,1,0,0,0,1,127,1,0,0,0,1,129,1,0,0,0,1,131,1, + 0,0,0,1,133,1,0,0,0,1,135,1,0,0,0,1,137,1,0,0,0,1,139,1,0,0,0,1,141,1,0, + 0,0,1,143,1,0,0,0,1,145,1,0,0,0,1,147,1,0,0,0,1,149,1,0,0,0,1,151,1,0,0, + 0,1,153,1,0,0,0,1,155,1,0,0,0,1,157,1,0,0,0,1,159,1,0,0,0,1,161,1,0,0,0, + 1,163,1,0,0,0,1,165,1,0,0,0,1,167,1,0,0,0,1,169,1,0,0,0,1,171,1,0,0,0,1, + 175,1,0,0,0,1,177,1,0,0,0,1,179,1,0,0,0,1,181,1,0,0,0,2,183,1,0,0,0,2,185, + 1,0,0,0,2,187,1,0,0,0,2,189,1,0,0,0,2,191,1,0,0,0,3,193,1,0,0,0,3,195,1, + 0,0,0,3,197,1,0,0,0,3,199,1,0,0,0,3,201,1,0,0,0,3,203,1,0,0,0,3,205,1,0, + 0,0,3,209,1,0,0,0,3,211,1,0,0,0,3,213,1,0,0,0,3,215,1,0,0,0,3,217,1,0,0, + 0,3,219,1,0,0,0,4,221,1,0,0,0,4,223,1,0,0,0,4,225,1,0,0,0,4,227,1,0,0,0, + 4,229,1,0,0,0,4,235,1,0,0,0,4,237,1,0,0,0,4,239,1,0,0,0,4,241,1,0,0,0,5, + 243,1,0,0,0,5,245,1,0,0,0,5,247,1,0,0,0,5,249,1,0,0,0,5,251,1,0,0,0,5,253, + 1,0,0,0,5,255,1,0,0,0,5,257,1,0,0,0,5,259,1,0,0,0,5,261,1,0,0,0,5,263,1, + 0,0,0,6,265,1,0,0,0,6,267,1,0,0,0,6,269,1,0,0,0,6,271,1,0,0,0,6,275,1,0, + 0,0,6,277,1,0,0,0,6,279,1,0,0,0,6,281,1,0,0,0,6,283,1,0,0,0,7,285,1,0,0, + 0,7,287,1,0,0,0,7,289,1,0,0,0,7,291,1,0,0,0,7,293,1,0,0,0,7,295,1,0,0,0, + 7,297,1,0,0,0,7,299,1,0,0,0,7,301,1,0,0,0,7,303,1,0,0,0,7,305,1,0,0,0,7, + 307,1,0,0,0,8,309,1,0,0,0,8,311,1,0,0,0,8,313,1,0,0,0,8,315,1,0,0,0,8,317, + 1,0,0,0,8,319,1,0,0,0,8,321,1,0,0,0,8,323,1,0,0,0,8,325,1,0,0,0,9,327,1, + 0,0,0,9,329,1,0,0,0,9,331,1,0,0,0,9,333,1,0,0,0,9,335,1,0,0,0,10,337,1, + 0,0,0,10,339,1,0,0,0,10,341,1,0,0,0,10,343,1,0,0,0,10,345,1,0,0,0,10,347, + 1,0,0,0,11,349,1,0,0,0,11,351,1,0,0,0,11,353,1,0,0,0,11,355,1,0,0,0,11, + 357,1,0,0,0,11,359,1,0,0,0,11,361,1,0,0,0,11,363,1,0,0,0,11,365,1,0,0,0, + 11,367,1,0,0,0,12,369,1,0,0,0,12,371,1,0,0,0,12,373,1,0,0,0,12,375,1,0, + 0,0,12,377,1,0,0,0,12,379,1,0,0,0,12,381,1,0,0,0,13,383,1,0,0,0,13,385, + 1,0,0,0,13,387,1,0,0,0,13,389,1,0,0,0,13,391,1,0,0,0,13,393,1,0,0,0,14, + 395,1,0,0,0,14,397,1,0,0,0,14,399,1,0,0,0,14,401,1,0,0,0,14,403,1,0,0,0, + 14,405,1,0,0,0,14,407,1,0,0,0,14,409,1,0,0,0,14,411,1,0,0,0,15,413,1,0, + 0,0,17,423,1,0,0,0,19,430,1,0,0,0,21,439,1,0,0,0,23,446,1,0,0,0,25,456, + 1,0,0,0,27,463,1,0,0,0,29,470,1,0,0,0,31,477,1,0,0,0,33,485,1,0,0,0,35, + 497,1,0,0,0,37,506,1,0,0,0,39,512,1,0,0,0,41,519,1,0,0,0,43,526,1,0,0,0, + 45,534,1,0,0,0,47,542,1,0,0,0,49,557,1,0,0,0,51,567,1,0,0,0,53,579,1,0, + 0,0,55,585,1,0,0,0,57,602,1,0,0,0,59,618,1,0,0,0,61,624,1,0,0,0,63,626, + 1,0,0,0,65,630,1,0,0,0,67,632,1,0,0,0,69,634,1,0,0,0,71,637,1,0,0,0,73, + 639,1,0,0,0,75,648,1,0,0,0,77,650,1,0,0,0,79,655,1,0,0,0,81,657,1,0,0,0, + 83,662,1,0,0,0,85,693,1,0,0,0,87,696,1,0,0,0,89,742,1,0,0,0,91,744,1,0, + 0,0,93,747,1,0,0,0,95,751,1,0,0,0,97,755,1,0,0,0,99,757,1,0,0,0,101,760, + 1,0,0,0,103,762,1,0,0,0,105,767,1,0,0,0,107,769,1,0,0,0,109,775,1,0,0,0, + 111,781,1,0,0,0,113,784,1,0,0,0,115,787,1,0,0,0,117,792,1,0,0,0,119,797, + 1,0,0,0,121,799,1,0,0,0,123,803,1,0,0,0,125,808,1,0,0,0,127,814,1,0,0,0, + 129,817,1,0,0,0,131,819,1,0,0,0,133,825,1,0,0,0,135,827,1,0,0,0,137,832, + 1,0,0,0,139,835,1,0,0,0,141,838,1,0,0,0,143,841,1,0,0,0,145,843,1,0,0,0, + 147,846,1,0,0,0,149,848,1,0,0,0,151,851,1,0,0,0,153,853,1,0,0,0,155,855, + 1,0,0,0,157,857,1,0,0,0,159,859,1,0,0,0,161,861,1,0,0,0,163,866,1,0,0,0, + 165,887,1,0,0,0,167,889,1,0,0,0,169,894,1,0,0,0,171,915,1,0,0,0,173,917, + 1,0,0,0,175,925,1,0,0,0,177,927,1,0,0,0,179,931,1,0,0,0,181,935,1,0,0,0, + 183,939,1,0,0,0,185,944,1,0,0,0,187,949,1,0,0,0,189,953,1,0,0,0,191,957, + 1,0,0,0,193,961,1,0,0,0,195,966,1,0,0,0,197,970,1,0,0,0,199,974,1,0,0,0, + 201,978,1,0,0,0,203,982,1,0,0,0,205,986,1,0,0,0,207,998,1,0,0,0,209,1001, + 1,0,0,0,211,1005,1,0,0,0,213,1009,1,0,0,0,215,1013,1,0,0,0,217,1017,1,0, + 0,0,219,1021,1,0,0,0,221,1025,1,0,0,0,223,1030,1,0,0,0,225,1034,1,0,0,0, + 227,1038,1,0,0,0,229,1043,1,0,0,0,231,1052,1,0,0,0,233,1073,1,0,0,0,235, + 1077,1,0,0,0,237,1081,1,0,0,0,239,1085,1,0,0,0,241,1089,1,0,0,0,243,1093, + 1,0,0,0,245,1098,1,0,0,0,247,1102,1,0,0,0,249,1106,1,0,0,0,251,1110,1,0, + 0,0,253,1115,1,0,0,0,255,1120,1,0,0,0,257,1123,1,0,0,0,259,1127,1,0,0,0, + 261,1131,1,0,0,0,263,1135,1,0,0,0,265,1139,1,0,0,0,267,1144,1,0,0,0,269, + 1149,1,0,0,0,271,1154,1,0,0,0,273,1161,1,0,0,0,275,1170,1,0,0,0,277,1177, + 1,0,0,0,279,1181,1,0,0,0,281,1185,1,0,0,0,283,1189,1,0,0,0,285,1193,1,0, + 0,0,287,1199,1,0,0,0,289,1203,1,0,0,0,291,1207,1,0,0,0,293,1211,1,0,0,0, + 295,1215,1,0,0,0,297,1219,1,0,0,0,299,1223,1,0,0,0,301,1228,1,0,0,0,303, + 1233,1,0,0,0,305,1237,1,0,0,0,307,1241,1,0,0,0,309,1245,1,0,0,0,311,1250, + 1,0,0,0,313,1254,1,0,0,0,315,1259,1,0,0,0,317,1264,1,0,0,0,319,1268,1,0, + 0,0,321,1272,1,0,0,0,323,1276,1,0,0,0,325,1280,1,0,0,0,327,1284,1,0,0,0, + 329,1289,1,0,0,0,331,1294,1,0,0,0,333,1298,1,0,0,0,335,1302,1,0,0,0,337, + 1306,1,0,0,0,339,1311,1,0,0,0,341,1320,1,0,0,0,343,1324,1,0,0,0,345,1328, + 1,0,0,0,347,1332,1,0,0,0,349,1336,1,0,0,0,351,1341,1,0,0,0,353,1345,1,0, + 0,0,355,1349,1,0,0,0,357,1353,1,0,0,0,359,1358,1,0,0,0,361,1362,1,0,0,0, + 363,1366,1,0,0,0,365,1370,1,0,0,0,367,1374,1,0,0,0,369,1378,1,0,0,0,371, + 1384,1,0,0,0,373,1388,1,0,0,0,375,1392,1,0,0,0,377,1396,1,0,0,0,379,1400, + 1,0,0,0,381,1404,1,0,0,0,383,1408,1,0,0,0,385,1413,1,0,0,0,387,1419,1,0, + 0,0,389,1425,1,0,0,0,391,1429,1,0,0,0,393,1433,1,0,0,0,395,1437,1,0,0,0, + 397,1443,1,0,0,0,399,1449,1,0,0,0,401,1453,1,0,0,0,403,1457,1,0,0,0,405, + 1461,1,0,0,0,407,1467,1,0,0,0,409,1473,1,0,0,0,411,1479,1,0,0,0,413,414, + 7,0,0,0,414,415,7,1,0,0,415,416,7,2,0,0,416,417,7,2,0,0,417,418,7,3,0,0, + 418,419,7,4,0,0,419,420,7,5,0,0,420,421,1,0,0,0,421,422,6,0,0,0,422,16, + 1,0,0,0,423,424,7,0,0,0,424,425,7,6,0,0,425,426,7,7,0,0,426,427,7,8,0,0, + 427,428,1,0,0,0,428,429,6,1,1,0,429,18,1,0,0,0,430,431,7,3,0,0,431,432, + 7,9,0,0,432,433,7,6,0,0,433,434,7,1,0,0,434,435,7,4,0,0,435,436,7,10,0, + 0,436,437,1,0,0,0,437,438,6,2,2,0,438,20,1,0,0,0,439,440,7,3,0,0,440,441, + 7,11,0,0,441,442,7,12,0,0,442,443,7,13,0,0,443,444,1,0,0,0,444,445,6,3, + 0,0,445,22,1,0,0,0,446,447,7,3,0,0,447,448,7,14,0,0,448,449,7,8,0,0,449, + 450,7,13,0,0,450,451,7,12,0,0,451,452,7,1,0,0,452,453,7,9,0,0,453,454,1, + 0,0,0,454,455,6,4,3,0,455,24,1,0,0,0,456,457,7,15,0,0,457,458,7,6,0,0,458, + 459,7,7,0,0,459,460,7,16,0,0,460,461,1,0,0,0,461,462,6,5,4,0,462,26,1,0, + 0,0,463,464,7,17,0,0,464,465,7,6,0,0,465,466,7,7,0,0,466,467,7,18,0,0,467, + 468,1,0,0,0,468,469,6,6,0,0,469,28,1,0,0,0,470,471,7,18,0,0,471,472,7,3, + 0,0,472,473,7,3,0,0,473,474,7,8,0,0,474,475,1,0,0,0,475,476,6,7,1,0,476, + 30,1,0,0,0,477,478,7,13,0,0,478,479,7,1,0,0,479,480,7,16,0,0,480,481,7, + 1,0,0,481,482,7,5,0,0,482,483,1,0,0,0,483,484,6,8,0,0,484,32,1,0,0,0,485, + 486,7,16,0,0,486,487,7,11,0,0,487,488,5,95,0,0,488,489,7,3,0,0,489,490, + 7,14,0,0,490,491,7,8,0,0,491,492,7,12,0,0,492,493,7,9,0,0,493,494,7,0,0, + 0,494,495,1,0,0,0,495,496,6,9,5,0,496,34,1,0,0,0,497,498,7,6,0,0,498,499, + 7,3,0,0,499,500,7,9,0,0,500,501,7,12,0,0,501,502,7,16,0,0,502,503,7,3,0, + 0,503,504,1,0,0,0,504,505,6,10,6,0,505,36,1,0,0,0,506,507,7,6,0,0,507,508, + 7,7,0,0,508,509,7,19,0,0,509,510,1,0,0,0,510,511,6,11,0,0,511,38,1,0,0, + 0,512,513,7,2,0,0,513,514,7,10,0,0,514,515,7,7,0,0,515,516,7,19,0,0,516, + 517,1,0,0,0,517,518,6,12,7,0,518,40,1,0,0,0,519,520,7,2,0,0,520,521,7,7, + 0,0,521,522,7,6,0,0,522,523,7,5,0,0,523,524,1,0,0,0,524,525,6,13,0,0,525, + 42,1,0,0,0,526,527,7,2,0,0,527,528,7,5,0,0,528,529,7,12,0,0,529,530,7,5, + 0,0,530,531,7,2,0,0,531,532,1,0,0,0,532,533,6,14,0,0,533,44,1,0,0,0,534, + 535,7,19,0,0,535,536,7,10,0,0,536,537,7,3,0,0,537,538,7,6,0,0,538,539,7, + 3,0,0,539,540,1,0,0,0,540,541,6,15,0,0,541,46,1,0,0,0,542,543,4,16,0,0, + 543,544,7,1,0,0,544,545,7,9,0,0,545,546,7,13,0,0,546,547,7,1,0,0,547,548, + 7,9,0,0,548,549,7,3,0,0,549,550,7,2,0,0,550,551,7,5,0,0,551,552,7,12,0, + 0,552,553,7,5,0,0,553,554,7,2,0,0,554,555,1,0,0,0,555,556,6,16,0,0,556, + 48,1,0,0,0,557,558,4,17,1,0,558,559,7,13,0,0,559,560,7,7,0,0,560,561,7, + 7,0,0,561,562,7,18,0,0,562,563,7,20,0,0,563,564,7,8,0,0,564,565,1,0,0,0, + 565,566,6,17,8,0,566,50,1,0,0,0,567,568,4,18,2,0,568,569,7,16,0,0,569,570, + 7,3,0,0,570,571,7,5,0,0,571,572,7,6,0,0,572,573,7,1,0,0,573,574,7,4,0,0, + 574,575,7,2,0,0,575,576,1,0,0,0,576,577,6,18,9,0,577,52,1,0,0,0,578,580, + 8,21,0,0,579,578,1,0,0,0,580,581,1,0,0,0,581,579,1,0,0,0,581,582,1,0,0, + 0,582,583,1,0,0,0,583,584,6,19,0,0,584,54,1,0,0,0,585,586,5,47,0,0,586, + 587,5,47,0,0,587,591,1,0,0,0,588,590,8,22,0,0,589,588,1,0,0,0,590,593,1, + 0,0,0,591,589,1,0,0,0,591,592,1,0,0,0,592,595,1,0,0,0,593,591,1,0,0,0,594, + 596,5,13,0,0,595,594,1,0,0,0,595,596,1,0,0,0,596,598,1,0,0,0,597,599,5, + 10,0,0,598,597,1,0,0,0,598,599,1,0,0,0,599,600,1,0,0,0,600,601,6,20,10, + 0,601,56,1,0,0,0,602,603,5,47,0,0,603,604,5,42,0,0,604,609,1,0,0,0,605, + 608,3,57,21,0,606,608,9,0,0,0,607,605,1,0,0,0,607,606,1,0,0,0,608,611,1, + 0,0,0,609,610,1,0,0,0,609,607,1,0,0,0,610,612,1,0,0,0,611,609,1,0,0,0,612, + 613,5,42,0,0,613,614,5,47,0,0,614,615,1,0,0,0,615,616,6,21,10,0,616,58, + 1,0,0,0,617,619,7,23,0,0,618,617,1,0,0,0,619,620,1,0,0,0,620,618,1,0,0, + 0,620,621,1,0,0,0,621,622,1,0,0,0,622,623,6,22,10,0,623,60,1,0,0,0,624, + 625,5,58,0,0,625,62,1,0,0,0,626,627,5,124,0,0,627,628,1,0,0,0,628,629,6, + 24,11,0,629,64,1,0,0,0,630,631,7,24,0,0,631,66,1,0,0,0,632,633,7,25,0,0, + 633,68,1,0,0,0,634,635,5,92,0,0,635,636,7,26,0,0,636,70,1,0,0,0,637,638, + 8,27,0,0,638,72,1,0,0,0,639,641,7,3,0,0,640,642,7,28,0,0,641,640,1,0,0, + 0,641,642,1,0,0,0,642,644,1,0,0,0,643,645,3,65,25,0,644,643,1,0,0,0,645, + 646,1,0,0,0,646,644,1,0,0,0,646,647,1,0,0,0,647,74,1,0,0,0,648,649,5,64, + 0,0,649,76,1,0,0,0,650,651,5,96,0,0,651,78,1,0,0,0,652,656,8,29,0,0,653, + 654,5,96,0,0,654,656,5,96,0,0,655,652,1,0,0,0,655,653,1,0,0,0,656,80,1, + 0,0,0,657,658,5,95,0,0,658,82,1,0,0,0,659,663,3,67,26,0,660,663,3,65,25, + 0,661,663,3,81,33,0,662,659,1,0,0,0,662,660,1,0,0,0,662,661,1,0,0,0,663, + 84,1,0,0,0,664,669,5,34,0,0,665,668,3,69,27,0,666,668,3,71,28,0,667,665, + 1,0,0,0,667,666,1,0,0,0,668,671,1,0,0,0,669,667,1,0,0,0,669,670,1,0,0,0, + 670,672,1,0,0,0,671,669,1,0,0,0,672,694,5,34,0,0,673,674,5,34,0,0,674,675, + 5,34,0,0,675,676,5,34,0,0,676,680,1,0,0,0,677,679,8,22,0,0,678,677,1,0, + 0,0,679,682,1,0,0,0,680,681,1,0,0,0,680,678,1,0,0,0,681,683,1,0,0,0,682, + 680,1,0,0,0,683,684,5,34,0,0,684,685,5,34,0,0,685,686,5,34,0,0,686,688, + 1,0,0,0,687,689,5,34,0,0,688,687,1,0,0,0,688,689,1,0,0,0,689,691,1,0,0, + 0,690,692,5,34,0,0,691,690,1,0,0,0,691,692,1,0,0,0,692,694,1,0,0,0,693, + 664,1,0,0,0,693,673,1,0,0,0,694,86,1,0,0,0,695,697,3,65,25,0,696,695,1, + 0,0,0,697,698,1,0,0,0,698,696,1,0,0,0,698,699,1,0,0,0,699,88,1,0,0,0,700, + 702,3,65,25,0,701,700,1,0,0,0,702,703,1,0,0,0,703,701,1,0,0,0,703,704,1, + 0,0,0,704,705,1,0,0,0,705,709,3,105,45,0,706,708,3,65,25,0,707,706,1,0, + 0,0,708,711,1,0,0,0,709,707,1,0,0,0,709,710,1,0,0,0,710,743,1,0,0,0,711, + 709,1,0,0,0,712,714,3,105,45,0,713,715,3,65,25,0,714,713,1,0,0,0,715,716, + 1,0,0,0,716,714,1,0,0,0,716,717,1,0,0,0,717,743,1,0,0,0,718,720,3,65,25, + 0,719,718,1,0,0,0,720,721,1,0,0,0,721,719,1,0,0,0,721,722,1,0,0,0,722,730, + 1,0,0,0,723,727,3,105,45,0,724,726,3,65,25,0,725,724,1,0,0,0,726,729,1, + 0,0,0,727,725,1,0,0,0,727,728,1,0,0,0,728,731,1,0,0,0,729,727,1,0,0,0,730, + 723,1,0,0,0,730,731,1,0,0,0,731,732,1,0,0,0,732,733,3,73,29,0,733,743,1, + 0,0,0,734,736,3,105,45,0,735,737,3,65,25,0,736,735,1,0,0,0,737,738,1,0, + 0,0,738,736,1,0,0,0,738,739,1,0,0,0,739,740,1,0,0,0,740,741,3,73,29,0,741, + 743,1,0,0,0,742,701,1,0,0,0,742,712,1,0,0,0,742,719,1,0,0,0,742,734,1,0, + 0,0,743,90,1,0,0,0,744,745,7,30,0,0,745,746,7,31,0,0,746,92,1,0,0,0,747, + 748,7,12,0,0,748,749,7,9,0,0,749,750,7,0,0,0,750,94,1,0,0,0,751,752,7,12, + 0,0,752,753,7,2,0,0,753,754,7,4,0,0,754,96,1,0,0,0,755,756,5,61,0,0,756, + 98,1,0,0,0,757,758,5,58,0,0,758,759,5,58,0,0,759,100,1,0,0,0,760,761,5, + 44,0,0,761,102,1,0,0,0,762,763,7,0,0,0,763,764,7,3,0,0,764,765,7,2,0,0, + 765,766,7,4,0,0,766,104,1,0,0,0,767,768,5,46,0,0,768,106,1,0,0,0,769,770, + 7,15,0,0,770,771,7,12,0,0,771,772,7,13,0,0,772,773,7,2,0,0,773,774,7,3, + 0,0,774,108,1,0,0,0,775,776,7,15,0,0,776,777,7,1,0,0,777,778,7,6,0,0,778, + 779,7,2,0,0,779,780,7,5,0,0,780,110,1,0,0,0,781,782,7,1,0,0,782,783,7,9, + 0,0,783,112,1,0,0,0,784,785,7,1,0,0,785,786,7,2,0,0,786,114,1,0,0,0,787, + 788,7,13,0,0,788,789,7,12,0,0,789,790,7,2,0,0,790,791,7,5,0,0,791,116,1, + 0,0,0,792,793,7,13,0,0,793,794,7,1,0,0,794,795,7,18,0,0,795,796,7,3,0,0, + 796,118,1,0,0,0,797,798,5,40,0,0,798,120,1,0,0,0,799,800,7,9,0,0,800,801, + 7,7,0,0,801,802,7,5,0,0,802,122,1,0,0,0,803,804,7,9,0,0,804,805,7,20,0, + 0,805,806,7,13,0,0,806,807,7,13,0,0,807,124,1,0,0,0,808,809,7,9,0,0,809, + 810,7,20,0,0,810,811,7,13,0,0,811,812,7,13,0,0,812,813,7,2,0,0,813,126, + 1,0,0,0,814,815,7,7,0,0,815,816,7,6,0,0,816,128,1,0,0,0,817,818,5,63,0, + 0,818,130,1,0,0,0,819,820,7,6,0,0,820,821,7,13,0,0,821,822,7,1,0,0,822, + 823,7,18,0,0,823,824,7,3,0,0,824,132,1,0,0,0,825,826,5,41,0,0,826,134,1, + 0,0,0,827,828,7,5,0,0,828,829,7,6,0,0,829,830,7,20,0,0,830,831,7,3,0,0, + 831,136,1,0,0,0,832,833,5,61,0,0,833,834,5,61,0,0,834,138,1,0,0,0,835,836, + 5,61,0,0,836,837,5,126,0,0,837,140,1,0,0,0,838,839,5,33,0,0,839,840,5,61, + 0,0,840,142,1,0,0,0,841,842,5,60,0,0,842,144,1,0,0,0,843,844,5,60,0,0,844, + 845,5,61,0,0,845,146,1,0,0,0,846,847,5,62,0,0,847,148,1,0,0,0,848,849,5, + 62,0,0,849,850,5,61,0,0,850,150,1,0,0,0,851,852,5,43,0,0,852,152,1,0,0, + 0,853,854,5,45,0,0,854,154,1,0,0,0,855,856,5,42,0,0,856,156,1,0,0,0,857, + 858,5,47,0,0,858,158,1,0,0,0,859,860,5,37,0,0,860,160,1,0,0,0,861,862,4, + 73,3,0,862,863,3,61,23,0,863,864,1,0,0,0,864,865,6,73,12,0,865,162,1,0, + 0,0,866,867,3,45,15,0,867,868,1,0,0,0,868,869,6,74,13,0,869,164,1,0,0,0, + 870,873,3,129,57,0,871,874,3,67,26,0,872,874,3,81,33,0,873,871,1,0,0,0, + 873,872,1,0,0,0,874,878,1,0,0,0,875,877,3,83,34,0,876,875,1,0,0,0,877,880, + 1,0,0,0,878,876,1,0,0,0,878,879,1,0,0,0,879,888,1,0,0,0,880,878,1,0,0,0, + 881,883,3,129,57,0,882,884,3,65,25,0,883,882,1,0,0,0,884,885,1,0,0,0,885, + 883,1,0,0,0,885,886,1,0,0,0,886,888,1,0,0,0,887,870,1,0,0,0,887,881,1,0, + 0,0,888,166,1,0,0,0,889,890,5,91,0,0,890,891,1,0,0,0,891,892,6,76,0,0,892, + 893,6,76,0,0,893,168,1,0,0,0,894,895,5,93,0,0,895,896,1,0,0,0,896,897,6, + 77,11,0,897,898,6,77,11,0,898,170,1,0,0,0,899,903,3,67,26,0,900,902,3,83, + 34,0,901,900,1,0,0,0,902,905,1,0,0,0,903,901,1,0,0,0,903,904,1,0,0,0,904, + 916,1,0,0,0,905,903,1,0,0,0,906,909,3,81,33,0,907,909,3,75,30,0,908,906, + 1,0,0,0,908,907,1,0,0,0,909,911,1,0,0,0,910,912,3,83,34,0,911,910,1,0,0, + 0,912,913,1,0,0,0,913,911,1,0,0,0,913,914,1,0,0,0,914,916,1,0,0,0,915,899, + 1,0,0,0,915,908,1,0,0,0,916,172,1,0,0,0,917,919,3,77,31,0,918,920,3,79, + 32,0,919,918,1,0,0,0,920,921,1,0,0,0,921,919,1,0,0,0,921,922,1,0,0,0,922, + 923,1,0,0,0,923,924,3,77,31,0,924,174,1,0,0,0,925,926,3,173,79,0,926,176, + 1,0,0,0,927,928,3,55,20,0,928,929,1,0,0,0,929,930,6,81,10,0,930,178,1,0, + 0,0,931,932,3,57,21,0,932,933,1,0,0,0,933,934,6,82,10,0,934,180,1,0,0,0, + 935,936,3,59,22,0,936,937,1,0,0,0,937,938,6,83,10,0,938,182,1,0,0,0,939, + 940,3,167,76,0,940,941,1,0,0,0,941,942,6,84,14,0,942,943,6,84,15,0,943, + 184,1,0,0,0,944,945,3,63,24,0,945,946,1,0,0,0,946,947,6,85,16,0,947,948, + 6,85,11,0,948,186,1,0,0,0,949,950,3,59,22,0,950,951,1,0,0,0,951,952,6,86, + 10,0,952,188,1,0,0,0,953,954,3,55,20,0,954,955,1,0,0,0,955,956,6,87,10, + 0,956,190,1,0,0,0,957,958,3,57,21,0,958,959,1,0,0,0,959,960,6,88,10,0,960, + 192,1,0,0,0,961,962,3,63,24,0,962,963,1,0,0,0,963,964,6,89,16,0,964,965, + 6,89,11,0,965,194,1,0,0,0,966,967,3,167,76,0,967,968,1,0,0,0,968,969,6, + 90,14,0,969,196,1,0,0,0,970,971,3,169,77,0,971,972,1,0,0,0,972,973,6,91, + 17,0,973,198,1,0,0,0,974,975,3,61,23,0,975,976,1,0,0,0,976,977,6,92,12, + 0,977,200,1,0,0,0,978,979,3,101,43,0,979,980,1,0,0,0,980,981,6,93,18,0, + 981,202,1,0,0,0,982,983,3,97,41,0,983,984,1,0,0,0,984,985,6,94,19,0,985, + 204,1,0,0,0,986,987,7,16,0,0,987,988,7,3,0,0,988,989,7,5,0,0,989,990,7, + 12,0,0,990,991,7,0,0,0,991,992,7,12,0,0,992,993,7,5,0,0,993,994,7,12,0, + 0,994,206,1,0,0,0,995,999,8,32,0,0,996,997,5,47,0,0,997,999,8,33,0,0,998, + 995,1,0,0,0,998,996,1,0,0,0,999,208,1,0,0,0,1000,1002,3,207,96,0,1001,1000, + 1,0,0,0,1002,1003,1,0,0,0,1003,1001,1,0,0,0,1003,1004,1,0,0,0,1004,210, + 1,0,0,0,1005,1006,3,209,97,0,1006,1007,1,0,0,0,1007,1008,6,98,20,0,1008, + 212,1,0,0,0,1009,1010,3,85,35,0,1010,1011,1,0,0,0,1011,1012,6,99,21,0,1012, + 214,1,0,0,0,1013,1014,3,55,20,0,1014,1015,1,0,0,0,1015,1016,6,100,10,0, + 1016,216,1,0,0,0,1017,1018,3,57,21,0,1018,1019,1,0,0,0,1019,1020,6,101, + 10,0,1020,218,1,0,0,0,1021,1022,3,59,22,0,1022,1023,1,0,0,0,1023,1024,6, + 102,10,0,1024,220,1,0,0,0,1025,1026,3,63,24,0,1026,1027,1,0,0,0,1027,1028, + 6,103,16,0,1028,1029,6,103,11,0,1029,222,1,0,0,0,1030,1031,3,105,45,0,1031, + 1032,1,0,0,0,1032,1033,6,104,22,0,1033,224,1,0,0,0,1034,1035,3,101,43,0, + 1035,1036,1,0,0,0,1036,1037,6,105,18,0,1037,226,1,0,0,0,1038,1039,4,106, + 4,0,1039,1040,3,129,57,0,1040,1041,1,0,0,0,1041,1042,6,106,23,0,1042,228, + 1,0,0,0,1043,1044,4,107,5,0,1044,1045,3,165,75,0,1045,1046,1,0,0,0,1046, + 1047,6,107,24,0,1047,230,1,0,0,0,1048,1053,3,67,26,0,1049,1053,3,65,25, + 0,1050,1053,3,81,33,0,1051,1053,3,155,70,0,1052,1048,1,0,0,0,1052,1049, + 1,0,0,0,1052,1050,1,0,0,0,1052,1051,1,0,0,0,1053,232,1,0,0,0,1054,1057, + 3,67,26,0,1055,1057,3,155,70,0,1056,1054,1,0,0,0,1056,1055,1,0,0,0,1057, + 1061,1,0,0,0,1058,1060,3,231,108,0,1059,1058,1,0,0,0,1060,1063,1,0,0,0, + 1061,1059,1,0,0,0,1061,1062,1,0,0,0,1062,1074,1,0,0,0,1063,1061,1,0,0,0, + 1064,1067,3,81,33,0,1065,1067,3,75,30,0,1066,1064,1,0,0,0,1066,1065,1,0, + 0,0,1067,1069,1,0,0,0,1068,1070,3,231,108,0,1069,1068,1,0,0,0,1070,1071, + 1,0,0,0,1071,1069,1,0,0,0,1071,1072,1,0,0,0,1072,1074,1,0,0,0,1073,1056, + 1,0,0,0,1073,1066,1,0,0,0,1074,234,1,0,0,0,1075,1078,3,233,109,0,1076,1078, + 3,173,79,0,1077,1075,1,0,0,0,1077,1076,1,0,0,0,1078,1079,1,0,0,0,1079,1077, + 1,0,0,0,1079,1080,1,0,0,0,1080,236,1,0,0,0,1081,1082,3,55,20,0,1082,1083, + 1,0,0,0,1083,1084,6,111,10,0,1084,238,1,0,0,0,1085,1086,3,57,21,0,1086, + 1087,1,0,0,0,1087,1088,6,112,10,0,1088,240,1,0,0,0,1089,1090,3,59,22,0, + 1090,1091,1,0,0,0,1091,1092,6,113,10,0,1092,242,1,0,0,0,1093,1094,3,63, + 24,0,1094,1095,1,0,0,0,1095,1096,6,114,16,0,1096,1097,6,114,11,0,1097,244, + 1,0,0,0,1098,1099,3,97,41,0,1099,1100,1,0,0,0,1100,1101,6,115,19,0,1101, + 246,1,0,0,0,1102,1103,3,101,43,0,1103,1104,1,0,0,0,1104,1105,6,116,18,0, + 1105,248,1,0,0,0,1106,1107,3,105,45,0,1107,1108,1,0,0,0,1108,1109,6,117, + 22,0,1109,250,1,0,0,0,1110,1111,4,118,6,0,1111,1112,3,129,57,0,1112,1113, + 1,0,0,0,1113,1114,6,118,23,0,1114,252,1,0,0,0,1115,1116,4,119,7,0,1116, + 1117,3,165,75,0,1117,1118,1,0,0,0,1118,1119,6,119,24,0,1119,254,1,0,0,0, + 1120,1121,7,12,0,0,1121,1122,7,2,0,0,1122,256,1,0,0,0,1123,1124,3,235,110, + 0,1124,1125,1,0,0,0,1125,1126,6,121,25,0,1126,258,1,0,0,0,1127,1128,3,55, + 20,0,1128,1129,1,0,0,0,1129,1130,6,122,10,0,1130,260,1,0,0,0,1131,1132, + 3,57,21,0,1132,1133,1,0,0,0,1133,1134,6,123,10,0,1134,262,1,0,0,0,1135, + 1136,3,59,22,0,1136,1137,1,0,0,0,1137,1138,6,124,10,0,1138,264,1,0,0,0, + 1139,1140,3,63,24,0,1140,1141,1,0,0,0,1141,1142,6,125,16,0,1142,1143,6, + 125,11,0,1143,266,1,0,0,0,1144,1145,3,167,76,0,1145,1146,1,0,0,0,1146,1147, + 6,126,14,0,1147,1148,6,126,26,0,1148,268,1,0,0,0,1149,1150,7,7,0,0,1150, + 1151,7,9,0,0,1151,1152,1,0,0,0,1152,1153,6,127,27,0,1153,270,1,0,0,0,1154, + 1155,7,19,0,0,1155,1156,7,1,0,0,1156,1157,7,5,0,0,1157,1158,7,10,0,0,1158, + 1159,1,0,0,0,1159,1160,6,128,27,0,1160,272,1,0,0,0,1161,1162,8,34,0,0,1162, + 274,1,0,0,0,1163,1165,3,273,129,0,1164,1163,1,0,0,0,1165,1166,1,0,0,0,1166, + 1164,1,0,0,0,1166,1167,1,0,0,0,1167,1168,1,0,0,0,1168,1169,3,61,23,0,1169, + 1171,1,0,0,0,1170,1164,1,0,0,0,1170,1171,1,0,0,0,1171,1173,1,0,0,0,1172, + 1174,3,273,129,0,1173,1172,1,0,0,0,1174,1175,1,0,0,0,1175,1173,1,0,0,0, + 1175,1176,1,0,0,0,1176,276,1,0,0,0,1177,1178,3,275,130,0,1178,1179,1,0, + 0,0,1179,1180,6,131,28,0,1180,278,1,0,0,0,1181,1182,3,55,20,0,1182,1183, + 1,0,0,0,1183,1184,6,132,10,0,1184,280,1,0,0,0,1185,1186,3,57,21,0,1186, + 1187,1,0,0,0,1187,1188,6,133,10,0,1188,282,1,0,0,0,1189,1190,3,59,22,0, + 1190,1191,1,0,0,0,1191,1192,6,134,10,0,1192,284,1,0,0,0,1193,1194,3,63, + 24,0,1194,1195,1,0,0,0,1195,1196,6,135,16,0,1196,1197,6,135,11,0,1197,1198, + 6,135,11,0,1198,286,1,0,0,0,1199,1200,3,97,41,0,1200,1201,1,0,0,0,1201, + 1202,6,136,19,0,1202,288,1,0,0,0,1203,1204,3,101,43,0,1204,1205,1,0,0,0, + 1205,1206,6,137,18,0,1206,290,1,0,0,0,1207,1208,3,105,45,0,1208,1209,1, + 0,0,0,1209,1210,6,138,22,0,1210,292,1,0,0,0,1211,1212,3,271,128,0,1212, + 1213,1,0,0,0,1213,1214,6,139,29,0,1214,294,1,0,0,0,1215,1216,3,235,110, + 0,1216,1217,1,0,0,0,1217,1218,6,140,25,0,1218,296,1,0,0,0,1219,1220,3,175, + 80,0,1220,1221,1,0,0,0,1221,1222,6,141,30,0,1222,298,1,0,0,0,1223,1224, + 4,142,8,0,1224,1225,3,129,57,0,1225,1226,1,0,0,0,1226,1227,6,142,23,0,1227, + 300,1,0,0,0,1228,1229,4,143,9,0,1229,1230,3,165,75,0,1230,1231,1,0,0,0, + 1231,1232,6,143,24,0,1232,302,1,0,0,0,1233,1234,3,55,20,0,1234,1235,1,0, + 0,0,1235,1236,6,144,10,0,1236,304,1,0,0,0,1237,1238,3,57,21,0,1238,1239, + 1,0,0,0,1239,1240,6,145,10,0,1240,306,1,0,0,0,1241,1242,3,59,22,0,1242, + 1243,1,0,0,0,1243,1244,6,146,10,0,1244,308,1,0,0,0,1245,1246,3,63,24,0, + 1246,1247,1,0,0,0,1247,1248,6,147,16,0,1248,1249,6,147,11,0,1249,310,1, + 0,0,0,1250,1251,3,105,45,0,1251,1252,1,0,0,0,1252,1253,6,148,22,0,1253, + 312,1,0,0,0,1254,1255,4,149,10,0,1255,1256,3,129,57,0,1256,1257,1,0,0,0, + 1257,1258,6,149,23,0,1258,314,1,0,0,0,1259,1260,4,150,11,0,1260,1261,3, + 165,75,0,1261,1262,1,0,0,0,1262,1263,6,150,24,0,1263,316,1,0,0,0,1264,1265, + 3,175,80,0,1265,1266,1,0,0,0,1266,1267,6,151,30,0,1267,318,1,0,0,0,1268, + 1269,3,171,78,0,1269,1270,1,0,0,0,1270,1271,6,152,31,0,1271,320,1,0,0,0, + 1272,1273,3,55,20,0,1273,1274,1,0,0,0,1274,1275,6,153,10,0,1275,322,1,0, + 0,0,1276,1277,3,57,21,0,1277,1278,1,0,0,0,1278,1279,6,154,10,0,1279,324, + 1,0,0,0,1280,1281,3,59,22,0,1281,1282,1,0,0,0,1282,1283,6,155,10,0,1283, + 326,1,0,0,0,1284,1285,3,63,24,0,1285,1286,1,0,0,0,1286,1287,6,156,16,0, + 1287,1288,6,156,11,0,1288,328,1,0,0,0,1289,1290,7,1,0,0,1290,1291,7,9,0, + 0,1291,1292,7,15,0,0,1292,1293,7,7,0,0,1293,330,1,0,0,0,1294,1295,3,55, + 20,0,1295,1296,1,0,0,0,1296,1297,6,158,10,0,1297,332,1,0,0,0,1298,1299, + 3,57,21,0,1299,1300,1,0,0,0,1300,1301,6,159,10,0,1301,334,1,0,0,0,1302, + 1303,3,59,22,0,1303,1304,1,0,0,0,1304,1305,6,160,10,0,1305,336,1,0,0,0, + 1306,1307,3,169,77,0,1307,1308,1,0,0,0,1308,1309,6,161,17,0,1309,1310,6, + 161,11,0,1310,338,1,0,0,0,1311,1312,3,61,23,0,1312,1313,1,0,0,0,1313,1314, + 6,162,12,0,1314,340,1,0,0,0,1315,1321,3,75,30,0,1316,1321,3,65,25,0,1317, + 1321,3,105,45,0,1318,1321,3,67,26,0,1319,1321,3,81,33,0,1320,1315,1,0,0, + 0,1320,1316,1,0,0,0,1320,1317,1,0,0,0,1320,1318,1,0,0,0,1320,1319,1,0,0, + 0,1321,1322,1,0,0,0,1322,1320,1,0,0,0,1322,1323,1,0,0,0,1323,342,1,0,0, + 0,1324,1325,3,55,20,0,1325,1326,1,0,0,0,1326,1327,6,164,10,0,1327,344,1, + 0,0,0,1328,1329,3,57,21,0,1329,1330,1,0,0,0,1330,1331,6,165,10,0,1331,346, + 1,0,0,0,1332,1333,3,59,22,0,1333,1334,1,0,0,0,1334,1335,6,166,10,0,1335, + 348,1,0,0,0,1336,1337,3,63,24,0,1337,1338,1,0,0,0,1338,1339,6,167,16,0, + 1339,1340,6,167,11,0,1340,350,1,0,0,0,1341,1342,3,61,23,0,1342,1343,1,0, + 0,0,1343,1344,6,168,12,0,1344,352,1,0,0,0,1345,1346,3,101,43,0,1346,1347, + 1,0,0,0,1347,1348,6,169,18,0,1348,354,1,0,0,0,1349,1350,3,105,45,0,1350, + 1351,1,0,0,0,1351,1352,6,170,22,0,1352,356,1,0,0,0,1353,1354,3,269,127, + 0,1354,1355,1,0,0,0,1355,1356,6,171,32,0,1356,1357,6,171,33,0,1357,358, + 1,0,0,0,1358,1359,3,209,97,0,1359,1360,1,0,0,0,1360,1361,6,172,20,0,1361, + 360,1,0,0,0,1362,1363,3,85,35,0,1363,1364,1,0,0,0,1364,1365,6,173,21,0, + 1365,362,1,0,0,0,1366,1367,3,55,20,0,1367,1368,1,0,0,0,1368,1369,6,174, + 10,0,1369,364,1,0,0,0,1370,1371,3,57,21,0,1371,1372,1,0,0,0,1372,1373,6, + 175,10,0,1373,366,1,0,0,0,1374,1375,3,59,22,0,1375,1376,1,0,0,0,1376,1377, + 6,176,10,0,1377,368,1,0,0,0,1378,1379,3,63,24,0,1379,1380,1,0,0,0,1380, + 1381,6,177,16,0,1381,1382,6,177,11,0,1382,1383,6,177,11,0,1383,370,1,0, + 0,0,1384,1385,3,101,43,0,1385,1386,1,0,0,0,1386,1387,6,178,18,0,1387,372, + 1,0,0,0,1388,1389,3,105,45,0,1389,1390,1,0,0,0,1390,1391,6,179,22,0,1391, + 374,1,0,0,0,1392,1393,3,235,110,0,1393,1394,1,0,0,0,1394,1395,6,180,25, + 0,1395,376,1,0,0,0,1396,1397,3,55,20,0,1397,1398,1,0,0,0,1398,1399,6,181, + 10,0,1399,378,1,0,0,0,1400,1401,3,57,21,0,1401,1402,1,0,0,0,1402,1403,6, + 182,10,0,1403,380,1,0,0,0,1404,1405,3,59,22,0,1405,1406,1,0,0,0,1406,1407, + 6,183,10,0,1407,382,1,0,0,0,1408,1409,3,63,24,0,1409,1410,1,0,0,0,1410, + 1411,6,184,16,0,1411,1412,6,184,11,0,1412,384,1,0,0,0,1413,1414,3,209,97, + 0,1414,1415,1,0,0,0,1415,1416,6,185,20,0,1416,1417,6,185,11,0,1417,1418, + 6,185,34,0,1418,386,1,0,0,0,1419,1420,3,85,35,0,1420,1421,1,0,0,0,1421, + 1422,6,186,21,0,1422,1423,6,186,11,0,1423,1424,6,186,34,0,1424,388,1,0, + 0,0,1425,1426,3,55,20,0,1426,1427,1,0,0,0,1427,1428,6,187,10,0,1428,390, + 1,0,0,0,1429,1430,3,57,21,0,1430,1431,1,0,0,0,1431,1432,6,188,10,0,1432, + 392,1,0,0,0,1433,1434,3,59,22,0,1434,1435,1,0,0,0,1435,1436,6,189,10,0, + 1436,394,1,0,0,0,1437,1438,3,61,23,0,1438,1439,1,0,0,0,1439,1440,6,190, + 12,0,1440,1441,6,190,11,0,1441,1442,6,190,9,0,1442,396,1,0,0,0,1443,1444, + 3,101,43,0,1444,1445,1,0,0,0,1445,1446,6,191,18,0,1446,1447,6,191,11,0, + 1447,1448,6,191,9,0,1448,398,1,0,0,0,1449,1450,3,55,20,0,1450,1451,1,0, + 0,0,1451,1452,6,192,10,0,1452,400,1,0,0,0,1453,1454,3,57,21,0,1454,1455, + 1,0,0,0,1455,1456,6,193,10,0,1456,402,1,0,0,0,1457,1458,3,59,22,0,1458, + 1459,1,0,0,0,1459,1460,6,194,10,0,1460,404,1,0,0,0,1461,1462,3,175,80,0, + 1462,1463,1,0,0,0,1463,1464,6,195,11,0,1464,1465,6,195,0,0,1465,1466,6, + 195,30,0,1466,406,1,0,0,0,1467,1468,3,171,78,0,1468,1469,1,0,0,0,1469,1470, + 6,196,11,0,1470,1471,6,196,0,0,1471,1472,6,196,31,0,1472,408,1,0,0,0,1473, + 1474,3,91,38,0,1474,1475,1,0,0,0,1475,1476,6,197,11,0,1476,1477,6,197,0, + 0,1477,1478,6,197,35,0,1478,410,1,0,0,0,1479,1480,3,63,24,0,1480,1481,1, + 0,0,0,1481,1482,6,198,16,0,1482,1483,6,198,11,0,1483,412,1,0,0,0,65,0,1, + 2,3,4,5,6,7,8,9,10,11,12,13,14,581,591,595,598,607,609,620,641,646,655, + 662,667,669,680,688,691,693,698,703,709,716,721,727,730,738,742,873,878, + 885,887,903,908,913,915,921,998,1003,1052,1056,1061,1066,1071,1073,1077, + 1079,1166,1170,1175,1320,1322,36,5,1,0,5,4,0,5,6,0,5,2,0,5,3,0,5,8,0,5, + 5,0,5,9,0,5,11,0,5,13,0,0,1,0,4,0,0,7,24,0,7,16,0,7,65,0,5,0,0,7,25,0,7, + 66,0,7,34,0,7,32,0,7,76,0,7,26,0,7,36,0,7,48,0,7,64,0,7,80,0,5,10,0,5,7, + 0,7,90,0,7,89,0,7,68,0,7,67,0,7,88,0,5,12,0,5,14,0,7,29,0]; private static __ATN: ATN; public static get _ATN(): ATN { diff --git a/packages/kbn-esql-ast/src/antlr/esql_parser.g4 b/packages/kbn-esql-ast/src/antlr/esql_parser.g4 index 261b4f712b5b3..6a76e32d28f36 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_parser.g4 +++ b/packages/kbn-esql-ast/src/antlr/esql_parser.g4 @@ -79,7 +79,7 @@ regexBooleanExpression ; matchBooleanExpression - : valueExpression MATCH queryString=string + : fieldExp=qualifiedName COLON queryString=constant ; valueExpression @@ -107,9 +107,7 @@ functionExpression ; functionName - // Additional function identifiers that are already a reserved word in the language - : MATCH - | identifierOrParameter + : identifierOrParameter ; dataType diff --git a/packages/kbn-esql-ast/src/antlr/esql_parser.interp b/packages/kbn-esql-ast/src/antlr/esql_parser.interp index b52d842e79fb2..a2b339f378f12 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_parser.interp +++ b/packages/kbn-esql-ast/src/antlr/esql_parser.interp @@ -23,6 +23,7 @@ null null null null +':' '|' null null @@ -62,7 +63,6 @@ null '*' '/' '%' -'match' null null ']' @@ -103,7 +103,6 @@ null null null null -':' null null null @@ -146,6 +145,7 @@ UNKNOWN_CMD LINE_COMMENT MULTILINE_COMMENT WS +COLON PIPE QUOTED_STRING INTEGER_LITERAL @@ -185,7 +185,6 @@ MINUS ASTERISK SLASH PERCENT -MATCH NAMED_OR_POSITIONAL_PARAM OPENING_BRACKET CLOSING_BRACKET @@ -226,7 +225,6 @@ INFO SHOW_LINE_COMMENT SHOW_MULTILINE_COMMENT SHOW_WS -COLON SETTING SETTING_LINE_COMMENT SETTTING_MULTILINE_COMMENT @@ -310,4 +308,4 @@ inlinestatsCommand atn: -[4, 1, 120, 605, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 134, 8, 1, 10, 1, 12, 1, 137, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 145, 8, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 3, 163, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 175, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 182, 8, 5, 10, 5, 12, 5, 185, 9, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 192, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 198, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 206, 8, 5, 10, 5, 12, 5, 209, 9, 5, 1, 6, 1, 6, 3, 6, 213, 8, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 3, 6, 220, 8, 6, 1, 6, 1, 6, 1, 6, 3, 6, 225, 8, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 236, 8, 8, 1, 9, 1, 9, 1, 9, 1, 9, 3, 9, 242, 8, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 5, 9, 250, 8, 9, 10, 9, 12, 9, 253, 9, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 3, 10, 263, 8, 10, 1, 10, 1, 10, 1, 10, 5, 10, 268, 8, 10, 10, 10, 12, 10, 271, 9, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 5, 11, 279, 8, 11, 10, 11, 12, 11, 282, 9, 11, 3, 11, 284, 8, 11, 1, 11, 1, 11, 1, 12, 1, 12, 3, 12, 290, 8, 12, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 5, 15, 300, 8, 15, 10, 15, 12, 15, 303, 9, 15, 1, 16, 1, 16, 1, 16, 3, 16, 308, 8, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 5, 17, 316, 8, 17, 10, 17, 12, 17, 319, 9, 17, 1, 17, 3, 17, 322, 8, 17, 1, 18, 1, 18, 1, 18, 3, 18, 327, 8, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 20, 1, 20, 1, 21, 1, 21, 3, 21, 337, 8, 21, 1, 22, 1, 22, 1, 22, 1, 22, 5, 22, 343, 8, 22, 10, 22, 12, 22, 346, 9, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 5, 24, 356, 8, 24, 10, 24, 12, 24, 359, 9, 24, 1, 24, 3, 24, 362, 8, 24, 1, 24, 1, 24, 3, 24, 366, 8, 24, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 3, 26, 373, 8, 26, 1, 26, 1, 26, 3, 26, 377, 8, 26, 1, 27, 1, 27, 1, 27, 5, 27, 382, 8, 27, 10, 27, 12, 27, 385, 9, 27, 1, 28, 1, 28, 1, 28, 3, 28, 390, 8, 28, 1, 29, 1, 29, 1, 29, 5, 29, 395, 8, 29, 10, 29, 12, 29, 398, 9, 29, 1, 30, 1, 30, 1, 30, 5, 30, 403, 8, 30, 10, 30, 12, 30, 406, 9, 30, 1, 31, 1, 31, 1, 31, 5, 31, 411, 8, 31, 10, 31, 12, 31, 414, 9, 31, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 3, 33, 421, 8, 33, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 436, 8, 34, 10, 34, 12, 34, 439, 9, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 447, 8, 34, 10, 34, 12, 34, 450, 9, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 458, 8, 34, 10, 34, 12, 34, 461, 9, 34, 1, 34, 1, 34, 3, 34, 465, 8, 34, 1, 35, 1, 35, 3, 35, 469, 8, 35, 1, 36, 1, 36, 1, 36, 3, 36, 474, 8, 36, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 5, 38, 483, 8, 38, 10, 38, 12, 38, 486, 9, 38, 1, 39, 1, 39, 3, 39, 490, 8, 39, 1, 39, 1, 39, 3, 39, 494, 8, 39, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 42, 5, 42, 506, 8, 42, 10, 42, 12, 42, 509, 9, 42, 1, 43, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 3, 44, 519, 8, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 5, 47, 531, 8, 47, 10, 47, 12, 47, 534, 9, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 50, 1, 50, 3, 50, 544, 8, 50, 1, 51, 3, 51, 547, 8, 51, 1, 51, 1, 51, 1, 52, 3, 52, 552, 8, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 574, 8, 58, 1, 58, 1, 58, 1, 58, 1, 58, 5, 58, 580, 8, 58, 10, 58, 12, 58, 583, 9, 58, 3, 58, 585, 8, 58, 1, 59, 1, 59, 1, 59, 3, 59, 590, 8, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 603, 8, 61, 1, 61, 0, 4, 2, 10, 18, 20, 62, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 0, 8, 1, 0, 58, 59, 1, 0, 60, 62, 2, 0, 25, 25, 76, 76, 1, 0, 67, 68, 2, 0, 30, 30, 34, 34, 2, 0, 37, 37, 40, 40, 2, 0, 36, 36, 50, 50, 2, 0, 51, 51, 53, 57, 631, 0, 124, 1, 0, 0, 0, 2, 127, 1, 0, 0, 0, 4, 144, 1, 0, 0, 0, 6, 162, 1, 0, 0, 0, 8, 164, 1, 0, 0, 0, 10, 197, 1, 0, 0, 0, 12, 224, 1, 0, 0, 0, 14, 226, 1, 0, 0, 0, 16, 235, 1, 0, 0, 0, 18, 241, 1, 0, 0, 0, 20, 262, 1, 0, 0, 0, 22, 272, 1, 0, 0, 0, 24, 289, 1, 0, 0, 0, 26, 291, 1, 0, 0, 0, 28, 293, 1, 0, 0, 0, 30, 296, 1, 0, 0, 0, 32, 307, 1, 0, 0, 0, 34, 311, 1, 0, 0, 0, 36, 326, 1, 0, 0, 0, 38, 330, 1, 0, 0, 0, 40, 332, 1, 0, 0, 0, 42, 336, 1, 0, 0, 0, 44, 338, 1, 0, 0, 0, 46, 347, 1, 0, 0, 0, 48, 351, 1, 0, 0, 0, 50, 367, 1, 0, 0, 0, 52, 370, 1, 0, 0, 0, 54, 378, 1, 0, 0, 0, 56, 386, 1, 0, 0, 0, 58, 391, 1, 0, 0, 0, 60, 399, 1, 0, 0, 0, 62, 407, 1, 0, 0, 0, 64, 415, 1, 0, 0, 0, 66, 420, 1, 0, 0, 0, 68, 464, 1, 0, 0, 0, 70, 468, 1, 0, 0, 0, 72, 473, 1, 0, 0, 0, 74, 475, 1, 0, 0, 0, 76, 478, 1, 0, 0, 0, 78, 487, 1, 0, 0, 0, 80, 495, 1, 0, 0, 0, 82, 498, 1, 0, 0, 0, 84, 501, 1, 0, 0, 0, 86, 510, 1, 0, 0, 0, 88, 514, 1, 0, 0, 0, 90, 520, 1, 0, 0, 0, 92, 524, 1, 0, 0, 0, 94, 527, 1, 0, 0, 0, 96, 535, 1, 0, 0, 0, 98, 539, 1, 0, 0, 0, 100, 543, 1, 0, 0, 0, 102, 546, 1, 0, 0, 0, 104, 551, 1, 0, 0, 0, 106, 555, 1, 0, 0, 0, 108, 557, 1, 0, 0, 0, 110, 559, 1, 0, 0, 0, 112, 562, 1, 0, 0, 0, 114, 566, 1, 0, 0, 0, 116, 569, 1, 0, 0, 0, 118, 589, 1, 0, 0, 0, 120, 593, 1, 0, 0, 0, 122, 598, 1, 0, 0, 0, 124, 125, 3, 2, 1, 0, 125, 126, 5, 0, 0, 1, 126, 1, 1, 0, 0, 0, 127, 128, 6, 1, -1, 0, 128, 129, 3, 4, 2, 0, 129, 135, 1, 0, 0, 0, 130, 131, 10, 1, 0, 0, 131, 132, 5, 24, 0, 0, 132, 134, 3, 6, 3, 0, 133, 130, 1, 0, 0, 0, 134, 137, 1, 0, 0, 0, 135, 133, 1, 0, 0, 0, 135, 136, 1, 0, 0, 0, 136, 3, 1, 0, 0, 0, 137, 135, 1, 0, 0, 0, 138, 145, 3, 110, 55, 0, 139, 145, 3, 34, 17, 0, 140, 145, 3, 28, 14, 0, 141, 145, 3, 114, 57, 0, 142, 143, 4, 2, 1, 0, 143, 145, 3, 48, 24, 0, 144, 138, 1, 0, 0, 0, 144, 139, 1, 0, 0, 0, 144, 140, 1, 0, 0, 0, 144, 141, 1, 0, 0, 0, 144, 142, 1, 0, 0, 0, 145, 5, 1, 0, 0, 0, 146, 163, 3, 50, 25, 0, 147, 163, 3, 8, 4, 0, 148, 163, 3, 80, 40, 0, 149, 163, 3, 74, 37, 0, 150, 163, 3, 52, 26, 0, 151, 163, 3, 76, 38, 0, 152, 163, 3, 82, 41, 0, 153, 163, 3, 84, 42, 0, 154, 163, 3, 88, 44, 0, 155, 163, 3, 90, 45, 0, 156, 163, 3, 116, 58, 0, 157, 163, 3, 92, 46, 0, 158, 159, 4, 3, 2, 0, 159, 163, 3, 122, 61, 0, 160, 161, 4, 3, 3, 0, 161, 163, 3, 120, 60, 0, 162, 146, 1, 0, 0, 0, 162, 147, 1, 0, 0, 0, 162, 148, 1, 0, 0, 0, 162, 149, 1, 0, 0, 0, 162, 150, 1, 0, 0, 0, 162, 151, 1, 0, 0, 0, 162, 152, 1, 0, 0, 0, 162, 153, 1, 0, 0, 0, 162, 154, 1, 0, 0, 0, 162, 155, 1, 0, 0, 0, 162, 156, 1, 0, 0, 0, 162, 157, 1, 0, 0, 0, 162, 158, 1, 0, 0, 0, 162, 160, 1, 0, 0, 0, 163, 7, 1, 0, 0, 0, 164, 165, 5, 16, 0, 0, 165, 166, 3, 10, 5, 0, 166, 9, 1, 0, 0, 0, 167, 168, 6, 5, -1, 0, 168, 169, 5, 43, 0, 0, 169, 198, 3, 10, 5, 8, 170, 198, 3, 16, 8, 0, 171, 198, 3, 12, 6, 0, 172, 174, 3, 16, 8, 0, 173, 175, 5, 43, 0, 0, 174, 173, 1, 0, 0, 0, 174, 175, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 177, 5, 38, 0, 0, 177, 178, 5, 42, 0, 0, 178, 183, 3, 16, 8, 0, 179, 180, 5, 33, 0, 0, 180, 182, 3, 16, 8, 0, 181, 179, 1, 0, 0, 0, 182, 185, 1, 0, 0, 0, 183, 181, 1, 0, 0, 0, 183, 184, 1, 0, 0, 0, 184, 186, 1, 0, 0, 0, 185, 183, 1, 0, 0, 0, 186, 187, 5, 49, 0, 0, 187, 198, 1, 0, 0, 0, 188, 189, 3, 16, 8, 0, 189, 191, 5, 39, 0, 0, 190, 192, 5, 43, 0, 0, 191, 190, 1, 0, 0, 0, 191, 192, 1, 0, 0, 0, 192, 193, 1, 0, 0, 0, 193, 194, 5, 44, 0, 0, 194, 198, 1, 0, 0, 0, 195, 196, 4, 5, 4, 0, 196, 198, 3, 14, 7, 0, 197, 167, 1, 0, 0, 0, 197, 170, 1, 0, 0, 0, 197, 171, 1, 0, 0, 0, 197, 172, 1, 0, 0, 0, 197, 188, 1, 0, 0, 0, 197, 195, 1, 0, 0, 0, 198, 207, 1, 0, 0, 0, 199, 200, 10, 5, 0, 0, 200, 201, 5, 29, 0, 0, 201, 206, 3, 10, 5, 6, 202, 203, 10, 4, 0, 0, 203, 204, 5, 46, 0, 0, 204, 206, 3, 10, 5, 5, 205, 199, 1, 0, 0, 0, 205, 202, 1, 0, 0, 0, 206, 209, 1, 0, 0, 0, 207, 205, 1, 0, 0, 0, 207, 208, 1, 0, 0, 0, 208, 11, 1, 0, 0, 0, 209, 207, 1, 0, 0, 0, 210, 212, 3, 16, 8, 0, 211, 213, 5, 43, 0, 0, 212, 211, 1, 0, 0, 0, 212, 213, 1, 0, 0, 0, 213, 214, 1, 0, 0, 0, 214, 215, 5, 41, 0, 0, 215, 216, 3, 106, 53, 0, 216, 225, 1, 0, 0, 0, 217, 219, 3, 16, 8, 0, 218, 220, 5, 43, 0, 0, 219, 218, 1, 0, 0, 0, 219, 220, 1, 0, 0, 0, 220, 221, 1, 0, 0, 0, 221, 222, 5, 48, 0, 0, 222, 223, 3, 106, 53, 0, 223, 225, 1, 0, 0, 0, 224, 210, 1, 0, 0, 0, 224, 217, 1, 0, 0, 0, 225, 13, 1, 0, 0, 0, 226, 227, 3, 16, 8, 0, 227, 228, 5, 63, 0, 0, 228, 229, 3, 106, 53, 0, 229, 15, 1, 0, 0, 0, 230, 236, 3, 18, 9, 0, 231, 232, 3, 18, 9, 0, 232, 233, 3, 108, 54, 0, 233, 234, 3, 18, 9, 0, 234, 236, 1, 0, 0, 0, 235, 230, 1, 0, 0, 0, 235, 231, 1, 0, 0, 0, 236, 17, 1, 0, 0, 0, 237, 238, 6, 9, -1, 0, 238, 242, 3, 20, 10, 0, 239, 240, 7, 0, 0, 0, 240, 242, 3, 18, 9, 3, 241, 237, 1, 0, 0, 0, 241, 239, 1, 0, 0, 0, 242, 251, 1, 0, 0, 0, 243, 244, 10, 2, 0, 0, 244, 245, 7, 1, 0, 0, 245, 250, 3, 18, 9, 3, 246, 247, 10, 1, 0, 0, 247, 248, 7, 0, 0, 0, 248, 250, 3, 18, 9, 2, 249, 243, 1, 0, 0, 0, 249, 246, 1, 0, 0, 0, 250, 253, 1, 0, 0, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 19, 1, 0, 0, 0, 253, 251, 1, 0, 0, 0, 254, 255, 6, 10, -1, 0, 255, 263, 3, 68, 34, 0, 256, 263, 3, 58, 29, 0, 257, 263, 3, 22, 11, 0, 258, 259, 5, 42, 0, 0, 259, 260, 3, 10, 5, 0, 260, 261, 5, 49, 0, 0, 261, 263, 1, 0, 0, 0, 262, 254, 1, 0, 0, 0, 262, 256, 1, 0, 0, 0, 262, 257, 1, 0, 0, 0, 262, 258, 1, 0, 0, 0, 263, 269, 1, 0, 0, 0, 264, 265, 10, 1, 0, 0, 265, 266, 5, 32, 0, 0, 266, 268, 3, 26, 13, 0, 267, 264, 1, 0, 0, 0, 268, 271, 1, 0, 0, 0, 269, 267, 1, 0, 0, 0, 269, 270, 1, 0, 0, 0, 270, 21, 1, 0, 0, 0, 271, 269, 1, 0, 0, 0, 272, 273, 3, 24, 12, 0, 273, 283, 5, 42, 0, 0, 274, 284, 5, 60, 0, 0, 275, 280, 3, 10, 5, 0, 276, 277, 5, 33, 0, 0, 277, 279, 3, 10, 5, 0, 278, 276, 1, 0, 0, 0, 279, 282, 1, 0, 0, 0, 280, 278, 1, 0, 0, 0, 280, 281, 1, 0, 0, 0, 281, 284, 1, 0, 0, 0, 282, 280, 1, 0, 0, 0, 283, 274, 1, 0, 0, 0, 283, 275, 1, 0, 0, 0, 283, 284, 1, 0, 0, 0, 284, 285, 1, 0, 0, 0, 285, 286, 5, 49, 0, 0, 286, 23, 1, 0, 0, 0, 287, 290, 5, 63, 0, 0, 288, 290, 3, 72, 36, 0, 289, 287, 1, 0, 0, 0, 289, 288, 1, 0, 0, 0, 290, 25, 1, 0, 0, 0, 291, 292, 3, 64, 32, 0, 292, 27, 1, 0, 0, 0, 293, 294, 5, 12, 0, 0, 294, 295, 3, 30, 15, 0, 295, 29, 1, 0, 0, 0, 296, 301, 3, 32, 16, 0, 297, 298, 5, 33, 0, 0, 298, 300, 3, 32, 16, 0, 299, 297, 1, 0, 0, 0, 300, 303, 1, 0, 0, 0, 301, 299, 1, 0, 0, 0, 301, 302, 1, 0, 0, 0, 302, 31, 1, 0, 0, 0, 303, 301, 1, 0, 0, 0, 304, 305, 3, 58, 29, 0, 305, 306, 5, 31, 0, 0, 306, 308, 1, 0, 0, 0, 307, 304, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 309, 1, 0, 0, 0, 309, 310, 3, 10, 5, 0, 310, 33, 1, 0, 0, 0, 311, 312, 5, 6, 0, 0, 312, 317, 3, 36, 18, 0, 313, 314, 5, 33, 0, 0, 314, 316, 3, 36, 18, 0, 315, 313, 1, 0, 0, 0, 316, 319, 1, 0, 0, 0, 317, 315, 1, 0, 0, 0, 317, 318, 1, 0, 0, 0, 318, 321, 1, 0, 0, 0, 319, 317, 1, 0, 0, 0, 320, 322, 3, 42, 21, 0, 321, 320, 1, 0, 0, 0, 321, 322, 1, 0, 0, 0, 322, 35, 1, 0, 0, 0, 323, 324, 3, 38, 19, 0, 324, 325, 5, 104, 0, 0, 325, 327, 1, 0, 0, 0, 326, 323, 1, 0, 0, 0, 326, 327, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 329, 3, 40, 20, 0, 329, 37, 1, 0, 0, 0, 330, 331, 5, 76, 0, 0, 331, 39, 1, 0, 0, 0, 332, 333, 7, 2, 0, 0, 333, 41, 1, 0, 0, 0, 334, 337, 3, 44, 22, 0, 335, 337, 3, 46, 23, 0, 336, 334, 1, 0, 0, 0, 336, 335, 1, 0, 0, 0, 337, 43, 1, 0, 0, 0, 338, 339, 5, 75, 0, 0, 339, 344, 5, 76, 0, 0, 340, 341, 5, 33, 0, 0, 341, 343, 5, 76, 0, 0, 342, 340, 1, 0, 0, 0, 343, 346, 1, 0, 0, 0, 344, 342, 1, 0, 0, 0, 344, 345, 1, 0, 0, 0, 345, 45, 1, 0, 0, 0, 346, 344, 1, 0, 0, 0, 347, 348, 5, 65, 0, 0, 348, 349, 3, 44, 22, 0, 349, 350, 5, 66, 0, 0, 350, 47, 1, 0, 0, 0, 351, 352, 5, 19, 0, 0, 352, 357, 3, 36, 18, 0, 353, 354, 5, 33, 0, 0, 354, 356, 3, 36, 18, 0, 355, 353, 1, 0, 0, 0, 356, 359, 1, 0, 0, 0, 357, 355, 1, 0, 0, 0, 357, 358, 1, 0, 0, 0, 358, 361, 1, 0, 0, 0, 359, 357, 1, 0, 0, 0, 360, 362, 3, 54, 27, 0, 361, 360, 1, 0, 0, 0, 361, 362, 1, 0, 0, 0, 362, 365, 1, 0, 0, 0, 363, 364, 5, 28, 0, 0, 364, 366, 3, 30, 15, 0, 365, 363, 1, 0, 0, 0, 365, 366, 1, 0, 0, 0, 366, 49, 1, 0, 0, 0, 367, 368, 5, 4, 0, 0, 368, 369, 3, 30, 15, 0, 369, 51, 1, 0, 0, 0, 370, 372, 5, 15, 0, 0, 371, 373, 3, 54, 27, 0, 372, 371, 1, 0, 0, 0, 372, 373, 1, 0, 0, 0, 373, 376, 1, 0, 0, 0, 374, 375, 5, 28, 0, 0, 375, 377, 3, 30, 15, 0, 376, 374, 1, 0, 0, 0, 376, 377, 1, 0, 0, 0, 377, 53, 1, 0, 0, 0, 378, 383, 3, 56, 28, 0, 379, 380, 5, 33, 0, 0, 380, 382, 3, 56, 28, 0, 381, 379, 1, 0, 0, 0, 382, 385, 1, 0, 0, 0, 383, 381, 1, 0, 0, 0, 383, 384, 1, 0, 0, 0, 384, 55, 1, 0, 0, 0, 385, 383, 1, 0, 0, 0, 386, 389, 3, 32, 16, 0, 387, 388, 5, 16, 0, 0, 388, 390, 3, 10, 5, 0, 389, 387, 1, 0, 0, 0, 389, 390, 1, 0, 0, 0, 390, 57, 1, 0, 0, 0, 391, 396, 3, 72, 36, 0, 392, 393, 5, 35, 0, 0, 393, 395, 3, 72, 36, 0, 394, 392, 1, 0, 0, 0, 395, 398, 1, 0, 0, 0, 396, 394, 1, 0, 0, 0, 396, 397, 1, 0, 0, 0, 397, 59, 1, 0, 0, 0, 398, 396, 1, 0, 0, 0, 399, 404, 3, 66, 33, 0, 400, 401, 5, 35, 0, 0, 401, 403, 3, 66, 33, 0, 402, 400, 1, 0, 0, 0, 403, 406, 1, 0, 0, 0, 404, 402, 1, 0, 0, 0, 404, 405, 1, 0, 0, 0, 405, 61, 1, 0, 0, 0, 406, 404, 1, 0, 0, 0, 407, 412, 3, 60, 30, 0, 408, 409, 5, 33, 0, 0, 409, 411, 3, 60, 30, 0, 410, 408, 1, 0, 0, 0, 411, 414, 1, 0, 0, 0, 412, 410, 1, 0, 0, 0, 412, 413, 1, 0, 0, 0, 413, 63, 1, 0, 0, 0, 414, 412, 1, 0, 0, 0, 415, 416, 7, 3, 0, 0, 416, 65, 1, 0, 0, 0, 417, 421, 5, 80, 0, 0, 418, 419, 4, 33, 10, 0, 419, 421, 3, 70, 35, 0, 420, 417, 1, 0, 0, 0, 420, 418, 1, 0, 0, 0, 421, 67, 1, 0, 0, 0, 422, 465, 5, 44, 0, 0, 423, 424, 3, 104, 52, 0, 424, 425, 5, 67, 0, 0, 425, 465, 1, 0, 0, 0, 426, 465, 3, 102, 51, 0, 427, 465, 3, 104, 52, 0, 428, 465, 3, 98, 49, 0, 429, 465, 3, 70, 35, 0, 430, 465, 3, 106, 53, 0, 431, 432, 5, 65, 0, 0, 432, 437, 3, 100, 50, 0, 433, 434, 5, 33, 0, 0, 434, 436, 3, 100, 50, 0, 435, 433, 1, 0, 0, 0, 436, 439, 1, 0, 0, 0, 437, 435, 1, 0, 0, 0, 437, 438, 1, 0, 0, 0, 438, 440, 1, 0, 0, 0, 439, 437, 1, 0, 0, 0, 440, 441, 5, 66, 0, 0, 441, 465, 1, 0, 0, 0, 442, 443, 5, 65, 0, 0, 443, 448, 3, 98, 49, 0, 444, 445, 5, 33, 0, 0, 445, 447, 3, 98, 49, 0, 446, 444, 1, 0, 0, 0, 447, 450, 1, 0, 0, 0, 448, 446, 1, 0, 0, 0, 448, 449, 1, 0, 0, 0, 449, 451, 1, 0, 0, 0, 450, 448, 1, 0, 0, 0, 451, 452, 5, 66, 0, 0, 452, 465, 1, 0, 0, 0, 453, 454, 5, 65, 0, 0, 454, 459, 3, 106, 53, 0, 455, 456, 5, 33, 0, 0, 456, 458, 3, 106, 53, 0, 457, 455, 1, 0, 0, 0, 458, 461, 1, 0, 0, 0, 459, 457, 1, 0, 0, 0, 459, 460, 1, 0, 0, 0, 460, 462, 1, 0, 0, 0, 461, 459, 1, 0, 0, 0, 462, 463, 5, 66, 0, 0, 463, 465, 1, 0, 0, 0, 464, 422, 1, 0, 0, 0, 464, 423, 1, 0, 0, 0, 464, 426, 1, 0, 0, 0, 464, 427, 1, 0, 0, 0, 464, 428, 1, 0, 0, 0, 464, 429, 1, 0, 0, 0, 464, 430, 1, 0, 0, 0, 464, 431, 1, 0, 0, 0, 464, 442, 1, 0, 0, 0, 464, 453, 1, 0, 0, 0, 465, 69, 1, 0, 0, 0, 466, 469, 5, 47, 0, 0, 467, 469, 5, 64, 0, 0, 468, 466, 1, 0, 0, 0, 468, 467, 1, 0, 0, 0, 469, 71, 1, 0, 0, 0, 470, 474, 3, 64, 32, 0, 471, 472, 4, 36, 11, 0, 472, 474, 3, 70, 35, 0, 473, 470, 1, 0, 0, 0, 473, 471, 1, 0, 0, 0, 474, 73, 1, 0, 0, 0, 475, 476, 5, 9, 0, 0, 476, 477, 5, 26, 0, 0, 477, 75, 1, 0, 0, 0, 478, 479, 5, 14, 0, 0, 479, 484, 3, 78, 39, 0, 480, 481, 5, 33, 0, 0, 481, 483, 3, 78, 39, 0, 482, 480, 1, 0, 0, 0, 483, 486, 1, 0, 0, 0, 484, 482, 1, 0, 0, 0, 484, 485, 1, 0, 0, 0, 485, 77, 1, 0, 0, 0, 486, 484, 1, 0, 0, 0, 487, 489, 3, 10, 5, 0, 488, 490, 7, 4, 0, 0, 489, 488, 1, 0, 0, 0, 489, 490, 1, 0, 0, 0, 490, 493, 1, 0, 0, 0, 491, 492, 5, 45, 0, 0, 492, 494, 7, 5, 0, 0, 493, 491, 1, 0, 0, 0, 493, 494, 1, 0, 0, 0, 494, 79, 1, 0, 0, 0, 495, 496, 5, 8, 0, 0, 496, 497, 3, 62, 31, 0, 497, 81, 1, 0, 0, 0, 498, 499, 5, 2, 0, 0, 499, 500, 3, 62, 31, 0, 500, 83, 1, 0, 0, 0, 501, 502, 5, 11, 0, 0, 502, 507, 3, 86, 43, 0, 503, 504, 5, 33, 0, 0, 504, 506, 3, 86, 43, 0, 505, 503, 1, 0, 0, 0, 506, 509, 1, 0, 0, 0, 507, 505, 1, 0, 0, 0, 507, 508, 1, 0, 0, 0, 508, 85, 1, 0, 0, 0, 509, 507, 1, 0, 0, 0, 510, 511, 3, 60, 30, 0, 511, 512, 5, 84, 0, 0, 512, 513, 3, 60, 30, 0, 513, 87, 1, 0, 0, 0, 514, 515, 5, 1, 0, 0, 515, 516, 3, 20, 10, 0, 516, 518, 3, 106, 53, 0, 517, 519, 3, 94, 47, 0, 518, 517, 1, 0, 0, 0, 518, 519, 1, 0, 0, 0, 519, 89, 1, 0, 0, 0, 520, 521, 5, 7, 0, 0, 521, 522, 3, 20, 10, 0, 522, 523, 3, 106, 53, 0, 523, 91, 1, 0, 0, 0, 524, 525, 5, 10, 0, 0, 525, 526, 3, 58, 29, 0, 526, 93, 1, 0, 0, 0, 527, 532, 3, 96, 48, 0, 528, 529, 5, 33, 0, 0, 529, 531, 3, 96, 48, 0, 530, 528, 1, 0, 0, 0, 531, 534, 1, 0, 0, 0, 532, 530, 1, 0, 0, 0, 532, 533, 1, 0, 0, 0, 533, 95, 1, 0, 0, 0, 534, 532, 1, 0, 0, 0, 535, 536, 3, 64, 32, 0, 536, 537, 5, 31, 0, 0, 537, 538, 3, 68, 34, 0, 538, 97, 1, 0, 0, 0, 539, 540, 7, 6, 0, 0, 540, 99, 1, 0, 0, 0, 541, 544, 3, 102, 51, 0, 542, 544, 3, 104, 52, 0, 543, 541, 1, 0, 0, 0, 543, 542, 1, 0, 0, 0, 544, 101, 1, 0, 0, 0, 545, 547, 7, 0, 0, 0, 546, 545, 1, 0, 0, 0, 546, 547, 1, 0, 0, 0, 547, 548, 1, 0, 0, 0, 548, 549, 5, 27, 0, 0, 549, 103, 1, 0, 0, 0, 550, 552, 7, 0, 0, 0, 551, 550, 1, 0, 0, 0, 551, 552, 1, 0, 0, 0, 552, 553, 1, 0, 0, 0, 553, 554, 5, 26, 0, 0, 554, 105, 1, 0, 0, 0, 555, 556, 5, 25, 0, 0, 556, 107, 1, 0, 0, 0, 557, 558, 7, 7, 0, 0, 558, 109, 1, 0, 0, 0, 559, 560, 5, 5, 0, 0, 560, 561, 3, 112, 56, 0, 561, 111, 1, 0, 0, 0, 562, 563, 5, 65, 0, 0, 563, 564, 3, 2, 1, 0, 564, 565, 5, 66, 0, 0, 565, 113, 1, 0, 0, 0, 566, 567, 5, 13, 0, 0, 567, 568, 5, 100, 0, 0, 568, 115, 1, 0, 0, 0, 569, 570, 5, 3, 0, 0, 570, 573, 5, 90, 0, 0, 571, 572, 5, 88, 0, 0, 572, 574, 3, 60, 30, 0, 573, 571, 1, 0, 0, 0, 573, 574, 1, 0, 0, 0, 574, 584, 1, 0, 0, 0, 575, 576, 5, 89, 0, 0, 576, 581, 3, 118, 59, 0, 577, 578, 5, 33, 0, 0, 578, 580, 3, 118, 59, 0, 579, 577, 1, 0, 0, 0, 580, 583, 1, 0, 0, 0, 581, 579, 1, 0, 0, 0, 581, 582, 1, 0, 0, 0, 582, 585, 1, 0, 0, 0, 583, 581, 1, 0, 0, 0, 584, 575, 1, 0, 0, 0, 584, 585, 1, 0, 0, 0, 585, 117, 1, 0, 0, 0, 586, 587, 3, 60, 30, 0, 587, 588, 5, 31, 0, 0, 588, 590, 1, 0, 0, 0, 589, 586, 1, 0, 0, 0, 589, 590, 1, 0, 0, 0, 590, 591, 1, 0, 0, 0, 591, 592, 3, 60, 30, 0, 592, 119, 1, 0, 0, 0, 593, 594, 5, 18, 0, 0, 594, 595, 3, 36, 18, 0, 595, 596, 5, 88, 0, 0, 596, 597, 3, 62, 31, 0, 597, 121, 1, 0, 0, 0, 598, 599, 5, 17, 0, 0, 599, 602, 3, 54, 27, 0, 600, 601, 5, 28, 0, 0, 601, 603, 3, 30, 15, 0, 602, 600, 1, 0, 0, 0, 602, 603, 1, 0, 0, 0, 603, 123, 1, 0, 0, 0, 59, 135, 144, 162, 174, 183, 191, 197, 205, 207, 212, 219, 224, 235, 241, 249, 251, 262, 269, 280, 283, 289, 301, 307, 317, 321, 326, 336, 344, 357, 361, 365, 372, 376, 383, 389, 396, 404, 412, 420, 437, 448, 459, 464, 468, 473, 484, 489, 493, 507, 518, 532, 543, 546, 551, 573, 581, 584, 589, 602] \ No newline at end of file +[4, 1, 119, 603, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 134, 8, 1, 10, 1, 12, 1, 137, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 145, 8, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 3, 163, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 175, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 182, 8, 5, 10, 5, 12, 5, 185, 9, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 192, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 198, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 206, 8, 5, 10, 5, 12, 5, 209, 9, 5, 1, 6, 1, 6, 3, 6, 213, 8, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 3, 6, 220, 8, 6, 1, 6, 1, 6, 1, 6, 3, 6, 225, 8, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 236, 8, 8, 1, 9, 1, 9, 1, 9, 1, 9, 3, 9, 242, 8, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 5, 9, 250, 8, 9, 10, 9, 12, 9, 253, 9, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 3, 10, 263, 8, 10, 1, 10, 1, 10, 1, 10, 5, 10, 268, 8, 10, 10, 10, 12, 10, 271, 9, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 5, 11, 279, 8, 11, 10, 11, 12, 11, 282, 9, 11, 3, 11, 284, 8, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 5, 15, 298, 8, 15, 10, 15, 12, 15, 301, 9, 15, 1, 16, 1, 16, 1, 16, 3, 16, 306, 8, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 5, 17, 314, 8, 17, 10, 17, 12, 17, 317, 9, 17, 1, 17, 3, 17, 320, 8, 17, 1, 18, 1, 18, 1, 18, 3, 18, 325, 8, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 20, 1, 20, 1, 21, 1, 21, 3, 21, 335, 8, 21, 1, 22, 1, 22, 1, 22, 1, 22, 5, 22, 341, 8, 22, 10, 22, 12, 22, 344, 9, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 5, 24, 354, 8, 24, 10, 24, 12, 24, 357, 9, 24, 1, 24, 3, 24, 360, 8, 24, 1, 24, 1, 24, 3, 24, 364, 8, 24, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 3, 26, 371, 8, 26, 1, 26, 1, 26, 3, 26, 375, 8, 26, 1, 27, 1, 27, 1, 27, 5, 27, 380, 8, 27, 10, 27, 12, 27, 383, 9, 27, 1, 28, 1, 28, 1, 28, 3, 28, 388, 8, 28, 1, 29, 1, 29, 1, 29, 5, 29, 393, 8, 29, 10, 29, 12, 29, 396, 9, 29, 1, 30, 1, 30, 1, 30, 5, 30, 401, 8, 30, 10, 30, 12, 30, 404, 9, 30, 1, 31, 1, 31, 1, 31, 5, 31, 409, 8, 31, 10, 31, 12, 31, 412, 9, 31, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 3, 33, 419, 8, 33, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 434, 8, 34, 10, 34, 12, 34, 437, 9, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 445, 8, 34, 10, 34, 12, 34, 448, 9, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 456, 8, 34, 10, 34, 12, 34, 459, 9, 34, 1, 34, 1, 34, 3, 34, 463, 8, 34, 1, 35, 1, 35, 3, 35, 467, 8, 35, 1, 36, 1, 36, 1, 36, 3, 36, 472, 8, 36, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 5, 38, 481, 8, 38, 10, 38, 12, 38, 484, 9, 38, 1, 39, 1, 39, 3, 39, 488, 8, 39, 1, 39, 1, 39, 3, 39, 492, 8, 39, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 42, 5, 42, 504, 8, 42, 10, 42, 12, 42, 507, 9, 42, 1, 43, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 3, 44, 517, 8, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 5, 47, 529, 8, 47, 10, 47, 12, 47, 532, 9, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 50, 1, 50, 3, 50, 542, 8, 50, 1, 51, 3, 51, 545, 8, 51, 1, 51, 1, 51, 1, 52, 3, 52, 550, 8, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 572, 8, 58, 1, 58, 1, 58, 1, 58, 1, 58, 5, 58, 578, 8, 58, 10, 58, 12, 58, 581, 9, 58, 3, 58, 583, 8, 58, 1, 59, 1, 59, 1, 59, 3, 59, 588, 8, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 601, 8, 61, 1, 61, 0, 4, 2, 10, 18, 20, 62, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 0, 8, 1, 0, 59, 60, 1, 0, 61, 63, 2, 0, 26, 26, 76, 76, 1, 0, 67, 68, 2, 0, 31, 31, 35, 35, 2, 0, 38, 38, 41, 41, 2, 0, 37, 37, 51, 51, 2, 0, 52, 52, 54, 58, 628, 0, 124, 1, 0, 0, 0, 2, 127, 1, 0, 0, 0, 4, 144, 1, 0, 0, 0, 6, 162, 1, 0, 0, 0, 8, 164, 1, 0, 0, 0, 10, 197, 1, 0, 0, 0, 12, 224, 1, 0, 0, 0, 14, 226, 1, 0, 0, 0, 16, 235, 1, 0, 0, 0, 18, 241, 1, 0, 0, 0, 20, 262, 1, 0, 0, 0, 22, 272, 1, 0, 0, 0, 24, 287, 1, 0, 0, 0, 26, 289, 1, 0, 0, 0, 28, 291, 1, 0, 0, 0, 30, 294, 1, 0, 0, 0, 32, 305, 1, 0, 0, 0, 34, 309, 1, 0, 0, 0, 36, 324, 1, 0, 0, 0, 38, 328, 1, 0, 0, 0, 40, 330, 1, 0, 0, 0, 42, 334, 1, 0, 0, 0, 44, 336, 1, 0, 0, 0, 46, 345, 1, 0, 0, 0, 48, 349, 1, 0, 0, 0, 50, 365, 1, 0, 0, 0, 52, 368, 1, 0, 0, 0, 54, 376, 1, 0, 0, 0, 56, 384, 1, 0, 0, 0, 58, 389, 1, 0, 0, 0, 60, 397, 1, 0, 0, 0, 62, 405, 1, 0, 0, 0, 64, 413, 1, 0, 0, 0, 66, 418, 1, 0, 0, 0, 68, 462, 1, 0, 0, 0, 70, 466, 1, 0, 0, 0, 72, 471, 1, 0, 0, 0, 74, 473, 1, 0, 0, 0, 76, 476, 1, 0, 0, 0, 78, 485, 1, 0, 0, 0, 80, 493, 1, 0, 0, 0, 82, 496, 1, 0, 0, 0, 84, 499, 1, 0, 0, 0, 86, 508, 1, 0, 0, 0, 88, 512, 1, 0, 0, 0, 90, 518, 1, 0, 0, 0, 92, 522, 1, 0, 0, 0, 94, 525, 1, 0, 0, 0, 96, 533, 1, 0, 0, 0, 98, 537, 1, 0, 0, 0, 100, 541, 1, 0, 0, 0, 102, 544, 1, 0, 0, 0, 104, 549, 1, 0, 0, 0, 106, 553, 1, 0, 0, 0, 108, 555, 1, 0, 0, 0, 110, 557, 1, 0, 0, 0, 112, 560, 1, 0, 0, 0, 114, 564, 1, 0, 0, 0, 116, 567, 1, 0, 0, 0, 118, 587, 1, 0, 0, 0, 120, 591, 1, 0, 0, 0, 122, 596, 1, 0, 0, 0, 124, 125, 3, 2, 1, 0, 125, 126, 5, 0, 0, 1, 126, 1, 1, 0, 0, 0, 127, 128, 6, 1, -1, 0, 128, 129, 3, 4, 2, 0, 129, 135, 1, 0, 0, 0, 130, 131, 10, 1, 0, 0, 131, 132, 5, 25, 0, 0, 132, 134, 3, 6, 3, 0, 133, 130, 1, 0, 0, 0, 134, 137, 1, 0, 0, 0, 135, 133, 1, 0, 0, 0, 135, 136, 1, 0, 0, 0, 136, 3, 1, 0, 0, 0, 137, 135, 1, 0, 0, 0, 138, 145, 3, 110, 55, 0, 139, 145, 3, 34, 17, 0, 140, 145, 3, 28, 14, 0, 141, 145, 3, 114, 57, 0, 142, 143, 4, 2, 1, 0, 143, 145, 3, 48, 24, 0, 144, 138, 1, 0, 0, 0, 144, 139, 1, 0, 0, 0, 144, 140, 1, 0, 0, 0, 144, 141, 1, 0, 0, 0, 144, 142, 1, 0, 0, 0, 145, 5, 1, 0, 0, 0, 146, 163, 3, 50, 25, 0, 147, 163, 3, 8, 4, 0, 148, 163, 3, 80, 40, 0, 149, 163, 3, 74, 37, 0, 150, 163, 3, 52, 26, 0, 151, 163, 3, 76, 38, 0, 152, 163, 3, 82, 41, 0, 153, 163, 3, 84, 42, 0, 154, 163, 3, 88, 44, 0, 155, 163, 3, 90, 45, 0, 156, 163, 3, 116, 58, 0, 157, 163, 3, 92, 46, 0, 158, 159, 4, 3, 2, 0, 159, 163, 3, 122, 61, 0, 160, 161, 4, 3, 3, 0, 161, 163, 3, 120, 60, 0, 162, 146, 1, 0, 0, 0, 162, 147, 1, 0, 0, 0, 162, 148, 1, 0, 0, 0, 162, 149, 1, 0, 0, 0, 162, 150, 1, 0, 0, 0, 162, 151, 1, 0, 0, 0, 162, 152, 1, 0, 0, 0, 162, 153, 1, 0, 0, 0, 162, 154, 1, 0, 0, 0, 162, 155, 1, 0, 0, 0, 162, 156, 1, 0, 0, 0, 162, 157, 1, 0, 0, 0, 162, 158, 1, 0, 0, 0, 162, 160, 1, 0, 0, 0, 163, 7, 1, 0, 0, 0, 164, 165, 5, 16, 0, 0, 165, 166, 3, 10, 5, 0, 166, 9, 1, 0, 0, 0, 167, 168, 6, 5, -1, 0, 168, 169, 5, 44, 0, 0, 169, 198, 3, 10, 5, 8, 170, 198, 3, 16, 8, 0, 171, 198, 3, 12, 6, 0, 172, 174, 3, 16, 8, 0, 173, 175, 5, 44, 0, 0, 174, 173, 1, 0, 0, 0, 174, 175, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 177, 5, 39, 0, 0, 177, 178, 5, 43, 0, 0, 178, 183, 3, 16, 8, 0, 179, 180, 5, 34, 0, 0, 180, 182, 3, 16, 8, 0, 181, 179, 1, 0, 0, 0, 182, 185, 1, 0, 0, 0, 183, 181, 1, 0, 0, 0, 183, 184, 1, 0, 0, 0, 184, 186, 1, 0, 0, 0, 185, 183, 1, 0, 0, 0, 186, 187, 5, 50, 0, 0, 187, 198, 1, 0, 0, 0, 188, 189, 3, 16, 8, 0, 189, 191, 5, 40, 0, 0, 190, 192, 5, 44, 0, 0, 191, 190, 1, 0, 0, 0, 191, 192, 1, 0, 0, 0, 192, 193, 1, 0, 0, 0, 193, 194, 5, 45, 0, 0, 194, 198, 1, 0, 0, 0, 195, 196, 4, 5, 4, 0, 196, 198, 3, 14, 7, 0, 197, 167, 1, 0, 0, 0, 197, 170, 1, 0, 0, 0, 197, 171, 1, 0, 0, 0, 197, 172, 1, 0, 0, 0, 197, 188, 1, 0, 0, 0, 197, 195, 1, 0, 0, 0, 198, 207, 1, 0, 0, 0, 199, 200, 10, 5, 0, 0, 200, 201, 5, 30, 0, 0, 201, 206, 3, 10, 5, 6, 202, 203, 10, 4, 0, 0, 203, 204, 5, 47, 0, 0, 204, 206, 3, 10, 5, 5, 205, 199, 1, 0, 0, 0, 205, 202, 1, 0, 0, 0, 206, 209, 1, 0, 0, 0, 207, 205, 1, 0, 0, 0, 207, 208, 1, 0, 0, 0, 208, 11, 1, 0, 0, 0, 209, 207, 1, 0, 0, 0, 210, 212, 3, 16, 8, 0, 211, 213, 5, 44, 0, 0, 212, 211, 1, 0, 0, 0, 212, 213, 1, 0, 0, 0, 213, 214, 1, 0, 0, 0, 214, 215, 5, 42, 0, 0, 215, 216, 3, 106, 53, 0, 216, 225, 1, 0, 0, 0, 217, 219, 3, 16, 8, 0, 218, 220, 5, 44, 0, 0, 219, 218, 1, 0, 0, 0, 219, 220, 1, 0, 0, 0, 220, 221, 1, 0, 0, 0, 221, 222, 5, 49, 0, 0, 222, 223, 3, 106, 53, 0, 223, 225, 1, 0, 0, 0, 224, 210, 1, 0, 0, 0, 224, 217, 1, 0, 0, 0, 225, 13, 1, 0, 0, 0, 226, 227, 3, 58, 29, 0, 227, 228, 5, 24, 0, 0, 228, 229, 3, 68, 34, 0, 229, 15, 1, 0, 0, 0, 230, 236, 3, 18, 9, 0, 231, 232, 3, 18, 9, 0, 232, 233, 3, 108, 54, 0, 233, 234, 3, 18, 9, 0, 234, 236, 1, 0, 0, 0, 235, 230, 1, 0, 0, 0, 235, 231, 1, 0, 0, 0, 236, 17, 1, 0, 0, 0, 237, 238, 6, 9, -1, 0, 238, 242, 3, 20, 10, 0, 239, 240, 7, 0, 0, 0, 240, 242, 3, 18, 9, 3, 241, 237, 1, 0, 0, 0, 241, 239, 1, 0, 0, 0, 242, 251, 1, 0, 0, 0, 243, 244, 10, 2, 0, 0, 244, 245, 7, 1, 0, 0, 245, 250, 3, 18, 9, 3, 246, 247, 10, 1, 0, 0, 247, 248, 7, 0, 0, 0, 248, 250, 3, 18, 9, 2, 249, 243, 1, 0, 0, 0, 249, 246, 1, 0, 0, 0, 250, 253, 1, 0, 0, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 19, 1, 0, 0, 0, 253, 251, 1, 0, 0, 0, 254, 255, 6, 10, -1, 0, 255, 263, 3, 68, 34, 0, 256, 263, 3, 58, 29, 0, 257, 263, 3, 22, 11, 0, 258, 259, 5, 43, 0, 0, 259, 260, 3, 10, 5, 0, 260, 261, 5, 50, 0, 0, 261, 263, 1, 0, 0, 0, 262, 254, 1, 0, 0, 0, 262, 256, 1, 0, 0, 0, 262, 257, 1, 0, 0, 0, 262, 258, 1, 0, 0, 0, 263, 269, 1, 0, 0, 0, 264, 265, 10, 1, 0, 0, 265, 266, 5, 33, 0, 0, 266, 268, 3, 26, 13, 0, 267, 264, 1, 0, 0, 0, 268, 271, 1, 0, 0, 0, 269, 267, 1, 0, 0, 0, 269, 270, 1, 0, 0, 0, 270, 21, 1, 0, 0, 0, 271, 269, 1, 0, 0, 0, 272, 273, 3, 24, 12, 0, 273, 283, 5, 43, 0, 0, 274, 284, 5, 61, 0, 0, 275, 280, 3, 10, 5, 0, 276, 277, 5, 34, 0, 0, 277, 279, 3, 10, 5, 0, 278, 276, 1, 0, 0, 0, 279, 282, 1, 0, 0, 0, 280, 278, 1, 0, 0, 0, 280, 281, 1, 0, 0, 0, 281, 284, 1, 0, 0, 0, 282, 280, 1, 0, 0, 0, 283, 274, 1, 0, 0, 0, 283, 275, 1, 0, 0, 0, 283, 284, 1, 0, 0, 0, 284, 285, 1, 0, 0, 0, 285, 286, 5, 50, 0, 0, 286, 23, 1, 0, 0, 0, 287, 288, 3, 72, 36, 0, 288, 25, 1, 0, 0, 0, 289, 290, 3, 64, 32, 0, 290, 27, 1, 0, 0, 0, 291, 292, 5, 12, 0, 0, 292, 293, 3, 30, 15, 0, 293, 29, 1, 0, 0, 0, 294, 299, 3, 32, 16, 0, 295, 296, 5, 34, 0, 0, 296, 298, 3, 32, 16, 0, 297, 295, 1, 0, 0, 0, 298, 301, 1, 0, 0, 0, 299, 297, 1, 0, 0, 0, 299, 300, 1, 0, 0, 0, 300, 31, 1, 0, 0, 0, 301, 299, 1, 0, 0, 0, 302, 303, 3, 58, 29, 0, 303, 304, 5, 32, 0, 0, 304, 306, 1, 0, 0, 0, 305, 302, 1, 0, 0, 0, 305, 306, 1, 0, 0, 0, 306, 307, 1, 0, 0, 0, 307, 308, 3, 10, 5, 0, 308, 33, 1, 0, 0, 0, 309, 310, 5, 6, 0, 0, 310, 315, 3, 36, 18, 0, 311, 312, 5, 34, 0, 0, 312, 314, 3, 36, 18, 0, 313, 311, 1, 0, 0, 0, 314, 317, 1, 0, 0, 0, 315, 313, 1, 0, 0, 0, 315, 316, 1, 0, 0, 0, 316, 319, 1, 0, 0, 0, 317, 315, 1, 0, 0, 0, 318, 320, 3, 42, 21, 0, 319, 318, 1, 0, 0, 0, 319, 320, 1, 0, 0, 0, 320, 35, 1, 0, 0, 0, 321, 322, 3, 38, 19, 0, 322, 323, 5, 24, 0, 0, 323, 325, 1, 0, 0, 0, 324, 321, 1, 0, 0, 0, 324, 325, 1, 0, 0, 0, 325, 326, 1, 0, 0, 0, 326, 327, 3, 40, 20, 0, 327, 37, 1, 0, 0, 0, 328, 329, 5, 76, 0, 0, 329, 39, 1, 0, 0, 0, 330, 331, 7, 2, 0, 0, 331, 41, 1, 0, 0, 0, 332, 335, 3, 44, 22, 0, 333, 335, 3, 46, 23, 0, 334, 332, 1, 0, 0, 0, 334, 333, 1, 0, 0, 0, 335, 43, 1, 0, 0, 0, 336, 337, 5, 75, 0, 0, 337, 342, 5, 76, 0, 0, 338, 339, 5, 34, 0, 0, 339, 341, 5, 76, 0, 0, 340, 338, 1, 0, 0, 0, 341, 344, 1, 0, 0, 0, 342, 340, 1, 0, 0, 0, 342, 343, 1, 0, 0, 0, 343, 45, 1, 0, 0, 0, 344, 342, 1, 0, 0, 0, 345, 346, 5, 65, 0, 0, 346, 347, 3, 44, 22, 0, 347, 348, 5, 66, 0, 0, 348, 47, 1, 0, 0, 0, 349, 350, 5, 19, 0, 0, 350, 355, 3, 36, 18, 0, 351, 352, 5, 34, 0, 0, 352, 354, 3, 36, 18, 0, 353, 351, 1, 0, 0, 0, 354, 357, 1, 0, 0, 0, 355, 353, 1, 0, 0, 0, 355, 356, 1, 0, 0, 0, 356, 359, 1, 0, 0, 0, 357, 355, 1, 0, 0, 0, 358, 360, 3, 54, 27, 0, 359, 358, 1, 0, 0, 0, 359, 360, 1, 0, 0, 0, 360, 363, 1, 0, 0, 0, 361, 362, 5, 29, 0, 0, 362, 364, 3, 30, 15, 0, 363, 361, 1, 0, 0, 0, 363, 364, 1, 0, 0, 0, 364, 49, 1, 0, 0, 0, 365, 366, 5, 4, 0, 0, 366, 367, 3, 30, 15, 0, 367, 51, 1, 0, 0, 0, 368, 370, 5, 15, 0, 0, 369, 371, 3, 54, 27, 0, 370, 369, 1, 0, 0, 0, 370, 371, 1, 0, 0, 0, 371, 374, 1, 0, 0, 0, 372, 373, 5, 29, 0, 0, 373, 375, 3, 30, 15, 0, 374, 372, 1, 0, 0, 0, 374, 375, 1, 0, 0, 0, 375, 53, 1, 0, 0, 0, 376, 381, 3, 56, 28, 0, 377, 378, 5, 34, 0, 0, 378, 380, 3, 56, 28, 0, 379, 377, 1, 0, 0, 0, 380, 383, 1, 0, 0, 0, 381, 379, 1, 0, 0, 0, 381, 382, 1, 0, 0, 0, 382, 55, 1, 0, 0, 0, 383, 381, 1, 0, 0, 0, 384, 387, 3, 32, 16, 0, 385, 386, 5, 16, 0, 0, 386, 388, 3, 10, 5, 0, 387, 385, 1, 0, 0, 0, 387, 388, 1, 0, 0, 0, 388, 57, 1, 0, 0, 0, 389, 394, 3, 72, 36, 0, 390, 391, 5, 36, 0, 0, 391, 393, 3, 72, 36, 0, 392, 390, 1, 0, 0, 0, 393, 396, 1, 0, 0, 0, 394, 392, 1, 0, 0, 0, 394, 395, 1, 0, 0, 0, 395, 59, 1, 0, 0, 0, 396, 394, 1, 0, 0, 0, 397, 402, 3, 66, 33, 0, 398, 399, 5, 36, 0, 0, 399, 401, 3, 66, 33, 0, 400, 398, 1, 0, 0, 0, 401, 404, 1, 0, 0, 0, 402, 400, 1, 0, 0, 0, 402, 403, 1, 0, 0, 0, 403, 61, 1, 0, 0, 0, 404, 402, 1, 0, 0, 0, 405, 410, 3, 60, 30, 0, 406, 407, 5, 34, 0, 0, 407, 409, 3, 60, 30, 0, 408, 406, 1, 0, 0, 0, 409, 412, 1, 0, 0, 0, 410, 408, 1, 0, 0, 0, 410, 411, 1, 0, 0, 0, 411, 63, 1, 0, 0, 0, 412, 410, 1, 0, 0, 0, 413, 414, 7, 3, 0, 0, 414, 65, 1, 0, 0, 0, 415, 419, 5, 80, 0, 0, 416, 417, 4, 33, 10, 0, 417, 419, 3, 70, 35, 0, 418, 415, 1, 0, 0, 0, 418, 416, 1, 0, 0, 0, 419, 67, 1, 0, 0, 0, 420, 463, 5, 45, 0, 0, 421, 422, 3, 104, 52, 0, 422, 423, 5, 67, 0, 0, 423, 463, 1, 0, 0, 0, 424, 463, 3, 102, 51, 0, 425, 463, 3, 104, 52, 0, 426, 463, 3, 98, 49, 0, 427, 463, 3, 70, 35, 0, 428, 463, 3, 106, 53, 0, 429, 430, 5, 65, 0, 0, 430, 435, 3, 100, 50, 0, 431, 432, 5, 34, 0, 0, 432, 434, 3, 100, 50, 0, 433, 431, 1, 0, 0, 0, 434, 437, 1, 0, 0, 0, 435, 433, 1, 0, 0, 0, 435, 436, 1, 0, 0, 0, 436, 438, 1, 0, 0, 0, 437, 435, 1, 0, 0, 0, 438, 439, 5, 66, 0, 0, 439, 463, 1, 0, 0, 0, 440, 441, 5, 65, 0, 0, 441, 446, 3, 98, 49, 0, 442, 443, 5, 34, 0, 0, 443, 445, 3, 98, 49, 0, 444, 442, 1, 0, 0, 0, 445, 448, 1, 0, 0, 0, 446, 444, 1, 0, 0, 0, 446, 447, 1, 0, 0, 0, 447, 449, 1, 0, 0, 0, 448, 446, 1, 0, 0, 0, 449, 450, 5, 66, 0, 0, 450, 463, 1, 0, 0, 0, 451, 452, 5, 65, 0, 0, 452, 457, 3, 106, 53, 0, 453, 454, 5, 34, 0, 0, 454, 456, 3, 106, 53, 0, 455, 453, 1, 0, 0, 0, 456, 459, 1, 0, 0, 0, 457, 455, 1, 0, 0, 0, 457, 458, 1, 0, 0, 0, 458, 460, 1, 0, 0, 0, 459, 457, 1, 0, 0, 0, 460, 461, 5, 66, 0, 0, 461, 463, 1, 0, 0, 0, 462, 420, 1, 0, 0, 0, 462, 421, 1, 0, 0, 0, 462, 424, 1, 0, 0, 0, 462, 425, 1, 0, 0, 0, 462, 426, 1, 0, 0, 0, 462, 427, 1, 0, 0, 0, 462, 428, 1, 0, 0, 0, 462, 429, 1, 0, 0, 0, 462, 440, 1, 0, 0, 0, 462, 451, 1, 0, 0, 0, 463, 69, 1, 0, 0, 0, 464, 467, 5, 48, 0, 0, 465, 467, 5, 64, 0, 0, 466, 464, 1, 0, 0, 0, 466, 465, 1, 0, 0, 0, 467, 71, 1, 0, 0, 0, 468, 472, 3, 64, 32, 0, 469, 470, 4, 36, 11, 0, 470, 472, 3, 70, 35, 0, 471, 468, 1, 0, 0, 0, 471, 469, 1, 0, 0, 0, 472, 73, 1, 0, 0, 0, 473, 474, 5, 9, 0, 0, 474, 475, 5, 27, 0, 0, 475, 75, 1, 0, 0, 0, 476, 477, 5, 14, 0, 0, 477, 482, 3, 78, 39, 0, 478, 479, 5, 34, 0, 0, 479, 481, 3, 78, 39, 0, 480, 478, 1, 0, 0, 0, 481, 484, 1, 0, 0, 0, 482, 480, 1, 0, 0, 0, 482, 483, 1, 0, 0, 0, 483, 77, 1, 0, 0, 0, 484, 482, 1, 0, 0, 0, 485, 487, 3, 10, 5, 0, 486, 488, 7, 4, 0, 0, 487, 486, 1, 0, 0, 0, 487, 488, 1, 0, 0, 0, 488, 491, 1, 0, 0, 0, 489, 490, 5, 46, 0, 0, 490, 492, 7, 5, 0, 0, 491, 489, 1, 0, 0, 0, 491, 492, 1, 0, 0, 0, 492, 79, 1, 0, 0, 0, 493, 494, 5, 8, 0, 0, 494, 495, 3, 62, 31, 0, 495, 81, 1, 0, 0, 0, 496, 497, 5, 2, 0, 0, 497, 498, 3, 62, 31, 0, 498, 83, 1, 0, 0, 0, 499, 500, 5, 11, 0, 0, 500, 505, 3, 86, 43, 0, 501, 502, 5, 34, 0, 0, 502, 504, 3, 86, 43, 0, 503, 501, 1, 0, 0, 0, 504, 507, 1, 0, 0, 0, 505, 503, 1, 0, 0, 0, 505, 506, 1, 0, 0, 0, 506, 85, 1, 0, 0, 0, 507, 505, 1, 0, 0, 0, 508, 509, 3, 60, 30, 0, 509, 510, 5, 84, 0, 0, 510, 511, 3, 60, 30, 0, 511, 87, 1, 0, 0, 0, 512, 513, 5, 1, 0, 0, 513, 514, 3, 20, 10, 0, 514, 516, 3, 106, 53, 0, 515, 517, 3, 94, 47, 0, 516, 515, 1, 0, 0, 0, 516, 517, 1, 0, 0, 0, 517, 89, 1, 0, 0, 0, 518, 519, 5, 7, 0, 0, 519, 520, 3, 20, 10, 0, 520, 521, 3, 106, 53, 0, 521, 91, 1, 0, 0, 0, 522, 523, 5, 10, 0, 0, 523, 524, 3, 58, 29, 0, 524, 93, 1, 0, 0, 0, 525, 530, 3, 96, 48, 0, 526, 527, 5, 34, 0, 0, 527, 529, 3, 96, 48, 0, 528, 526, 1, 0, 0, 0, 529, 532, 1, 0, 0, 0, 530, 528, 1, 0, 0, 0, 530, 531, 1, 0, 0, 0, 531, 95, 1, 0, 0, 0, 532, 530, 1, 0, 0, 0, 533, 534, 3, 64, 32, 0, 534, 535, 5, 32, 0, 0, 535, 536, 3, 68, 34, 0, 536, 97, 1, 0, 0, 0, 537, 538, 7, 6, 0, 0, 538, 99, 1, 0, 0, 0, 539, 542, 3, 102, 51, 0, 540, 542, 3, 104, 52, 0, 541, 539, 1, 0, 0, 0, 541, 540, 1, 0, 0, 0, 542, 101, 1, 0, 0, 0, 543, 545, 7, 0, 0, 0, 544, 543, 1, 0, 0, 0, 544, 545, 1, 0, 0, 0, 545, 546, 1, 0, 0, 0, 546, 547, 5, 28, 0, 0, 547, 103, 1, 0, 0, 0, 548, 550, 7, 0, 0, 0, 549, 548, 1, 0, 0, 0, 549, 550, 1, 0, 0, 0, 550, 551, 1, 0, 0, 0, 551, 552, 5, 27, 0, 0, 552, 105, 1, 0, 0, 0, 553, 554, 5, 26, 0, 0, 554, 107, 1, 0, 0, 0, 555, 556, 7, 7, 0, 0, 556, 109, 1, 0, 0, 0, 557, 558, 5, 5, 0, 0, 558, 559, 3, 112, 56, 0, 559, 111, 1, 0, 0, 0, 560, 561, 5, 65, 0, 0, 561, 562, 3, 2, 1, 0, 562, 563, 5, 66, 0, 0, 563, 113, 1, 0, 0, 0, 564, 565, 5, 13, 0, 0, 565, 566, 5, 100, 0, 0, 566, 115, 1, 0, 0, 0, 567, 568, 5, 3, 0, 0, 568, 571, 5, 90, 0, 0, 569, 570, 5, 88, 0, 0, 570, 572, 3, 60, 30, 0, 571, 569, 1, 0, 0, 0, 571, 572, 1, 0, 0, 0, 572, 582, 1, 0, 0, 0, 573, 574, 5, 89, 0, 0, 574, 579, 3, 118, 59, 0, 575, 576, 5, 34, 0, 0, 576, 578, 3, 118, 59, 0, 577, 575, 1, 0, 0, 0, 578, 581, 1, 0, 0, 0, 579, 577, 1, 0, 0, 0, 579, 580, 1, 0, 0, 0, 580, 583, 1, 0, 0, 0, 581, 579, 1, 0, 0, 0, 582, 573, 1, 0, 0, 0, 582, 583, 1, 0, 0, 0, 583, 117, 1, 0, 0, 0, 584, 585, 3, 60, 30, 0, 585, 586, 5, 32, 0, 0, 586, 588, 1, 0, 0, 0, 587, 584, 1, 0, 0, 0, 587, 588, 1, 0, 0, 0, 588, 589, 1, 0, 0, 0, 589, 590, 3, 60, 30, 0, 590, 119, 1, 0, 0, 0, 591, 592, 5, 18, 0, 0, 592, 593, 3, 36, 18, 0, 593, 594, 5, 88, 0, 0, 594, 595, 3, 62, 31, 0, 595, 121, 1, 0, 0, 0, 596, 597, 5, 17, 0, 0, 597, 600, 3, 54, 27, 0, 598, 599, 5, 29, 0, 0, 599, 601, 3, 30, 15, 0, 600, 598, 1, 0, 0, 0, 600, 601, 1, 0, 0, 0, 601, 123, 1, 0, 0, 0, 58, 135, 144, 162, 174, 183, 191, 197, 205, 207, 212, 219, 224, 235, 241, 249, 251, 262, 269, 280, 283, 299, 305, 315, 319, 324, 334, 342, 355, 359, 363, 370, 374, 381, 387, 394, 402, 410, 418, 435, 446, 457, 462, 466, 471, 482, 487, 491, 505, 516, 530, 541, 544, 549, 571, 579, 582, 587, 600] \ No newline at end of file diff --git a/packages/kbn-esql-ast/src/antlr/esql_parser.tokens b/packages/kbn-esql-ast/src/antlr/esql_parser.tokens index 4d1f426289149..3dd1a2c754038 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_parser.tokens +++ b/packages/kbn-esql-ast/src/antlr/esql_parser.tokens @@ -21,46 +21,46 @@ UNKNOWN_CMD=20 LINE_COMMENT=21 MULTILINE_COMMENT=22 WS=23 -PIPE=24 -QUOTED_STRING=25 -INTEGER_LITERAL=26 -DECIMAL_LITERAL=27 -BY=28 -AND=29 -ASC=30 -ASSIGN=31 -CAST_OP=32 -COMMA=33 -DESC=34 -DOT=35 -FALSE=36 -FIRST=37 -IN=38 -IS=39 -LAST=40 -LIKE=41 -LP=42 -NOT=43 -NULL=44 -NULLS=45 -OR=46 -PARAM=47 -RLIKE=48 -RP=49 -TRUE=50 -EQ=51 -CIEQ=52 -NEQ=53 -LT=54 -LTE=55 -GT=56 -GTE=57 -PLUS=58 -MINUS=59 -ASTERISK=60 -SLASH=61 -PERCENT=62 -MATCH=63 +COLON=24 +PIPE=25 +QUOTED_STRING=26 +INTEGER_LITERAL=27 +DECIMAL_LITERAL=28 +BY=29 +AND=30 +ASC=31 +ASSIGN=32 +CAST_OP=33 +COMMA=34 +DESC=35 +DOT=36 +FALSE=37 +FIRST=38 +IN=39 +IS=40 +LAST=41 +LIKE=42 +LP=43 +NOT=44 +NULL=45 +NULLS=46 +OR=47 +PARAM=48 +RLIKE=49 +RP=50 +TRUE=51 +EQ=52 +CIEQ=53 +NEQ=54 +LT=55 +LTE=56 +GT=57 +GTE=58 +PLUS=59 +MINUS=60 +ASTERISK=61 +SLASH=62 +PERCENT=63 NAMED_OR_POSITIONAL_PARAM=64 OPENING_BRACKET=65 CLOSING_BRACKET=66 @@ -101,23 +101,22 @@ INFO=100 SHOW_LINE_COMMENT=101 SHOW_MULTILINE_COMMENT=102 SHOW_WS=103 -COLON=104 -SETTING=105 -SETTING_LINE_COMMENT=106 -SETTTING_MULTILINE_COMMENT=107 -SETTING_WS=108 -LOOKUP_LINE_COMMENT=109 -LOOKUP_MULTILINE_COMMENT=110 -LOOKUP_WS=111 -LOOKUP_FIELD_LINE_COMMENT=112 -LOOKUP_FIELD_MULTILINE_COMMENT=113 -LOOKUP_FIELD_WS=114 -METRICS_LINE_COMMENT=115 -METRICS_MULTILINE_COMMENT=116 -METRICS_WS=117 -CLOSING_METRICS_LINE_COMMENT=118 -CLOSING_METRICS_MULTILINE_COMMENT=119 -CLOSING_METRICS_WS=120 +SETTING=104 +SETTING_LINE_COMMENT=105 +SETTTING_MULTILINE_COMMENT=106 +SETTING_WS=107 +LOOKUP_LINE_COMMENT=108 +LOOKUP_MULTILINE_COMMENT=109 +LOOKUP_WS=110 +LOOKUP_FIELD_LINE_COMMENT=111 +LOOKUP_FIELD_MULTILINE_COMMENT=112 +LOOKUP_FIELD_WS=113 +METRICS_LINE_COMMENT=114 +METRICS_MULTILINE_COMMENT=115 +METRICS_WS=116 +CLOSING_METRICS_LINE_COMMENT=117 +CLOSING_METRICS_MULTILINE_COMMENT=118 +CLOSING_METRICS_WS=119 'dissect'=1 'drop'=2 'enrich'=3 @@ -134,47 +133,46 @@ CLOSING_METRICS_WS=120 'sort'=14 'stats'=15 'where'=16 -'|'=24 -'by'=28 -'and'=29 -'asc'=30 -'='=31 -'::'=32 -','=33 -'desc'=34 -'.'=35 -'false'=36 -'first'=37 -'in'=38 -'is'=39 -'last'=40 -'like'=41 -'('=42 -'not'=43 -'null'=44 -'nulls'=45 -'or'=46 -'?'=47 -'rlike'=48 -')'=49 -'true'=50 -'=='=51 -'=~'=52 -'!='=53 -'<'=54 -'<='=55 -'>'=56 -'>='=57 -'+'=58 -'-'=59 -'*'=60 -'/'=61 -'%'=62 -'match'=63 +':'=24 +'|'=25 +'by'=29 +'and'=30 +'asc'=31 +'='=32 +'::'=33 +','=34 +'desc'=35 +'.'=36 +'false'=37 +'first'=38 +'in'=39 +'is'=40 +'last'=41 +'like'=42 +'('=43 +'not'=44 +'null'=45 +'nulls'=46 +'or'=47 +'?'=48 +'rlike'=49 +')'=50 +'true'=51 +'=='=52 +'=~'=53 +'!='=54 +'<'=55 +'<='=56 +'>'=57 +'>='=58 +'+'=59 +'-'=60 +'*'=61 +'/'=62 +'%'=63 ']'=66 'metadata'=75 'as'=84 'on'=88 'with'=89 'info'=100 -':'=104 diff --git a/packages/kbn-esql-ast/src/antlr/esql_parser.ts b/packages/kbn-esql-ast/src/antlr/esql_parser.ts index b0af12e1ebc1e..4dc0c5c628e37 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_parser.ts +++ b/packages/kbn-esql-ast/src/antlr/esql_parser.ts @@ -51,46 +51,46 @@ export default class esql_parser extends parser_config { public static readonly LINE_COMMENT = 21; public static readonly MULTILINE_COMMENT = 22; public static readonly WS = 23; - public static readonly PIPE = 24; - public static readonly QUOTED_STRING = 25; - public static readonly INTEGER_LITERAL = 26; - public static readonly DECIMAL_LITERAL = 27; - public static readonly BY = 28; - public static readonly AND = 29; - public static readonly ASC = 30; - public static readonly ASSIGN = 31; - public static readonly CAST_OP = 32; - public static readonly COMMA = 33; - public static readonly DESC = 34; - public static readonly DOT = 35; - public static readonly FALSE = 36; - public static readonly FIRST = 37; - public static readonly IN = 38; - public static readonly IS = 39; - public static readonly LAST = 40; - public static readonly LIKE = 41; - public static readonly LP = 42; - public static readonly NOT = 43; - public static readonly NULL = 44; - public static readonly NULLS = 45; - public static readonly OR = 46; - public static readonly PARAM = 47; - public static readonly RLIKE = 48; - public static readonly RP = 49; - public static readonly TRUE = 50; - public static readonly EQ = 51; - public static readonly CIEQ = 52; - public static readonly NEQ = 53; - public static readonly LT = 54; - public static readonly LTE = 55; - public static readonly GT = 56; - public static readonly GTE = 57; - public static readonly PLUS = 58; - public static readonly MINUS = 59; - public static readonly ASTERISK = 60; - public static readonly SLASH = 61; - public static readonly PERCENT = 62; - public static readonly MATCH = 63; + public static readonly COLON = 24; + public static readonly PIPE = 25; + public static readonly QUOTED_STRING = 26; + public static readonly INTEGER_LITERAL = 27; + public static readonly DECIMAL_LITERAL = 28; + public static readonly BY = 29; + public static readonly AND = 30; + public static readonly ASC = 31; + public static readonly ASSIGN = 32; + public static readonly CAST_OP = 33; + public static readonly COMMA = 34; + public static readonly DESC = 35; + public static readonly DOT = 36; + public static readonly FALSE = 37; + public static readonly FIRST = 38; + public static readonly IN = 39; + public static readonly IS = 40; + public static readonly LAST = 41; + public static readonly LIKE = 42; + public static readonly LP = 43; + public static readonly NOT = 44; + public static readonly NULL = 45; + public static readonly NULLS = 46; + public static readonly OR = 47; + public static readonly PARAM = 48; + public static readonly RLIKE = 49; + public static readonly RP = 50; + public static readonly TRUE = 51; + public static readonly EQ = 52; + public static readonly CIEQ = 53; + public static readonly NEQ = 54; + public static readonly LT = 55; + public static readonly LTE = 56; + public static readonly GT = 57; + public static readonly GTE = 58; + public static readonly PLUS = 59; + public static readonly MINUS = 60; + public static readonly ASTERISK = 61; + public static readonly SLASH = 62; + public static readonly PERCENT = 63; public static readonly NAMED_OR_POSITIONAL_PARAM = 64; public static readonly OPENING_BRACKET = 65; public static readonly CLOSING_BRACKET = 66; @@ -131,23 +131,22 @@ export default class esql_parser extends parser_config { public static readonly SHOW_LINE_COMMENT = 101; public static readonly SHOW_MULTILINE_COMMENT = 102; public static readonly SHOW_WS = 103; - public static readonly COLON = 104; - public static readonly SETTING = 105; - public static readonly SETTING_LINE_COMMENT = 106; - public static readonly SETTTING_MULTILINE_COMMENT = 107; - public static readonly SETTING_WS = 108; - public static readonly LOOKUP_LINE_COMMENT = 109; - public static readonly LOOKUP_MULTILINE_COMMENT = 110; - public static readonly LOOKUP_WS = 111; - public static readonly LOOKUP_FIELD_LINE_COMMENT = 112; - public static readonly LOOKUP_FIELD_MULTILINE_COMMENT = 113; - public static readonly LOOKUP_FIELD_WS = 114; - public static readonly METRICS_LINE_COMMENT = 115; - public static readonly METRICS_MULTILINE_COMMENT = 116; - public static readonly METRICS_WS = 117; - public static readonly CLOSING_METRICS_LINE_COMMENT = 118; - public static readonly CLOSING_METRICS_MULTILINE_COMMENT = 119; - public static readonly CLOSING_METRICS_WS = 120; + public static readonly SETTING = 104; + public static readonly SETTING_LINE_COMMENT = 105; + public static readonly SETTTING_MULTILINE_COMMENT = 106; + public static readonly SETTING_WS = 107; + public static readonly LOOKUP_LINE_COMMENT = 108; + public static readonly LOOKUP_MULTILINE_COMMENT = 109; + public static readonly LOOKUP_WS = 110; + public static readonly LOOKUP_FIELD_LINE_COMMENT = 111; + public static readonly LOOKUP_FIELD_MULTILINE_COMMENT = 112; + public static readonly LOOKUP_FIELD_WS = 113; + public static readonly METRICS_LINE_COMMENT = 114; + public static readonly METRICS_MULTILINE_COMMENT = 115; + public static readonly METRICS_WS = 116; + public static readonly CLOSING_METRICS_LINE_COMMENT = 117; + public static readonly CLOSING_METRICS_MULTILINE_COMMENT = 118; + public static readonly CLOSING_METRICS_WS = 119; public static override readonly EOF = Token.EOF; public static readonly RULE_singleStatement = 0; public static readonly RULE_query = 1; @@ -224,26 +223,26 @@ export default class esql_parser extends parser_config { null, null, null, null, null, null, - "'|'", null, + "':'", "'|'", null, null, - "'by'", "'and'", - "'asc'", "'='", - "'::'", "','", - "'desc'", "'.'", - "'false'", "'first'", - "'in'", "'is'", - "'last'", "'like'", - "'('", "'not'", - "'null'", "'nulls'", - "'or'", "'?'", - "'rlike'", "')'", - "'true'", "'=='", - "'=~'", "'!='", - "'<'", "'<='", - "'>'", "'>='", - "'+'", "'-'", - "'*'", "'/'", - "'%'", "'match'", + null, "'by'", + "'and'", "'asc'", + "'='", "'::'", + "','", "'desc'", + "'.'", "'false'", + "'first'", "'in'", + "'is'", "'last'", + "'like'", "'('", + "'not'", "'null'", + "'nulls'", "'or'", + "'?'", "'rlike'", + "')'", "'true'", + "'=='", "'=~'", + "'!='", "'<'", + "'<='", "'>'", + "'>='", "'+'", + "'-'", "'*'", + "'/'", "'%'", null, null, "']'", null, null, null, @@ -262,9 +261,7 @@ export default class esql_parser extends parser_config { null, null, null, null, null, null, - "'info'", null, - null, null, - "':'" ]; + "'info'" ]; public static readonly symbolicNames: (string | null)[] = [ null, "DISSECT", "DROP", "ENRICH", "EVAL", "EXPLAIN", @@ -280,8 +277,8 @@ export default class esql_parser extends parser_config { "UNKNOWN_CMD", "LINE_COMMENT", "MULTILINE_COMMENT", - "WS", "PIPE", - "QUOTED_STRING", + "WS", "COLON", + "PIPE", "QUOTED_STRING", "INTEGER_LITERAL", "DECIMAL_LITERAL", "BY", "AND", @@ -302,7 +299,7 @@ export default class esql_parser extends parser_config { "GTE", "PLUS", "MINUS", "ASTERISK", "SLASH", "PERCENT", - "MATCH", "NAMED_OR_POSITIONAL_PARAM", + "NAMED_OR_POSITIONAL_PARAM", "OPENING_BRACKET", "CLOSING_BRACKET", "UNQUOTED_IDENTIFIER", @@ -339,7 +336,7 @@ export default class esql_parser extends parser_config { "INFO", "SHOW_LINE_COMMENT", "SHOW_MULTILINE_COMMENT", "SHOW_WS", - "COLON", "SETTING", + "SETTING", "SETTING_LINE_COMMENT", "SETTTING_MULTILINE_COMMENT", "SETTING_WS", @@ -767,7 +764,7 @@ export default class esql_parser extends parser_config { this.state = 174; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===43) { + if (_la===44) { { this.state = 173; this.match(esql_parser.NOT); @@ -783,7 +780,7 @@ export default class esql_parser extends parser_config { this.state = 183; this._errHandler.sync(this); _la = this._input.LA(1); - while (_la===33) { + while (_la===34) { { { this.state = 179; @@ -812,7 +809,7 @@ export default class esql_parser extends parser_config { this.state = 191; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===43) { + if (_la===44) { { this.state = 190; this.match(esql_parser.NOT); @@ -921,7 +918,7 @@ export default class esql_parser extends parser_config { this.state = 212; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===43) { + if (_la===44) { { this.state = 211; this.match(esql_parser.NOT); @@ -942,7 +939,7 @@ export default class esql_parser extends parser_config { this.state = 219; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===43) { + if (_la===44) { { this.state = 218; this.match(esql_parser.NOT); @@ -979,11 +976,11 @@ export default class esql_parser extends parser_config { this.enterOuterAlt(localctx, 1); { this.state = 226; - this.valueExpression(); + localctx._fieldExp = this.qualifiedName(); this.state = 227; - this.match(esql_parser.MATCH); + this.match(esql_parser.COLON); this.state = 228; - localctx._queryString = this.string_(); + localctx._queryString = this.constant(); } } catch (re) { @@ -1085,7 +1082,7 @@ export default class esql_parser extends parser_config { this.state = 239; (localctx as ArithmeticUnaryContext)._operator = this._input.LT(1); _la = this._input.LA(1); - if(!(_la===58 || _la===59)) { + if(!(_la===59 || _la===60)) { (localctx as ArithmeticUnaryContext)._operator = this._errHandler.recoverInline(this); } else { @@ -1123,7 +1120,7 @@ export default class esql_parser extends parser_config { this.state = 244; (localctx as ArithmeticBinaryContext)._operator = this._input.LT(1); _la = this._input.LA(1); - if(!(((((_la - 60)) & ~0x1F) === 0 && ((1 << (_la - 60)) & 7) !== 0))) { + if(!(((((_la - 61)) & ~0x1F) === 0 && ((1 << (_la - 61)) & 7) !== 0))) { (localctx as ArithmeticBinaryContext)._operator = this._errHandler.recoverInline(this); } else { @@ -1146,7 +1143,7 @@ export default class esql_parser extends parser_config { this.state = 247; (localctx as ArithmeticBinaryContext)._operator = this._input.LT(1); _la = this._input.LA(1); - if(!(_la===58 || _la===59)) { + if(!(_la===59 || _la===60)) { (localctx as ArithmeticBinaryContext)._operator = this._errHandler.recoverInline(this); } else { @@ -1318,7 +1315,7 @@ export default class esql_parser extends parser_config { this.state = 280; this._errHandler.sync(this); _la = this._input.LA(1); - while (_la===33) { + while (_la===34) { { { this.state = 276; @@ -1358,23 +1355,10 @@ export default class esql_parser extends parser_config { let localctx: FunctionNameContext = new FunctionNameContext(this, this._ctx, this.state); this.enterRule(localctx, 24, esql_parser.RULE_functionName); try { - this.state = 289; - this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 20, this._ctx) ) { - case 1: - this.enterOuterAlt(localctx, 1); - { - this.state = 287; - this.match(esql_parser.MATCH); - } - break; - case 2: - this.enterOuterAlt(localctx, 2); - { - this.state = 288; - this.identifierOrParameter(); - } - break; + this.enterOuterAlt(localctx, 1); + { + this.state = 287; + this.identifierOrParameter(); } } catch (re) { @@ -1399,7 +1383,7 @@ export default class esql_parser extends parser_config { localctx = new ToDataTypeContext(this, localctx); this.enterOuterAlt(localctx, 1); { - this.state = 291; + this.state = 289; this.identifier(); } } @@ -1424,9 +1408,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 293; + this.state = 291; this.match(esql_parser.ROW); - this.state = 294; + this.state = 292; this.fields(); } } @@ -1452,25 +1436,25 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 296; + this.state = 294; this.field(); - this.state = 301; + this.state = 299; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 21, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 20, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 297; + this.state = 295; this.match(esql_parser.COMMA); - this.state = 298; + this.state = 296; this.field(); } } } - this.state = 303; + this.state = 301; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 21, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 20, this._ctx); } } } @@ -1495,19 +1479,19 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 307; + this.state = 305; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 22, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 21, this._ctx) ) { case 1: { - this.state = 304; + this.state = 302; this.qualifiedName(); - this.state = 305; + this.state = 303; this.match(esql_parser.ASSIGN); } break; } - this.state = 309; + this.state = 307; this.booleanExpression(0); } } @@ -1533,34 +1517,34 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 311; + this.state = 309; this.match(esql_parser.FROM); - this.state = 312; + this.state = 310; this.indexPattern(); - this.state = 317; + this.state = 315; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 23, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 22, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 313; + this.state = 311; this.match(esql_parser.COMMA); - this.state = 314; + this.state = 312; this.indexPattern(); } } } - this.state = 319; + this.state = 317; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 23, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 22, this._ctx); } - this.state = 321; + this.state = 319; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 24, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 23, this._ctx) ) { case 1: { - this.state = 320; + this.state = 318; this.metadata(); } break; @@ -1588,19 +1572,19 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 326; + this.state = 324; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 25, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 24, this._ctx) ) { case 1: { - this.state = 323; + this.state = 321; this.clusterString(); - this.state = 324; + this.state = 322; this.match(esql_parser.COLON); } break; } - this.state = 328; + this.state = 326; this.indexString(); } } @@ -1625,7 +1609,7 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 330; + this.state = 328; this.match(esql_parser.UNQUOTED_SOURCE); } } @@ -1651,9 +1635,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 332; + this.state = 330; _la = this._input.LA(1); - if(!(_la===25 || _la===76)) { + if(!(_la===26 || _la===76)) { this._errHandler.recoverInline(this); } else { @@ -1681,20 +1665,20 @@ export default class esql_parser extends parser_config { let localctx: MetadataContext = new MetadataContext(this, this._ctx, this.state); this.enterRule(localctx, 42, esql_parser.RULE_metadata); try { - this.state = 336; + this.state = 334; this._errHandler.sync(this); switch (this._input.LA(1)) { case 75: this.enterOuterAlt(localctx, 1); { - this.state = 334; + this.state = 332; this.metadataOption(); } break; case 65: this.enterOuterAlt(localctx, 2); { - this.state = 335; + this.state = 333; this.deprecated_metadata(); } break; @@ -1724,27 +1708,27 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 338; + this.state = 336; this.match(esql_parser.METADATA); - this.state = 339; + this.state = 337; this.match(esql_parser.UNQUOTED_SOURCE); - this.state = 344; + this.state = 342; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 27, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 26, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 340; + this.state = 338; this.match(esql_parser.COMMA); - this.state = 341; + this.state = 339; this.match(esql_parser.UNQUOTED_SOURCE); } } } - this.state = 346; + this.state = 344; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 27, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 26, this._ctx); } } } @@ -1769,11 +1753,11 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 347; + this.state = 345; this.match(esql_parser.OPENING_BRACKET); - this.state = 348; + this.state = 346; this.metadataOption(); - this.state = 349; + this.state = 347; this.match(esql_parser.CLOSING_BRACKET); } } @@ -1799,46 +1783,46 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 351; + this.state = 349; this.match(esql_parser.DEV_METRICS); - this.state = 352; + this.state = 350; this.indexPattern(); - this.state = 357; + this.state = 355; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 28, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 27, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 353; + this.state = 351; this.match(esql_parser.COMMA); - this.state = 354; + this.state = 352; this.indexPattern(); } } } - this.state = 359; + this.state = 357; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 28, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 27, this._ctx); } - this.state = 361; + this.state = 359; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 29, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 28, this._ctx) ) { case 1: { - this.state = 360; + this.state = 358; localctx._aggregates = this.aggFields(); } break; } - this.state = 365; + this.state = 363; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 30, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 29, this._ctx) ) { case 1: { - this.state = 363; + this.state = 361; this.match(esql_parser.BY); - this.state = 364; + this.state = 362; localctx._grouping = this.fields(); } break; @@ -1866,9 +1850,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 367; + this.state = 365; this.match(esql_parser.EVAL); - this.state = 368; + this.state = 366; this.fields(); } } @@ -1893,26 +1877,26 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 370; + this.state = 368; this.match(esql_parser.STATS); - this.state = 372; + this.state = 370; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 31, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 30, this._ctx) ) { case 1: { - this.state = 371; + this.state = 369; localctx._stats = this.aggFields(); } break; } - this.state = 376; + this.state = 374; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 32, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 31, this._ctx) ) { case 1: { - this.state = 374; + this.state = 372; this.match(esql_parser.BY); - this.state = 375; + this.state = 373; localctx._grouping = this.fields(); } break; @@ -1941,25 +1925,25 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 378; + this.state = 376; this.aggField(); - this.state = 383; + this.state = 381; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 33, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 32, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 379; + this.state = 377; this.match(esql_parser.COMMA); - this.state = 380; + this.state = 378; this.aggField(); } } } - this.state = 385; + this.state = 383; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 33, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 32, this._ctx); } } } @@ -1984,16 +1968,16 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 386; + this.state = 384; this.field(); - this.state = 389; + this.state = 387; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 34, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 33, this._ctx) ) { case 1: { - this.state = 387; + this.state = 385; this.match(esql_parser.WHERE); - this.state = 388; + this.state = 386; this.booleanExpression(0); } break; @@ -2022,25 +2006,25 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 391; + this.state = 389; this.identifierOrParameter(); - this.state = 396; + this.state = 394; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 35, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 34, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 392; + this.state = 390; this.match(esql_parser.DOT); - this.state = 393; + this.state = 391; this.identifierOrParameter(); } } } - this.state = 398; + this.state = 396; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 35, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 34, this._ctx); } } } @@ -2066,25 +2050,25 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 399; + this.state = 397; this.identifierPattern(); - this.state = 404; + this.state = 402; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 36, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 35, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 400; + this.state = 398; this.match(esql_parser.DOT); - this.state = 401; + this.state = 399; this.identifierPattern(); } } } - this.state = 406; + this.state = 404; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 36, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 35, this._ctx); } } } @@ -2110,25 +2094,25 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 407; + this.state = 405; this.qualifiedNamePattern(); - this.state = 412; + this.state = 410; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 37, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 36, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 408; + this.state = 406; this.match(esql_parser.COMMA); - this.state = 409; + this.state = 407; this.qualifiedNamePattern(); } } } - this.state = 414; + this.state = 412; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 37, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 36, this._ctx); } } } @@ -2154,7 +2138,7 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 415; + this.state = 413; _la = this._input.LA(1); if(!(_la===67 || _la===68)) { this._errHandler.recoverInline(this); @@ -2184,24 +2168,24 @@ export default class esql_parser extends parser_config { let localctx: IdentifierPatternContext = new IdentifierPatternContext(this, this._ctx, this.state); this.enterRule(localctx, 66, esql_parser.RULE_identifierPattern); try { - this.state = 420; + this.state = 418; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 38, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 37, this._ctx) ) { case 1: this.enterOuterAlt(localctx, 1); { - this.state = 417; + this.state = 415; this.match(esql_parser.ID_PATTERN); } break; case 2: this.enterOuterAlt(localctx, 2); { - this.state = 418; + this.state = 416; if (!(this.isDevVersion())) { throw this.createFailedPredicateException("this.isDevVersion()"); } - this.state = 419; + this.state = 417; this.parameter(); } break; @@ -2227,14 +2211,14 @@ export default class esql_parser extends parser_config { this.enterRule(localctx, 68, esql_parser.RULE_constant); let _la: number; try { - this.state = 464; + this.state = 462; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 42, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 41, this._ctx) ) { case 1: localctx = new NullLiteralContext(this, localctx); this.enterOuterAlt(localctx, 1); { - this.state = 422; + this.state = 420; this.match(esql_parser.NULL); } break; @@ -2242,9 +2226,9 @@ export default class esql_parser extends parser_config { localctx = new QualifiedIntegerLiteralContext(this, localctx); this.enterOuterAlt(localctx, 2); { - this.state = 423; + this.state = 421; this.integerValue(); - this.state = 424; + this.state = 422; this.match(esql_parser.UNQUOTED_IDENTIFIER); } break; @@ -2252,7 +2236,7 @@ export default class esql_parser extends parser_config { localctx = new DecimalLiteralContext(this, localctx); this.enterOuterAlt(localctx, 3); { - this.state = 426; + this.state = 424; this.decimalValue(); } break; @@ -2260,7 +2244,7 @@ export default class esql_parser extends parser_config { localctx = new IntegerLiteralContext(this, localctx); this.enterOuterAlt(localctx, 4); { - this.state = 427; + this.state = 425; this.integerValue(); } break; @@ -2268,7 +2252,7 @@ export default class esql_parser extends parser_config { localctx = new BooleanLiteralContext(this, localctx); this.enterOuterAlt(localctx, 5); { - this.state = 428; + this.state = 426; this.booleanValue(); } break; @@ -2276,7 +2260,7 @@ export default class esql_parser extends parser_config { localctx = new InputParameterContext(this, localctx); this.enterOuterAlt(localctx, 6); { - this.state = 429; + this.state = 427; this.parameter(); } break; @@ -2284,7 +2268,7 @@ export default class esql_parser extends parser_config { localctx = new StringLiteralContext(this, localctx); this.enterOuterAlt(localctx, 7); { - this.state = 430; + this.state = 428; this.string_(); } break; @@ -2292,27 +2276,27 @@ export default class esql_parser extends parser_config { localctx = new NumericArrayLiteralContext(this, localctx); this.enterOuterAlt(localctx, 8); { - this.state = 431; + this.state = 429; this.match(esql_parser.OPENING_BRACKET); - this.state = 432; + this.state = 430; this.numericValue(); - this.state = 437; + this.state = 435; this._errHandler.sync(this); _la = this._input.LA(1); - while (_la===33) { + while (_la===34) { { { - this.state = 433; + this.state = 431; this.match(esql_parser.COMMA); - this.state = 434; + this.state = 432; this.numericValue(); } } - this.state = 439; + this.state = 437; this._errHandler.sync(this); _la = this._input.LA(1); } - this.state = 440; + this.state = 438; this.match(esql_parser.CLOSING_BRACKET); } break; @@ -2320,27 +2304,27 @@ export default class esql_parser extends parser_config { localctx = new BooleanArrayLiteralContext(this, localctx); this.enterOuterAlt(localctx, 9); { - this.state = 442; + this.state = 440; this.match(esql_parser.OPENING_BRACKET); - this.state = 443; + this.state = 441; this.booleanValue(); - this.state = 448; + this.state = 446; this._errHandler.sync(this); _la = this._input.LA(1); - while (_la===33) { + while (_la===34) { { { - this.state = 444; + this.state = 442; this.match(esql_parser.COMMA); - this.state = 445; + this.state = 443; this.booleanValue(); } } - this.state = 450; + this.state = 448; this._errHandler.sync(this); _la = this._input.LA(1); } - this.state = 451; + this.state = 449; this.match(esql_parser.CLOSING_BRACKET); } break; @@ -2348,27 +2332,27 @@ export default class esql_parser extends parser_config { localctx = new StringArrayLiteralContext(this, localctx); this.enterOuterAlt(localctx, 10); { - this.state = 453; + this.state = 451; this.match(esql_parser.OPENING_BRACKET); - this.state = 454; + this.state = 452; this.string_(); - this.state = 459; + this.state = 457; this._errHandler.sync(this); _la = this._input.LA(1); - while (_la===33) { + while (_la===34) { { { - this.state = 455; + this.state = 453; this.match(esql_parser.COMMA); - this.state = 456; + this.state = 454; this.string_(); } } - this.state = 461; + this.state = 459; this._errHandler.sync(this); _la = this._input.LA(1); } - this.state = 462; + this.state = 460; this.match(esql_parser.CLOSING_BRACKET); } break; @@ -2393,14 +2377,14 @@ export default class esql_parser extends parser_config { let localctx: ParameterContext = new ParameterContext(this, this._ctx, this.state); this.enterRule(localctx, 70, esql_parser.RULE_parameter); try { - this.state = 468; + this.state = 466; this._errHandler.sync(this); switch (this._input.LA(1)) { - case 47: + case 48: localctx = new InputParamContext(this, localctx); this.enterOuterAlt(localctx, 1); { - this.state = 466; + this.state = 464; this.match(esql_parser.PARAM); } break; @@ -2408,7 +2392,7 @@ export default class esql_parser extends parser_config { localctx = new InputNamedOrPositionalParamContext(this, localctx); this.enterOuterAlt(localctx, 2); { - this.state = 467; + this.state = 465; this.match(esql_parser.NAMED_OR_POSITIONAL_PARAM); } break; @@ -2435,24 +2419,24 @@ export default class esql_parser extends parser_config { let localctx: IdentifierOrParameterContext = new IdentifierOrParameterContext(this, this._ctx, this.state); this.enterRule(localctx, 72, esql_parser.RULE_identifierOrParameter); try { - this.state = 473; + this.state = 471; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 44, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 43, this._ctx) ) { case 1: this.enterOuterAlt(localctx, 1); { - this.state = 470; + this.state = 468; this.identifier(); } break; case 2: this.enterOuterAlt(localctx, 2); { - this.state = 471; + this.state = 469; if (!(this.isDevVersion())) { throw this.createFailedPredicateException("this.isDevVersion()"); } - this.state = 472; + this.state = 470; this.parameter(); } break; @@ -2479,9 +2463,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 475; + this.state = 473; this.match(esql_parser.LIMIT); - this.state = 476; + this.state = 474; this.match(esql_parser.INTEGER_LITERAL); } } @@ -2507,27 +2491,27 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 478; + this.state = 476; this.match(esql_parser.SORT); - this.state = 479; + this.state = 477; this.orderExpression(); - this.state = 484; + this.state = 482; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 45, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 44, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 480; + this.state = 478; this.match(esql_parser.COMMA); - this.state = 481; + this.state = 479; this.orderExpression(); } } } - this.state = 486; + this.state = 484; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 45, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 44, this._ctx); } } } @@ -2553,17 +2537,17 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 487; + this.state = 485; this.booleanExpression(0); - this.state = 489; + this.state = 487; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 46, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 45, this._ctx) ) { case 1: { - this.state = 488; + this.state = 486; localctx._ordering = this._input.LT(1); _la = this._input.LA(1); - if(!(_la===30 || _la===34)) { + if(!(_la===31 || _la===35)) { localctx._ordering = this._errHandler.recoverInline(this); } else { @@ -2573,17 +2557,17 @@ export default class esql_parser extends parser_config { } break; } - this.state = 493; + this.state = 491; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 47, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 46, this._ctx) ) { case 1: { - this.state = 491; + this.state = 489; this.match(esql_parser.NULLS); - this.state = 492; + this.state = 490; localctx._nullOrdering = this._input.LT(1); _la = this._input.LA(1); - if(!(_la===37 || _la===40)) { + if(!(_la===38 || _la===41)) { localctx._nullOrdering = this._errHandler.recoverInline(this); } else { @@ -2616,9 +2600,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 495; + this.state = 493; this.match(esql_parser.KEEP); - this.state = 496; + this.state = 494; this.qualifiedNamePatterns(); } } @@ -2643,9 +2627,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 498; + this.state = 496; this.match(esql_parser.DROP); - this.state = 499; + this.state = 497; this.qualifiedNamePatterns(); } } @@ -2671,27 +2655,27 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 501; + this.state = 499; this.match(esql_parser.RENAME); - this.state = 502; + this.state = 500; this.renameClause(); - this.state = 507; + this.state = 505; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 48, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 47, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 503; + this.state = 501; this.match(esql_parser.COMMA); - this.state = 504; + this.state = 502; this.renameClause(); } } } - this.state = 509; + this.state = 507; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 48, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 47, this._ctx); } } } @@ -2716,11 +2700,11 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 510; + this.state = 508; localctx._oldName = this.qualifiedNamePattern(); - this.state = 511; + this.state = 509; this.match(esql_parser.AS); - this.state = 512; + this.state = 510; localctx._newName = this.qualifiedNamePattern(); } } @@ -2745,18 +2729,18 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 514; + this.state = 512; this.match(esql_parser.DISSECT); - this.state = 515; + this.state = 513; this.primaryExpression(0); - this.state = 516; + this.state = 514; this.string_(); - this.state = 518; + this.state = 516; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 49, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 48, this._ctx) ) { case 1: { - this.state = 517; + this.state = 515; this.commandOptions(); } break; @@ -2784,11 +2768,11 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 520; + this.state = 518; this.match(esql_parser.GROK); - this.state = 521; + this.state = 519; this.primaryExpression(0); - this.state = 522; + this.state = 520; this.string_(); } } @@ -2813,9 +2797,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 524; + this.state = 522; this.match(esql_parser.MV_EXPAND); - this.state = 525; + this.state = 523; this.qualifiedName(); } } @@ -2841,25 +2825,25 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 527; + this.state = 525; this.commandOption(); - this.state = 532; + this.state = 530; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 50, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 49, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 528; + this.state = 526; this.match(esql_parser.COMMA); - this.state = 529; + this.state = 527; this.commandOption(); } } } - this.state = 534; + this.state = 532; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 50, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 49, this._ctx); } } } @@ -2884,11 +2868,11 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 535; + this.state = 533; this.identifier(); - this.state = 536; + this.state = 534; this.match(esql_parser.ASSIGN); - this.state = 537; + this.state = 535; this.constant(); } } @@ -2914,9 +2898,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 539; + this.state = 537; _la = this._input.LA(1); - if(!(_la===36 || _la===50)) { + if(!(_la===37 || _la===51)) { this._errHandler.recoverInline(this); } else { @@ -2944,20 +2928,20 @@ export default class esql_parser extends parser_config { let localctx: NumericValueContext = new NumericValueContext(this, this._ctx, this.state); this.enterRule(localctx, 100, esql_parser.RULE_numericValue); try { - this.state = 543; + this.state = 541; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 51, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 50, this._ctx) ) { case 1: this.enterOuterAlt(localctx, 1); { - this.state = 541; + this.state = 539; this.decimalValue(); } break; case 2: this.enterOuterAlt(localctx, 2); { - this.state = 542; + this.state = 540; this.integerValue(); } break; @@ -2985,14 +2969,14 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 546; + this.state = 544; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===58 || _la===59) { + if (_la===59 || _la===60) { { - this.state = 545; + this.state = 543; _la = this._input.LA(1); - if(!(_la===58 || _la===59)) { + if(!(_la===59 || _la===60)) { this._errHandler.recoverInline(this); } else { @@ -3002,7 +2986,7 @@ export default class esql_parser extends parser_config { } } - this.state = 548; + this.state = 546; this.match(esql_parser.DECIMAL_LITERAL); } } @@ -3028,14 +3012,14 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 551; + this.state = 549; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===58 || _la===59) { + if (_la===59 || _la===60) { { - this.state = 550; + this.state = 548; _la = this._input.LA(1); - if(!(_la===58 || _la===59)) { + if(!(_la===59 || _la===60)) { this._errHandler.recoverInline(this); } else { @@ -3045,7 +3029,7 @@ export default class esql_parser extends parser_config { } } - this.state = 553; + this.state = 551; this.match(esql_parser.INTEGER_LITERAL); } } @@ -3070,7 +3054,7 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 555; + this.state = 553; this.match(esql_parser.QUOTED_STRING); } } @@ -3096,9 +3080,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 557; + this.state = 555; _la = this._input.LA(1); - if(!(((((_la - 51)) & ~0x1F) === 0 && ((1 << (_la - 51)) & 125) !== 0))) { + if(!(((((_la - 52)) & ~0x1F) === 0 && ((1 << (_la - 52)) & 125) !== 0))) { this._errHandler.recoverInline(this); } else { @@ -3128,9 +3112,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 559; + this.state = 557; this.match(esql_parser.EXPLAIN); - this.state = 560; + this.state = 558; this.subqueryExpression(); } } @@ -3155,11 +3139,11 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 562; + this.state = 560; this.match(esql_parser.OPENING_BRACKET); - this.state = 563; + this.state = 561; this.query(0); - this.state = 564; + this.state = 562; this.match(esql_parser.CLOSING_BRACKET); } } @@ -3185,9 +3169,9 @@ export default class esql_parser extends parser_config { localctx = new ShowInfoContext(this, localctx); this.enterOuterAlt(localctx, 1); { - this.state = 566; + this.state = 564; this.match(esql_parser.SHOW); - this.state = 567; + this.state = 565; this.match(esql_parser.INFO); } } @@ -3213,48 +3197,48 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 569; + this.state = 567; this.match(esql_parser.ENRICH); - this.state = 570; + this.state = 568; localctx._policyName = this.match(esql_parser.ENRICH_POLICY_NAME); - this.state = 573; + this.state = 571; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 54, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 53, this._ctx) ) { case 1: { - this.state = 571; + this.state = 569; this.match(esql_parser.ON); - this.state = 572; + this.state = 570; localctx._matchField = this.qualifiedNamePattern(); } break; } - this.state = 584; + this.state = 582; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 56, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 55, this._ctx) ) { case 1: { - this.state = 575; + this.state = 573; this.match(esql_parser.WITH); - this.state = 576; + this.state = 574; this.enrichWithClause(); - this.state = 581; + this.state = 579; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 55, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 54, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 577; + this.state = 575; this.match(esql_parser.COMMA); - this.state = 578; + this.state = 576; this.enrichWithClause(); } } } - this.state = 583; + this.state = 581; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 55, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 54, this._ctx); } } break; @@ -3282,19 +3266,19 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 589; + this.state = 587; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 57, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 56, this._ctx) ) { case 1: { - this.state = 586; + this.state = 584; localctx._newName = this.qualifiedNamePattern(); - this.state = 587; + this.state = 585; this.match(esql_parser.ASSIGN); } break; } - this.state = 591; + this.state = 589; localctx._enrichField = this.qualifiedNamePattern(); } } @@ -3319,13 +3303,13 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 593; + this.state = 591; this.match(esql_parser.DEV_LOOKUP); - this.state = 594; + this.state = 592; localctx._tableName = this.indexPattern(); - this.state = 595; + this.state = 593; this.match(esql_parser.ON); - this.state = 596; + this.state = 594; localctx._matchFields = this.qualifiedNamePatterns(); } } @@ -3350,18 +3334,18 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 598; + this.state = 596; this.match(esql_parser.DEV_INLINESTATS); - this.state = 599; + this.state = 597; localctx._stats = this.aggFields(); - this.state = 602; + this.state = 600; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 58, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 57, this._ctx) ) { case 1: { - this.state = 600; + this.state = 598; this.match(esql_parser.BY); - this.state = 601; + this.state = 599; localctx._grouping = this.fields(); } break; @@ -3469,7 +3453,7 @@ export default class esql_parser extends parser_config { return true; } - public static readonly _serializedATN: number[] = [4,1,120,605,2,0,7,0, + public static readonly _serializedATN: number[] = [4,1,119,603,2,0,7,0, 2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6,2,7,7,7,2,8,7,8,2,9,7,9, 2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14,7,14,2,15,7,15,2,16,7,16,2, 17,7,17,2,18,7,18,2,19,7,19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,2,24, @@ -3489,184 +3473,183 @@ export default class esql_parser extends parser_config { 9,1,9,5,9,250,8,9,10,9,12,9,253,9,9,1,10,1,10,1,10,1,10,1,10,1,10,1,10, 1,10,3,10,263,8,10,1,10,1,10,1,10,5,10,268,8,10,10,10,12,10,271,9,10,1, 11,1,11,1,11,1,11,1,11,1,11,5,11,279,8,11,10,11,12,11,282,9,11,3,11,284, - 8,11,1,11,1,11,1,12,1,12,3,12,290,8,12,1,13,1,13,1,14,1,14,1,14,1,15,1, - 15,1,15,5,15,300,8,15,10,15,12,15,303,9,15,1,16,1,16,1,16,3,16,308,8,16, - 1,16,1,16,1,17,1,17,1,17,1,17,5,17,316,8,17,10,17,12,17,319,9,17,1,17,3, - 17,322,8,17,1,18,1,18,1,18,3,18,327,8,18,1,18,1,18,1,19,1,19,1,20,1,20, - 1,21,1,21,3,21,337,8,21,1,22,1,22,1,22,1,22,5,22,343,8,22,10,22,12,22,346, - 9,22,1,23,1,23,1,23,1,23,1,24,1,24,1,24,1,24,5,24,356,8,24,10,24,12,24, - 359,9,24,1,24,3,24,362,8,24,1,24,1,24,3,24,366,8,24,1,25,1,25,1,25,1,26, - 1,26,3,26,373,8,26,1,26,1,26,3,26,377,8,26,1,27,1,27,1,27,5,27,382,8,27, - 10,27,12,27,385,9,27,1,28,1,28,1,28,3,28,390,8,28,1,29,1,29,1,29,5,29,395, - 8,29,10,29,12,29,398,9,29,1,30,1,30,1,30,5,30,403,8,30,10,30,12,30,406, - 9,30,1,31,1,31,1,31,5,31,411,8,31,10,31,12,31,414,9,31,1,32,1,32,1,33,1, - 33,1,33,3,33,421,8,33,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34, - 1,34,1,34,1,34,5,34,436,8,34,10,34,12,34,439,9,34,1,34,1,34,1,34,1,34,1, - 34,1,34,5,34,447,8,34,10,34,12,34,450,9,34,1,34,1,34,1,34,1,34,1,34,1,34, - 5,34,458,8,34,10,34,12,34,461,9,34,1,34,1,34,3,34,465,8,34,1,35,1,35,3, - 35,469,8,35,1,36,1,36,1,36,3,36,474,8,36,1,37,1,37,1,37,1,38,1,38,1,38, - 1,38,5,38,483,8,38,10,38,12,38,486,9,38,1,39,1,39,3,39,490,8,39,1,39,1, - 39,3,39,494,8,39,1,40,1,40,1,40,1,41,1,41,1,41,1,42,1,42,1,42,1,42,5,42, - 506,8,42,10,42,12,42,509,9,42,1,43,1,43,1,43,1,43,1,44,1,44,1,44,1,44,3, - 44,519,8,44,1,45,1,45,1,45,1,45,1,46,1,46,1,46,1,47,1,47,1,47,5,47,531, - 8,47,10,47,12,47,534,9,47,1,48,1,48,1,48,1,48,1,49,1,49,1,50,1,50,3,50, - 544,8,50,1,51,3,51,547,8,51,1,51,1,51,1,52,3,52,552,8,52,1,52,1,52,1,53, - 1,53,1,54,1,54,1,55,1,55,1,55,1,56,1,56,1,56,1,56,1,57,1,57,1,57,1,58,1, - 58,1,58,1,58,3,58,574,8,58,1,58,1,58,1,58,1,58,5,58,580,8,58,10,58,12,58, - 583,9,58,3,58,585,8,58,1,59,1,59,1,59,3,59,590,8,59,1,59,1,59,1,60,1,60, - 1,60,1,60,1,60,1,61,1,61,1,61,1,61,3,61,603,8,61,1,61,0,4,2,10,18,20,62, - 0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50, - 52,54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98, - 100,102,104,106,108,110,112,114,116,118,120,122,0,8,1,0,58,59,1,0,60,62, - 2,0,25,25,76,76,1,0,67,68,2,0,30,30,34,34,2,0,37,37,40,40,2,0,36,36,50, - 50,2,0,51,51,53,57,631,0,124,1,0,0,0,2,127,1,0,0,0,4,144,1,0,0,0,6,162, - 1,0,0,0,8,164,1,0,0,0,10,197,1,0,0,0,12,224,1,0,0,0,14,226,1,0,0,0,16,235, - 1,0,0,0,18,241,1,0,0,0,20,262,1,0,0,0,22,272,1,0,0,0,24,289,1,0,0,0,26, - 291,1,0,0,0,28,293,1,0,0,0,30,296,1,0,0,0,32,307,1,0,0,0,34,311,1,0,0,0, - 36,326,1,0,0,0,38,330,1,0,0,0,40,332,1,0,0,0,42,336,1,0,0,0,44,338,1,0, - 0,0,46,347,1,0,0,0,48,351,1,0,0,0,50,367,1,0,0,0,52,370,1,0,0,0,54,378, - 1,0,0,0,56,386,1,0,0,0,58,391,1,0,0,0,60,399,1,0,0,0,62,407,1,0,0,0,64, - 415,1,0,0,0,66,420,1,0,0,0,68,464,1,0,0,0,70,468,1,0,0,0,72,473,1,0,0,0, - 74,475,1,0,0,0,76,478,1,0,0,0,78,487,1,0,0,0,80,495,1,0,0,0,82,498,1,0, - 0,0,84,501,1,0,0,0,86,510,1,0,0,0,88,514,1,0,0,0,90,520,1,0,0,0,92,524, - 1,0,0,0,94,527,1,0,0,0,96,535,1,0,0,0,98,539,1,0,0,0,100,543,1,0,0,0,102, - 546,1,0,0,0,104,551,1,0,0,0,106,555,1,0,0,0,108,557,1,0,0,0,110,559,1,0, - 0,0,112,562,1,0,0,0,114,566,1,0,0,0,116,569,1,0,0,0,118,589,1,0,0,0,120, - 593,1,0,0,0,122,598,1,0,0,0,124,125,3,2,1,0,125,126,5,0,0,1,126,1,1,0,0, - 0,127,128,6,1,-1,0,128,129,3,4,2,0,129,135,1,0,0,0,130,131,10,1,0,0,131, - 132,5,24,0,0,132,134,3,6,3,0,133,130,1,0,0,0,134,137,1,0,0,0,135,133,1, - 0,0,0,135,136,1,0,0,0,136,3,1,0,0,0,137,135,1,0,0,0,138,145,3,110,55,0, - 139,145,3,34,17,0,140,145,3,28,14,0,141,145,3,114,57,0,142,143,4,2,1,0, - 143,145,3,48,24,0,144,138,1,0,0,0,144,139,1,0,0,0,144,140,1,0,0,0,144,141, - 1,0,0,0,144,142,1,0,0,0,145,5,1,0,0,0,146,163,3,50,25,0,147,163,3,8,4,0, - 148,163,3,80,40,0,149,163,3,74,37,0,150,163,3,52,26,0,151,163,3,76,38,0, - 152,163,3,82,41,0,153,163,3,84,42,0,154,163,3,88,44,0,155,163,3,90,45,0, - 156,163,3,116,58,0,157,163,3,92,46,0,158,159,4,3,2,0,159,163,3,122,61,0, - 160,161,4,3,3,0,161,163,3,120,60,0,162,146,1,0,0,0,162,147,1,0,0,0,162, - 148,1,0,0,0,162,149,1,0,0,0,162,150,1,0,0,0,162,151,1,0,0,0,162,152,1,0, - 0,0,162,153,1,0,0,0,162,154,1,0,0,0,162,155,1,0,0,0,162,156,1,0,0,0,162, - 157,1,0,0,0,162,158,1,0,0,0,162,160,1,0,0,0,163,7,1,0,0,0,164,165,5,16, - 0,0,165,166,3,10,5,0,166,9,1,0,0,0,167,168,6,5,-1,0,168,169,5,43,0,0,169, - 198,3,10,5,8,170,198,3,16,8,0,171,198,3,12,6,0,172,174,3,16,8,0,173,175, - 5,43,0,0,174,173,1,0,0,0,174,175,1,0,0,0,175,176,1,0,0,0,176,177,5,38,0, - 0,177,178,5,42,0,0,178,183,3,16,8,0,179,180,5,33,0,0,180,182,3,16,8,0,181, - 179,1,0,0,0,182,185,1,0,0,0,183,181,1,0,0,0,183,184,1,0,0,0,184,186,1,0, - 0,0,185,183,1,0,0,0,186,187,5,49,0,0,187,198,1,0,0,0,188,189,3,16,8,0,189, - 191,5,39,0,0,190,192,5,43,0,0,191,190,1,0,0,0,191,192,1,0,0,0,192,193,1, - 0,0,0,193,194,5,44,0,0,194,198,1,0,0,0,195,196,4,5,4,0,196,198,3,14,7,0, - 197,167,1,0,0,0,197,170,1,0,0,0,197,171,1,0,0,0,197,172,1,0,0,0,197,188, - 1,0,0,0,197,195,1,0,0,0,198,207,1,0,0,0,199,200,10,5,0,0,200,201,5,29,0, - 0,201,206,3,10,5,6,202,203,10,4,0,0,203,204,5,46,0,0,204,206,3,10,5,5,205, - 199,1,0,0,0,205,202,1,0,0,0,206,209,1,0,0,0,207,205,1,0,0,0,207,208,1,0, - 0,0,208,11,1,0,0,0,209,207,1,0,0,0,210,212,3,16,8,0,211,213,5,43,0,0,212, - 211,1,0,0,0,212,213,1,0,0,0,213,214,1,0,0,0,214,215,5,41,0,0,215,216,3, - 106,53,0,216,225,1,0,0,0,217,219,3,16,8,0,218,220,5,43,0,0,219,218,1,0, - 0,0,219,220,1,0,0,0,220,221,1,0,0,0,221,222,5,48,0,0,222,223,3,106,53,0, - 223,225,1,0,0,0,224,210,1,0,0,0,224,217,1,0,0,0,225,13,1,0,0,0,226,227, - 3,16,8,0,227,228,5,63,0,0,228,229,3,106,53,0,229,15,1,0,0,0,230,236,3,18, - 9,0,231,232,3,18,9,0,232,233,3,108,54,0,233,234,3,18,9,0,234,236,1,0,0, - 0,235,230,1,0,0,0,235,231,1,0,0,0,236,17,1,0,0,0,237,238,6,9,-1,0,238,242, - 3,20,10,0,239,240,7,0,0,0,240,242,3,18,9,3,241,237,1,0,0,0,241,239,1,0, - 0,0,242,251,1,0,0,0,243,244,10,2,0,0,244,245,7,1,0,0,245,250,3,18,9,3,246, - 247,10,1,0,0,247,248,7,0,0,0,248,250,3,18,9,2,249,243,1,0,0,0,249,246,1, - 0,0,0,250,253,1,0,0,0,251,249,1,0,0,0,251,252,1,0,0,0,252,19,1,0,0,0,253, - 251,1,0,0,0,254,255,6,10,-1,0,255,263,3,68,34,0,256,263,3,58,29,0,257,263, - 3,22,11,0,258,259,5,42,0,0,259,260,3,10,5,0,260,261,5,49,0,0,261,263,1, - 0,0,0,262,254,1,0,0,0,262,256,1,0,0,0,262,257,1,0,0,0,262,258,1,0,0,0,263, - 269,1,0,0,0,264,265,10,1,0,0,265,266,5,32,0,0,266,268,3,26,13,0,267,264, - 1,0,0,0,268,271,1,0,0,0,269,267,1,0,0,0,269,270,1,0,0,0,270,21,1,0,0,0, - 271,269,1,0,0,0,272,273,3,24,12,0,273,283,5,42,0,0,274,284,5,60,0,0,275, - 280,3,10,5,0,276,277,5,33,0,0,277,279,3,10,5,0,278,276,1,0,0,0,279,282, - 1,0,0,0,280,278,1,0,0,0,280,281,1,0,0,0,281,284,1,0,0,0,282,280,1,0,0,0, - 283,274,1,0,0,0,283,275,1,0,0,0,283,284,1,0,0,0,284,285,1,0,0,0,285,286, - 5,49,0,0,286,23,1,0,0,0,287,290,5,63,0,0,288,290,3,72,36,0,289,287,1,0, - 0,0,289,288,1,0,0,0,290,25,1,0,0,0,291,292,3,64,32,0,292,27,1,0,0,0,293, - 294,5,12,0,0,294,295,3,30,15,0,295,29,1,0,0,0,296,301,3,32,16,0,297,298, - 5,33,0,0,298,300,3,32,16,0,299,297,1,0,0,0,300,303,1,0,0,0,301,299,1,0, - 0,0,301,302,1,0,0,0,302,31,1,0,0,0,303,301,1,0,0,0,304,305,3,58,29,0,305, - 306,5,31,0,0,306,308,1,0,0,0,307,304,1,0,0,0,307,308,1,0,0,0,308,309,1, - 0,0,0,309,310,3,10,5,0,310,33,1,0,0,0,311,312,5,6,0,0,312,317,3,36,18,0, - 313,314,5,33,0,0,314,316,3,36,18,0,315,313,1,0,0,0,316,319,1,0,0,0,317, - 315,1,0,0,0,317,318,1,0,0,0,318,321,1,0,0,0,319,317,1,0,0,0,320,322,3,42, - 21,0,321,320,1,0,0,0,321,322,1,0,0,0,322,35,1,0,0,0,323,324,3,38,19,0,324, - 325,5,104,0,0,325,327,1,0,0,0,326,323,1,0,0,0,326,327,1,0,0,0,327,328,1, - 0,0,0,328,329,3,40,20,0,329,37,1,0,0,0,330,331,5,76,0,0,331,39,1,0,0,0, - 332,333,7,2,0,0,333,41,1,0,0,0,334,337,3,44,22,0,335,337,3,46,23,0,336, - 334,1,0,0,0,336,335,1,0,0,0,337,43,1,0,0,0,338,339,5,75,0,0,339,344,5,76, - 0,0,340,341,5,33,0,0,341,343,5,76,0,0,342,340,1,0,0,0,343,346,1,0,0,0,344, - 342,1,0,0,0,344,345,1,0,0,0,345,45,1,0,0,0,346,344,1,0,0,0,347,348,5,65, - 0,0,348,349,3,44,22,0,349,350,5,66,0,0,350,47,1,0,0,0,351,352,5,19,0,0, - 352,357,3,36,18,0,353,354,5,33,0,0,354,356,3,36,18,0,355,353,1,0,0,0,356, - 359,1,0,0,0,357,355,1,0,0,0,357,358,1,0,0,0,358,361,1,0,0,0,359,357,1,0, - 0,0,360,362,3,54,27,0,361,360,1,0,0,0,361,362,1,0,0,0,362,365,1,0,0,0,363, - 364,5,28,0,0,364,366,3,30,15,0,365,363,1,0,0,0,365,366,1,0,0,0,366,49,1, - 0,0,0,367,368,5,4,0,0,368,369,3,30,15,0,369,51,1,0,0,0,370,372,5,15,0,0, - 371,373,3,54,27,0,372,371,1,0,0,0,372,373,1,0,0,0,373,376,1,0,0,0,374,375, - 5,28,0,0,375,377,3,30,15,0,376,374,1,0,0,0,376,377,1,0,0,0,377,53,1,0,0, - 0,378,383,3,56,28,0,379,380,5,33,0,0,380,382,3,56,28,0,381,379,1,0,0,0, - 382,385,1,0,0,0,383,381,1,0,0,0,383,384,1,0,0,0,384,55,1,0,0,0,385,383, - 1,0,0,0,386,389,3,32,16,0,387,388,5,16,0,0,388,390,3,10,5,0,389,387,1,0, - 0,0,389,390,1,0,0,0,390,57,1,0,0,0,391,396,3,72,36,0,392,393,5,35,0,0,393, - 395,3,72,36,0,394,392,1,0,0,0,395,398,1,0,0,0,396,394,1,0,0,0,396,397,1, - 0,0,0,397,59,1,0,0,0,398,396,1,0,0,0,399,404,3,66,33,0,400,401,5,35,0,0, - 401,403,3,66,33,0,402,400,1,0,0,0,403,406,1,0,0,0,404,402,1,0,0,0,404,405, - 1,0,0,0,405,61,1,0,0,0,406,404,1,0,0,0,407,412,3,60,30,0,408,409,5,33,0, - 0,409,411,3,60,30,0,410,408,1,0,0,0,411,414,1,0,0,0,412,410,1,0,0,0,412, - 413,1,0,0,0,413,63,1,0,0,0,414,412,1,0,0,0,415,416,7,3,0,0,416,65,1,0,0, - 0,417,421,5,80,0,0,418,419,4,33,10,0,419,421,3,70,35,0,420,417,1,0,0,0, - 420,418,1,0,0,0,421,67,1,0,0,0,422,465,5,44,0,0,423,424,3,104,52,0,424, - 425,5,67,0,0,425,465,1,0,0,0,426,465,3,102,51,0,427,465,3,104,52,0,428, - 465,3,98,49,0,429,465,3,70,35,0,430,465,3,106,53,0,431,432,5,65,0,0,432, - 437,3,100,50,0,433,434,5,33,0,0,434,436,3,100,50,0,435,433,1,0,0,0,436, - 439,1,0,0,0,437,435,1,0,0,0,437,438,1,0,0,0,438,440,1,0,0,0,439,437,1,0, - 0,0,440,441,5,66,0,0,441,465,1,0,0,0,442,443,5,65,0,0,443,448,3,98,49,0, - 444,445,5,33,0,0,445,447,3,98,49,0,446,444,1,0,0,0,447,450,1,0,0,0,448, - 446,1,0,0,0,448,449,1,0,0,0,449,451,1,0,0,0,450,448,1,0,0,0,451,452,5,66, - 0,0,452,465,1,0,0,0,453,454,5,65,0,0,454,459,3,106,53,0,455,456,5,33,0, - 0,456,458,3,106,53,0,457,455,1,0,0,0,458,461,1,0,0,0,459,457,1,0,0,0,459, - 460,1,0,0,0,460,462,1,0,0,0,461,459,1,0,0,0,462,463,5,66,0,0,463,465,1, - 0,0,0,464,422,1,0,0,0,464,423,1,0,0,0,464,426,1,0,0,0,464,427,1,0,0,0,464, - 428,1,0,0,0,464,429,1,0,0,0,464,430,1,0,0,0,464,431,1,0,0,0,464,442,1,0, - 0,0,464,453,1,0,0,0,465,69,1,0,0,0,466,469,5,47,0,0,467,469,5,64,0,0,468, - 466,1,0,0,0,468,467,1,0,0,0,469,71,1,0,0,0,470,474,3,64,32,0,471,472,4, - 36,11,0,472,474,3,70,35,0,473,470,1,0,0,0,473,471,1,0,0,0,474,73,1,0,0, - 0,475,476,5,9,0,0,476,477,5,26,0,0,477,75,1,0,0,0,478,479,5,14,0,0,479, - 484,3,78,39,0,480,481,5,33,0,0,481,483,3,78,39,0,482,480,1,0,0,0,483,486, - 1,0,0,0,484,482,1,0,0,0,484,485,1,0,0,0,485,77,1,0,0,0,486,484,1,0,0,0, - 487,489,3,10,5,0,488,490,7,4,0,0,489,488,1,0,0,0,489,490,1,0,0,0,490,493, - 1,0,0,0,491,492,5,45,0,0,492,494,7,5,0,0,493,491,1,0,0,0,493,494,1,0,0, - 0,494,79,1,0,0,0,495,496,5,8,0,0,496,497,3,62,31,0,497,81,1,0,0,0,498,499, - 5,2,0,0,499,500,3,62,31,0,500,83,1,0,0,0,501,502,5,11,0,0,502,507,3,86, - 43,0,503,504,5,33,0,0,504,506,3,86,43,0,505,503,1,0,0,0,506,509,1,0,0,0, - 507,505,1,0,0,0,507,508,1,0,0,0,508,85,1,0,0,0,509,507,1,0,0,0,510,511, - 3,60,30,0,511,512,5,84,0,0,512,513,3,60,30,0,513,87,1,0,0,0,514,515,5,1, - 0,0,515,516,3,20,10,0,516,518,3,106,53,0,517,519,3,94,47,0,518,517,1,0, - 0,0,518,519,1,0,0,0,519,89,1,0,0,0,520,521,5,7,0,0,521,522,3,20,10,0,522, - 523,3,106,53,0,523,91,1,0,0,0,524,525,5,10,0,0,525,526,3,58,29,0,526,93, - 1,0,0,0,527,532,3,96,48,0,528,529,5,33,0,0,529,531,3,96,48,0,530,528,1, - 0,0,0,531,534,1,0,0,0,532,530,1,0,0,0,532,533,1,0,0,0,533,95,1,0,0,0,534, - 532,1,0,0,0,535,536,3,64,32,0,536,537,5,31,0,0,537,538,3,68,34,0,538,97, - 1,0,0,0,539,540,7,6,0,0,540,99,1,0,0,0,541,544,3,102,51,0,542,544,3,104, - 52,0,543,541,1,0,0,0,543,542,1,0,0,0,544,101,1,0,0,0,545,547,7,0,0,0,546, - 545,1,0,0,0,546,547,1,0,0,0,547,548,1,0,0,0,548,549,5,27,0,0,549,103,1, - 0,0,0,550,552,7,0,0,0,551,550,1,0,0,0,551,552,1,0,0,0,552,553,1,0,0,0,553, - 554,5,26,0,0,554,105,1,0,0,0,555,556,5,25,0,0,556,107,1,0,0,0,557,558,7, - 7,0,0,558,109,1,0,0,0,559,560,5,5,0,0,560,561,3,112,56,0,561,111,1,0,0, - 0,562,563,5,65,0,0,563,564,3,2,1,0,564,565,5,66,0,0,565,113,1,0,0,0,566, - 567,5,13,0,0,567,568,5,100,0,0,568,115,1,0,0,0,569,570,5,3,0,0,570,573, - 5,90,0,0,571,572,5,88,0,0,572,574,3,60,30,0,573,571,1,0,0,0,573,574,1,0, - 0,0,574,584,1,0,0,0,575,576,5,89,0,0,576,581,3,118,59,0,577,578,5,33,0, - 0,578,580,3,118,59,0,579,577,1,0,0,0,580,583,1,0,0,0,581,579,1,0,0,0,581, - 582,1,0,0,0,582,585,1,0,0,0,583,581,1,0,0,0,584,575,1,0,0,0,584,585,1,0, - 0,0,585,117,1,0,0,0,586,587,3,60,30,0,587,588,5,31,0,0,588,590,1,0,0,0, - 589,586,1,0,0,0,589,590,1,0,0,0,590,591,1,0,0,0,591,592,3,60,30,0,592,119, - 1,0,0,0,593,594,5,18,0,0,594,595,3,36,18,0,595,596,5,88,0,0,596,597,3,62, - 31,0,597,121,1,0,0,0,598,599,5,17,0,0,599,602,3,54,27,0,600,601,5,28,0, - 0,601,603,3,30,15,0,602,600,1,0,0,0,602,603,1,0,0,0,603,123,1,0,0,0,59, + 8,11,1,11,1,11,1,12,1,12,1,13,1,13,1,14,1,14,1,14,1,15,1,15,1,15,5,15,298, + 8,15,10,15,12,15,301,9,15,1,16,1,16,1,16,3,16,306,8,16,1,16,1,16,1,17,1, + 17,1,17,1,17,5,17,314,8,17,10,17,12,17,317,9,17,1,17,3,17,320,8,17,1,18, + 1,18,1,18,3,18,325,8,18,1,18,1,18,1,19,1,19,1,20,1,20,1,21,1,21,3,21,335, + 8,21,1,22,1,22,1,22,1,22,5,22,341,8,22,10,22,12,22,344,9,22,1,23,1,23,1, + 23,1,23,1,24,1,24,1,24,1,24,5,24,354,8,24,10,24,12,24,357,9,24,1,24,3,24, + 360,8,24,1,24,1,24,3,24,364,8,24,1,25,1,25,1,25,1,26,1,26,3,26,371,8,26, + 1,26,1,26,3,26,375,8,26,1,27,1,27,1,27,5,27,380,8,27,10,27,12,27,383,9, + 27,1,28,1,28,1,28,3,28,388,8,28,1,29,1,29,1,29,5,29,393,8,29,10,29,12,29, + 396,9,29,1,30,1,30,1,30,5,30,401,8,30,10,30,12,30,404,9,30,1,31,1,31,1, + 31,5,31,409,8,31,10,31,12,31,412,9,31,1,32,1,32,1,33,1,33,1,33,3,33,419, + 8,33,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34,5, + 34,434,8,34,10,34,12,34,437,9,34,1,34,1,34,1,34,1,34,1,34,1,34,5,34,445, + 8,34,10,34,12,34,448,9,34,1,34,1,34,1,34,1,34,1,34,1,34,5,34,456,8,34,10, + 34,12,34,459,9,34,1,34,1,34,3,34,463,8,34,1,35,1,35,3,35,467,8,35,1,36, + 1,36,1,36,3,36,472,8,36,1,37,1,37,1,37,1,38,1,38,1,38,1,38,5,38,481,8,38, + 10,38,12,38,484,9,38,1,39,1,39,3,39,488,8,39,1,39,1,39,3,39,492,8,39,1, + 40,1,40,1,40,1,41,1,41,1,41,1,42,1,42,1,42,1,42,5,42,504,8,42,10,42,12, + 42,507,9,42,1,43,1,43,1,43,1,43,1,44,1,44,1,44,1,44,3,44,517,8,44,1,45, + 1,45,1,45,1,45,1,46,1,46,1,46,1,47,1,47,1,47,5,47,529,8,47,10,47,12,47, + 532,9,47,1,48,1,48,1,48,1,48,1,49,1,49,1,50,1,50,3,50,542,8,50,1,51,3,51, + 545,8,51,1,51,1,51,1,52,3,52,550,8,52,1,52,1,52,1,53,1,53,1,54,1,54,1,55, + 1,55,1,55,1,56,1,56,1,56,1,56,1,57,1,57,1,57,1,58,1,58,1,58,1,58,3,58,572, + 8,58,1,58,1,58,1,58,1,58,5,58,578,8,58,10,58,12,58,581,9,58,3,58,583,8, + 58,1,59,1,59,1,59,3,59,588,8,59,1,59,1,59,1,60,1,60,1,60,1,60,1,60,1,61, + 1,61,1,61,1,61,3,61,601,8,61,1,61,0,4,2,10,18,20,62,0,2,4,6,8,10,12,14, + 16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62, + 64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108, + 110,112,114,116,118,120,122,0,8,1,0,59,60,1,0,61,63,2,0,26,26,76,76,1,0, + 67,68,2,0,31,31,35,35,2,0,38,38,41,41,2,0,37,37,51,51,2,0,52,52,54,58,628, + 0,124,1,0,0,0,2,127,1,0,0,0,4,144,1,0,0,0,6,162,1,0,0,0,8,164,1,0,0,0,10, + 197,1,0,0,0,12,224,1,0,0,0,14,226,1,0,0,0,16,235,1,0,0,0,18,241,1,0,0,0, + 20,262,1,0,0,0,22,272,1,0,0,0,24,287,1,0,0,0,26,289,1,0,0,0,28,291,1,0, + 0,0,30,294,1,0,0,0,32,305,1,0,0,0,34,309,1,0,0,0,36,324,1,0,0,0,38,328, + 1,0,0,0,40,330,1,0,0,0,42,334,1,0,0,0,44,336,1,0,0,0,46,345,1,0,0,0,48, + 349,1,0,0,0,50,365,1,0,0,0,52,368,1,0,0,0,54,376,1,0,0,0,56,384,1,0,0,0, + 58,389,1,0,0,0,60,397,1,0,0,0,62,405,1,0,0,0,64,413,1,0,0,0,66,418,1,0, + 0,0,68,462,1,0,0,0,70,466,1,0,0,0,72,471,1,0,0,0,74,473,1,0,0,0,76,476, + 1,0,0,0,78,485,1,0,0,0,80,493,1,0,0,0,82,496,1,0,0,0,84,499,1,0,0,0,86, + 508,1,0,0,0,88,512,1,0,0,0,90,518,1,0,0,0,92,522,1,0,0,0,94,525,1,0,0,0, + 96,533,1,0,0,0,98,537,1,0,0,0,100,541,1,0,0,0,102,544,1,0,0,0,104,549,1, + 0,0,0,106,553,1,0,0,0,108,555,1,0,0,0,110,557,1,0,0,0,112,560,1,0,0,0,114, + 564,1,0,0,0,116,567,1,0,0,0,118,587,1,0,0,0,120,591,1,0,0,0,122,596,1,0, + 0,0,124,125,3,2,1,0,125,126,5,0,0,1,126,1,1,0,0,0,127,128,6,1,-1,0,128, + 129,3,4,2,0,129,135,1,0,0,0,130,131,10,1,0,0,131,132,5,25,0,0,132,134,3, + 6,3,0,133,130,1,0,0,0,134,137,1,0,0,0,135,133,1,0,0,0,135,136,1,0,0,0,136, + 3,1,0,0,0,137,135,1,0,0,0,138,145,3,110,55,0,139,145,3,34,17,0,140,145, + 3,28,14,0,141,145,3,114,57,0,142,143,4,2,1,0,143,145,3,48,24,0,144,138, + 1,0,0,0,144,139,1,0,0,0,144,140,1,0,0,0,144,141,1,0,0,0,144,142,1,0,0,0, + 145,5,1,0,0,0,146,163,3,50,25,0,147,163,3,8,4,0,148,163,3,80,40,0,149,163, + 3,74,37,0,150,163,3,52,26,0,151,163,3,76,38,0,152,163,3,82,41,0,153,163, + 3,84,42,0,154,163,3,88,44,0,155,163,3,90,45,0,156,163,3,116,58,0,157,163, + 3,92,46,0,158,159,4,3,2,0,159,163,3,122,61,0,160,161,4,3,3,0,161,163,3, + 120,60,0,162,146,1,0,0,0,162,147,1,0,0,0,162,148,1,0,0,0,162,149,1,0,0, + 0,162,150,1,0,0,0,162,151,1,0,0,0,162,152,1,0,0,0,162,153,1,0,0,0,162,154, + 1,0,0,0,162,155,1,0,0,0,162,156,1,0,0,0,162,157,1,0,0,0,162,158,1,0,0,0, + 162,160,1,0,0,0,163,7,1,0,0,0,164,165,5,16,0,0,165,166,3,10,5,0,166,9,1, + 0,0,0,167,168,6,5,-1,0,168,169,5,44,0,0,169,198,3,10,5,8,170,198,3,16,8, + 0,171,198,3,12,6,0,172,174,3,16,8,0,173,175,5,44,0,0,174,173,1,0,0,0,174, + 175,1,0,0,0,175,176,1,0,0,0,176,177,5,39,0,0,177,178,5,43,0,0,178,183,3, + 16,8,0,179,180,5,34,0,0,180,182,3,16,8,0,181,179,1,0,0,0,182,185,1,0,0, + 0,183,181,1,0,0,0,183,184,1,0,0,0,184,186,1,0,0,0,185,183,1,0,0,0,186,187, + 5,50,0,0,187,198,1,0,0,0,188,189,3,16,8,0,189,191,5,40,0,0,190,192,5,44, + 0,0,191,190,1,0,0,0,191,192,1,0,0,0,192,193,1,0,0,0,193,194,5,45,0,0,194, + 198,1,0,0,0,195,196,4,5,4,0,196,198,3,14,7,0,197,167,1,0,0,0,197,170,1, + 0,0,0,197,171,1,0,0,0,197,172,1,0,0,0,197,188,1,0,0,0,197,195,1,0,0,0,198, + 207,1,0,0,0,199,200,10,5,0,0,200,201,5,30,0,0,201,206,3,10,5,6,202,203, + 10,4,0,0,203,204,5,47,0,0,204,206,3,10,5,5,205,199,1,0,0,0,205,202,1,0, + 0,0,206,209,1,0,0,0,207,205,1,0,0,0,207,208,1,0,0,0,208,11,1,0,0,0,209, + 207,1,0,0,0,210,212,3,16,8,0,211,213,5,44,0,0,212,211,1,0,0,0,212,213,1, + 0,0,0,213,214,1,0,0,0,214,215,5,42,0,0,215,216,3,106,53,0,216,225,1,0,0, + 0,217,219,3,16,8,0,218,220,5,44,0,0,219,218,1,0,0,0,219,220,1,0,0,0,220, + 221,1,0,0,0,221,222,5,49,0,0,222,223,3,106,53,0,223,225,1,0,0,0,224,210, + 1,0,0,0,224,217,1,0,0,0,225,13,1,0,0,0,226,227,3,58,29,0,227,228,5,24,0, + 0,228,229,3,68,34,0,229,15,1,0,0,0,230,236,3,18,9,0,231,232,3,18,9,0,232, + 233,3,108,54,0,233,234,3,18,9,0,234,236,1,0,0,0,235,230,1,0,0,0,235,231, + 1,0,0,0,236,17,1,0,0,0,237,238,6,9,-1,0,238,242,3,20,10,0,239,240,7,0,0, + 0,240,242,3,18,9,3,241,237,1,0,0,0,241,239,1,0,0,0,242,251,1,0,0,0,243, + 244,10,2,0,0,244,245,7,1,0,0,245,250,3,18,9,3,246,247,10,1,0,0,247,248, + 7,0,0,0,248,250,3,18,9,2,249,243,1,0,0,0,249,246,1,0,0,0,250,253,1,0,0, + 0,251,249,1,0,0,0,251,252,1,0,0,0,252,19,1,0,0,0,253,251,1,0,0,0,254,255, + 6,10,-1,0,255,263,3,68,34,0,256,263,3,58,29,0,257,263,3,22,11,0,258,259, + 5,43,0,0,259,260,3,10,5,0,260,261,5,50,0,0,261,263,1,0,0,0,262,254,1,0, + 0,0,262,256,1,0,0,0,262,257,1,0,0,0,262,258,1,0,0,0,263,269,1,0,0,0,264, + 265,10,1,0,0,265,266,5,33,0,0,266,268,3,26,13,0,267,264,1,0,0,0,268,271, + 1,0,0,0,269,267,1,0,0,0,269,270,1,0,0,0,270,21,1,0,0,0,271,269,1,0,0,0, + 272,273,3,24,12,0,273,283,5,43,0,0,274,284,5,61,0,0,275,280,3,10,5,0,276, + 277,5,34,0,0,277,279,3,10,5,0,278,276,1,0,0,0,279,282,1,0,0,0,280,278,1, + 0,0,0,280,281,1,0,0,0,281,284,1,0,0,0,282,280,1,0,0,0,283,274,1,0,0,0,283, + 275,1,0,0,0,283,284,1,0,0,0,284,285,1,0,0,0,285,286,5,50,0,0,286,23,1,0, + 0,0,287,288,3,72,36,0,288,25,1,0,0,0,289,290,3,64,32,0,290,27,1,0,0,0,291, + 292,5,12,0,0,292,293,3,30,15,0,293,29,1,0,0,0,294,299,3,32,16,0,295,296, + 5,34,0,0,296,298,3,32,16,0,297,295,1,0,0,0,298,301,1,0,0,0,299,297,1,0, + 0,0,299,300,1,0,0,0,300,31,1,0,0,0,301,299,1,0,0,0,302,303,3,58,29,0,303, + 304,5,32,0,0,304,306,1,0,0,0,305,302,1,0,0,0,305,306,1,0,0,0,306,307,1, + 0,0,0,307,308,3,10,5,0,308,33,1,0,0,0,309,310,5,6,0,0,310,315,3,36,18,0, + 311,312,5,34,0,0,312,314,3,36,18,0,313,311,1,0,0,0,314,317,1,0,0,0,315, + 313,1,0,0,0,315,316,1,0,0,0,316,319,1,0,0,0,317,315,1,0,0,0,318,320,3,42, + 21,0,319,318,1,0,0,0,319,320,1,0,0,0,320,35,1,0,0,0,321,322,3,38,19,0,322, + 323,5,24,0,0,323,325,1,0,0,0,324,321,1,0,0,0,324,325,1,0,0,0,325,326,1, + 0,0,0,326,327,3,40,20,0,327,37,1,0,0,0,328,329,5,76,0,0,329,39,1,0,0,0, + 330,331,7,2,0,0,331,41,1,0,0,0,332,335,3,44,22,0,333,335,3,46,23,0,334, + 332,1,0,0,0,334,333,1,0,0,0,335,43,1,0,0,0,336,337,5,75,0,0,337,342,5,76, + 0,0,338,339,5,34,0,0,339,341,5,76,0,0,340,338,1,0,0,0,341,344,1,0,0,0,342, + 340,1,0,0,0,342,343,1,0,0,0,343,45,1,0,0,0,344,342,1,0,0,0,345,346,5,65, + 0,0,346,347,3,44,22,0,347,348,5,66,0,0,348,47,1,0,0,0,349,350,5,19,0,0, + 350,355,3,36,18,0,351,352,5,34,0,0,352,354,3,36,18,0,353,351,1,0,0,0,354, + 357,1,0,0,0,355,353,1,0,0,0,355,356,1,0,0,0,356,359,1,0,0,0,357,355,1,0, + 0,0,358,360,3,54,27,0,359,358,1,0,0,0,359,360,1,0,0,0,360,363,1,0,0,0,361, + 362,5,29,0,0,362,364,3,30,15,0,363,361,1,0,0,0,363,364,1,0,0,0,364,49,1, + 0,0,0,365,366,5,4,0,0,366,367,3,30,15,0,367,51,1,0,0,0,368,370,5,15,0,0, + 369,371,3,54,27,0,370,369,1,0,0,0,370,371,1,0,0,0,371,374,1,0,0,0,372,373, + 5,29,0,0,373,375,3,30,15,0,374,372,1,0,0,0,374,375,1,0,0,0,375,53,1,0,0, + 0,376,381,3,56,28,0,377,378,5,34,0,0,378,380,3,56,28,0,379,377,1,0,0,0, + 380,383,1,0,0,0,381,379,1,0,0,0,381,382,1,0,0,0,382,55,1,0,0,0,383,381, + 1,0,0,0,384,387,3,32,16,0,385,386,5,16,0,0,386,388,3,10,5,0,387,385,1,0, + 0,0,387,388,1,0,0,0,388,57,1,0,0,0,389,394,3,72,36,0,390,391,5,36,0,0,391, + 393,3,72,36,0,392,390,1,0,0,0,393,396,1,0,0,0,394,392,1,0,0,0,394,395,1, + 0,0,0,395,59,1,0,0,0,396,394,1,0,0,0,397,402,3,66,33,0,398,399,5,36,0,0, + 399,401,3,66,33,0,400,398,1,0,0,0,401,404,1,0,0,0,402,400,1,0,0,0,402,403, + 1,0,0,0,403,61,1,0,0,0,404,402,1,0,0,0,405,410,3,60,30,0,406,407,5,34,0, + 0,407,409,3,60,30,0,408,406,1,0,0,0,409,412,1,0,0,0,410,408,1,0,0,0,410, + 411,1,0,0,0,411,63,1,0,0,0,412,410,1,0,0,0,413,414,7,3,0,0,414,65,1,0,0, + 0,415,419,5,80,0,0,416,417,4,33,10,0,417,419,3,70,35,0,418,415,1,0,0,0, + 418,416,1,0,0,0,419,67,1,0,0,0,420,463,5,45,0,0,421,422,3,104,52,0,422, + 423,5,67,0,0,423,463,1,0,0,0,424,463,3,102,51,0,425,463,3,104,52,0,426, + 463,3,98,49,0,427,463,3,70,35,0,428,463,3,106,53,0,429,430,5,65,0,0,430, + 435,3,100,50,0,431,432,5,34,0,0,432,434,3,100,50,0,433,431,1,0,0,0,434, + 437,1,0,0,0,435,433,1,0,0,0,435,436,1,0,0,0,436,438,1,0,0,0,437,435,1,0, + 0,0,438,439,5,66,0,0,439,463,1,0,0,0,440,441,5,65,0,0,441,446,3,98,49,0, + 442,443,5,34,0,0,443,445,3,98,49,0,444,442,1,0,0,0,445,448,1,0,0,0,446, + 444,1,0,0,0,446,447,1,0,0,0,447,449,1,0,0,0,448,446,1,0,0,0,449,450,5,66, + 0,0,450,463,1,0,0,0,451,452,5,65,0,0,452,457,3,106,53,0,453,454,5,34,0, + 0,454,456,3,106,53,0,455,453,1,0,0,0,456,459,1,0,0,0,457,455,1,0,0,0,457, + 458,1,0,0,0,458,460,1,0,0,0,459,457,1,0,0,0,460,461,5,66,0,0,461,463,1, + 0,0,0,462,420,1,0,0,0,462,421,1,0,0,0,462,424,1,0,0,0,462,425,1,0,0,0,462, + 426,1,0,0,0,462,427,1,0,0,0,462,428,1,0,0,0,462,429,1,0,0,0,462,440,1,0, + 0,0,462,451,1,0,0,0,463,69,1,0,0,0,464,467,5,48,0,0,465,467,5,64,0,0,466, + 464,1,0,0,0,466,465,1,0,0,0,467,71,1,0,0,0,468,472,3,64,32,0,469,470,4, + 36,11,0,470,472,3,70,35,0,471,468,1,0,0,0,471,469,1,0,0,0,472,73,1,0,0, + 0,473,474,5,9,0,0,474,475,5,27,0,0,475,75,1,0,0,0,476,477,5,14,0,0,477, + 482,3,78,39,0,478,479,5,34,0,0,479,481,3,78,39,0,480,478,1,0,0,0,481,484, + 1,0,0,0,482,480,1,0,0,0,482,483,1,0,0,0,483,77,1,0,0,0,484,482,1,0,0,0, + 485,487,3,10,5,0,486,488,7,4,0,0,487,486,1,0,0,0,487,488,1,0,0,0,488,491, + 1,0,0,0,489,490,5,46,0,0,490,492,7,5,0,0,491,489,1,0,0,0,491,492,1,0,0, + 0,492,79,1,0,0,0,493,494,5,8,0,0,494,495,3,62,31,0,495,81,1,0,0,0,496,497, + 5,2,0,0,497,498,3,62,31,0,498,83,1,0,0,0,499,500,5,11,0,0,500,505,3,86, + 43,0,501,502,5,34,0,0,502,504,3,86,43,0,503,501,1,0,0,0,504,507,1,0,0,0, + 505,503,1,0,0,0,505,506,1,0,0,0,506,85,1,0,0,0,507,505,1,0,0,0,508,509, + 3,60,30,0,509,510,5,84,0,0,510,511,3,60,30,0,511,87,1,0,0,0,512,513,5,1, + 0,0,513,514,3,20,10,0,514,516,3,106,53,0,515,517,3,94,47,0,516,515,1,0, + 0,0,516,517,1,0,0,0,517,89,1,0,0,0,518,519,5,7,0,0,519,520,3,20,10,0,520, + 521,3,106,53,0,521,91,1,0,0,0,522,523,5,10,0,0,523,524,3,58,29,0,524,93, + 1,0,0,0,525,530,3,96,48,0,526,527,5,34,0,0,527,529,3,96,48,0,528,526,1, + 0,0,0,529,532,1,0,0,0,530,528,1,0,0,0,530,531,1,0,0,0,531,95,1,0,0,0,532, + 530,1,0,0,0,533,534,3,64,32,0,534,535,5,32,0,0,535,536,3,68,34,0,536,97, + 1,0,0,0,537,538,7,6,0,0,538,99,1,0,0,0,539,542,3,102,51,0,540,542,3,104, + 52,0,541,539,1,0,0,0,541,540,1,0,0,0,542,101,1,0,0,0,543,545,7,0,0,0,544, + 543,1,0,0,0,544,545,1,0,0,0,545,546,1,0,0,0,546,547,5,28,0,0,547,103,1, + 0,0,0,548,550,7,0,0,0,549,548,1,0,0,0,549,550,1,0,0,0,550,551,1,0,0,0,551, + 552,5,27,0,0,552,105,1,0,0,0,553,554,5,26,0,0,554,107,1,0,0,0,555,556,7, + 7,0,0,556,109,1,0,0,0,557,558,5,5,0,0,558,559,3,112,56,0,559,111,1,0,0, + 0,560,561,5,65,0,0,561,562,3,2,1,0,562,563,5,66,0,0,563,113,1,0,0,0,564, + 565,5,13,0,0,565,566,5,100,0,0,566,115,1,0,0,0,567,568,5,3,0,0,568,571, + 5,90,0,0,569,570,5,88,0,0,570,572,3,60,30,0,571,569,1,0,0,0,571,572,1,0, + 0,0,572,582,1,0,0,0,573,574,5,89,0,0,574,579,3,118,59,0,575,576,5,34,0, + 0,576,578,3,118,59,0,577,575,1,0,0,0,578,581,1,0,0,0,579,577,1,0,0,0,579, + 580,1,0,0,0,580,583,1,0,0,0,581,579,1,0,0,0,582,573,1,0,0,0,582,583,1,0, + 0,0,583,117,1,0,0,0,584,585,3,60,30,0,585,586,5,32,0,0,586,588,1,0,0,0, + 587,584,1,0,0,0,587,588,1,0,0,0,588,589,1,0,0,0,589,590,3,60,30,0,590,119, + 1,0,0,0,591,592,5,18,0,0,592,593,3,36,18,0,593,594,5,88,0,0,594,595,3,62, + 31,0,595,121,1,0,0,0,596,597,5,17,0,0,597,600,3,54,27,0,598,599,5,29,0, + 0,599,601,3,30,15,0,600,598,1,0,0,0,600,601,1,0,0,0,601,123,1,0,0,0,58, 135,144,162,174,183,191,197,205,207,212,219,224,235,241,249,251,262,269, - 280,283,289,301,307,317,321,326,336,344,357,361,365,372,376,383,389,396, - 404,412,420,437,448,459,464,468,473,484,489,493,507,518,532,543,546,551, - 573,581,584,589,602]; + 280,283,299,305,315,319,324,334,342,355,359,363,370,374,381,387,394,402, + 410,418,435,446,457,462,466,471,482,487,491,505,516,530,541,544,549,571, + 579,582,587,600]; private static __ATN: ATN; public static get _ATN(): ATN { @@ -4124,19 +4107,20 @@ export class RegexBooleanExpressionContext extends ParserRuleContext { export class MatchBooleanExpressionContext extends ParserRuleContext { - public _queryString!: StringContext; + public _fieldExp!: QualifiedNameContext; + public _queryString!: ConstantContext; constructor(parser?: esql_parser, parent?: ParserRuleContext, invokingState?: number) { super(parent, invokingState); this.parser = parser; } - public valueExpression(): ValueExpressionContext { - return this.getTypedRuleContext(ValueExpressionContext, 0) as ValueExpressionContext; + public COLON(): TerminalNode { + return this.getToken(esql_parser.COLON, 0); } - public MATCH(): TerminalNode { - return this.getToken(esql_parser.MATCH, 0); + public qualifiedName(): QualifiedNameContext { + return this.getTypedRuleContext(QualifiedNameContext, 0) as QualifiedNameContext; } - public string_(): StringContext { - return this.getTypedRuleContext(StringContext, 0) as StringContext; + public constant(): ConstantContext { + return this.getTypedRuleContext(ConstantContext, 0) as ConstantContext; } public get ruleIndex(): number { return esql_parser.RULE_matchBooleanExpression; @@ -4484,9 +4468,6 @@ export class FunctionNameContext extends ParserRuleContext { super(parent, invokingState); this.parser = parser; } - public MATCH(): TerminalNode { - return this.getToken(esql_parser.MATCH, 0); - } public identifierOrParameter(): IdentifierOrParameterContext { return this.getTypedRuleContext(IdentifierOrParameterContext, 0) as IdentifierOrParameterContext; } diff --git a/packages/kbn-esql-ast/src/parser/__tests__/function.test.ts b/packages/kbn-esql-ast/src/parser/__tests__/function.test.ts index d05ed36204b17..486feae97f98c 100644 --- a/packages/kbn-esql-ast/src/parser/__tests__/function.test.ts +++ b/packages/kbn-esql-ast/src/parser/__tests__/function.test.ts @@ -8,6 +8,7 @@ */ import { parse } from '..'; +import { EsqlQuery } from '../../query'; import { Walker } from '../../walker'; describe('function AST nodes', () => { @@ -323,3 +324,86 @@ describe('function AST nodes', () => { }); }); }); + +describe('location', () => { + const getFunctionTexts = (src: string) => { + const query = EsqlQuery.fromSrc(src); + const functions = Walker.matchAll(query.ast, { type: 'function' }); + const texts: string[] = functions.map((fn) => { + return [...src].slice(fn.location.min, fn.location.max + 1).join(''); + }); + + return texts; + }; + + it('correctly cuts out function source texts', () => { + const texts = getFunctionTexts( + 'FROM index | LIMIT 1 | STATS agg() | LIMIT 2 | STATS max(a, b, c), max2(d.e)' + ); + + expect(texts).toEqual(['agg()', 'max(a, b, c)', 'max2(d.e)']); + }); + + it('functions in binary expressions', () => { + const texts = getFunctionTexts('FROM index | STATS foo = agg(f1) + agg(f2), a.b = agg(f3)'); + + expect(texts).toEqual([ + 'foo = agg(f1) + agg(f2)', + 'agg(f1) + agg(f2)', + 'agg(f1)', + 'agg(f2)', + 'a.b = agg(f3)', + 'agg(f3)', + ]); + }); + + it('with the simplest comment after function name identifier', () => { + const texts1 = getFunctionTexts('FROM index | STATS agg/* */(1)'); + expect(texts1).toEqual(['agg/* */(1)']); + + const texts2 = getFunctionTexts('FROM index | STATS agg/* A */(a)'); + expect(texts2).toEqual(['agg/* A */(a)']); + + const texts3 = getFunctionTexts('FROM index | STATS agg /* A */ (*)'); + expect(texts3).toEqual(['agg /* A */ (*)']); + }); + + it('with the simplest emoji comment after function name identifier', () => { + const texts = getFunctionTexts('FROM index | STATS agg/* 😎 */(*)'); + expect(texts).toEqual(['agg/* 😎 */(*)']); + }); + + it('with the simplest emoji comment after function name identifier, followed by another arg', () => { + const texts = getFunctionTexts('FROM index | STATS agg/* 😎 */(*), abc'); + expect(texts).toEqual(['agg/* 😎 */(*)']); + }); + + it('simple emoji comment twice', () => { + const texts = getFunctionTexts('FROM index | STATS agg/* 😎 */(*), max/* 😎 */(*)'); + expect(texts).toEqual(['agg/* 😎 */(*)', 'max/* 😎 */(*)']); + }); + + it('with comment and emoji after function name identifier', () => { + const texts = getFunctionTexts('FROM index | STATS agg /* haha 😅 */ (*)'); + + expect(texts).toEqual(['agg /* haha 😅 */ (*)']); + }); + + it('with comment inside argument list', () => { + const texts = getFunctionTexts('FROM index | STATS agg ( /* haha 😅 */ )'); + + expect(texts).toEqual(['agg ( /* haha 😅 */ )']); + }); + + it('with emoji and comment in argument lists', () => { + const texts = getFunctionTexts( + 'FROM index | STATS agg( /* haha 😅 */ max(foo), bar, baz), test( /* asdf */ * /* asdf */)' + ); + + expect(texts).toEqual([ + 'agg( /* haha 😅 */ max(foo), bar, baz)', + 'max(foo)', + 'test( /* asdf */ * /* asdf */)', + ]); + }); +}); diff --git a/packages/kbn-esql-ast/src/parser/helpers.ts b/packages/kbn-esql-ast/src/parser/helpers.ts index f11cb396f2980..528176684418f 100644 --- a/packages/kbn-esql-ast/src/parser/helpers.ts +++ b/packages/kbn-esql-ast/src/parser/helpers.ts @@ -41,17 +41,16 @@ export const formatIdentifierParts = (parts: string[]): string => parts.map(formatIdentifier).join('.'); export const getPosition = ( - token: Pick | null, - lastToken?: Pick | undefined + start: Pick | null, + stop?: Pick | undefined ) => { - if (!token || token.start < 0) { + if (!start || start.start < 0) { return { min: 0, max: 0 }; } - const endFirstToken = token.stop > -1 ? Math.max(token.stop + 1, token.start) : undefined; - const endLastToken = lastToken?.stop; + const endFirstToken = start.stop > -1 ? Math.max(start.stop + 1, start.start) : undefined; return { - min: token.start, - max: endLastToken ?? endFirstToken ?? Infinity, + min: start.start, + max: stop?.stop ?? endFirstToken ?? Infinity, }; }; diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.where.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.where.test.ts new file mode 100644 index 0000000000000..3345f7646e2ff --- /dev/null +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.where.test.ts @@ -0,0 +1,334 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { ESQL_COMMON_NUMERIC_TYPES } from '../../shared/esql_types'; +import { pipeCompleteItem } from '../complete_items'; +import { getDateLiterals } from '../factories'; +import { log10ParameterTypes, powParameterTypes } from './constants'; +import { + attachTriggerCommand, + fields, + getFieldNamesByType, + getFunctionSignaturesByReturnType, + setup, +} from './helpers'; + +describe('WHERE ', () => { + const allEvalFns = getFunctionSignaturesByReturnType('where', 'any', { + scalar: true, + }); + test('beginning an expression', async () => { + const { assertSuggestions } = await setup(); + + await assertSuggestions('from a | where /', [ + ...getFieldNamesByType('any') + .map((field) => `${field} `) + .map(attachTriggerCommand), + ...allEvalFns, + ]); + await assertSuggestions( + 'from a | eval var0 = 1 | where /', + [ + ...getFieldNamesByType('any') + .map((name) => `${name} `) + .map(attachTriggerCommand), + attachTriggerCommand('var0 '), + ...allEvalFns, + ], + { + callbacks: { + getColumnsFor: () => Promise.resolve([...fields, { name: 'var0', type: 'integer' }]), + }, + } + ); + }); + + describe('within the expression', () => { + test('after a field name', async () => { + const { assertSuggestions } = await setup(); + + await assertSuggestions('from a | where keywordField /', [ + // all functions compatible with a keywordField type + ...getFunctionSignaturesByReturnType( + 'where', + 'boolean', + { + builtin: true, + }, + undefined, + ['and', 'or', 'not'] + ), + ]); + }); + + test('suggests dates after a comparison with a date', async () => { + const { assertSuggestions } = await setup(); + + const expectedComparisonWithDateSuggestions = [ + ...getDateLiterals(), + ...getFieldNamesByType(['date']), + // all functions compatible with a keywordField type + ...getFunctionSignaturesByReturnType('where', ['date'], { scalar: true }), + ]; + await assertSuggestions( + 'from a | where dateField == /', + expectedComparisonWithDateSuggestions + ); + + await assertSuggestions( + 'from a | where dateField < /', + expectedComparisonWithDateSuggestions + ); + + await assertSuggestions( + 'from a | where dateField >= /', + expectedComparisonWithDateSuggestions + ); + }); + + test('after a comparison with a string field', async () => { + const { assertSuggestions } = await setup(); + + const expectedComparisonWithTextFieldSuggestions = [ + ...getFieldNamesByType(['text', 'keyword', 'ip', 'version']), + ...getFunctionSignaturesByReturnType('where', ['text', 'keyword', 'ip', 'version'], { + scalar: true, + }), + ]; + + await assertSuggestions( + 'from a | where textField >= /', + expectedComparisonWithTextFieldSuggestions + ); + await assertSuggestions( + 'from a | where textField >= textField/', + expectedComparisonWithTextFieldSuggestions + ); + }); + + test('after a logical operator', async () => { + const { assertSuggestions } = await setup(); + + for (const op of ['and', 'or']) { + await assertSuggestions(`from a | where keywordField >= keywordField ${op} /`, [ + ...getFieldNamesByType('any'), + ...getFunctionSignaturesByReturnType('where', 'any', { scalar: true }), + ]); + await assertSuggestions(`from a | where keywordField >= keywordField ${op} doubleField /`, [ + ...getFunctionSignaturesByReturnType('where', 'boolean', { builtin: true }, ['double']), + ]); + await assertSuggestions( + `from a | where keywordField >= keywordField ${op} doubleField == /`, + [ + ...getFieldNamesByType(ESQL_COMMON_NUMERIC_TYPES), + ...getFunctionSignaturesByReturnType('where', ESQL_COMMON_NUMERIC_TYPES, { + scalar: true, + }), + ] + ); + } + }); + + test('suggests operators after a field name', async () => { + const { assertSuggestions } = await setup(); + + await assertSuggestions('from a | stats a=avg(doubleField) | where a /', [ + ...getFunctionSignaturesByReturnType('where', 'any', { builtin: true, skipAssign: true }, [ + 'double', + ]), + ]); + }); + + test('accounts for fields lost in previous commands', async () => { + const { assertSuggestions } = await setup(); + + // Mind this test: suggestion is aware of previous commands when checking for fields + // in this case the doubleField has been wiped by the STATS command and suggest cannot find it's type + await assertSuggestions('from a | stats a=avg(doubleField) | where doubleField /', [], { + callbacks: { getColumnsFor: () => Promise.resolve([{ name: 'a', type: 'double' }]) }, + }); + }); + + test('suggests function arguments', async () => { + const { assertSuggestions } = await setup(); + + // The editor automatically inject the final bracket, so it is not useful to test with just open bracket + await assertSuggestions( + 'from a | where log10(/)', + [ + ...getFieldNamesByType(log10ParameterTypes), + ...getFunctionSignaturesByReturnType( + 'where', + log10ParameterTypes, + { scalar: true }, + undefined, + ['log10'] + ), + ], + { triggerCharacter: '(' } + ); + await assertSuggestions( + 'from a | WHERE pow(doubleField, /)', + [ + ...getFieldNamesByType(powParameterTypes), + ...getFunctionSignaturesByReturnType( + 'where', + powParameterTypes, + { scalar: true }, + undefined, + ['pow'] + ), + ], + { triggerCharacter: ',' } + ); + }); + + test('suggests boolean and numeric operators after a numeric function result', async () => { + const { assertSuggestions } = await setup(); + + await assertSuggestions('from a | where log10(doubleField) /', [ + ...getFunctionSignaturesByReturnType('where', 'double', { builtin: true }, ['double']), + ...getFunctionSignaturesByReturnType('where', 'boolean', { builtin: true }, ['double']), + ]); + }); + + test('suggestions after NOT', async () => { + const { assertSuggestions } = await setup(); + await assertSuggestions('from index | WHERE keywordField not /', [ + 'LIKE $0', + 'RLIKE $0', + 'IN $0', + ]); + await assertSuggestions('from index | WHERE keywordField NOT /', [ + 'LIKE $0', + 'RLIKE $0', + 'IN $0', + ]); + await assertSuggestions('from index | WHERE not /', [ + ...getFieldNamesByType('boolean').map((name) => attachTriggerCommand(`${name} `)), + ...getFunctionSignaturesByReturnType('where', 'boolean', { scalar: true }), + ]); + await assertSuggestions('FROM index | WHERE NOT ENDS_WITH(keywordField, "foo") /', [ + ...getFunctionSignaturesByReturnType('where', 'boolean', { builtin: true }, ['boolean']), + pipeCompleteItem, + ]); + await assertSuggestions('from index | WHERE keywordField IS NOT/', [ + '!= $0', + '== $0', + 'AND $0', + 'IN $0', + 'IS NOT NULL', + 'IS NULL', + 'NOT', + 'OR $0', + '| ', + ]); + + await assertSuggestions('from index | WHERE keywordField IS NOT /', [ + '!= $0', + '== $0', + 'AND $0', + 'IN $0', + 'IS NOT NULL', + 'IS NULL', + 'NOT', + 'OR $0', + '| ', + ]); + }); + + test('suggestions after IN', async () => { + const { assertSuggestions } = await setup(); + + await assertSuggestions('from index | WHERE doubleField in /', ['( $0 )']); + await assertSuggestions('from index | WHERE doubleField not in /', ['( $0 )']); + await assertSuggestions( + 'from index | WHERE doubleField not in (/)', + [ + ...getFieldNamesByType('double').filter((name) => name !== 'doubleField'), + ...getFunctionSignaturesByReturnType('where', 'double', { scalar: true }), + ], + { triggerCharacter: '(' } + ); + await assertSuggestions('from index | WHERE doubleField in ( `any#Char$Field`, /)', [ + ...getFieldNamesByType('double').filter( + (name) => name !== '`any#Char$Field`' && name !== 'doubleField' + ), + ...getFunctionSignaturesByReturnType('where', 'double', { scalar: true }), + ]); + await assertSuggestions('from index | WHERE doubleField not in ( `any#Char$Field`, /)', [ + ...getFieldNamesByType('double').filter( + (name) => name !== '`any#Char$Field`' && name !== 'doubleField' + ), + ...getFunctionSignaturesByReturnType('where', 'double', { scalar: true }), + ]); + }); + + test('suggestions after IS (NOT) NULL', async () => { + const { assertSuggestions } = await setup(); + + await assertSuggestions('FROM index | WHERE tags.keyword IS NULL /', [ + 'AND $0', + 'OR $0', + '| ', + ]); + + await assertSuggestions('FROM index | WHERE tags.keyword IS NOT NULL /', [ + 'AND $0', + 'OR $0', + '| ', + ]); + }); + + test('suggestions after an arithmetic expression', async () => { + const { assertSuggestions } = await setup(); + + await assertSuggestions('FROM index | WHERE doubleField + doubleField /', [ + ...getFunctionSignaturesByReturnType('where', 'any', { builtin: true, skipAssign: true }, [ + 'double', + ]), + ]); + }); + + test('pipe suggestion after complete expression', async () => { + const { suggest } = await setup(); + expect(await suggest('from index | WHERE doubleField != doubleField /')).toContainEqual( + expect.objectContaining({ + label: '|', + }) + ); + }); + + test('attaches ranges', async () => { + const { suggest } = await setup(); + + const suggestions = await suggest('FROM index | WHERE doubleField IS N/'); + + expect(suggestions).toContainEqual( + expect.objectContaining({ + text: 'IS NOT NULL', + rangeToReplace: { + start: 32, + end: 36, + }, + }) + ); + + expect(suggestions).toContainEqual( + expect.objectContaining({ + text: 'IS NULL', + rangeToReplace: { + start: 32, + end: 36, + }, + }) + ); + }); + }); +}); diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.suggest.eval.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.suggest.eval.test.ts index 81fd8f7f43902..5c67bfedbae75 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.suggest.eval.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.suggest.eval.test.ts @@ -535,17 +535,6 @@ describe('autocomplete.suggest', () => { { triggerCharacter: ' ' } ); await assertSuggestions('from a | eval a = 1 year /', [',', '| ', 'IS NOT NULL', 'IS NULL']); - await assertSuggestions('from a | eval a = 1 day + 2 /', [',', '| ']); - await assertSuggestions( - 'from a | eval 1 day + 2 /', - [ - ...dateSuggestions, - ...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [ - 'integer', - ]), - ], - { triggerCharacter: ' ' } - ); await assertSuggestions( 'from a | eval var0=date_trunc(/)', [ diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/hidden_functions_and_commands.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/hidden_functions_and_commands.test.ts index cc4562f999fe3..02cc79c326792 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/hidden_functions_and_commands.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/hidden_functions_and_commands.test.ts @@ -50,6 +50,7 @@ describe('hidden functions', () => { expect(suggestedFunctions).toContain('VISIBLE_FUNCTION($0)'); expect(suggestedFunctions).not.toContain('HIDDEN_FUNCTION($0)'); }); + it('does not suggest hidden agg functions', async () => { setTestFunctions([ { diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts index 7a1b2c191b400..9a99cdc9a8c68 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts @@ -11,12 +11,7 @@ import { suggest } from './autocomplete'; import { scalarFunctionDefinitions } from '../definitions/generated/scalar_functions'; import { timeUnitsToSuggest } from '../definitions/literals'; import { commandDefinitions as unmodifiedCommandDefinitions } from '../definitions/commands'; -import { - getDateLiterals, - getSafeInsertText, - TIME_SYSTEM_PARAMS, - TRIGGER_SUGGESTION_COMMAND, -} from './factories'; +import { getSafeInsertText, TIME_SYSTEM_PARAMS, TRIGGER_SUGGESTION_COMMAND } from './factories'; import { camelCase } from 'lodash'; import { getAstAndSyntaxErrors } from '@kbn/esql-ast'; import { @@ -34,8 +29,7 @@ import { fields, } from './__tests__/helpers'; import { METADATA_FIELDS } from '../shared/constants'; -import { ESQL_COMMON_NUMERIC_TYPES, ESQL_STRING_TYPES } from '../shared/esql_types'; -import { log10ParameterTypes, powParameterTypes } from './__tests__/constants'; +import { ESQL_STRING_TYPES } from '../shared/esql_types'; import { getRecommendedQueries } from './recommended_queries/templates'; import { getDateHistogramCompletionItem } from './commands/stats/util'; @@ -130,149 +124,6 @@ describe('autocomplete', () => { } }); - describe('where', () => { - const allEvalFns = getFunctionSignaturesByReturnType('where', 'any', { - scalar: true, - }); - testSuggestions('from a | where /', [ - ...getFieldNamesByType('any').map((field) => `${field} `), - ...allEvalFns, - ]); - testSuggestions('from a | eval var0 = 1 | where /', [ - ...getFieldNamesByType('any').map((name) => `${name} `), - 'var0', - ...allEvalFns, - ]); - testSuggestions('from a | where keywordField /', [ - // all functions compatible with a keywordField type - ...getFunctionSignaturesByReturnType( - 'where', - 'boolean', - { - builtin: true, - }, - undefined, - ['and', 'or', 'not'] - ), - ]); - - const expectedComparisonWithDateSuggestions = [ - ...getDateLiterals(), - ...getFieldNamesByType(['date']), - // all functions compatible with a keywordField type - ...getFunctionSignaturesByReturnType('where', ['date'], { scalar: true }), - ]; - testSuggestions('from a | where dateField == /', expectedComparisonWithDateSuggestions); - - testSuggestions('from a | where dateField < /', expectedComparisonWithDateSuggestions); - - testSuggestions('from a | where dateField >= /', expectedComparisonWithDateSuggestions); - - const expectedComparisonWithTextFieldSuggestions = [ - ...getFieldNamesByType(['text', 'keyword', 'ip', 'version']), - ...getFunctionSignaturesByReturnType('where', ['text', 'keyword', 'ip', 'version'], { - scalar: true, - }), - ]; - testSuggestions('from a | where textField >= /', expectedComparisonWithTextFieldSuggestions); - testSuggestions( - 'from a | where textField >= textField/', - expectedComparisonWithTextFieldSuggestions - ); - for (const op of ['and', 'or']) { - testSuggestions(`from a | where keywordField >= keywordField ${op} /`, [ - ...getFieldNamesByType('any'), - ...getFunctionSignaturesByReturnType('where', 'any', { scalar: true }), - ]); - testSuggestions(`from a | where keywordField >= keywordField ${op} doubleField /`, [ - ...getFunctionSignaturesByReturnType('where', 'boolean', { builtin: true }, ['double']), - ]); - testSuggestions(`from a | where keywordField >= keywordField ${op} doubleField == /`, [ - ...getFieldNamesByType(ESQL_COMMON_NUMERIC_TYPES), - ...getFunctionSignaturesByReturnType('where', ESQL_COMMON_NUMERIC_TYPES, { - scalar: true, - }), - ]); - } - testSuggestions('from a | stats a=avg(doubleField) | where a /', [ - ...getFunctionSignaturesByReturnType('where', 'any', { builtin: true, skipAssign: true }, [ - 'double', - ]), - ]); - // Mind this test: suggestion is aware of previous commands when checking for fields - // in this case the doubleField has been wiped by the STATS command and suggest cannot find it's type - // @TODO: verify this is the correct behaviour in this case or if we want a "generic" suggestion anyway - testSuggestions( - 'from a | stats a=avg(doubleField) | where doubleField /', - [], - undefined, - // make the fields suggest aware of the previous STATS, leave the other callbacks untouched - [[{ name: 'a', type: 'double' }], undefined, undefined] - ); - // The editor automatically inject the final bracket, so it is not useful to test with just open bracket - testSuggestions( - 'from a | where log10(/)', - [ - ...getFieldNamesByType(log10ParameterTypes), - ...getFunctionSignaturesByReturnType( - 'where', - log10ParameterTypes, - { scalar: true }, - undefined, - ['log10'] - ), - ], - '(' - ); - testSuggestions('from a | where log10(doubleField) /', [ - ...getFunctionSignaturesByReturnType('where', 'double', { builtin: true }, ['double']), - ...getFunctionSignaturesByReturnType('where', 'boolean', { builtin: true }, ['double']), - ]); - testSuggestions( - 'from a | WHERE pow(doubleField, /)', - [ - ...getFieldNamesByType(powParameterTypes), - ...getFunctionSignaturesByReturnType( - 'where', - powParameterTypes, - { scalar: true }, - undefined, - ['pow'] - ), - ], - ',' - ); - - testSuggestions('from index | WHERE keywordField not /', ['LIKE $0', 'RLIKE $0', 'IN $0']); - testSuggestions('from index | WHERE keywordField NOT /', ['LIKE $0', 'RLIKE $0', 'IN $0']); - testSuggestions('from index | WHERE not /', [ - ...getFieldNamesByType('boolean'), - ...getFunctionSignaturesByReturnType('eval', 'boolean', { scalar: true }), - ]); - testSuggestions('from index | WHERE doubleField in /', ['( $0 )']); - testSuggestions('from index | WHERE doubleField not in /', ['( $0 )']); - testSuggestions( - 'from index | WHERE doubleField not in (/)', - [ - ...getFieldNamesByType('double').filter((name) => name !== 'doubleField'), - ...getFunctionSignaturesByReturnType('where', 'double', { scalar: true }), - ], - '(' - ); - testSuggestions('from index | WHERE doubleField in ( `any#Char$Field`, /)', [ - ...getFieldNamesByType('double').filter( - (name) => name !== '`any#Char$Field`' && name !== 'doubleField' - ), - ...getFunctionSignaturesByReturnType('where', 'double', { scalar: true }), - ]); - testSuggestions('from index | WHERE doubleField not in ( `any#Char$Field`, /)', [ - ...getFieldNamesByType('double').filter( - (name) => name !== '`any#Char$Field`' && name !== 'doubleField' - ), - ...getFunctionSignaturesByReturnType('where', 'double', { scalar: true }), - ]); - }); - describe('grok', () => { const constantPattern = '"%{WORD:firstWord}"'; const subExpressions = [ @@ -766,6 +617,21 @@ describe('autocomplete', () => { ['and', 'or', 'not'] ) ); + + // WHERE function + testSuggestions( + 'FROM index1 | WHERE ABS(integerField) i/', + getFunctionSignaturesByReturnType( + 'where', + 'any', + { + builtin: true, + skipAssign: true, + }, + ['integer'], + ['and', 'or', 'not'] + ) + ); }); describe('advancing the cursor and opening the suggestion menu automatically ✨', () => { @@ -1295,27 +1161,35 @@ describe('autocomplete', () => { describe('Replacement ranges are attached when needed', () => { testSuggestions('FROM a | WHERE doubleField IS NOT N/', [ - { text: 'IS NOT NULL', rangeToReplace: { start: 28, end: 35 } }, - { text: 'IS NULL', rangeToReplace: { start: 36, end: 36 } }, + { text: 'IS NOT NULL', rangeToReplace: { start: 28, end: 36 } }, + { text: 'IS NULL', rangeToReplace: { start: 37, end: 37 } }, '!= $0', '== $0', 'IN $0', 'AND $0', 'NOT', 'OR $0', + // pipe doesn't make sense here, but Monaco will filter it out. + // see https://github.com/elastic/kibana/issues/199401 for an explanation + // of why this happens + '| ', ]); testSuggestions('FROM a | WHERE doubleField IS N/', [ - { text: 'IS NOT NULL', rangeToReplace: { start: 28, end: 31 } }, - { text: 'IS NULL', rangeToReplace: { start: 28, end: 31 } }, - { text: '!= $0', rangeToReplace: { start: 32, end: 32 } }, + { text: 'IS NOT NULL', rangeToReplace: { start: 28, end: 32 } }, + { text: 'IS NULL', rangeToReplace: { start: 28, end: 32 } }, + { text: '!= $0', rangeToReplace: { start: 33, end: 33 } }, '== $0', 'IN $0', 'AND $0', 'NOT', 'OR $0', + // pipe doesn't make sense here, but Monaco will filter it out. + // see https://github.com/elastic/kibana/issues/199401 for an explanation + // of why this happens + '| ', ]); testSuggestions('FROM a | EVAL doubleField IS NOT N/', [ - { text: 'IS NOT NULL', rangeToReplace: { start: 27, end: 34 } }, + { text: 'IS NOT NULL', rangeToReplace: { start: 27, end: 35 } }, 'IS NULL', '!= $0', '== $0', @@ -1324,6 +1198,7 @@ describe('autocomplete', () => { 'NOT', 'OR $0', ]); + describe('dot-separated field names', () => { testSuggestions( 'FROM a | KEEP field.nam/', diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts index ecb46682b041e..bae10b4c321f4 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts @@ -24,7 +24,6 @@ import { getCommandOption, getFunctionDefinition, getLastNonWhitespaceChar, - isArrayType, isAssignment, isAssignmentComplete, isColumnItem, @@ -48,6 +47,7 @@ import { sourceExists, findFinalWord, getAllCommands, + getExpressionType, } from '../shared/helpers'; import { collectVariables, excludeVariablesFromCurrentCommand } from '../shared/variables'; import type { ESQLPolicy, ESQLRealField, ESQLVariable, ReferenceMaps } from '../validation/types'; @@ -56,10 +56,7 @@ import { colonCompleteItem, commaCompleteItem, getAssignmentDefinitionCompletitionItem, - getBuiltinCompatibleFunctionDefinition, getCommandAutocompleteDefinitions, - getNextTokenForNot, - listCompleteItem, pipeCompleteItem, semiColonCompleteItem, } from './complete_items'; @@ -80,6 +77,8 @@ import { getDateLiterals, buildFieldsDefinitionsWithMetadata, TRIGGER_SUGGESTION_COMMAND, + getOperatorSuggestions, + getSuggestionsAfterNot, } from './factories'; import { EDITOR_MARKER, METADATA_FIELDS } from '../shared/constants'; import { getAstContext, removeMarkerArgFromArgsList } from '../shared/context'; @@ -92,10 +91,8 @@ import { import { ESQLCallbacks, ESQLSourceResult } from '../shared/types'; import { getFunctionsToIgnoreForStats, - getOverlapRange, getQueryForFields, getSourcesFromCommands, - getSupportedTypesForBinaryOperators, isAggFunctionUsedAlready, removeQuoteForSuggestedSources, getValidSignaturesAndTypesToSuggestNext, @@ -103,8 +100,10 @@ import { getFieldsOrFunctionsSuggestions, pushItUpInTheList, extractTypeFromASTArg, + getSuggestionsToRightOfOperatorExpression, + checkFunctionInvocationComplete, } from './helper'; -import { FunctionParameter, isParameterType, isReturnType } from '../definitions/types'; +import { FunctionParameter, isParameterType } from '../definitions/types'; import { metadataOption } from '../definitions/options'; import { comparisonFunctions } from '../definitions/builtin'; import { getRecommendedQueriesSuggestions } from './recommended_queries/suggestions'; @@ -321,16 +320,12 @@ function findNewVariable(variables: Map) { function workoutBuiltinOptions( nodeArg: ESQLAstItem, references: Pick -): { skipAssign: boolean; commandsToInclude?: string[] } { - const commandsToInclude = - (isSingleItem(nodeArg) && nodeArg.text?.toLowerCase().trim().endsWith('null')) ?? false - ? ['and', 'or'] - : undefined; - +): { ignored?: string[] } { // skip assign operator if it's a function or an existing field to avoid promoting shadowing return { - skipAssign: Boolean(!isColumnItem(nodeArg) || getColumnForASTNode(nodeArg, references)), - commandsToInclude, + ignored: Boolean(!isColumnItem(nodeArg) || getColumnForASTNode(nodeArg, references)) + ? ['='] + : undefined, }; } @@ -340,42 +335,19 @@ function areCurrentArgsValid( references: Pick ) { // unfortunately here we need to bake some command-specific logic - if (command.name === 'stats') { - if (node) { - // consider the following expressions not complete yet - // ... | stats a - // ... | stats a = - if (isColumnItem(node) || (isAssignment(node) && !isAssignmentComplete(node))) { - return false; - } - } - } if (command.name === 'eval') { if (node) { if (isFunctionItem(node)) { if (isAssignment(node)) { return isAssignmentComplete(node); } else { - return isFunctionArgComplete(node, references).complete; + return checkFunctionInvocationComplete(node, (expression) => + getExpressionType(expression, references.fields, references.variables) + ).complete; } } } } - if (command.name === 'where') { - if (node) { - if ( - isColumnItem(node) || - (isFunctionItem(node) && !isFunctionArgComplete(node, references).complete) - ) { - return false; - } else { - return ( - extractTypeFromASTArg(node, references) === - getCommandDefinition(command.name).signature.params[0].type - ); - } - } - } if (command.name === 'rename') { if (node) { if (isColumnItem(node)) { @@ -386,45 +358,6 @@ function areCurrentArgsValid( return true; } -// @TODO: refactor this to be shared with validation -function isFunctionArgComplete( - arg: ESQLFunction, - references: Pick -) { - const fnDefinition = getFunctionDefinition(arg.name); - if (!fnDefinition) { - return { complete: false }; - } - const cleanedArgs = removeMarkerArgFromArgsList(arg)!.args; - const argLengthCheck = fnDefinition.signatures.some((def) => { - if (def.minParams && cleanedArgs.length >= def.minParams) { - return true; - } - if (cleanedArgs.length === def.params.length) { - return true; - } - return cleanedArgs.length >= def.params.filter(({ optional }) => !optional).length; - }); - if (!argLengthCheck) { - return { complete: false, reason: 'fewArgs' }; - } - if (fnDefinition.name === 'in' && Array.isArray(arg.args[1]) && !arg.args[1].length) { - return { complete: false, reason: 'fewArgs' }; - } - const hasCorrectTypes = fnDefinition.signatures.some((def) => { - return arg.args.every((a, index) => { - return ( - (fnDefinition.name.endsWith('null') && def.params[index].type === 'any') || - def.params[index].type === extractTypeFromASTArg(a, references) - ); - }); - }); - if (!hasCorrectTypes) { - return { complete: false, reason: 'wrongTypes' }; - } - return { complete: true }; -} - function extractArgMeta( commandOrOption: ESQLCommand | ESQLCommandOption, node: ESQLSingleAstItem | undefined @@ -478,6 +411,8 @@ async function getSuggestionsWithinCommandExpression( getColumnsByType, (col: string) => Boolean(getColumnByName(col, references)), () => findNewVariable(anyVariables), + (expression: ESQLAstItem | undefined) => + getExpressionType(expression, references.fields, references.variables), getPreferences ); } else { @@ -629,7 +564,7 @@ async function getExpressionSuggestionsByType( // ... | ROW field NOT // ... | EVAL field NOT // there's not way to know the type of the field here, so suggest anything - suggestions.push(...getNextTokenForNot(command.name, option?.name, 'any')); + suggestions.push(...getSuggestionsAfterNot()); } else { // i.e. // ... | ROW @@ -717,13 +652,11 @@ async function getExpressionSuggestionsByType( const nodeArgType = extractTypeFromASTArg(nodeArg, references); if (isParameterType(nodeArgType)) { suggestions.push( - ...getBuiltinCompatibleFunctionDefinition( - command.name, - undefined, - nodeArgType, - undefined, - workoutBuiltinOptions(nodeArg, references) - ) + ...getOperatorSuggestions({ + command: command.name, + leftParamType: nodeArgType, + ignored: workoutBuiltinOptions(nodeArg, references).ignored, + }) ); } else { suggestions.push(getAssignmentDefinitionCompletitionItem()); @@ -754,9 +687,7 @@ async function getExpressionSuggestionsByType( )) ); if (['show', 'meta'].includes(command.name)) { - suggestions.push( - ...getBuiltinCompatibleFunctionDefinition(command.name, undefined, 'any') - ); + suggestions.push(...getOperatorSuggestions({ command: command.name })); } } } @@ -770,13 +701,11 @@ async function getExpressionSuggestionsByType( const [rightArg] = nodeArg.args[1] as [ESQLSingleAstItem]; const nodeArgType = extractTypeFromASTArg(rightArg, references); suggestions.push( - ...getBuiltinCompatibleFunctionDefinition( - command.name, - undefined, - isParameterType(nodeArgType) ? nodeArgType : 'any', - undefined, - workoutBuiltinOptions(rightArg, references) - ) + ...getOperatorSuggestions({ + command: command.name, + leftParamType: isParameterType(nodeArgType) ? nodeArgType : 'any', + ignored: workoutBuiltinOptions(nodeArg, references).ignored, + }) ); if (isNumericType(nodeArgType) && isLiteralItem(rightArg)) { // ... EVAL var = 1 @@ -808,18 +737,16 @@ async function getExpressionSuggestionsByType( )) ); } else { - const nodeArgType = extractTypeFromASTArg(nodeArg, references); suggestions.push( - ...(await getBuiltinFunctionNextArgument( - innerText, - command, - option, - argDef, - nodeArg, - (nodeArgType as string) || 'any', - references, - getFieldsByType - )) + ...(await getSuggestionsToRightOfOperatorExpression({ + queryText: innerText, + commandName: command.name, + optionName: option?.name, + rootOperator: nodeArg, + getExpressionType: (expression) => + getExpressionType(expression, references.fields, references.variables), + getColumnsByType: getFieldsByType, + })) ); if (nodeArg.args.some(isTimeIntervalItem)) { const lastFnArg = nodeArg.args[nodeArg.args.length - 1]; @@ -859,7 +786,7 @@ async function getExpressionSuggestionsByType( // i.e. // ... | WHERE field NOT // there's not way to know the type of the field here, so suggest anything - suggestions.push(...getNextTokenForNot(command.name, option?.name, 'any')); + suggestions.push(...getSuggestionsAfterNot()); } else { // ... | // In this case start suggesting something not strictly based on type @@ -906,28 +833,25 @@ async function getExpressionSuggestionsByType( ); } else { suggestions.push( - ...(await getBuiltinFunctionNextArgument( - innerText, - command, - option, - argDef, - nodeArg, - nodeArgType as string, - references, - getFieldsByType - )) + ...(await getSuggestionsToRightOfOperatorExpression({ + queryText: innerText, + commandName: command.name, + optionName: option?.name, + rootOperator: nodeArg, + getExpressionType: (expression) => + getExpressionType(expression, references.fields, references.variables), + getColumnsByType: getFieldsByType, + })) ); } } else if (isParameterType(nodeArgType)) { // i.e. ... | field suggestions.push( - ...getBuiltinCompatibleFunctionDefinition( - command.name, - undefined, - nodeArgType, - undefined, - workoutBuiltinOptions(nodeArg, references) - ) + ...getOperatorSuggestions({ + command: command.name, + leftParamType: nodeArgType, + ignored: workoutBuiltinOptions(nodeArg, references).ignored, + }) ); } } @@ -1080,110 +1004,6 @@ async function getExpressionSuggestionsByType( return uniqBy(suggestions, (suggestion) => suggestion.text); } -async function getBuiltinFunctionNextArgument( - queryText: string, - command: ESQLCommand, - option: ESQLCommandOption | undefined, - argDef: { type: string }, - nodeArg: ESQLFunction, - nodeArgType: string, - references: Pick, - getFieldsByType: GetColumnsByTypeFn -) { - const suggestions = []; - const isFnComplete = isFunctionArgComplete(nodeArg, references); - - if (isFnComplete.complete) { - // i.e. ... | field > 0 - // i.e. ... | field + otherN - suggestions.push( - ...getBuiltinCompatibleFunctionDefinition( - command.name, - option?.name, - isParameterType(nodeArgType) ? nodeArgType : 'any', - undefined, - workoutBuiltinOptions(nodeArg, references) - ) - ); - } else { - // i.e. ... | field >= - // i.e. ... | field + - // i.e. ... | field and - - // Because it's an incomplete function, need to extract the type of the current argument - // and suggest the next argument based on types - - // pick the last arg and check its type to verify whether is incomplete for the given function - const cleanedArgs = removeMarkerArgFromArgsList(nodeArg)!.args; - const nestedType = extractTypeFromASTArg(nodeArg.args[cleanedArgs.length - 1], references); - - if (isFnComplete.reason === 'fewArgs') { - const fnDef = getFunctionDefinition(nodeArg.name); - if ( - fnDef?.signatures.every(({ params }) => - params.some(({ type }) => isArrayType(type as string)) - ) - ) { - suggestions.push(listCompleteItem); - } else { - const finalType = nestedType || nodeArgType || 'any'; - const supportedTypes = getSupportedTypesForBinaryOperators(fnDef, finalType as string); - - suggestions.push( - ...(await getFieldsOrFunctionsSuggestions( - // this is a special case with AND/OR - // expression AND/OR - // technically another boolean value should be suggested, but it is a better experience - // to actually suggest a wider set of fields/functions - finalType === 'boolean' && getFunctionDefinition(nodeArg.name)?.type === 'builtin' - ? ['any'] - : (supportedTypes as string[]), - command.name, - option?.name, - getFieldsByType, - { - functions: true, - fields: true, - variables: references.variables, - } - )) - ); - } - } - if (isFnComplete.reason === 'wrongTypes') { - if (nestedType) { - // suggest something to complete the builtin function - if ( - nestedType !== argDef.type && - isParameterType(nestedType) && - isReturnType(argDef.type) - ) { - suggestions.push( - ...getBuiltinCompatibleFunctionDefinition( - command.name, - undefined, - nestedType, - [argDef.type], - workoutBuiltinOptions(nodeArg, references) - ) - ); - } - } - } - } - return suggestions.map((s) => { - const overlap = getOverlapRange(queryText, s.text); - const offset = overlap.start === overlap.end ? 1 : 0; - return { - ...s, - rangeToReplace: { - start: overlap.start + offset, - end: overlap.end + offset, - }, - }; - }); -} - const addCommaIf = (condition: boolean, text: string) => (condition ? `${text},` : text); async function getFunctionArgsSuggestions( @@ -1615,10 +1435,7 @@ async function getOptionArgsSuggestions( // ... | ENRICH ... WITH a // effectively only assign will apper suggestions.push( - ...pushItUpInTheList( - getBuiltinCompatibleFunctionDefinition(command.name, undefined, 'any'), - true - ) + ...pushItUpInTheList(getOperatorSuggestions({ command: command.name }), true) ); } diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/commands/stats/index.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/commands/stats/index.ts index 46a37d36eacc9..ac70ac1a1a5ca 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/commands/stats/index.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/commands/stats/index.ts @@ -7,7 +7,8 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { ESQLCommand } from '@kbn/esql-ast'; +import type { ESQLAstItem, ESQLCommand } from '@kbn/esql-ast'; +import { SupportedDataType } from '../../../definitions/types'; import type { GetColumnsByTypeFn, SuggestionRawDefinition } from '../../types'; import { TRIGGER_SUGGESTION_COMMAND, @@ -24,6 +25,7 @@ export async function suggest( getColumnsByType: GetColumnsByTypeFn, _columnExists: (column: string) => boolean, getSuggestedVariableName: () => string, + _getExpressionType: (expression: ESQLAstItem | undefined) => SupportedDataType | 'unknown', getPreferences?: () => Promise<{ histogramBarTarget: number } | undefined> ): Promise { const pos = getPosition(innerText, command); diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/commands/where/index.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/commands/where/index.ts new file mode 100644 index 0000000000000..dc2ab341e961e --- /dev/null +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/commands/where/index.ts @@ -0,0 +1,183 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { + Walker, + type ESQLAstItem, + type ESQLCommand, + type ESQLSingleAstItem, + type ESQLFunction, +} from '@kbn/esql-ast'; +import { logicalOperators } from '../../../definitions/builtin'; +import { isParameterType, type SupportedDataType } from '../../../definitions/types'; +import { isFunctionItem } from '../../../shared/helpers'; +import type { GetColumnsByTypeFn, SuggestionRawDefinition } from '../../types'; +import { + getFunctionSuggestions, + getOperatorSuggestion, + getOperatorSuggestions, + getSuggestionsAfterNot, +} from '../../factories'; +import { getOverlapRange, getSuggestionsToRightOfOperatorExpression } from '../../helper'; +import { getPosition } from './util'; +import { pipeCompleteItem } from '../../complete_items'; + +export async function suggest( + innerText: string, + command: ESQLCommand<'where'>, + getColumnsByType: GetColumnsByTypeFn, + _columnExists: (column: string) => boolean, + _getSuggestedVariableName: () => string, + getExpressionType: (expression: ESQLAstItem | undefined) => SupportedDataType | 'unknown', + _getPreferences?: () => Promise<{ histogramBarTarget: number } | undefined> +): Promise { + const suggestions: SuggestionRawDefinition[] = []; + + /** + * The logic for WHERE suggestions is basically the logic for expression suggestions. + * I assume we will eventually extract much of this to be a shared function among WHERE and EVAL + * and anywhere else the user can enter a generic expression. + */ + const expressionRoot = command.args[0] as ESQLSingleAstItem | undefined; + + switch (getPosition(innerText, command)) { + /** + * After a column name + */ + case 'after_column': + const columnType = getExpressionType(expressionRoot); + + if (!isParameterType(columnType)) { + break; + } + + suggestions.push( + ...getOperatorSuggestions({ + command: 'where', + leftParamType: columnType, + // no assignments allowed in WHERE + ignored: ['='], + }) + ); + break; + + /** + * After a complete (non-operator) function call + */ + case 'after_function': + const returnType = getExpressionType(expressionRoot); + + if (!isParameterType(returnType)) { + break; + } + + suggestions.push( + ...getOperatorSuggestions({ + command: 'where', + leftParamType: returnType, + ignored: ['='], + }) + ); + + break; + + /** + * After a NOT keyword + * + * the NOT function is a special operator that can be used in different ways, + * and not all these are mapped within the AST data structure: in particular + * NOT + * is an incomplete statement and it results in a missing AST node, so we need to detect + * from the query string itself + * + * (this comment was copied but seems to still apply) + */ + case 'after_not': + if (expressionRoot && isFunctionItem(expressionRoot) && expressionRoot.name === 'not') { + suggestions.push( + ...getFunctionSuggestions({ command: 'where', returnTypes: ['boolean'] }), + ...(await getColumnsByType('boolean', [], { advanceCursor: true, openSuggestions: true })) + ); + } else { + suggestions.push(...getSuggestionsAfterNot()); + } + + break; + + /** + * After an operator (e.g. AND, OR, IS NULL, +, etc.) + */ + case 'after_operator': + if (!expressionRoot) { + break; + } + + if (!isFunctionItem(expressionRoot) || expressionRoot.subtype === 'variadic-call') { + // this is already guaranteed in the getPosition function, but TypeScript doesn't know + break; + } + + let rightmostOperator = expressionRoot; + // get rightmost function + const walker = new Walker({ + visitFunction: (fn: ESQLFunction) => { + if (fn.location.min > rightmostOperator.location.min && fn.subtype !== 'variadic-call') + rightmostOperator = fn; + }, + }); + walker.walkFunction(expressionRoot); + + // See https://github.com/elastic/kibana/issues/199401 for an explanation of + // why this check has to be so convoluted + if (rightmostOperator.text.toLowerCase().trim().endsWith('null')) { + suggestions.push(...logicalOperators.map(getOperatorSuggestion)); + break; + } + + suggestions.push( + ...(await getSuggestionsToRightOfOperatorExpression({ + queryText: innerText, + commandName: 'where', + rootOperator: rightmostOperator, + preferredExpressionType: 'boolean', + getExpressionType, + getColumnsByType, + })) + ); + + break; + + case 'empty_expression': + const columnSuggestions = await getColumnsByType('any', [], { + advanceCursor: true, + openSuggestions: true, + }); + suggestions.push(...columnSuggestions, ...getFunctionSuggestions({ command: 'where' })); + + break; + } + + // Is this a complete expression of the right type? + // If so, we can call it done and suggest a pipe + if (getExpressionType(expressionRoot) === 'boolean') { + suggestions.push(pipeCompleteItem); + } + + return suggestions.map((s) => { + const overlap = getOverlapRange(innerText, s.text); + const offset = overlap.start === overlap.end ? 1 : 0; + return { + ...s, + rangeToReplace: { + start: overlap.start + offset, + end: overlap.end + offset, + }, + }; + }); +} diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/commands/where/util.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/commands/where/util.ts new file mode 100644 index 0000000000000..c969e7e37461f --- /dev/null +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/commands/where/util.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { ESQLCommand, ESQLSingleAstItem } from '@kbn/esql-ast'; +import { isColumnItem, isFunctionItem } from '../../../shared/helpers'; + +export type CaretPosition = + | 'after_column' + | 'after_function' + | 'after_not' + | 'after_operator' + | 'empty_expression'; + +export const getPosition = (innerText: string, command: ESQLCommand): CaretPosition => { + const expressionRoot = command.args[0] as ESQLSingleAstItem | undefined; + + const endsWithNot = / not$/i.test(innerText.trimEnd()); + if ( + endsWithNot && + !( + expressionRoot && + isFunctionItem(expressionRoot) && + // See https://github.com/elastic/kibana/issues/199401 + // for more information on this check... + ['is null', 'is not null'].includes(expressionRoot.name) + ) + ) { + return 'after_not'; + } + + if (expressionRoot) { + if (isColumnItem(expressionRoot)) { + return 'after_column'; + } + + if (isFunctionItem(expressionRoot) && expressionRoot.subtype === 'variadic-call') { + return 'after_function'; + } + + if (isFunctionItem(expressionRoot) && expressionRoot.subtype !== 'variadic-call') { + return 'after_operator'; + } + } + + return 'empty_expression'; +}; diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/complete_items.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/complete_items.ts index b115e30c47efe..0c448d4814f96 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/complete_items.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/complete_items.ts @@ -11,81 +11,17 @@ import { i18n } from '@kbn/i18n'; import type { ItemKind, SuggestionRawDefinition } from './types'; import { builtinFunctions } from '../definitions/builtin'; import { - getSuggestionBuiltinDefinition, + getOperatorSuggestion, getSuggestionCommandDefinition, TRIGGER_SUGGESTION_COMMAND, - buildConstantsDefinitions, } from './factories'; -import { CommandDefinition, FunctionParameterType, FunctionReturnType } from '../definitions/types'; -import { getTestFunctions } from '../shared/test_functions'; +import { CommandDefinition } from '../definitions/types'; export function getAssignmentDefinitionCompletitionItem() { const assignFn = builtinFunctions.find(({ name }) => name === '=')!; - return getSuggestionBuiltinDefinition(assignFn); + return getOperatorSuggestion(assignFn); } -export const getNextTokenForNot = ( - command: string, - option: string | undefined, - argType: string -): SuggestionRawDefinition[] => { - const compatibleFunctions = builtinFunctions.filter( - ({ name, supportedCommands, supportedOptions, ignoreAsSuggestion }) => - !ignoreAsSuggestion && - !/not_/.test(name) && - (option ? supportedOptions?.includes(option) : supportedCommands.includes(command)) - ); - if (argType === 'string' || argType === 'any') { - // suggest IS, LIKE, RLIKE and TRUE/FALSE - return compatibleFunctions - .filter(({ name }) => name === 'like' || name === 'rlike' || name === 'in') - .map(getSuggestionBuiltinDefinition); - } - if (argType === 'boolean') { - // suggest IS, NOT and TRUE/FALSE - return [ - ...compatibleFunctions - .filter(({ name }) => name === 'in') - .map(getSuggestionBuiltinDefinition), - ...buildConstantsDefinitions(['true', 'false']), - ]; - } - return []; -}; - -export const getBuiltinCompatibleFunctionDefinition = ( - command: string, - option: string | undefined, - argType: FunctionParameterType, - returnTypes?: FunctionReturnType[], - { skipAssign, commandsToInclude }: { skipAssign?: boolean; commandsToInclude?: string[] } = {} -): SuggestionRawDefinition[] => { - const compatibleFunctions = [...builtinFunctions, ...getTestFunctions()].filter( - ({ name, supportedCommands, supportedOptions, signatures, ignoreAsSuggestion }) => - (command === 'where' && commandsToInclude ? commandsToInclude.indexOf(name) > -1 : true) && - !ignoreAsSuggestion && - (!skipAssign || name !== '=') && - (option ? supportedOptions?.includes(option) : supportedCommands.includes(command)) && - signatures.some( - ({ params }) => - !params.length || params.some((pArg) => pArg.type === argType || pArg.type === 'any') - ) - ); - if (!returnTypes) { - return compatibleFunctions.map(getSuggestionBuiltinDefinition); - } - return compatibleFunctions - .filter((mathDefinition) => - mathDefinition.signatures.some( - (signature) => - returnTypes[0] === 'unknown' || - returnTypes[0] === 'any' || - returnTypes.includes(signature.returnType) - ) - ) - .map(getSuggestionBuiltinDefinition); -}; - export const getCommandAutocompleteDefinitions = ( commands: Array> ): SuggestionRawDefinition[] => diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts index 9b7e2b0bf71a5..88560f6d2f4c5 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts @@ -20,6 +20,7 @@ import { CommandDefinition, CommandOptionsDefinition, CommandModeDefinition, + FunctionParameterType, } from '../definitions/types'; import { shouldBeQuotedSource, getCommandDefinition, shouldBeQuotedText } from '../shared/helpers'; import { buildDocumentation, buildFunctionDocumentation } from './documentation_util'; @@ -27,6 +28,7 @@ import { DOUBLE_BACKTICK, SINGLE_TICK_REGEX } from '../shared/constants'; import { ESQLRealField } from '../validation/types'; import { isNumericType } from '../shared/esql_types'; import { getTestFunctions } from '../shared/test_functions'; +import { builtinFunctions } from '../definitions/builtin'; const allFunctions = memoize( () => @@ -75,7 +77,7 @@ export function getFunctionSuggestion(fn: FunctionDefinition): SuggestionRawDefi }; } -export function getSuggestionBuiltinDefinition(fn: FunctionDefinition): SuggestionRawDefinition { +export function getOperatorSuggestion(fn: FunctionDefinition): SuggestionRawDefinition { const hasArgs = fn.signatures.some(({ params }) => params.length > 1); return { label: fn.name.toUpperCase(), @@ -91,21 +93,22 @@ export function getSuggestionBuiltinDefinition(fn: FunctionDefinition): Suggesti }; } -/** - * Builds suggestions for functions based on the provided predicates. - * - * @param predicates a set of conditions that must be met for a function to be included in the suggestions - * @returns - */ -export const getFunctionSuggestions = (predicates?: { +interface FunctionFilterPredicates { command?: string; option?: string | undefined; returnTypes?: string[]; ignored?: string[]; -}): SuggestionRawDefinition[] => { - const functions = allFunctions(); - const { command, option, returnTypes, ignored = [] } = predicates ?? {}; - const filteredFunctions: FunctionDefinition[] = functions.filter( +} + +export const filterFunctionDefinitions = ( + functions: FunctionDefinition[], + predicates: FunctionFilterPredicates | undefined +): FunctionDefinition[] => { + if (!predicates) { + return functions; + } + const { command, option, returnTypes, ignored = [] } = predicates; + return functions.filter( ({ name, supportedCommands, supportedOptions, ignoreAsSuggestion, signatures }) => { if (ignoreAsSuggestion) { return false; @@ -130,8 +133,53 @@ export const getFunctionSuggestions = (predicates?: { return true; } ); +}; + +/** + * Builds suggestions for functions based on the provided predicates. + * + * @param predicates a set of conditions that must be met for a function to be included in the suggestions + * @returns + */ +export const getFunctionSuggestions = ( + predicates?: FunctionFilterPredicates +): SuggestionRawDefinition[] => { + return filterFunctionDefinitions(allFunctions(), predicates).map(getFunctionSuggestion); +}; + +/** + * Builds suggestions for operators based on the provided predicates. + * + * @param predicates a set of conditions that must be met for an operator to be included in the suggestions + * @returns + */ +export const getOperatorSuggestions = ( + predicates?: FunctionFilterPredicates & { leftParamType?: FunctionParameterType } +): SuggestionRawDefinition[] => { + const filteredDefinitions = filterFunctionDefinitions( + getTestFunctions().length ? [...builtinFunctions, ...getTestFunctions()] : builtinFunctions, + predicates + ); + + // make sure the operator has at least one signature that matches + // the type of the existing left argument if provided (e.g. "doubleField ") + return ( + predicates?.leftParamType + ? filteredDefinitions.filter(({ signatures }) => + signatures.some( + ({ params }) => + !params.length || + params.some((pArg) => pArg.type === predicates?.leftParamType || pArg.type === 'any') + ) + ) + : filteredDefinitions + ).map(getOperatorSuggestion); +}; - return filteredFunctions.map(getFunctionSuggestion); +export const getSuggestionsAfterNot = (): SuggestionRawDefinition[] => { + return builtinFunctions + .filter(({ name }) => name === 'like' || name === 'rlike' || name === 'in') + .map(getOperatorSuggestion); }; export function getSuggestionCommandDefinition( diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/helper.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/helper.test.ts new file mode 100644 index 0000000000000..c4133592c425d --- /dev/null +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/helper.test.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { getOverlapRange } from './helper'; + +describe('getOverlapRange', () => { + it('should return the overlap range', () => { + expect(getOverlapRange('IS N', 'IS NOT NULL')).toEqual({ start: 1, end: 5 }); + expect(getOverlapRange('I', 'IS NOT NULL')).toEqual({ start: 1, end: 2 }); + }); + + it('full query', () => { + expect(getOverlapRange('FROM index | WHERE field IS N', 'IS NOT NULL')).toEqual({ + start: 26, + end: 30, + }); + }); +}); diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/helper.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/helper.ts index 3ccddfc5ff241..67ea324a1a69a 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/helper.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/helper.ts @@ -15,15 +15,18 @@ import type { ESQLSource, } from '@kbn/esql-ast'; import { uniqBy } from 'lodash'; -import type { - FunctionDefinition, - FunctionReturnType, - SupportedDataType, +import { + isParameterType, + type FunctionDefinition, + type FunctionReturnType, + type SupportedDataType, + isReturnType, } from '../definitions/types'; import { findFinalWord, getColumnForASTNode, getFunctionDefinition, + isArrayType, isAssignment, isColumnItem, isFunctionItem, @@ -39,9 +42,12 @@ import { getFunctionSuggestions, getCompatibleLiterals, getDateLiterals, + getOperatorSuggestions, } from './factories'; import { EDITOR_MARKER } from '../shared/constants'; import { ESQLRealField, ESQLVariable, ReferenceMaps } from '../validation/types'; +import { listCompleteItem } from './complete_items'; +import { removeMarkerArgFromArgsList } from '../shared/context'; function extractFunctionArgs(args: ESQLAstItem[]): ESQLFunction[] { return args.flatMap((arg) => (isAssignment(arg) ? arg.args[1] : arg)).filter(isFunctionItem); @@ -208,9 +214,10 @@ export function getOverlapRange( } } + // add one since Monaco columns are 1-based return { - start: Math.min(query.length - overlapLength + 1, query.length), - end: query.length, + start: query.length - overlapLength + 1, + end: query.length + 1, }; } @@ -445,6 +452,7 @@ export function pushItUpInTheList(suggestions: SuggestionRawDefinition[], should })); } +/** @deprecated — use getExpressionType instead (packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts) */ export function extractTypeFromASTArg( arg: ESQLAstItem, references: Pick @@ -479,3 +487,182 @@ export function extractTypeFromASTArg( } } } + +// @TODO: refactor this to be shared with validation +export function checkFunctionInvocationComplete( + func: ESQLFunction, + getExpressionType: (expression: ESQLAstItem) => SupportedDataType | 'unknown' +): { + complete: boolean; + reason?: 'tooFewArgs' | 'wrongTypes'; +} { + const fnDefinition = getFunctionDefinition(func.name); + if (!fnDefinition) { + return { complete: false }; + } + const cleanedArgs = removeMarkerArgFromArgsList(func)!.args; + const argLengthCheck = fnDefinition.signatures.some((def) => { + if (def.minParams && cleanedArgs.length >= def.minParams) { + return true; + } + if (cleanedArgs.length === def.params.length) { + return true; + } + return cleanedArgs.length >= def.params.filter(({ optional }) => !optional).length; + }); + if (!argLengthCheck) { + return { complete: false, reason: 'tooFewArgs' }; + } + if (fnDefinition.name === 'in' && Array.isArray(func.args[1]) && !func.args[1].length) { + return { complete: false, reason: 'tooFewArgs' }; + } + const hasCorrectTypes = fnDefinition.signatures.some((def) => { + return func.args.every((a, index) => { + return ( + (fnDefinition.name.endsWith('null') && def.params[index].type === 'any') || + def.params[index].type === getExpressionType(a) + ); + }); + }); + if (!hasCorrectTypes) { + return { complete: false, reason: 'wrongTypes' }; + } + return { complete: true }; +} + +/** + * This function is used to + * - suggest the next argument for an incomplete or incorrect binary operator expression (e.g. field > ) + * - suggest an operator to the right of a complete binary operator expression (e.g. field > 0 ) + * - suggest an operator to the right of a complete unary operator (e.g. field IS NOT NULL ) + * + * TODO — is this function doing too much? + */ +export async function getSuggestionsToRightOfOperatorExpression({ + queryText, + commandName, + optionName, + rootOperator: operator, + preferredExpressionType, + getExpressionType, + getColumnsByType, +}: { + queryText: string; + commandName: string; + optionName?: string; + rootOperator: ESQLFunction; + preferredExpressionType?: SupportedDataType; + getExpressionType: (expression: ESQLAstItem) => SupportedDataType | 'unknown'; + getColumnsByType: GetColumnsByTypeFn; +}) { + const suggestions = []; + const isFnComplete = checkFunctionInvocationComplete(operator, getExpressionType); + if (isFnComplete.complete) { + // i.e. ... | field > 0 + // i.e. ... | field + otherN + const operatorReturnType = getExpressionType(operator); + suggestions.push( + ...getOperatorSuggestions({ + command: commandName, + option: optionName, + // here we use the operator return type because we're suggesting operators that could + // accept the result of the existing operator as a left operand + leftParamType: + operatorReturnType === 'unknown' || operatorReturnType === 'unsupported' + ? 'any' + : operatorReturnType, + ignored: ['='], + }) + ); + } else { + // i.e. ... | field >= + // i.e. ... | field + + // i.e. ... | field and + + // Because it's an incomplete function, need to extract the type of the current argument + // and suggest the next argument based on types + + // pick the last arg and check its type to verify whether is incomplete for the given function + const cleanedArgs = removeMarkerArgFromArgsList(operator)!.args; + const leftArgType = getExpressionType(operator.args[cleanedArgs.length - 1]); + + if (isFnComplete.reason === 'tooFewArgs') { + const fnDef = getFunctionDefinition(operator.name); + if ( + fnDef?.signatures.every(({ params }) => + params.some(({ type }) => isArrayType(type as string)) + ) + ) { + suggestions.push(listCompleteItem); + } else { + const finalType = leftArgType || leftArgType || 'any'; + const supportedTypes = getSupportedTypesForBinaryOperators(fnDef, finalType as string); + + // this is a special case with AND/OR + // expression AND/OR + // technically another boolean value should be suggested, but it is a better experience + // to actually suggest a wider set of fields/functions + const typeToUse = + finalType === 'boolean' && getFunctionDefinition(operator.name)?.type === 'builtin' + ? ['any'] + : (supportedTypes as string[]); + + // TODO replace with fields callback + function suggestions + suggestions.push( + ...(await getFieldsOrFunctionsSuggestions( + typeToUse, + commandName, + optionName, + getColumnsByType, + { + functions: true, + fields: true, + } + )) + ); + } + } + + /** + * If the caller has supplied a preferred expression type, we can suggest operators that + * would move the user toward that expression type. + * + * e.g. if we have a preferred type of boolean and we have `timestamp > "2002" AND doubleField` + * this is an incorrect signature for AND because the left side is boolean and the right side is double + * + * Knowing that we prefer boolean expressions, we suggest operators that would accept doubleField as a left operand + * and also return a boolean value. + * + * I believe this is only used in WHERE and probably bears some rethinking. + */ + if (isFnComplete.reason === 'wrongTypes') { + if (leftArgType && preferredExpressionType) { + // suggest something to complete the operator + if ( + leftArgType !== preferredExpressionType && + isParameterType(leftArgType) && + isReturnType(preferredExpressionType) + ) { + suggestions.push( + ...getOperatorSuggestions({ + command: commandName, + leftParamType: leftArgType, + returnTypes: [preferredExpressionType], + }) + ); + } + } + } + } + return suggestions.map((s) => { + const overlap = getOverlapRange(queryText, s.text); + const offset = overlap.start === overlap.end ? 1 : 0; + return { + ...s, + rangeToReplace: { + start: overlap.start + offset, + end: overlap.end + offset, + }, + }; + }); +} diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/builtin.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/builtin.ts index e71ed32e4c79d..3f5040efbcb10 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/builtin.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/builtin.ts @@ -528,7 +528,7 @@ const inFunctions: FunctionDefinition[] = [ ], })); -const logicFunctions: FunctionDefinition[] = [ +export const logicalOperators: FunctionDefinition[] = [ { name: 'and', description: i18n.translate('kbn-esql-validation-autocomplete.esql.definition.andDoc', { @@ -649,7 +649,7 @@ export const builtinFunctions: FunctionDefinition[] = [ ...comparisonFunctions, ...likeFunctions, ...inFunctions, - ...logicFunctions, + ...logicalOperators, ...nullFunctions, ...otherDefinitions, ]; diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/commands.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/commands.ts index dc64c664a14fd..950dac5e2d50b 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/commands.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/commands.ts @@ -37,6 +37,7 @@ import { suggest as suggestForSort } from '../autocomplete/commands/sort'; import { suggest as suggestForKeep } from '../autocomplete/commands/keep'; import { suggest as suggestForDrop } from '../autocomplete/commands/drop'; import { suggest as suggestForStats } from '../autocomplete/commands/stats'; +import { suggest as suggestForWhere } from '../autocomplete/commands/where'; const statsValidator = (command: ESQLCommand) => { const messages: ESQLMessage[] = []; @@ -411,6 +412,7 @@ export const commandDefinitions: Array> = [ }, options: [], modes: [], + suggest: suggestForWhere, }, { name: 'dissect', diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/generated/scalar_functions.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/generated/scalar_functions.ts index 4b0ea8ee564ed..739a12095ac23 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/generated/scalar_functions.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/generated/scalar_functions.ts @@ -1294,7 +1294,7 @@ const dateDiffDefinition: FunctionDefinition = { validate: undefined, examples: [ 'ROW date1 = TO_DATETIME("2023-12-02T11:00:00.000Z"), date2 = TO_DATETIME("2023-12-02T11:00:00.001Z")\n| EVAL dd_ms = DATE_DIFF("microseconds", date1, date2)', - 'ROW end_23="2023-12-31T23:59:59.999Z"::DATETIME,\n start_24="2024-01-01T00:00:00.000Z"::DATETIME,\n end_24="2024-12-31T23:59:59.999"::DATETIME\n| EVAL end23_to_start24=DATE_DIFF("year", end_23, start_24)\n| EVAL end23_to_end24=DATE_DIFF("year", end_23, end_24)\n| EVAL start_to_end_24=DATE_DIFF("year", start_24, end_24)', + 'ROW end_23=TO_DATETIME("2023-12-31T23:59:59.999Z"),\n start_24=TO_DATETIME("2024-01-01T00:00:00.000Z"),\n end_24=TO_DATETIME("2024-12-31T23:59:59.999")\n| EVAL end23_to_start24=DATE_DIFF("year", end_23, start_24)\n| EVAL end23_to_end24=DATE_DIFF("year", end_23, end_24)\n| EVAL start_to_end_24=DATE_DIFF("year", start_24, end_24)', ], }; diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/types.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/types.ts index ff461683d8e76..a86811f535f8b 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/types.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/types.ts @@ -7,7 +7,13 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { ESQLCommand, ESQLCommandOption, ESQLFunction, ESQLMessage } from '@kbn/esql-ast'; +import type { + ESQLAstItem, + ESQLCommand, + ESQLCommandOption, + ESQLFunction, + ESQLMessage, +} from '@kbn/esql-ast'; import { GetColumnsByTypeFn, SuggestionRawDefinition } from '../autocomplete/types'; /** @@ -173,6 +179,7 @@ export interface CommandBaseDefinition { getColumnsByType: GetColumnsByTypeFn, columnExists: (column: string) => boolean, getSuggestedVariableName: () => string, + getExpressionType: (expression: ESQLAstItem | undefined) => SupportedDataType | 'unknown', getPreferences?: () => Promise<{ histogramBarTarget: number } | undefined> ) => Promise; /** @deprecated this property will disappear in the future */ diff --git a/packages/kbn-esql-validation-autocomplete/src/shared/helpers.test.ts b/packages/kbn-esql-validation-autocomplete/src/shared/helpers.test.ts index 97f35e1c66722..f880143108ce6 100644 --- a/packages/kbn-esql-validation-autocomplete/src/shared/helpers.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/shared/helpers.test.ts @@ -57,6 +57,10 @@ describe('getExpressionType', () => { return root.commands[1].args[0]; }; + test('empty expression', () => { + expect(getExpressionType(getASTForExpression(''))).toBe('unknown'); + }); + describe('literal expressions', () => { const cases: Array<{ expression: string; expectedType: SupportedDataType }> = [ { @@ -289,6 +293,19 @@ describe('getExpressionType', () => { it('supports COUNT(*)', () => { expect(getExpressionType(getASTForExpression('COUNT(*)'))).toBe('long'); }); + + it('accounts for the "any" parameter type', () => { + setTestFunctions([ + { + type: 'eval', + name: 'test', + description: 'Test function', + supportedCommands: ['eval'], + signatures: [{ params: [{ name: 'arg', type: 'any' }], returnType: 'keyword' }], + }, + ]); + expect(getExpressionType(getASTForExpression('test(1)'))).toBe('keyword'); + }); }); describe('lists', () => { diff --git a/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts b/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts index e86cb4f6ae8f2..2c864a487026c 100644 --- a/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts +++ b/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts @@ -630,6 +630,10 @@ export function findPreviousWord(text: string) { return words[words.length - 2]; } +export function endsInWhitespace(text: string) { + return /\s$/.test(text); +} + /** * Returns the word at the end of the text if there is one. * @param text @@ -805,10 +809,14 @@ export function getParamAtPosition( * Determines the type of the expression */ export function getExpressionType( - root: ESQLAstItem, + root: ESQLAstItem | undefined, fields?: Map, variables?: Map ): SupportedDataType | 'unknown' { + if (!root) { + return 'unknown'; + } + if (!isSingleItem(root)) { if (root.length === 0) { return 'unknown'; @@ -905,7 +913,8 @@ export function getExpressionType( const param = getParamAtPosition(signature, i); return ( param && - (param.type === argType || + (param.type === 'any' || + param.type === argType || (argType === 'keyword' && ['date', 'date_period'].includes(param.type))) ); }); diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.inlinestats.ts b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.inlinestats.ts index c1c7340da78f8..a8fa55128251c 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.inlinestats.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.inlinestats.ts @@ -126,10 +126,10 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => { const { expectErrors } = await setup(); await expectErrors('from a_index | INLINESTATS doubleField=', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); await expectErrors('from a_index | INLINESTATS doubleField=5 by ', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); await expectErrors('from a_index | INLINESTATS avg(doubleField) by wrongField', [ 'Unknown column [wrongField]', @@ -186,7 +186,7 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => { const { expectErrors } = await setup(); await expectErrors('from a_index | INLINESTATS by ', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); }); diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.metrics.ts b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.metrics.ts index 79dc4e21fe9d7..5384fdc136b4e 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.metrics.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.metrics.ts @@ -117,11 +117,11 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => { await expectErrors('metrics a_index doubleField=', [ expect.any(String), - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); await expectErrors('metrics a_index doubleField=5 by ', [ expect.any(String), - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); }); diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.stats.ts b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.stats.ts index c499f2477e146..c250166b88968 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.stats.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.stats.ts @@ -117,10 +117,10 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => { const { expectErrors } = await setup(); await expectErrors('from a_index | stats doubleField=', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); await expectErrors('from a_index | stats doubleField=5 by ', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); await expectErrors('from a_index | stats avg(doubleField) by wrongField', [ 'Unknown column [wrongField]', @@ -176,7 +176,7 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => { const { expectErrors } = await setup(); await expectErrors('from a_index | stats by ', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); }); diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json index d799b30d5f20a..4767031d06813 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json +++ b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json @@ -223,7 +223,7 @@ { "query": "row", "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, @@ -331,7 +331,7 @@ { "query": "row var = 1 in (", "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", "Error: [in] function expects exactly 2 arguments, got 1." ], "warning": [] @@ -2645,7 +2645,7 @@ { "query": "from a_index | dissect", "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, @@ -2739,7 +2739,7 @@ { "query": "from a_index | grok", "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, @@ -3540,21 +3540,21 @@ { "query": "from a_index | where *+ doubleField", "error": [ - "SyntaxError: extraneous input '*' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: extraneous input '*' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, { "query": "from a_index | where /+ doubleField", "error": [ - "SyntaxError: extraneous input '/' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: extraneous input '/' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, { "query": "from a_index | where %+ doubleField", "error": [ - "SyntaxError: extraneous input '%' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: extraneous input '%' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, @@ -4441,7 +4441,7 @@ { "query": "from a_index | eval ", "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, @@ -4484,7 +4484,7 @@ { "query": "from a_index | eval a=b, ", "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", "Unknown column [b]" ], "warning": [] @@ -4511,7 +4511,7 @@ { "query": "from a_index | eval a=round(doubleField), ", "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, @@ -5617,21 +5617,21 @@ { "query": "from a_index | eval *+ doubleField", "error": [ - "SyntaxError: extraneous input '*' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: extraneous input '*' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, { "query": "from a_index | eval /+ doubleField", "error": [ - "SyntaxError: extraneous input '/' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: extraneous input '/' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, { "query": "from a_index | eval %+ doubleField", "error": [ - "SyntaxError: extraneous input '%' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: extraneous input '%' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, @@ -6955,7 +6955,7 @@ { "query": "from a_index | eval not", "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", "Error: [not] function expects exactly one argument, got 0." ], "warning": [] @@ -6963,7 +6963,7 @@ { "query": "from a_index | eval in", "error": [ - "SyntaxError: mismatched input 'in' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: mismatched input 'in' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, @@ -9012,7 +9012,7 @@ { "query": "from a_index | sort ", "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, @@ -9031,7 +9031,7 @@ { "query": "from a_index | sort doubleField, ", "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts index c29273e4c3f35..04f53e6de12bb 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts @@ -306,7 +306,7 @@ describe('validation logic', () => { describe('row', () => { testErrorsAndWarnings('row', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); testErrorsAndWarnings('row missing_column', ['Unknown column [missing_column]']); testErrorsAndWarnings('row fn()', ['Unknown function [fn]']); @@ -335,7 +335,7 @@ describe('validation logic', () => { "SyntaxError: mismatched input '' expecting '('", ]); testErrorsAndWarnings('row var = 1 in (', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", 'Error: [in] function expects exactly 2 arguments, got 1.', ]); testErrorsAndWarnings('row var = 1 not in ', [ @@ -690,7 +690,7 @@ describe('validation logic', () => { describe('dissect', () => { testErrorsAndWarnings('from a_index | dissect', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); testErrorsAndWarnings('from a_index | dissect textField', [ "SyntaxError: missing QUOTED_STRING at ''", @@ -740,7 +740,7 @@ describe('validation logic', () => { describe('grok', () => { testErrorsAndWarnings('from a_index | grok', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); testErrorsAndWarnings('from a_index | grok textField', [ "SyntaxError: missing QUOTED_STRING at ''", @@ -824,7 +824,7 @@ describe('validation logic', () => { } for (const wrongOp of ['*', '/', '%']) { testErrorsAndWarnings(`from a_index | where ${wrongOp}+ doubleField`, [ - `SyntaxError: extraneous input '${wrongOp}' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}`, + `SyntaxError: extraneous input '${wrongOp}' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}`, ]); } @@ -897,7 +897,7 @@ describe('validation logic', () => { describe('eval', () => { testErrorsAndWarnings('from a_index | eval ', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); testErrorsAndWarnings('from a_index | eval textField ', []); testErrorsAndWarnings('from a_index | eval b = textField', []); @@ -910,7 +910,7 @@ describe('validation logic', () => { ]); testErrorsAndWarnings('from a_index | eval a=b', ['Unknown column [b]']); testErrorsAndWarnings('from a_index | eval a=b, ', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", 'Unknown column [b]', ]); testErrorsAndWarnings('from a_index | eval a=round', ['Unknown column [round]']); @@ -919,7 +919,7 @@ describe('validation logic', () => { ]); testErrorsAndWarnings('from a_index | eval a=round(doubleField) ', []); testErrorsAndWarnings('from a_index | eval a=round(doubleField), ', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); testErrorsAndWarnings('from a_index | eval a=round(doubleField) + round(doubleField) ', []); testErrorsAndWarnings('from a_index | eval a=round(doubleField) + round(textField) ', [ @@ -982,7 +982,7 @@ describe('validation logic', () => { for (const wrongOp of ['*', '/', '%']) { testErrorsAndWarnings(`from a_index | eval ${wrongOp}+ doubleField`, [ - `SyntaxError: extraneous input '${wrongOp}' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}`, + `SyntaxError: extraneous input '${wrongOp}' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}`, ]); } testErrorsAndWarnings( @@ -1201,11 +1201,11 @@ describe('validation logic', () => { [] ); testErrorsAndWarnings('from a_index | eval not', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", 'Error: [not] function expects exactly one argument, got 0.', ]); testErrorsAndWarnings('from a_index | eval in', [ - "SyntaxError: mismatched input 'in' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input 'in' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); testErrorsAndWarnings('from a_index | eval textField in textField', [ @@ -1287,12 +1287,12 @@ describe('validation logic', () => { describe('sort', () => { testErrorsAndWarnings('from a_index | sort ', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); testErrorsAndWarnings('from a_index | sort "field" ', []); testErrorsAndWarnings('from a_index | sort wrongField ', ['Unknown column [wrongField]']); testErrorsAndWarnings('from a_index | sort doubleField, ', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); testErrorsAndWarnings('from a_index | sort doubleField, textField', []); for (const dir of ['desc', 'asc']) { diff --git a/packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts b/packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts index 30682d763e0b0..a243dbb7598c1 100644 --- a/packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts +++ b/packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts @@ -49,18 +49,10 @@ export const MANIFEST_V2: JSONSchema = { `, }, group: { - enum: ['common', 'platform', 'observability', 'security', 'search'], + enum: ['platform', 'observability', 'security', 'search'], description: desc` Specifies the group to which this module pertains. `, - default: 'common', - }, - visibility: { - enum: ['private', 'shared'], - description: desc` - Specifies the visibility of this module, i.e. whether it can be accessed by everybody or only modules in the same group - `, - default: 'shared', }, devOnly: { type: 'boolean', @@ -112,6 +104,37 @@ export const MANIFEST_V2: JSONSchema = { type: 'string', }, }, + allOf: [ + { + if: { + properties: { group: { const: 'platform' } }, + }, + then: { + properties: { + visibility: { + enum: ['private', 'shared'], + description: desc` + Specifies the visibility of this module, i.e. whether it can be accessed by everybody or only modules in the same group + `, + default: 'shared', + }, + }, + required: ['visibility'], + }, + else: { + properties: { + visibility: { + const: 'private', + description: desc` + Specifies the visibility of this module, i.e. whether it can be accessed by everybody or only modules in the same group + `, + default: 'private', + }, + }, + required: ['visibility'], + }, + }, + ], oneOf: [ { type: 'object', diff --git a/packages/kbn-management/settings/setting_ids/index.ts b/packages/kbn-management/settings/setting_ids/index.ts index 9246133f199ef..3505534239d13 100644 --- a/packages/kbn-management/settings/setting_ids/index.ts +++ b/packages/kbn-management/settings/setting_ids/index.ts @@ -34,7 +34,6 @@ export const HISTOGRAM_BAR_TARGET_ID = 'histogram:barTarget'; export const HISTOGRAM_MAX_BARS_ID = 'histogram:maxBars'; export const HISTORY_LIMIT_ID = 'history:limit'; export const META_FIELDS_ID = 'metaFields'; -export const METRICS_ALLOW_CHECKING_FOR_FAILED_SHARDS_ID = 'metrics:allowCheckingForFailedShards'; export const METRICS_ALLOW_STRING_INDICES_ID = 'metrics:allowStringIndices'; export const METRICS_MAX_BUCKETS_ID = 'metrics:max_buckets'; export const QUERY_ALLOW_LEADING_WILDCARDS_ID = 'query:allowLeadingWildcards'; diff --git a/packages/kbn-monaco/src/esql/lib/esql_theme.ts b/packages/kbn-monaco/src/esql/lib/esql_theme.ts index bf5e2c597eb6c..330e55de86155 100644 --- a/packages/kbn-monaco/src/esql/lib/esql_theme.ts +++ b/packages/kbn-monaco/src/esql/lib/esql_theme.ts @@ -74,7 +74,6 @@ export const buildESQlTheme = (): monaco.editor.IStandaloneThemeData => ({ 'asc', 'desc', 'nulls_order', - 'match', ], euiThemeVars.euiColorAccentText, true // isBold diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 7936e52ccbf18..b0357853720cb 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -2,7 +2,7 @@ pageLoadAssetSize: actions: 20000 advancedSettings: 27596 aiAssistantManagementSelection: 19146 - aiops: 10000 + aiops: 16000 alerting: 106936 apm: 64385 banners: 17946 @@ -138,7 +138,7 @@ pageLoadAssetSize: screenshotMode: 17856 screenshotting: 22870 searchAssistant: 19831 - searchConnectors: 30000 + searchConnectors: 65000 searchHomepage: 19831 searchIndices: 20519 searchInferenceEndpoints: 20470 diff --git a/packages/kbn-screenshotting-server/src/paths.ts b/packages/kbn-screenshotting-server/src/paths.ts index 9e8200c0839ab..e4c5a89d77627 100644 --- a/packages/kbn-screenshotting-server/src/paths.ts +++ b/packages/kbn-screenshotting-server/src/paths.ts @@ -46,10 +46,10 @@ export class ChromiumArchivePaths { platform: 'darwin', architecture: 'x64', archiveFilename: 'chrome-mac.zip', - archiveChecksum: '0a3d18efd00b3406f66139a673616b4b2b4b00323776678cb82295996f5a6733', - binaryChecksum: '8bcdaa973ee11110f6b70eaac2418fda3bb64446cf37f964fce331cdc8907a20', + archiveChecksum: '04f0132019c15660eea0b9d261fd14940c33b625c253689fcb5b09d58c4dbfe7', + binaryChecksum: 'a3ada6874ee052c096f09481fba75fcdabb96a8a9ad94a96949946a2485feccf', binaryRelativePath: 'chrome-mac/Chromium.app/Contents/MacOS/Chromium', - revision: 1331485, // 1331488 is not available for Mac_x64 + revision: 1355985, location: 'common', archivePath: 'Mac', isPreInstalled: false, @@ -58,10 +58,10 @@ export class ChromiumArchivePaths { platform: 'darwin', architecture: 'arm64', archiveFilename: 'chrome-mac.zip', - archiveChecksum: '426eddf16acb88b9446a91de53cc4364c7d487414248f33e30f68cf488cea0c0', - binaryChecksum: '827931739bfdd2b6790a81d5ade8886c159cd051581d79b84d1ede447293e9cf', + archiveChecksum: '6c75bb645696aed0e60b17e0e50423b97d21ca11f2c5cdfbaf17edbf582cec94', + binaryChecksum: '2f819f59379917056e07d640f75b1dbe22a830c2655e32ab0543013b7198c139', binaryRelativePath: 'chrome-mac/Chromium.app/Contents/MacOS/Chromium', - revision: 1331488, + revision: 1355985, location: 'common', archivePath: 'Mac_Arm', isPreInstalled: false, @@ -69,22 +69,22 @@ export class ChromiumArchivePaths { { platform: 'linux', architecture: 'x64', - archiveFilename: 'chromium-fe621c5-locales-linux_x64.zip', - archiveChecksum: '12ce2e0eac184072dfcbc7a267328e3eb7fbe10a682997f4111c0378f2397341', - binaryChecksum: '670481cfa8db209401106cd23051009d390c03608724d0822a12c8c0a92b4c25', + archiveFilename: 'chromium-53ac076-locales-linux_x64.zip', + archiveChecksum: '50424bf105710d184198484a8a666db414627596002dacf80e83b00c8da71115', + binaryChecksum: 'afbc87a7f946bd6df763ffffb38dd4d75ee50c28ba705ac177dc893030d20206', binaryRelativePath: 'headless_shell-linux_x64/headless_shell', - revision: 1331488, + revision: 1356013, location: 'custom', isPreInstalled: true, }, { platform: 'linux', architecture: 'arm64', - archiveFilename: 'chromium-fe621c5-locales-linux_arm64.zip', - archiveChecksum: 'f7333eaff5235046c8775f0c1a0b7395b7ebc2e054ea638710cf511c4b6f9daf', - binaryChecksum: '8a3a3371b3d04f4b0880b137a3611c223e0d8e65a218943cb7be1ec4a91f5e35', + archiveFilename: 'chromium-53ac076-locales-linux_arm64.zip', + archiveChecksum: '24ffa183a6bf355209f3960a2377a1f8cc75aef093fe1934fcc72d2a5f9a274b', + binaryChecksum: 'db1c0226e03dfc26a6d61e02a885912906529e8477ac3214962b160d1e99f25c', binaryRelativePath: 'headless_shell-linux_arm64/headless_shell', - revision: 1331488, + revision: 1356013, location: 'custom', isPreInstalled: true, }, @@ -92,10 +92,10 @@ export class ChromiumArchivePaths { platform: 'win32', architecture: 'x64', archiveFilename: 'chrome-win.zip', - archiveChecksum: 'fa62be702f55f37e455bab4291c59ceb40e81e1922d30cf9453a4ee176b909bc', - binaryChecksum: '1345e66583bad1a1f16885f381d1173de8bf931487da9ba155e1b58bf23b2c66', + archiveChecksum: 'f86aadca5d1ab02fc05b580f23a30ee02d34bd348f9a3f0032b7117027676727', + binaryChecksum: 'b7b98dd681dfea2333a0136ba5788e38010730bb2e42eafa291b16931f00449d', binaryRelativePath: path.join('chrome-win', 'chrome.exe'), - revision: 1331487, // 1331488 is not available for win32 + revision: 1355984, location: 'common', archivePath: 'Win', isPreInstalled: true, diff --git a/x-pack/plugins/search_connectors/common/connectors.ts b/packages/kbn-search-connectors/constants/connectors.ts similarity index 50% rename from x-pack/plugins/search_connectors/common/connectors.ts rename to packages/kbn-search-connectors/constants/connectors.ts index b0bc5564e9750..ad5c716234133 100644 --- a/x-pack/plugins/search_connectors/common/connectors.ts +++ b/packages/kbn-search-connectors/constants/connectors.ts @@ -1,42 +1,207 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import { i18n } from '@kbn/i18n'; +import { + ConnectorClientSideDefinition, + ConnectorServerSideDefinition, +} from '../types/connector_definition'; -export interface ConnectorServerSideDefinition { - categories?: string[]; - description?: string; - iconPath: string; - isBeta: boolean; - isNative: boolean; - isTechPreview?: boolean; - keywords: string[]; - name: string; - serviceType: string; -} +import { docLinks } from './doc_links'; + +// needs to be a function because, docLinks are only populated with actual +// documentation links in browser after SearchConnectorsPlugin starts +export const getConnectorsDict = (): Record => ({ + azure_blob_storage: { + docsUrl: docLinks.connectorsAzureBlobStorage, + externalAuthDocsUrl: 'https://learn.microsoft.com/azure/storage/common/authorize-data-access', + externalDocsUrl: 'https://learn.microsoft.com/azure/storage/blobs/', + platinumOnly: true, + }, + box: { + docsUrl: docLinks.connectorsBox, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + confluence: { + docsUrl: docLinks.connectorsConfluence, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + custom: { + docsUrl: docLinks.connectors, + externalAuthDocsUrl: '', + externalDocsUrl: '', + }, + dropbox: { + docsUrl: docLinks.connectorsDropbox, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + github: { + docsUrl: docLinks.connectorsGithub, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + gmail: { + docsUrl: docLinks.connectorsGmail, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + google_cloud_storage: { + docsUrl: docLinks.connectorsGoogleCloudStorage, + externalAuthDocsUrl: 'https://cloud.google.com/storage/docs/authentication', + externalDocsUrl: 'https://cloud.google.com/storage/docs', + platinumOnly: true, + }, + google_drive: { + docsUrl: docLinks.connectorsGoogleDrive, + externalAuthDocsUrl: 'https://cloud.google.com/iam/docs/service-account-overview', + externalDocsUrl: 'https://developers.google.com/drive', + platinumOnly: true, + }, + jira: { + docsUrl: docLinks.connectorsJira, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + microsoft_teams: { + docsUrl: docLinks.connectorsTeams, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + mongodb: { + docsUrl: docLinks.connectorsMongoDB, + externalAuthDocsUrl: 'https://www.mongodb.com/docs/atlas/app-services/authentication/', + externalDocsUrl: 'https://www.mongodb.com/docs/', + platinumOnly: true, + }, + mssql: { + docsUrl: docLinks.connectorsMicrosoftSQL, + externalAuthDocsUrl: + 'https://learn.microsoft.com/sql/relational-databases/security/authentication-access/getting-started-with-database-engine-permissions', + externalDocsUrl: 'https://learn.microsoft.com/sql/', + platinumOnly: true, + }, + mysql: { + docsUrl: docLinks.connectorsMySQL, + externalDocsUrl: 'https://dev.mysql.com/doc/', + platinumOnly: true, + }, + network_drive: { + docsUrl: docLinks.connectorsNetworkDrive, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + notion: { + docsUrl: docLinks.connectorsNotion, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + onedrive: { + docsUrl: docLinks.connectorsOneDrive, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + oracle: { + docsUrl: docLinks.connectorsOracle, + externalAuthDocsUrl: + 'https://docs.oracle.com/en/database/oracle/oracle-database/19/dbseg/index.html', + externalDocsUrl: 'https://docs.oracle.com/database/oracle/oracle-database/', + platinumOnly: true, + }, + outlook: { + docsUrl: docLinks.connectorsOutlook, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + postgresql: { + docsUrl: docLinks.connectorsPostgreSQL, + externalAuthDocsUrl: 'https://www.postgresql.org/docs/15/auth-methods.html', + externalDocsUrl: 'https://www.postgresql.org/docs/', + platinumOnly: true, + }, + redis: { + docsUrl: docLinks.connectorsRedis, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + s3: { + docsUrl: docLinks.connectorsS3, + externalAuthDocsUrl: 'https://docs.aws.amazon.com/s3/index.html', + externalDocsUrl: '', + platinumOnly: true, + }, + salesforce: { + docsUrl: docLinks.connectorsSalesforce, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + servicenow: { + docsUrl: docLinks.connectorsServiceNow, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + sharepoint_online: { + docsUrl: docLinks.connectorsSharepointOnline, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + sharepoint_server: { + docsUrl: docLinks.connectorsSharepoint, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + slack: { + docsUrl: docLinks.connectorsSlack, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + zoom: { + docsUrl: docLinks.connectorsZoom, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, +}); /* The consumer should host these icons and transform the iconPath into something usable * Enterprise Search and Serverless Search do this right now */ - export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.azureBlob.description', - { - defaultMessage: 'Search over your content on Azure Blob Storage.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.azureBlob.description', { + defaultMessage: 'Search over your content on Azure Blob Storage.', + }), iconPath: 'azure_blob_storage.svg', isBeta: false, isNative: true, keywords: ['cloud', 'azure', 'blob', 's3', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.azureBlob.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.azureBlob.name', { defaultMessage: 'Azure Blob Storage', }), serviceType: 'azure_blob_storage', @@ -44,7 +209,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.confluence.description', + 'searchConnectors.content.nativeConnectors.confluence.description', { defaultMessage: 'Search over your content on Confluence Cloud.', } @@ -53,7 +218,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isBeta: false, isNative: true, keywords: ['confluence', 'cloud', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.confluence.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.confluence.name', { defaultMessage: 'Confluence Cloud & Server', }), serviceType: 'confluence', @@ -61,7 +226,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.confluenceDataCenter.description', + 'searchConnectors.content.nativeConnectors.confluenceDataCenter.description', { defaultMessage: 'Search over your content on Confluence Data Center.', } @@ -71,45 +236,36 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isNative: true, isTechPreview: true, keywords: ['confluence', 'data', 'center', 'connector'], - name: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.confluence_data_center.name', - { - defaultMessage: 'Confluence Data Center', - } - ), + name: i18n.translate('searchConnectors.content.nativeConnectors.confluence_data_center.name', { + defaultMessage: 'Confluence Data Center', + }), serviceType: 'confluence', }, { categories: ['search', 'elastic_stack', 'datastore', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.dropbox.description', - { - defaultMessage: 'Search over your files and folders stored on Dropbox.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.dropbox.description', { + defaultMessage: 'Search over your files and folders stored on Dropbox.', + }), iconPath: 'dropbox.svg', isBeta: false, isNative: true, isTechPreview: false, keywords: ['dropbox', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.dropbox.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.dropbox.name', { defaultMessage: 'Dropbox', }), serviceType: 'dropbox', }, { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client', 'jira'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.jira.description', - { - defaultMessage: 'Search over your content on Jira Cloud.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.jira.description', { + defaultMessage: 'Search over your content on Jira Cloud.', + }), iconPath: 'jira_cloud.svg', isBeta: false, isNative: true, keywords: ['jira', 'cloud', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.jira.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.jira.name', { defaultMessage: 'Jira Cloud', }), serviceType: 'jira', @@ -117,7 +273,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client', 'jira'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.jiraServer.description', + 'searchConnectors.content.nativeConnectors.jiraServer.description', { defaultMessage: 'Search over your content on Jira Server.', } @@ -126,7 +282,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isBeta: false, isNative: false, keywords: ['jira', 'server', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.jiraServer.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.jiraServer.name', { defaultMessage: 'Jira Server', }), serviceType: 'jira', @@ -134,7 +290,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.jiraDataCenter.description', + 'searchConnectors.content.nativeConnectors.jiraDataCenter.description', { defaultMessage: 'Search over your content on Jira Data Center.', } @@ -144,24 +300,21 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isTechPreview: true, isNative: true, keywords: ['jira', 'data', 'center', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.jira_data_center.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.jira_data_center.name', { defaultMessage: 'Jira Data Center', }), serviceType: 'jira', }, { categories: ['search', 'elastic_stack', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.github.description', - { - defaultMessage: 'Search over your projects and repos on GitHub.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.github.description', { + defaultMessage: 'Search over your projects and repos on GitHub.', + }), iconPath: 'github.svg', isBeta: false, isNative: true, keywords: ['github', 'cloud', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.github.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.github.name', { defaultMessage: 'GitHub & GitHub Enterprise Server', }), serviceType: 'github', @@ -169,7 +322,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.googleCloud.description', + 'searchConnectors.content.nativeConnectors.googleCloud.description', { defaultMessage: 'Search over your content on Google Cloud Storage.', } @@ -178,7 +331,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isBeta: false, isNative: true, keywords: ['google', 'cloud', 'blob', 's3', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.googleCloud.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.googleCloud.name', { defaultMessage: 'Google Cloud Storage', }), serviceType: 'google_cloud_storage', @@ -186,7 +339,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.googleDrive.description', + 'searchConnectors.content.nativeConnectors.googleDrive.description', { defaultMessage: 'Search over your content on Google Drive.', } @@ -195,24 +348,21 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isBeta: false, isNative: true, keywords: ['google', 'drive', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.googleDrive.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.googleDrive.name', { defaultMessage: 'Google Drive', }), serviceType: 'google_drive', }, { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.graphQL.description', - { - defaultMessage: 'Search over your content with GraphQL.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.graphQL.description', { + defaultMessage: 'Search over your content with GraphQL.', + }), iconPath: 'graphql.svg', isBeta: false, isNative: false, keywords: ['graphql', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.graphQL.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.graphQL.name', { defaultMessage: 'GraphQL', }), serviceType: 'graphql', @@ -220,58 +370,49 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ }, { categories: ['search', 'datastore', 'elastic_stack', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.mongoDB.description', - { - defaultMessage: 'Search over your MongoDB content.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.mongoDB.description', { + defaultMessage: 'Search over your MongoDB content.', + }), iconPath: 'mongodb.svg', isBeta: false, isNative: true, keywords: ['mongo', 'mongodb', 'database', 'nosql', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.mongodb.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.mongodb.name', { defaultMessage: 'MongoDB', }), serviceType: 'mongodb', }, { categories: ['search', 'datastore', 'elastic_stack', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.mysql.description', - { - defaultMessage: 'Search over your MySQL content.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.mysql.description', { + defaultMessage: 'Search over your MySQL content.', + }), iconPath: 'mysql.svg', isBeta: false, isNative: true, keywords: ['mysql', 'sql', 'database', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.mysql.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.mysql.name', { defaultMessage: 'MySQL', }), serviceType: 'mysql', }, { categories: ['search', 'custom', 'elastic_stack', 'datastore', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.msSql.description', - { - defaultMessage: 'Search over your content on Microsoft SQL Server.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.msSql.description', { + defaultMessage: 'Search over your content on Microsoft SQL Server.', + }), iconPath: 'mssql.svg', isBeta: false, isNative: true, keywords: ['mssql', 'microsoft', 'sql', 'database', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.microsoftSQL.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.microsoftSQL.name', { defaultMessage: 'Microsoft SQL', }), serviceType: 'mssql', }, { description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.netowkrDrive.description', + 'searchConnectors.content.nativeConnectors.netowkrDrive.description', { defaultMessage: 'Search over your Network Drive content.', } @@ -281,31 +422,28 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isBeta: false, isNative: true, keywords: ['network', 'drive', 'file', 'directory', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.networkDrive.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.networkDrive.name', { defaultMessage: 'Network drive', }), serviceType: 'network_drive', }, { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.notion.description', - { - defaultMessage: 'Search over your content on Notion.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.notion.description', { + defaultMessage: 'Search over your content on Notion.', + }), iconPath: 'notion.svg', isBeta: false, isNative: true, keywords: ['notion', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.notion.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.notion.name', { defaultMessage: 'Notion', }), serviceType: 'notion', }, { description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.postgreSQL.description', + 'searchConnectors.content.nativeConnectors.postgreSQL.description', { defaultMessage: 'Search over your content on PostgreSQL.', } @@ -315,25 +453,22 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isBeta: false, isNative: true, keywords: ['postgresql', 'sql', 'database', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.postgresql.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.postgresql.name', { defaultMessage: 'PostgreSQL', }), serviceType: 'postgresql', }, { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.redis.description', - { - defaultMessage: 'Search over your content on Redis.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.redis.description', { + defaultMessage: 'Search over your content on Redis.', + }), iconPath: 'redis.svg', isBeta: false, isNative: false, isTechPreview: true, keywords: ['redis', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.redis.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.redis.name', { defaultMessage: 'Redis', }), serviceType: 'redis', @@ -341,7 +476,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.salesforce.description', + 'searchConnectors.content.nativeConnectors.salesforce.description', { defaultMessage: 'Search over your content on Salesforce.', } @@ -350,7 +485,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isBeta: false, isNative: true, keywords: ['salesforce', 'cloud', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.salesforce.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.salesforce.name', { defaultMessage: 'Salesforce', }), serviceType: 'salesforce', @@ -358,7 +493,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'custom', 'datastore', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.salesforceSandbox.description', + 'searchConnectors.content.nativeConnectors.salesforceSandbox.description', { defaultMessage: 'Search over your content on Salesforce Sandbox.', } @@ -367,7 +502,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isBeta: false, isNative: true, keywords: ['salesforce', 'cloud', 'connector', 'sandbox'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.salesforceBox.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.salesforceBox.name', { defaultMessage: 'Salesforce Sandbox', }), serviceType: 'salesforce', @@ -375,7 +510,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.serviceNow.description', + 'searchConnectors.content.nativeConnectors.serviceNow.description', { defaultMessage: 'Search over your content on ServiceNow.', } @@ -385,7 +520,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isNative: true, isTechPreview: false, keywords: ['servicenow', 'cloud', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.serviceNow.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.serviceNow.name', { defaultMessage: 'ServiceNow', }), serviceType: 'servicenow', @@ -393,7 +528,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.sharepointOnline.description', + 'searchConnectors.content.nativeConnectors.sharepointOnline.description', { defaultMessage: 'Search over your content on SharePoint Online.', } @@ -403,24 +538,21 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isNative: true, isTechPreview: false, keywords: ['sharepoint', 'office365', 'cloud', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.sharepointOnline.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.sharepointOnline.name', { defaultMessage: 'Sharepoint Online', }), serviceType: 'sharepoint_online', }, { categories: ['search', 'elastic_stack', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.gmail.description', - { - defaultMessage: 'Search over your content on Gmail.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.gmail.description', { + defaultMessage: 'Search over your content on Gmail.', + }), iconPath: 'gmail.svg', isBeta: false, isNative: true, keywords: ['gmail', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.gmail.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.gmail.name', { defaultMessage: 'Gmail', }), serviceType: 'gmail', @@ -428,7 +560,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.openTextDocumentum.description', + 'searchConnectors.content.nativeConnectors.openTextDocumentum.description', { defaultMessage: 'Search over your content on OpenText Documentum.', } @@ -438,50 +570,41 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isNative: false, isTechPreview: true, keywords: ['opentext', 'documentum', 'connector'], - name: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.openTextDocumentum.name', - { - defaultMessage: 'OpenText Documentum', - } - ), + name: i18n.translate('searchConnectors.content.nativeConnectors.openTextDocumentum.name', { + defaultMessage: 'OpenText Documentum', + }), serviceType: 'opentext_documentum', }, { categories: ['search', 'elastic_stack', 'custom', 'datastore', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.oracle.description', - { - defaultMessage: 'Search over your content on Oracle.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.oracle.description', { + defaultMessage: 'Search over your content on Oracle.', + }), iconPath: 'oracle.svg', isBeta: false, isNative: true, keywords: ['oracle', 'sql', 'database', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.oracle.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.oracle.name', { defaultMessage: 'Oracle', }), serviceType: 'oracle', }, { categories: ['search', 'elastic_stack', 'custom', 'datastore', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.oneDrive.description', - { - defaultMessage: 'Search over your content on OneDrive.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.oneDrive.description', { + defaultMessage: 'Search over your content on OneDrive.', + }), iconPath: 'onedrive.svg', isBeta: false, isNative: true, keywords: ['network', 'drive', 'file', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.oneDrive.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.oneDrive.name', { defaultMessage: 'OneDrive', }), serviceType: 'onedrive', }, { - description: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.s3.description', { + description: i18n.translate('searchConnectors.content.nativeConnectors.s3.description', { defaultMessage: 'Search over your content on Amazon S3.', }), categories: ['search', 'datastore', 'elastic_stack', 'connector', 'connector_client'], @@ -489,25 +612,22 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isBeta: false, isNative: true, keywords: ['s3', 'cloud', 'amazon', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.s3.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.s3.name', { defaultMessage: 'S3', }), serviceType: 's3', }, { - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.slack.description', - { - defaultMessage: 'Search over your content on Slack.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.slack.description', { + defaultMessage: 'Search over your content on Slack.', + }), categories: ['search', 'elastic_stack', 'connector', 'connector_client'], iconPath: 'slack.svg', isBeta: false, isNative: true, isTechPreview: true, keywords: ['slack', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.slack.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.slack.name', { defaultMessage: 'Slack', }), serviceType: 'slack', @@ -515,7 +635,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.sharepointServer.description', + 'searchConnectors.content.nativeConnectors.sharepointServer.description', { defaultMessage: 'Search over your content on SharePoint Server.', } @@ -525,14 +645,14 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isNative: true, isTechPreview: false, keywords: ['sharepoint', 'cloud', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.sharepointServer.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.sharepointServer.name', { defaultMessage: 'Sharepoint Server', }), serviceType: 'sharepoint_server', }, { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client', 'box'], - description: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.box.description', { + description: i18n.translate('searchConnectors.content.nativeConnectors.box.description', { defaultMessage: 'Search over your content on Box.', }), iconPath: 'box.svg', @@ -540,60 +660,51 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isNative: true, isTechPreview: true, keywords: ['cloud', 'box'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.box.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.box.name', { defaultMessage: 'Box', }), serviceType: 'box', }, { - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.outlook.description', - { - defaultMessage: 'Search over your content on Outlook.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.outlook.description', { + defaultMessage: 'Search over your content on Outlook.', + }), categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client', 'outlook'], iconPath: 'outlook.svg', isBeta: false, isNative: true, keywords: ['outlook', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.outlook.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.outlook.name', { defaultMessage: 'Outlook', }), serviceType: 'outlook', }, { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client', 'teams'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.teams.description', - { - defaultMessage: 'Search over your content on Teams.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.teams.description', { + defaultMessage: 'Search over your content on Teams.', + }), iconPath: 'teams.svg', isBeta: false, isNative: true, isTechPreview: true, keywords: ['teams', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.teams.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.teams.name', { defaultMessage: 'Teams', }), serviceType: 'microsoft_teams', }, { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client', 'zoom'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.zoom.description', - { - defaultMessage: 'Search over your content on Zoom.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.zoom.description', { + defaultMessage: 'Search over your content on Zoom.', + }), iconPath: 'zoom.svg', isBeta: false, isNative: true, isTechPreview: true, keywords: ['zoom', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.zoom.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.zoom.name', { defaultMessage: 'Zoom', }), serviceType: 'zoom', @@ -601,7 +712,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'custom', 'elastic_stack', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.customConnector.description', + 'searchConnectors.content.nativeConnectors.customConnector.description', { defaultMessage: 'Search over data stored on custom data sources.', } @@ -610,7 +721,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isBeta: false, isNative: false, keywords: ['custom', 'connector', 'code'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.customConnector.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.customConnector.name', { defaultMessage: 'Customized connector', }), serviceType: '', diff --git a/x-pack/plugins/search_connectors/common/doc_links.ts b/packages/kbn-search-connectors/constants/doc_links.ts similarity index 90% rename from x-pack/plugins/search_connectors/common/doc_links.ts rename to packages/kbn-search-connectors/constants/doc_links.ts index 0c5edc1a07ca7..db4dc3870e5c4 100644 --- a/x-pack/plugins/search_connectors/common/doc_links.ts +++ b/packages/kbn-search-connectors/constants/doc_links.ts @@ -1,8 +1,10 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import { DocLinks } from '@kbn/doc-links'; diff --git a/packages/kbn-unified-field-list/src/services/field_stats_text_based/index.ts b/packages/kbn-search-connectors/constants/index.ts similarity index 86% rename from packages/kbn-unified-field-list/src/services/field_stats_text_based/index.ts rename to packages/kbn-search-connectors/constants/index.ts index 8915a30bf4f41..6019d3d61be2f 100644 --- a/packages/kbn-unified-field-list/src/services/field_stats_text_based/index.ts +++ b/packages/kbn-search-connectors/constants/index.ts @@ -7,4 +7,5 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export { loadFieldStatsTextBased } from './load_field_stats_text_based'; +export * from './connectors'; +export * from './doc_links'; diff --git a/packages/kbn-search-connectors/index.ts b/packages/kbn-search-connectors/index.ts index 2418d0f4d557d..5f45a1bd8ffb6 100644 --- a/packages/kbn-search-connectors/index.ts +++ b/packages/kbn-search-connectors/index.ts @@ -16,6 +16,7 @@ export const CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX = '.search-acl-filter-'; export const CRAWLER_SERVICE_TYPE = 'elastic-crawler'; export * from './components'; +export * from './constants'; export * from './lib'; export * from './types'; export * from './utils'; diff --git a/packages/kbn-search-connectors/tsconfig.json b/packages/kbn-search-connectors/tsconfig.json index cb54e57748e94..4aebaeb1fcb13 100644 --- a/packages/kbn-search-connectors/tsconfig.json +++ b/packages/kbn-search-connectors/tsconfig.json @@ -25,5 +25,6 @@ "@kbn/i18n-react", "@kbn/test-jest-helpers", "@kbn/std", + "@kbn/doc-links", ] } diff --git a/packages/kbn-search-connectors/types/connector_definition.ts b/packages/kbn-search-connectors/types/connector_definition.ts new file mode 100644 index 0000000000000..a2dccf6554959 --- /dev/null +++ b/packages/kbn-search-connectors/types/connector_definition.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export interface ConnectorClientSideDefinition { + docsUrl?: string; + externalAuthDocsUrl?: string; + externalDocsUrl: string; + platinumOnly?: boolean; +} + +export interface ConnectorServerSideDefinition { + categories?: string[]; + description?: string; + iconPath: string; + isBeta: boolean; + isNative: boolean; + isTechPreview?: boolean; + keywords: string[]; + name: string; + serviceType: string; +} + +export type ConnectorDefinition = ConnectorClientSideDefinition & ConnectorServerSideDefinition; diff --git a/packages/kbn-search-connectors/types/index.ts b/packages/kbn-search-connectors/types/index.ts index ca5c483ab51df..aaf98748cfcbd 100644 --- a/packages/kbn-search-connectors/types/index.ts +++ b/packages/kbn-search-connectors/types/index.ts @@ -7,6 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +export * from './connector_definition'; export * from './connectors'; export * from './connectors_api'; export * from './connector_stats'; diff --git a/packages/kbn-search-connectors/types/native_connectors.ts b/packages/kbn-search-connectors/types/native_connectors.ts index f9719cb84b801..c5ef7beab0ba5 100644 --- a/packages/kbn-search-connectors/types/native_connectors.ts +++ b/packages/kbn-search-connectors/types/native_connectors.ts @@ -11,54 +11,64 @@ import { i18n } from '@kbn/i18n'; import { DisplayType, FeatureName, FieldType, NativeConnector } from './connectors'; -const USERNAME_LABEL = i18n.translate('searchConnectors.nativeConnectors.usernameLabel', { +// assigning these to a local var significantly improves bundle size +// because it reduces references to the imported modules. +const { translate } = i18n; +const { TEXTBOX, TEXTAREA, NUMERIC, TOGGLE, DROPDOWN } = DisplayType; +const { + SYNC_RULES, + INCREMENTAL_SYNC, + DOCUMENT_LEVEL_SECURITY, + FILTERING_ADVANCED_CONFIG, + FILTERING_RULES, +} = FeatureName; +const { STRING, LIST, INTEGER, BOOLEAN } = FieldType; + +const USERNAME_LABEL = translate('searchConnectors.nativeConnectors.usernameLabel', { defaultMessage: 'Username', }); -const PASSWORD_LABEL = i18n.translate('searchConnectors.nativeConnectors.passwordLabel', { +const PASSWORD_LABEL = translate('searchConnectors.nativeConnectors.passwordLabel', { defaultMessage: 'Password', }); -const ENABLE_SSL_LABEL = i18n.translate('searchConnectors.nativeConnectors.enableSSL.label', { +const ENABLE_SSL_LABEL = translate('searchConnectors.nativeConnectors.enableSSL.label', { defaultMessage: 'Enable SSL', }); -const SSL_CERTIFICATE_LABEL = i18n.translate( - 'searchConnectors.nativeConnectors.sslCertificate.label', - { - defaultMessage: 'SSL certificate', - } -); +const SSL_CERTIFICATE_LABEL = translate('searchConnectors.nativeConnectors.sslCertificate.label', { + defaultMessage: 'SSL certificate', +}); -const RETRIES_PER_REQUEST_LABEL = i18n.translate( +const RETRIES_PER_REQUEST_LABEL = translate( 'searchConnectors.nativeConnectors.retriesPerRequest.label', { defaultMessage: 'Retries per request', } ); -const ADVANCED_RULES_IGNORED_LABEL = i18n.translate( +const ADVANCED_RULES_IGNORED_LABEL = translate( 'searchConnectors.nativeConnectors.advancedRulesIgnored.label', { defaultMessage: 'This configurable field is ignored when Advanced Sync Rules are used.', } ); -const MAX_CONCURRENT_DOWNLOADS_LABEL = i18n.translate( +const MAX_CONCURRENT_DOWNLOADS_LABEL = translate( 'searchConnectors.nativeConnectors.nativeConnectors.maximumConcurrentLabel', { defaultMessage: 'Maximum concurrent downloads', } ); -const USE_TEXT_EXTRACTION_SERVICE_LABEL = i18n.translate( +const USE_TEXT_EXTRACTION_SERVICE_LABEL = translate( 'searchConnectors.nativeConnectors.textExtractionService.label', { defaultMessage: 'Use text extraction service', } ); -const USE_TEXT_EXTRACTION_SERVICE_TOOLTIP = i18n.translate( +const USE_TEXT_EXTRACTION_SERVICE_TOOLTIP = translate( 'searchConnectors.nativeConnectors.textExtractionService.tooltip', { defaultMessage: @@ -67,7 +77,7 @@ const USE_TEXT_EXTRACTION_SERVICE_TOOLTIP = i18n.translate( } ); -const ENABLE_DOCUMENT_LEVEL_SECURITY_LABEL = i18n.translate( +const ENABLE_DOCUMENT_LEVEL_SECURITY_LABEL = translate( 'searchConnectors.nativeConnectors.enableDLS.label', { defaultMessage: 'Enable document level security', @@ -75,21 +85,21 @@ const ENABLE_DOCUMENT_LEVEL_SECURITY_LABEL = i18n.translate( ); const getEnableDocumentLevelSecurityTooltip = (serviceName: string) => - i18n.translate('searchConnectors.nativeConnectors.enableDLS.tooltip', { + translate('searchConnectors.nativeConnectors.enableDLS.tooltip', { defaultMessage: 'Document level security ensures identities and permissions set in {serviceName} are maintained in Elasticsearch. This enables you to restrict and personalize read-access users and groups have to documents in this index. Access control syncs ensure this metadata is kept up to date in your Elasticsearch documents.', values: { serviceName }, }); -const DATABASE_LABEL = i18n.translate('searchConnectors.nativeConnectors.databaseLabel', { +const DATABASE_LABEL = translate('searchConnectors.nativeConnectors.databaseLabel', { defaultMessage: 'Database', }); -const SCHEMA_LABEL = i18n.translate('searchConnectors.nativeConnectors.schemaLabel', { +const SCHEMA_LABEL = translate('searchConnectors.nativeConnectors.schemaLabel', { defaultMessage: 'Schema', }); -const PORT_LABEL = i18n.translate('searchConnectors.nativeConnectors.portLabel', { +const PORT_LABEL = translate('searchConnectors.nativeConnectors.portLabel', { defaultMessage: 'Port', }); @@ -103,19 +113,16 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record Account -> Settings -> Customer Id', }), - type: FieldType.STRING, + type: STRING, ui_restrictions: [], validations: [], value: '', @@ -1355,24 +1341,21 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record { }); } + // This is a workaround to run tests with React 17 and the latest @testing-library/react + // This will be removed once we upgrade to React 18 and start transitioning to the Concurrent Mode + // Tracking issue to clean this up https://github.com/elastic/kibana/issues/199100 + if (request === 'react-dom/client') { + return Path.resolve(__dirname, 'mocks/react_dom_client_mock.ts'); + } + if (request === `elastic-apm-node`) { return APM_AGENT_MOCK; } diff --git a/packages/kbn-test/src/jest/setup/react_testing_library.js b/packages/kbn-test/src/jest/setup/react_testing_library.js index a04ee097a5ec7..1444aa41949ef 100644 --- a/packages/kbn-test/src/jest/setup/react_testing_library.js +++ b/packages/kbn-test/src/jest/setup/react_testing_library.js @@ -19,3 +19,34 @@ import { configure } from '@testing-library/react'; // instead of default 'data-testid', use kibana's 'data-test-subj' configure({ testIdAttribute: 'data-test-subj', asyncUtilTimeout: 4500 }); + +/* eslint-env jest */ + +// This is a workaround to run tests with React 17 and the latest @testing-library/react +// Tracking issue to clean this up https://github.com/elastic/kibana/issues/199100 +jest.mock('@testing-library/react', () => { + const actual = jest.requireActual('@testing-library/react'); + + return { + ...actual, + render: (ui, options) => actual.render(ui, { ...options, legacyRoot: true }), + renderHook: (render, options) => actual.renderHook(render, { ...options, legacyRoot: true }), + }; +}); + +// This is a workaround to run tests with React 17 and the latest @testing-library/react +// And prevent the act warnings that were supposed to be muted by @testing-library +// The testing library mutes the act warnings in some cases by setting IS_REACT_ACT_ENVIRONMENT which is React@18 feature https://github.com/testing-library/react-testing-library/pull/1137/ +// Using this console override we're muting the act warnings as well +// Tracking issue to clean this up https://github.com/elastic/kibana/issues/199100 +// NOTE: we're not muting all the act warnings but only those that testing-library wanted to mute +const originalConsoleError = console.error; +console.error = (...args) => { + if (global.IS_REACT_ACT_ENVIRONMENT === false) { + if (args[0].includes('Warning: An update to %s inside a test was not wrapped in act')) { + return; + } + } + + originalConsoleError(...args); +}; diff --git a/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts b/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts index d5483f1fe0f9f..0b6ba0be80fab 100644 --- a/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts +++ b/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts @@ -109,6 +109,7 @@ const STANDARD_LIST_TYPES = [ 'synthetics-monitor', 'uptime-dynamic-settings', 'synthetics-privates-locations', + 'synthetics-private-location', 'osquery-saved-query', 'osquery-pack', diff --git a/packages/kbn-unified-field-list/src/components/field_stats/field_stats.test.tsx b/packages/kbn-unified-field-list/src/components/field_stats/field_stats.test.tsx index 951602334a622..beb0e1f05e1b8 100644 --- a/packages/kbn-unified-field-list/src/components/field_stats/field_stats.test.tsx +++ b/packages/kbn-unified-field-list/src/components/field_stats/field_stats.test.tsx @@ -830,4 +830,14 @@ describe('UnifiedFieldList FieldStats', () => { expect(wrapper.text()).toBe('Summarymin29674max36821994Calculated from 5000 sample records.'); }); + + it('should not request field stats for ES|QL query', async () => { + const wrapper = await mountComponent( + + ); + + expect(loadFieldStats).toHaveBeenCalledTimes(0); + + expect(wrapper.text()).toBe('Analysis is not available for this field.'); + }); }); diff --git a/packages/kbn-unified-field-list/src/components/field_stats/field_stats.tsx b/packages/kbn-unified-field-list/src/components/field_stats/field_stats.tsx index 8eada232cdeaf..58ff36069dd8c 100755 --- a/packages/kbn-unified-field-list/src/components/field_stats/field_stats.tsx +++ b/packages/kbn-unified-field-list/src/components/field_stats/field_stats.tsx @@ -42,7 +42,6 @@ import { canProvideNumberSummaryForField, } from '../../utils/can_provide_stats'; import { loadFieldStats } from '../../services/field_stats'; -import { loadFieldStatsTextBased } from '../../services/field_stats_text_based'; import type { AddFieldFilterHandler } from '../../types'; import { FieldTopValues, @@ -136,7 +135,7 @@ const FieldStatsComponent: React.FC = ({ const [dataView, changeDataView] = useState(null); const abortControllerRef = useRef(null); const isCanceledRef = useRef(false); - const isTextBased = !!query && isOfAggregateQueryType(query); + const isEsqlQuery = !!query && isOfAggregateQueryType(query); const setState: typeof changeState = useCallback( (nextState) => { @@ -178,6 +177,12 @@ const FieldStatsComponent: React.FC = ({ setDataView(loadedDataView); + if (isEsqlQuery) { + // Not supported yet for ES|QL queries + // Previous implementation was removed in https://github.com/elastic/kibana/pull/198948/ + return; + } + if (state.isLoading) { return; } @@ -187,32 +192,17 @@ const FieldStatsComponent: React.FC = ({ abortControllerRef.current?.abort(); abortControllerRef.current = new AbortController(); - const results = isTextBased - ? await loadFieldStatsTextBased({ - services: { data }, - dataView: loadedDataView, - field, - fromDate, - toDate, - baseQuery: query, - abortController: abortControllerRef.current, - }) - : await loadFieldStats({ - services: { data }, - dataView: loadedDataView, - field, - fromDate, - toDate, - dslQuery: - dslQuery ?? - buildEsQuery( - loadedDataView, - query ?? [], - filters ?? [], - getEsQueryConfig(uiSettings) - ), - abortController: abortControllerRef.current, - }); + const results = await loadFieldStats({ + services: { data }, + dataView: loadedDataView, + field, + fromDate, + toDate, + dslQuery: + dslQuery ?? + buildEsQuery(loadedDataView, query ?? [], filters ?? [], getEsQueryConfig(uiSettings)), + abortController: abortControllerRef.current, + }); abortControllerRef.current = null; @@ -297,7 +287,7 @@ const FieldStatsComponent: React.FC = ({ let title = <>; function combineWithTitleAndFooter(el: React.ReactElement) { - const countsElement = getCountsElement(state, services, isTextBased, dataTestSubject); + const countsElement = getCountsElement(state, services, isEsqlQuery, dataTestSubject); return ( <> @@ -319,7 +309,7 @@ const FieldStatsComponent: React.FC = ({ ); } - if (!canProvideStatsForField(field, isTextBased)) { + if (!canProvideStatsForField(field, isEsqlQuery)) { const messageNoAnalysis = ( = ({ : messageNoAnalysis; } - if (canProvideNumberSummaryForField(field, isTextBased) && isNumberSummaryValid(numberSummary)) { + if (canProvideNumberSummaryForField(field, isEsqlQuery) && isNumberSummaryValid(numberSummary)) { title = (
@@ -563,21 +553,19 @@ const FieldStatsComponent: React.FC = ({ function getCountsElement( state: FieldStatsState, services: FieldStatsServices, - isTextBased: boolean, + isEsqlQuery: boolean, dataTestSubject: string ): JSX.Element { const dataTestSubjDocsCount = 'unifiedFieldStats-statsFooter-docsCount'; const { fieldFormats } = services; - const { totalDocuments, sampledValues, sampledDocuments, topValues } = state; + const { totalDocuments, sampledDocuments } = state; - if (!totalDocuments) { + if (!totalDocuments || isEsqlQuery) { return <>; } - let labelElement; - - if (isTextBased) { - labelElement = topValues?.areExamples ? ( + const labelElement = + sampledDocuments && sampledDocuments < totalDocuments ? ( ) : ( {fieldFormats .getDefaultInstance(KBN_FIELD_TYPES.NUMBER, [ES_FIELD_TYPES.INTEGER]) - .convert(sampledValues)} + .convert(totalDocuments)} ), }} /> ); - } else { - labelElement = - sampledDocuments && sampledDocuments < totalDocuments ? ( - - {fieldFormats - .getDefaultInstance(KBN_FIELD_TYPES.NUMBER, [ES_FIELD_TYPES.INTEGER]) - .convert(sampledDocuments)} - - ), - }} - /> - ) : ( - - {fieldFormats - .getDefaultInstance(KBN_FIELD_TYPES.NUMBER, [ES_FIELD_TYPES.INTEGER]) - .convert(totalDocuments)} - - ), - }} - /> - ); - } return ( diff --git a/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.tsx b/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.tsx index 7864976c1180f..b139e7b5685c5 100644 --- a/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.tsx +++ b/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.tsx @@ -32,7 +32,7 @@ import type { UnifiedFieldListSidebarContainerStateService, AddFieldFilterHandler, } from '../../types'; -import { canProvideStatsForFieldTextBased } from '../../utils/can_provide_stats'; +import { canProvideStatsForEsqlField } from '../../utils/can_provide_stats'; interface GetCommonFieldItemButtonPropsParams { stateService: UnifiedFieldListSidebarContainerStateService; @@ -405,7 +405,7 @@ function UnifiedFieldListItemComponent({ /> )} renderContent={ - (searchMode === 'text-based' && canProvideStatsForFieldTextBased(field)) || + (searchMode === 'text-based' && canProvideStatsForEsqlField(field)) || searchMode === 'documents' ? renderPopover : undefined diff --git a/packages/kbn-unified-field-list/src/services/field_examples_calculator/field_examples_calculator.test.ts b/packages/kbn-unified-field-list/src/services/field_examples_calculator/field_examples_calculator.test.ts index 6e14c16f7e42c..7e77bd7852726 100644 --- a/packages/kbn-unified-field-list/src/services/field_examples_calculator/field_examples_calculator.test.ts +++ b/packages/kbn-unified-field-list/src/services/field_examples_calculator/field_examples_calculator.test.ts @@ -223,7 +223,7 @@ describe('fieldExamplesCalculator', function () { values: getFieldValues(hits, dataView.fields.getByName('extension')!, dataView), field: dataView.fields.getByName('extension')!, count: 3, - isTextBased: false, + isEsqlQuery: false, }; }); @@ -286,33 +286,19 @@ describe('fieldExamplesCalculator', function () { expect(getFieldExampleBuckets(params).sampledValues).toBe(5); }); - it('works for text-based', function () { - const result = getFieldExampleBuckets({ - values: [['a'], ['b'], ['a'], ['a']], - field: { name: 'message', type: 'string', esTypes: ['text'] } as DataViewField, - isTextBased: true, - }); - expect(result).toMatchInlineSnapshot(` - Object { - "buckets": Array [ - Object { - "count": 3, - "key": "a", - }, - Object { - "count": 1, - "key": "b", - }, - ], - "sampledDocuments": 4, - "sampledValues": 4, - } - `); + it('should not work for ES|QL', function () { + expect(() => + getFieldExampleBuckets({ + values: [['a'], ['b'], ['a'], ['a']], + field: { name: 'message', type: 'string', esTypes: ['text'] } as DataViewField, + isEsqlQuery: true, + }) + ).toThrowError(); expect(() => getFieldExampleBuckets({ values: [['a'], ['b'], ['a'], ['a']], field: { name: 'message', type: 'string', esTypes: ['keyword'] } as DataViewField, - isTextBased: true, + isEsqlQuery: true, }) ).toThrowError(); }); diff --git a/packages/kbn-unified-field-list/src/services/field_examples_calculator/field_examples_calculator.ts b/packages/kbn-unified-field-list/src/services/field_examples_calculator/field_examples_calculator.ts index e4413f3be7fe2..55d0c30b58e34 100644 --- a/packages/kbn-unified-field-list/src/services/field_examples_calculator/field_examples_calculator.ts +++ b/packages/kbn-unified-field-list/src/services/field_examples_calculator/field_examples_calculator.ts @@ -23,7 +23,7 @@ export interface FieldValueCountsParams { values: FieldHitValue[]; field: DataViewField; count?: number; - isTextBased: boolean; + isEsqlQuery: boolean; } export function getFieldExampleBuckets(params: FieldValueCountsParams, formatter?: FieldFormat) { @@ -31,7 +31,7 @@ export function getFieldExampleBuckets(params: FieldValueCountsParams, formatter count: DEFAULT_SIMPLE_EXAMPLES_SIZE, }); - if (!canProvideExamplesForField(params.field, params.isTextBased)) { + if (!canProvideExamplesForField(params.field, params.isEsqlQuery)) { throw new Error( `Analysis is not available this field type: "${params.field.type}". Field name: "${params.field.name}"` ); diff --git a/packages/kbn-unified-field-list/src/services/field_stats/field_stats_utils.ts b/packages/kbn-unified-field-list/src/services/field_stats/field_stats_utils.ts index 309f5f054683b..57a7d0be8fda9 100644 --- a/packages/kbn-unified-field-list/src/services/field_stats/field_stats_utils.ts +++ b/packages/kbn-unified-field-list/src/services/field_stats/field_stats_utils.ts @@ -416,7 +416,7 @@ export async function getSimpleExamples( values: getFieldValues(simpleExamplesResult.hits.hits, field, dataView), field, count: DEFAULT_SIMPLE_EXAMPLES_SIZE, - isTextBased: false, + isEsqlQuery: false, }, formatter ); diff --git a/packages/kbn-unified-field-list/src/services/field_stats_text_based/field_stats_utils_text_based.test.ts b/packages/kbn-unified-field-list/src/services/field_stats_text_based/field_stats_utils_text_based.test.ts deleted file mode 100644 index 553fdd749941f..0000000000000 --- a/packages/kbn-unified-field-list/src/services/field_stats_text_based/field_stats_utils_text_based.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import type { DataViewField } from '@kbn/data-views-plugin/common'; -import { buildSearchFilter, fetchAndCalculateFieldStats } from './field_stats_utils_text_based'; - -describe('fieldStatsUtilsTextBased', function () { - describe('buildSearchFilter()', () => { - it('should create a time range filter', () => { - expect( - buildSearchFilter({ - timeFieldName: 'timestamp', - fromDate: '2022-12-05T23:00:00.000Z', - toDate: '2023-01-05T09:33:05.359Z', - }) - ).toMatchInlineSnapshot(` - Object { - "range": Object { - "timestamp": Object { - "format": "strict_date_optional_time", - "gte": "2022-12-05T23:00:00.000Z", - "lte": "2023-01-05T09:33:05.359Z", - }, - }, - } - `); - }); - it('should not create a time range filter', () => { - expect( - buildSearchFilter({ - timeFieldName: undefined, - fromDate: '2022-12-05T23:00:00.000Z', - toDate: '2023-01-05T09:33:05.359Z', - }) - ).toBeNull(); - }); - }); - - describe('fetchAndCalculateFieldStats()', () => { - it('should provide top values', async () => { - const searchHandler = jest.fn().mockResolvedValue({ - values: [ - [3, 'a'], - [1, 'b'], - ], - }); - expect( - await fetchAndCalculateFieldStats({ - searchHandler, - esqlBaseQuery: 'from logs* | limit 1000', - field: { name: 'message', type: 'string', esTypes: ['keyword'] } as DataViewField, - }) - ).toMatchInlineSnapshot(` - Object { - "sampledDocuments": 4, - "sampledValues": 4, - "topValues": Object { - "buckets": Array [ - Object { - "count": 3, - "key": "a", - }, - Object { - "count": 1, - "key": "b", - }, - ], - }, - "totalDocuments": 4, - } - `); - expect(searchHandler).toHaveBeenCalledWith( - expect.objectContaining({ - query: - 'from logs* | limit 1000\n| WHERE `message` IS NOT NULL\n | STATS `message_terms` = count(`message`) BY `message`\n | SORT `message_terms` DESC\n | LIMIT 10', - }) - ); - }); - - it('should provide text examples', async () => { - const searchHandler = jest.fn().mockResolvedValue({ - values: [[['programming', 'cool']], ['elastic', 'cool']], - }); - expect( - await fetchAndCalculateFieldStats({ - searchHandler, - esqlBaseQuery: 'from logs* | limit 1000', - field: { name: 'message', type: 'string', esTypes: ['text'] } as DataViewField, - }) - ).toMatchInlineSnapshot(` - Object { - "sampledDocuments": 2, - "sampledValues": 4, - "topValues": Object { - "areExamples": true, - "buckets": Array [ - Object { - "count": 2, - "key": "cool", - }, - Object { - "count": 1, - "key": "elastic", - }, - Object { - "count": 1, - "key": "programming", - }, - ], - }, - "totalDocuments": 2, - } - `); - - expect(searchHandler).toHaveBeenCalledWith( - expect.objectContaining({ - query: - 'from logs* | limit 1000\n| WHERE `message` IS NOT NULL\n | KEEP `message`\n | LIMIT 100', - }) - ); - }); - }); -}); diff --git a/packages/kbn-unified-field-list/src/services/field_stats_text_based/field_stats_utils_text_based.ts b/packages/kbn-unified-field-list/src/services/field_stats_text_based/field_stats_utils_text_based.ts deleted file mode 100644 index b64d26b0cbb59..0000000000000 --- a/packages/kbn-unified-field-list/src/services/field_stats_text_based/field_stats_utils_text_based.ts +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import type { ESQLSearchResponse } from '@kbn/es-types'; -import { appendToESQLQuery } from '@kbn/esql-utils'; -import type { DataViewField } from '@kbn/data-views-plugin/common'; -import type { FieldStatsResponse } from '../../types'; -import { - DEFAULT_TOP_VALUES_SIZE, - DEFAULT_SIMPLE_EXAMPLES_SIZE, - SIMPLE_EXAMPLES_FETCH_SIZE, -} from '../../constants'; -import { - canProvideStatsForFieldTextBased, - canProvideTopValuesForFieldTextBased, - canProvideExamplesForField, -} from '../../utils/can_provide_stats'; -import { getFieldExampleBuckets } from '../field_examples_calculator'; - -export type SearchHandlerTextBased = ({ query }: { query: string }) => Promise; - -export function buildSearchFilter({ - timeFieldName, - fromDate, - toDate, -}: { - timeFieldName?: string; - fromDate: string; - toDate: string; -}) { - return timeFieldName - ? { - range: { - [timeFieldName]: { - gte: fromDate, - lte: toDate, - format: 'strict_date_optional_time', - }, - }, - } - : null; -} - -interface FetchAndCalculateFieldStatsParams { - searchHandler: SearchHandlerTextBased; - field: DataViewField; - esqlBaseQuery: string; -} - -export async function fetchAndCalculateFieldStats(params: FetchAndCalculateFieldStatsParams) { - const { field } = params; - if (!canProvideStatsForFieldTextBased(field)) { - return {}; - } - if (field.type === 'boolean') { - return await getStringTopValues(params, 3); - } - if (canProvideTopValuesForFieldTextBased(field)) { - return await getStringTopValues(params); - } - if (canProvideExamplesForField(field, true)) { - return await getSimpleTextExamples(params); - } - - return {}; -} - -export async function getStringTopValues( - params: FetchAndCalculateFieldStatsParams, - size = DEFAULT_TOP_VALUES_SIZE -): Promise> { - const { searchHandler, field, esqlBaseQuery } = params; - const safeEsqlFieldName = getSafeESQLFieldName(field.name); - const safeEsqlFieldNameTerms = getSafeESQLFieldName(`${field.name}_terms`); - const esqlQuery = appendToESQLQuery( - esqlBaseQuery, - `| WHERE ${safeEsqlFieldName} IS NOT NULL - | STATS ${safeEsqlFieldNameTerms} = count(${safeEsqlFieldName}) BY ${safeEsqlFieldName} - | SORT ${safeEsqlFieldNameTerms} DESC - | LIMIT ${size}` - ); - - const result = await searchHandler({ query: esqlQuery }); - const values = result?.values as Array<[number, string]>; - - if (!values?.length) { - return {}; - } - - const sampledValues = values?.reduce((acc: number, row) => acc + row[0], 0); - - const topValues = { - buckets: values.map((value) => ({ - count: value[0], - key: value[1], - })), - }; - - return { - totalDocuments: sampledValues, - sampledDocuments: sampledValues, - sampledValues, - topValues, - }; -} - -export async function getSimpleTextExamples( - params: FetchAndCalculateFieldStatsParams -): Promise> { - const { searchHandler, field, esqlBaseQuery } = params; - const safeEsqlFieldName = getSafeESQLFieldName(field.name); - const esqlQuery = appendToESQLQuery( - esqlBaseQuery, - `| WHERE ${safeEsqlFieldName} IS NOT NULL - | KEEP ${safeEsqlFieldName} - | LIMIT ${SIMPLE_EXAMPLES_FETCH_SIZE}` - ); - - const result = await searchHandler({ query: esqlQuery }); - const values = ((result?.values as Array<[string | string[]]>) || []).map((value) => - Array.isArray(value) && value.length === 1 ? value[0] : value - ); - - if (!values?.length) { - return {}; - } - - const sampledDocuments = values?.length; - - const fieldExampleBuckets = getFieldExampleBuckets({ - values, - field, - count: DEFAULT_SIMPLE_EXAMPLES_SIZE, - isTextBased: true, - }); - - return { - totalDocuments: sampledDocuments, - sampledDocuments: fieldExampleBuckets.sampledDocuments, - sampledValues: fieldExampleBuckets.sampledValues, - topValues: { - buckets: fieldExampleBuckets.buckets, - areExamples: true, - }, - }; -} - -function getSafeESQLFieldName(str: string): string { - return `\`${str}\``; -} diff --git a/packages/kbn-unified-field-list/src/services/field_stats_text_based/load_field_stats_text_based.ts b/packages/kbn-unified-field-list/src/services/field_stats_text_based/load_field_stats_text_based.ts deleted file mode 100644 index 5f77f15906896..0000000000000 --- a/packages/kbn-unified-field-list/src/services/field_stats_text_based/load_field_stats_text_based.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import type { DataView, DataViewField } from '@kbn/data-views-plugin/common'; -import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import type { AggregateQuery } from '@kbn/es-query'; -import { getESQLWithSafeLimit, getESQLResults } from '@kbn/esql-utils'; -import type { FieldStatsResponse } from '../../types'; -import { - buildSearchFilter, - SearchHandlerTextBased, - fetchAndCalculateFieldStats, -} from './field_stats_utils_text_based'; -import { ESQL_SAFE_LIMIT } from '../../constants'; - -interface FetchFieldStatsParamsTextBased { - services: { - data: DataPublicPluginStart; - }; - dataView: DataView; - field: DataViewField; - fromDate: string; - toDate: string; - baseQuery: AggregateQuery; - abortController?: AbortController; -} - -export type LoadFieldStatsTextBasedHandler = ( - params: FetchFieldStatsParamsTextBased -) => Promise>; - -/** - * Loads and aggregates stats data for an ES|QL query field - * @param services - * @param dataView - * @param field - * @param fromDate - * @param toDate - * @param baseQuery - * @param abortController - */ -export const loadFieldStatsTextBased: LoadFieldStatsTextBasedHandler = async ({ - services, - dataView, - field, - fromDate, - toDate, - baseQuery, - abortController, -}) => { - const { data } = services; - - try { - if (!dataView?.id || !field?.type) { - return {}; - } - - const searchHandler: SearchHandlerTextBased = async ({ query }) => { - const filter = buildSearchFilter({ timeFieldName: dataView.timeFieldName, fromDate, toDate }); - const result = await getESQLResults({ - esqlQuery: query, - filter, - search: data.search.search, - signal: abortController?.signal, - timeRange: { from: fromDate, to: toDate }, - }); - return result.response; - }; - - if (!('esql' in baseQuery)) { - throw new Error('query must be of type AggregateQuery'); - } - - return await fetchAndCalculateFieldStats({ - searchHandler, - field, - esqlBaseQuery: getESQLWithSafeLimit(baseQuery.esql, ESQL_SAFE_LIMIT), - }); - } catch (error) { - // console.error(error); - throw new Error('Could not provide field stats', { cause: error }); - } -}; diff --git a/packages/kbn-unified-field-list/src/utils/can_provide_stats.test.ts b/packages/kbn-unified-field-list/src/utils/can_provide_stats.test.ts index 297e1e26c8c56..c27a44494a3e7 100644 --- a/packages/kbn-unified-field-list/src/utils/can_provide_stats.test.ts +++ b/packages/kbn-unified-field-list/src/utils/can_provide_stats.test.ts @@ -10,7 +10,7 @@ import { canProvideStatsForField, canProvideExamplesForField, - canProvideStatsForFieldTextBased, + canProvideStatsForEsqlField, } from './can_provide_stats'; import type { DataViewField } from '@kbn/data-views-plugin/common'; import { stubLogstashDataView as dataView } from '@kbn/data-views-plugin/common/data_view.stub'; @@ -34,40 +34,12 @@ describe('can_provide_stats', function () { ); }); - it('works for text based columns', function () { + it('should not work for ES|QL columns', function () { expect( canProvideStatsForField( { name: 'message', type: 'string', esTypes: ['text'] } as DataViewField, true ) - ).toBe(true); - expect( - canProvideStatsForField( - { name: 'message', type: 'string', esTypes: ['keyword'] } as DataViewField, - true - ) - ).toBe(true); - expect( - canProvideStatsForField({ name: 'message', type: 'number' } as DataViewField, true) - ).toBe(true); - expect( - canProvideStatsForField({ name: 'message', type: 'boolean' } as DataViewField, true) - ).toBe(true); - expect(canProvideStatsForField({ name: 'message', type: 'ip' } as DataViewField, true)).toBe( - true - ); - expect( - canProvideStatsForField({ name: 'message', type: 'geo_point' } as DataViewField, true) - ).toBe(true); - expect( - canProvideStatsForField( - { name: '_id', type: 'string', esTypes: ['keyword'] } as DataViewField, - true - ) - ).toBe(true); - - expect( - canProvideStatsForField({ name: 'message', type: 'date' } as DataViewField, true) ).toBe(false); }); }); @@ -82,83 +54,24 @@ describe('can_provide_stats', function () { ); }); - it('works for text based columns', function () { + it('should not work for ES|QL columns', function () { expect( canProvideExamplesForField( { name: 'message', type: 'string', esTypes: ['text'] } as DataViewField, true ) - ).toBe(true); - expect( - canProvideExamplesForField( - { name: 'message', type: 'string', esTypes: ['keyword'] } as DataViewField, - true - ) - ).toBe(false); - expect( - canProvideExamplesForField({ name: 'message', type: 'number' } as DataViewField, true) - ).toBe(false); - expect( - canProvideExamplesForField({ name: 'message', type: 'boolean' } as DataViewField, true) ).toBe(false); - expect( - canProvideExamplesForField({ name: 'message', type: 'ip' } as DataViewField, true) - ).toBe(false); - expect( - canProvideExamplesForField({ name: 'message', type: 'geo_point' } as DataViewField, true) - ).toBe(true); - expect( - canProvideExamplesForField({ name: 'message', type: 'date' } as DataViewField, true) - ).toBe(false); - expect( - canProvideStatsForField( - { name: '_id', type: 'string', esTypes: ['keyword'] } as DataViewField, - true - ) - ).toBe(true); }); - describe('canProvideStatsForFieldTextBased', function () { - it('works for text based columns', function () { + describe('canProvideStatsForEsqlField', function () { + it('should not work for ES|QL columns', function () { expect( - canProvideStatsForFieldTextBased({ + canProvideStatsForEsqlField({ name: 'message', type: 'string', esTypes: ['text'], } as DataViewField) - ).toBe(true); - expect( - canProvideStatsForFieldTextBased({ - name: 'message', - type: 'string', - esTypes: ['keyword'], - } as DataViewField) - ).toBe(true); - expect( - canProvideStatsForFieldTextBased({ name: 'message', type: 'number' } as DataViewField) - ).toBe(true); - expect( - canProvideStatsForFieldTextBased({ name: 'message', type: 'boolean' } as DataViewField) - ).toBe(true); - expect( - canProvideStatsForFieldTextBased({ name: 'message', type: 'ip' } as DataViewField) - ).toBe(true); - expect( - canProvideStatsForFieldTextBased({ name: 'message', type: 'ip_range' } as DataViewField) ).toBe(false); - expect( - canProvideStatsForFieldTextBased({ name: 'message', type: 'geo_point' } as DataViewField) - ).toBe(true); - expect( - canProvideStatsForFieldTextBased({ name: 'message', type: 'date' } as DataViewField) - ).toBe(false); - expect( - canProvideStatsForFieldTextBased({ - name: '_id', - type: 'string', - esTypes: ['keyword'], - } as DataViewField) - ).toBe(true); }); }); }); diff --git a/packages/kbn-unified-field-list/src/utils/can_provide_stats.ts b/packages/kbn-unified-field-list/src/utils/can_provide_stats.ts index e84137fa17f2e..c3fd9734de5e3 100644 --- a/packages/kbn-unified-field-list/src/utils/can_provide_stats.ts +++ b/packages/kbn-unified-field-list/src/utils/can_provide_stats.ts @@ -9,22 +9,22 @@ import type { DataViewField } from '@kbn/data-views-plugin/common'; -export function canProvideStatsForField(field: DataViewField, isTextBased: boolean): boolean { - if (isTextBased) { - return canProvideStatsForFieldTextBased(field); +export function canProvideStatsForField(field: DataViewField, isEsqlQuery: boolean): boolean { + if (isEsqlQuery) { + return false; } return ( - (field.aggregatable && canProvideAggregatedStatsForField(field, isTextBased)) || + (field.aggregatable && canProvideAggregatedStatsForField(field, isEsqlQuery)) || ((!field.aggregatable || field.type === 'geo_point' || field.type === 'geo_shape') && - canProvideExamplesForField(field, isTextBased)) + canProvideExamplesForField(field, isEsqlQuery)) ); } export function canProvideAggregatedStatsForField( field: DataViewField, - isTextBased: boolean + isEsqlQuery: boolean ): boolean { - if (isTextBased) { + if (isEsqlQuery) { return false; } return !( @@ -39,20 +39,17 @@ export function canProvideAggregatedStatsForField( export function canProvideNumberSummaryForField( field: DataViewField, - isTextBased: boolean + isEsqlQuery: boolean ): boolean { - if (isTextBased) { + if (isEsqlQuery) { return false; } return field.timeSeriesMetric === 'counter'; } -export function canProvideExamplesForField(field: DataViewField, isTextBased: boolean): boolean { - if (isTextBased) { - return ( - (field.type === 'string' && !canProvideTopValuesForFieldTextBased(field)) || - ['geo_point', 'geo_shape'].includes(field.type) - ); +export function canProvideExamplesForField(field: DataViewField, isEsqlQuery: boolean): boolean { + if (isEsqlQuery) { + return false; } if (field.name === '_score') { return false; @@ -69,17 +66,6 @@ export function canProvideExamplesForField(field: DataViewField, isTextBased: bo ].includes(field.type); } -export function canProvideTopValuesForFieldTextBased(field: DataViewField): boolean { - if (field.name === '_id') { - return false; - } - const esTypes = field.esTypes?.[0]; - return ( - Boolean(field.type === 'string' && esTypes && ['keyword', 'version'].includes(esTypes)) || - ['keyword', 'version', 'ip', 'number', 'boolean'].includes(field.type) - ); -} - -export function canProvideStatsForFieldTextBased(field: DataViewField): boolean { - return canProvideTopValuesForFieldTextBased(field) || canProvideExamplesForField(field, true); +export function canProvideStatsForEsqlField(field: DataViewField): boolean { + return false; } diff --git a/packages/kbn-unified-field-list/tsconfig.json b/packages/kbn-unified-field-list/tsconfig.json index 830e56ac6ab00..54b67143b7c7b 100644 --- a/packages/kbn-unified-field-list/tsconfig.json +++ b/packages/kbn-unified-field-list/tsconfig.json @@ -32,7 +32,6 @@ "@kbn/shared-ux-button-toolbar", "@kbn/field-utils", "@kbn/visualization-utils", - "@kbn/esql-utils", "@kbn/search-types", "@kbn/fields-metadata-plugin", "@kbn/ui-theme" diff --git a/packages/shared-ux/page/analytics_no_data/impl/kibana.jsonc b/packages/shared-ux/page/analytics_no_data/impl/kibana.jsonc index b8690de58bdb9..45c7a028be286 100644 --- a/packages/shared-ux/page/analytics_no_data/impl/kibana.jsonc +++ b/packages/shared-ux/page/analytics_no_data/impl/kibana.jsonc @@ -1,5 +1,7 @@ { "type": "shared-browser", "id": "@kbn/shared-ux-page-analytics-no-data", - "owner": "@elastic/appex-sharedux" + "owner": "@elastic/appex-sharedux", + "group": "platform", + "visibility": "private" } diff --git a/packages/shared-ux/page/analytics_no_data/mocks/kibana.jsonc b/packages/shared-ux/page/analytics_no_data/mocks/kibana.jsonc index cde1666e15f14..e7d570e4239e6 100644 --- a/packages/shared-ux/page/analytics_no_data/mocks/kibana.jsonc +++ b/packages/shared-ux/page/analytics_no_data/mocks/kibana.jsonc @@ -1,5 +1,7 @@ { "type": "shared-common", "id": "@kbn/shared-ux-page-analytics-no-data-mocks", - "owner": "@elastic/appex-sharedux" + "owner": "@elastic/appex-sharedux", + "group": "platform", + "visibility": "private" } diff --git a/packages/shared-ux/page/analytics_no_data/types/kibana.jsonc b/packages/shared-ux/page/analytics_no_data/types/kibana.jsonc index df5498181fe69..fd1740c0d757e 100644 --- a/packages/shared-ux/page/analytics_no_data/types/kibana.jsonc +++ b/packages/shared-ux/page/analytics_no_data/types/kibana.jsonc @@ -1,5 +1,7 @@ { "type": "shared-browser", "id": "@kbn/shared-ux-page-analytics-no-data-types", - "owner": "@elastic/appex-sharedux" + "owner": "@elastic/appex-sharedux", + "group": "platform", + "visibility": "private" } diff --git a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts index aedc9e3364c8f..7fcf5dada3273 100644 --- a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts +++ b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts @@ -73,7 +73,7 @@ describe('checking migration metadata changes on all registered SO types', () => "canvas-element": "cdedc2123eb8a1506b87a56b0bcce60f4ec08bc8", "canvas-workpad": "9d82aafb19586b119e5c9382f938abe28c26ca5c", "canvas-workpad-template": "c077b0087346776bb3542b51e1385d172cb24179", - "cases": "2392189ed338857d4815a4cef6051f9ad124d39d", + "cases": "5433a9f1277f8f17bbc4fd20d33b1fc6d997931e", "cases-comments": "5cb0a421588831c2a950e50f486048b8aabbae25", "cases-configure": "44ed7b8e0f44df39516b8870589b89e32224d2bf", "cases-connector-mappings": "f9d1ac57e484e69506c36a8051e4d61f4a8cfd25", @@ -165,6 +165,7 @@ describe('checking migration metadata changes on all registered SO types', () => "synthetics-dynamic-settings": "4b40a93eb3e222619bf4e7fe34a9b9e7ab91a0a7", "synthetics-monitor": "5ceb25b6249bd26902c9b34273c71c3dce06dbea", "synthetics-param": "3ebb744e5571de678b1312d5c418c8188002cf5e", + "synthetics-private-location": "8cecc9e4f39637d2f8244eb7985c0690ceab24be", "synthetics-privates-locations": "f53d799d5c9bc8454aaa32c6abc99a899b025d5c", "tag": "e2544392fe6563e215bb677abc8b01c2601ef2dc", "task": "3c89a7c918d5b896a5f8800f06e9114ad7e7aea3", diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts index e95a82e63d0ff..ba06073e454a9 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts @@ -139,6 +139,7 @@ const previouslyRegisteredTypes = [ 'synthetics-monitor', 'synthetics-param', 'synthetics-privates-locations', + 'synthetics-private-location', 'tag', 'task', 'telemetry', diff --git a/src/plugins/advanced_settings/kibana.jsonc b/src/plugins/advanced_settings/kibana.jsonc index c0a338935a590..795827e204aa0 100644 --- a/src/plugins/advanced_settings/kibana.jsonc +++ b/src/plugins/advanced_settings/kibana.jsonc @@ -1,11 +1,16 @@ { "type": "plugin", "id": "@kbn/advanced-settings-plugin", - "owner": "@elastic/appex-sharedux @elastic/kibana-management", + "owner": [ + "@elastic/appex-sharedux", + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "private", "plugin": { "id": "advancedSettings", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "management" ], @@ -13,7 +18,6 @@ "home", "usageCollection" ], - "requiredBundles": [ - ] + "requiredBundles": [] } -} +} \ No newline at end of file diff --git a/src/plugins/ai_assistant_management/selection/kibana.jsonc b/src/plugins/ai_assistant_management/selection/kibana.jsonc index 2e653bb391c34..74640423685a9 100644 --- a/src/plugins/ai_assistant_management/selection/kibana.jsonc +++ b/src/plugins/ai_assistant_management/selection/kibana.jsonc @@ -1,16 +1,29 @@ { "type": "plugin", "id": "@kbn/ai-assistant-management-plugin", - "owner": "@elastic/obs-knowledge-team", + "owner": [ + "@elastic/obs-knowledge-team" + ], + // This should probably be platform. While the code owner is currently observability, the package is a platform AI assistant selector. + "group": "platform", + "visibility": "shared", "plugin": { "id": "aiAssistantManagementSelection", - "server": true, "browser": true, - "requiredPlugins": ["management"], - "optionalPlugins": ["home", "serverless", "features"], - "requiredBundles": ["kibanaReact"], + "server": true, "configPath": [ "aiAssistantManagementSelection" ], - }, + "requiredPlugins": [ + "management" + ], + "optionalPlugins": [ + "home", + "serverless", + "features" + ], + "requiredBundles": [ + "kibanaReact" + ] + } } diff --git a/src/plugins/bfetch/kibana.jsonc b/src/plugins/bfetch/kibana.jsonc index 97d9571238296..39a8866f3b79b 100644 --- a/src/plugins/bfetch/kibana.jsonc +++ b/src/plugins/bfetch/kibana.jsonc @@ -1,14 +1,18 @@ { "type": "plugin", "id": "@kbn/bfetch-plugin", - "owner": "@elastic/appex-sharedux", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared", "description": "Considering using bfetch capabilities when fetching large amounts of data. This services supports batching HTTP requests and streaming responses back.", "plugin": { "id": "bfetch", - "server": true, "browser": true, + "server": true, "requiredBundles": [ "kibanaUtils" ] } -} +} \ No newline at end of file diff --git a/src/plugins/chart_expressions/common/kibana.jsonc b/src/plugins/chart_expressions/common/kibana.jsonc index 546179cce219c..f3d05f4a0581e 100644 --- a/src/plugins/chart_expressions/common/kibana.jsonc +++ b/src/plugins/chart_expressions/common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/chart-expressions-common", - "owner": "@elastic/kibana-visualizations" -} + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/src/plugins/chart_expressions/expression_gauge/kibana.jsonc b/src/plugins/chart_expressions/expression_gauge/kibana.jsonc index 6f3182e033d6a..70d29fec6336a 100644 --- a/src/plugins/chart_expressions/expression_gauge/kibana.jsonc +++ b/src/plugins/chart_expressions/expression_gauge/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/expression-gauge-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "shared", "description": "Expression Gauge plugin adds a `gauge` renderer and function to the expression plugin. The renderer will display the `gauge` chart.", "plugin": { "id": "expressionGauge", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "expressions", "fieldFormats", @@ -25,4 +29,4 @@ "common" ] } -} +} \ No newline at end of file diff --git a/src/plugins/chart_expressions/expression_heatmap/kibana.jsonc b/src/plugins/chart_expressions/expression_heatmap/kibana.jsonc index aca569c8f606d..5852e882efe5d 100644 --- a/src/plugins/chart_expressions/expression_heatmap/kibana.jsonc +++ b/src/plugins/chart_expressions/expression_heatmap/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/expression-heatmap-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "shared", "description": "Expression Heatmap plugin adds a `heatmap` renderer and function to the expression plugin. The renderer will display the `heatmap` chart.", "plugin": { "id": "expressionHeatmap", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "expressions", "fieldFormats", @@ -25,4 +29,4 @@ "common" ] } -} +} \ No newline at end of file diff --git a/src/plugins/chart_expressions/expression_legacy_metric/kibana.jsonc b/src/plugins/chart_expressions/expression_legacy_metric/kibana.jsonc index b0d916119fd73..88fdca99e016b 100644 --- a/src/plugins/chart_expressions/expression_legacy_metric/kibana.jsonc +++ b/src/plugins/chart_expressions/expression_legacy_metric/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/expression-legacy-metric-vis-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "shared", "description": "Adds a `metric` renderer and function to the expression plugin. The renderer will display the `legacy metric` chart.", "plugin": { "id": "expressionLegacyMetricVis", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "expressions", "fieldFormats", @@ -22,4 +26,4 @@ "kibanaUtils" ] } -} +} \ No newline at end of file diff --git a/src/plugins/chart_expressions/expression_metric/kibana.jsonc b/src/plugins/chart_expressions/expression_metric/kibana.jsonc index c8c6f6b0c8565..2f65e12b11999 100644 --- a/src/plugins/chart_expressions/expression_metric/kibana.jsonc +++ b/src/plugins/chart_expressions/expression_metric/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/expression-metric-vis-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "shared", "description": "Adds a `metric` renderer and function to the expression plugin. The renderer will display the `metric` chart.", "plugin": { "id": "expressionMetricVis", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "expressions", "fieldFormats", @@ -22,4 +26,4 @@ "kibanaUtils" ] } -} +} \ No newline at end of file diff --git a/src/plugins/chart_expressions/expression_partition_vis/kibana.jsonc b/src/plugins/chart_expressions/expression_partition_vis/kibana.jsonc index f69f934fc3005..3ac2e44a23d97 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/kibana.jsonc +++ b/src/plugins/chart_expressions/expression_partition_vis/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/expression-partition-vis-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "shared", "description": "Expression Partition Visualization plugin adds a `partitionVis` renderer and `pieVis`, `mosaicVis`, `treemapVis`, `waffleVis` functions to the expression plugin. The renderer will display the `pie`, `waffle`, `treemap` and `mosaic` charts.", "plugin": { "id": "expressionPartitionVis", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "charts", "data", @@ -25,4 +29,4 @@ "common" ] } -} +} \ No newline at end of file diff --git a/src/plugins/chart_expressions/expression_tagcloud/kibana.jsonc b/src/plugins/chart_expressions/expression_tagcloud/kibana.jsonc index 4cb1898caaf43..a6b71200a4620 100644 --- a/src/plugins/chart_expressions/expression_tagcloud/kibana.jsonc +++ b/src/plugins/chart_expressions/expression_tagcloud/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/expression-tagcloud-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "shared", "description": "Expression Tagcloud plugin adds a `tagcloud` renderer and function to the expression plugin. The renderer will display the `Wordcloud` chart.", "plugin": { "id": "expressionTagcloud", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "data", "expressions", @@ -25,4 +29,4 @@ "common" ] } -} +} \ No newline at end of file diff --git a/src/plugins/chart_expressions/expression_xy/kibana.jsonc b/src/plugins/chart_expressions/expression_xy/kibana.jsonc index 80a414b5e4d0a..7f819610d7e32 100644 --- a/src/plugins/chart_expressions/expression_xy/kibana.jsonc +++ b/src/plugins/chart_expressions/expression_xy/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/expression-xy-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "shared", "description": "Expression XY plugin adds a `xy` renderer and function to the expression plugin. The renderer will display the `xy` chart.", "plugin": { "id": "expressionXY", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "expressions", "charts", @@ -21,4 +25,4 @@ ], "requiredBundles": [] } -} +} \ No newline at end of file diff --git a/src/plugins/charts/kibana.jsonc b/src/plugins/charts/kibana.jsonc index 8c00cd40f4ad3..16475bdda3b9f 100644 --- a/src/plugins/charts/kibana.jsonc +++ b/src/plugins/charts/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/charts-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "charts", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "expressions", "data" @@ -14,4 +18,4 @@ "common" ] } -} +} \ No newline at end of file diff --git a/src/plugins/console/kibana.jsonc b/src/plugins/console/kibana.jsonc index ae0cac514b67d..a57eb8f3eb3d1 100644 --- a/src/plugins/console/kibana.jsonc +++ b/src/plugins/console/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/console-plugin", - "owner": "@elastic/kibana-management", + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "console", - "server": true, "browser": true, + "server": true, "configPath": [ "console" ], @@ -23,4 +27,4 @@ "kibanaUtils" ] } -} +} \ No newline at end of file diff --git a/src/plugins/console/public/services/autocomplete.ts b/src/plugins/console/public/services/autocomplete.ts index d7c4d56d5b704..32ef978c8d587 100644 --- a/src/plugins/console/public/services/autocomplete.ts +++ b/src/plugins/console/public/services/autocomplete.ts @@ -53,7 +53,10 @@ export class AutocompleteInfo { case ENTITIES.INDICES: const includeAliases = true; const collaborator = this.mapping; - return () => this.alias.getIndices(includeAliases, collaborator); + return () => [ + ...this.alias.getIndices(includeAliases, collaborator), + ...this.dataStream.getDataStreams(), + ]; case ENTITIES.FIELDS: return this.mapping.getMappings( context.indices, diff --git a/src/plugins/console/server/routes/api/console/autocomplete_entities/index.ts b/src/plugins/console/server/routes/api/console/autocomplete_entities/index.ts index bf63f2048d66f..0dacd8e93cc9b 100644 --- a/src/plugins/console/server/routes/api/console/autocomplete_entities/index.ts +++ b/src/plugins/console/server/routes/api/console/autocomplete_entities/index.ts @@ -43,7 +43,10 @@ const getAliases = async (settings: SettingsToRetrieve, esClient: IScopedCluster const getDataStreams = async (settings: SettingsToRetrieve, esClient: IScopedClusterClient) => { if (settings.dataStreams) { - const dataStreams = await esClient.asCurrentUser.indices.getDataStream(); + const dataStreams = await esClient.asCurrentUser.indices.getDataStream({ + name: '*', + expand_wildcards: 'all', + }); return dataStreams; } // If the user doesn't want autocomplete suggestions, then clear any that exist. diff --git a/src/plugins/content_management/kibana.jsonc b/src/plugins/content_management/kibana.jsonc index 7ebfe75180658..a2d43504b52b2 100644 --- a/src/plugins/content_management/kibana.jsonc +++ b/src/plugins/content_management/kibana.jsonc @@ -1,14 +1,18 @@ { "type": "plugin", "id": "@kbn/content-management-plugin", - "owner": "@elastic/appex-sharedux", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared", "description": "Content management app", "plugin": { "id": "contentManagement", - "server": true, "browser": true, + "server": true, "optionalPlugins": [ "usageCollection" ] } -} +} \ No newline at end of file diff --git a/src/plugins/controls/common/constants.ts b/src/plugins/controls/common/constants.ts index e375a7b2315bc..d1434d4df2ae0 100644 --- a/src/plugins/controls/common/constants.ts +++ b/src/plugins/controls/common/constants.ts @@ -7,11 +7,25 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +import { ControlGroupChainingSystem } from './control_group'; import { ControlLabelPosition, ControlWidth } from './types'; -export const DEFAULT_CONTROL_WIDTH: ControlWidth = 'medium'; +export const CONTROL_WIDTH_OPTIONS = { SMALL: 'small', MEDIUM: 'medium', LARGE: 'large' } as const; +export const CONTROL_LABEL_POSITION_OPTIONS = { ONE_LINE: 'oneLine', TWO_LINE: 'twoLine' } as const; +export const CONTROL_CHAINING_OPTIONS = { NONE: 'NONE', HIERARCHICAL: 'HIERARCHICAL' } as const; +export const DEFAULT_CONTROL_WIDTH: ControlWidth = CONTROL_WIDTH_OPTIONS.MEDIUM; +export const DEFAULT_CONTROL_LABEL_POSITION: ControlLabelPosition = + CONTROL_LABEL_POSITION_OPTIONS.ONE_LINE; export const DEFAULT_CONTROL_GROW: boolean = true; -export const DEFAULT_CONTROL_LABEL_POSITION: ControlLabelPosition = 'oneLine'; +export const DEFAULT_CONTROL_CHAINING: ControlGroupChainingSystem = + CONTROL_CHAINING_OPTIONS.HIERARCHICAL; +export const DEFAULT_IGNORE_PARENT_SETTINGS = { + ignoreFilters: false, + ignoreQuery: false, + ignoreTimerange: false, + ignoreValidations: false, +} as const; +export const DEFAULT_AUTO_APPLY_SELECTIONS = true; export const TIME_SLIDER_CONTROL = 'timeSlider'; export const RANGE_SLIDER_CONTROL = 'rangeSliderControl'; diff --git a/src/plugins/controls/common/control_group/types.ts b/src/plugins/controls/common/control_group/types.ts index eb47d8b13eb79..ff1e4455046b8 100644 --- a/src/plugins/controls/common/control_group/types.ts +++ b/src/plugins/controls/common/control_group/types.ts @@ -9,10 +9,12 @@ import { DataViewField } from '@kbn/data-views-plugin/common'; import { ControlLabelPosition, DefaultControlState, ParentIgnoreSettings } from '../types'; +import { CONTROL_CHAINING_OPTIONS } from '../constants'; export const CONTROL_GROUP_TYPE = 'control_group'; -export type ControlGroupChainingSystem = 'HIERARCHICAL' | 'NONE'; +export type ControlGroupChainingSystem = + (typeof CONTROL_CHAINING_OPTIONS)[keyof typeof CONTROL_CHAINING_OPTIONS]; export type FieldFilterPredicate = (f: DataViewField) => boolean; @@ -45,15 +47,11 @@ export interface ControlGroupRuntimeState { - panelsJSON: string; // stringified version of ControlSerializedState - ignoreParentSettingsJSON: string; - // In runtime state, we refer to this property as `labelPosition`; - // to avoid migrations, we will continue to refer to this property as `controlStyle` in the serialized state - controlStyle: ControlLabelPosition; - // In runtime state, we refer to the inverse of this property as `autoApplySelections` - // to avoid migrations, we will continue to refer to this property as `showApplySelections` in the serialized state - showApplySelections?: boolean; + extends Omit { + // In runtime state, we refer to this property as `initialChildControlState`, but in + // the serialized state we transform the state object into an array of state objects + // to make it easier for API consumers to add new controls without specifying a uuid key. + controls: Array; } /** diff --git a/src/plugins/controls/common/index.ts b/src/plugins/controls/common/index.ts index dd9c56778bb68..031d3b348272f 100644 --- a/src/plugins/controls/common/index.ts +++ b/src/plugins/controls/common/index.ts @@ -17,9 +17,15 @@ export type { } from './types'; export { + DEFAULT_CONTROL_CHAINING, DEFAULT_CONTROL_GROW, DEFAULT_CONTROL_LABEL_POSITION, DEFAULT_CONTROL_WIDTH, + DEFAULT_IGNORE_PARENT_SETTINGS, + DEFAULT_AUTO_APPLY_SELECTIONS, + CONTROL_WIDTH_OPTIONS, + CONTROL_CHAINING_OPTIONS, + CONTROL_LABEL_POSITION_OPTIONS, OPTIONS_LIST_CONTROL, RANGE_SLIDER_CONTROL, TIME_SLIDER_CONTROL, diff --git a/src/plugins/controls/common/types.ts b/src/plugins/controls/common/types.ts index d3a6261aeb9da..d38ca80cb3815 100644 --- a/src/plugins/controls/common/types.ts +++ b/src/plugins/controls/common/types.ts @@ -7,12 +7,16 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export type ControlWidth = 'small' | 'medium' | 'large'; -export type ControlLabelPosition = 'twoLine' | 'oneLine'; +import { SerializableRecord } from '@kbn/utility-types'; +import { CONTROL_LABEL_POSITION_OPTIONS, CONTROL_WIDTH_OPTIONS } from './constants'; + +export type ControlWidth = (typeof CONTROL_WIDTH_OPTIONS)[keyof typeof CONTROL_WIDTH_OPTIONS]; +export type ControlLabelPosition = + (typeof CONTROL_LABEL_POSITION_OPTIONS)[keyof typeof CONTROL_LABEL_POSITION_OPTIONS]; export type TimeSlice = [number, number]; -export interface ParentIgnoreSettings { +export interface ParentIgnoreSettings extends SerializableRecord { ignoreFilters?: boolean; ignoreQuery?: boolean; ignoreTimerange?: boolean; diff --git a/src/plugins/controls/kibana.jsonc b/src/plugins/controls/kibana.jsonc index add8c14ee3391..76fb9f7960412 100644 --- a/src/plugins/controls/kibana.jsonc +++ b/src/plugins/controls/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/controls-plugin", - "owner": "@elastic/kibana-presentation", + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "shared", "description": "The Controls Plugin contains embeddable components intended to create a simple query interface for end users, and a powerful editing suite that allows dashboard authors to build controls", "plugin": { "id": "controls", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "presentationUtil", "embeddable", @@ -15,7 +19,9 @@ "unifiedSearch", "uiActions" ], - "extraPublicDirs": ["common"], - "requiredBundles": [] + "requiredBundles": [], + "extraPublicDirs": [ + "common" + ] } -} +} \ No newline at end of file diff --git a/src/plugins/controls/public/control_group/control_group_renderer/control_group_renderer.tsx b/src/plugins/controls/public/control_group/control_group_renderer/control_group_renderer.tsx index 6a50c60c4e597..1a05d4b25e22c 100644 --- a/src/plugins/controls/public/control_group/control_group_renderer/control_group_renderer.tsx +++ b/src/plugins/controls/public/control_group/control_group_renderer/control_group_renderer.tsx @@ -19,8 +19,11 @@ import { useSearchApi, type ViewMode as ViewModeType } from '@kbn/presentation-p import type { ControlGroupApi } from '../..'; import { CONTROL_GROUP_TYPE, + DEFAULT_CONTROL_LABEL_POSITION, type ControlGroupRuntimeState, type ControlGroupSerializedState, + DEFAULT_CONTROL_CHAINING, + DEFAULT_AUTO_APPLY_SELECTIONS, } from '../../../common'; import { type ControlGroupStateBuilder, @@ -136,16 +139,19 @@ export const ControlGroupRenderer = ({ ...initialState, editorConfig, }); - const state = { - ...omit(initialState, ['initialChildControlState', 'ignoreParentSettings']), + const state: ControlGroupSerializedState = { + ...omit(initialState, ['initialChildControlState']), editorConfig, - controlStyle: initialState?.labelPosition, - panelsJSON: JSON.stringify(initialState?.initialChildControlState ?? {}), - ignoreParentSettingsJSON: JSON.stringify(initialState?.ignoreParentSettings ?? {}), + autoApplySelections: initialState?.autoApplySelections ?? DEFAULT_AUTO_APPLY_SELECTIONS, + labelPosition: initialState?.labelPosition ?? DEFAULT_CONTROL_LABEL_POSITION, + chainingSystem: initialState?.chainingSystem ?? DEFAULT_CONTROL_CHAINING, + controls: Object.entries(initialState?.initialChildControlState ?? {}).map( + ([controlId, value]) => ({ ...value, id: controlId }) + ), }; if (!cancelled) { - setSerializedState(state as ControlGroupSerializedState); + setSerializedState(state); } })(); return () => { diff --git a/src/plugins/controls/public/control_group/get_control_group_factory.tsx b/src/plugins/controls/public/control_group/get_control_group_factory.tsx index 62af1d1f868a9..c8ee296d8a305 100644 --- a/src/plugins/controls/public/control_group/get_control_group_factory.tsx +++ b/src/plugins/controls/public/control_group/get_control_group_factory.tsx @@ -33,7 +33,11 @@ import type { ControlPanelsState, ParentIgnoreSettings, } from '../../common'; -import { CONTROL_GROUP_TYPE, DEFAULT_CONTROL_LABEL_POSITION } from '../../common'; +import { + CONTROL_GROUP_TYPE, + DEFAULT_CONTROL_CHAINING, + DEFAULT_CONTROL_LABEL_POSITION, +} from '../../common'; import { openDataControlEditor } from '../controls/data_controls/open_data_control_editor'; import { coreServices, dataViewsService } from '../services/kibana_services'; import { ControlGroup } from './components/control_group'; @@ -45,8 +49,6 @@ import { initSelectionsManager } from './selections_manager'; import type { ControlGroupApi } from './types'; import { deserializeControlGroup } from './utils/serialization_utils'; -const DEFAULT_CHAINING_SYSTEM = 'HIERARCHICAL'; - export const getControlGroupEmbeddableFactory = () => { const controlGroupEmbeddableFactory: ReactEmbeddableFactory< ControlGroupSerializedState, @@ -85,7 +87,7 @@ export const getControlGroupEmbeddableFactory = () => { }); const dataViews = new BehaviorSubject(undefined); const chainingSystem$ = new BehaviorSubject( - chainingSystem ?? DEFAULT_CHAINING_SYSTEM + chainingSystem ?? DEFAULT_CONTROL_CHAINING ); const ignoreParentSettings$ = new BehaviorSubject( ignoreParentSettings @@ -108,7 +110,7 @@ export const getControlGroupEmbeddableFactory = () => { chainingSystem: [ chainingSystem$, (next: ControlGroupChainingSystem) => chainingSystem$.next(next), - (a, b) => (a ?? DEFAULT_CHAINING_SYSTEM) === (b ?? DEFAULT_CHAINING_SYSTEM), + (a, b) => (a ?? DEFAULT_CONTROL_CHAINING) === (b ?? DEFAULT_CONTROL_CHAINING), ], ignoreParentSettings: [ ignoreParentSettings$, @@ -187,14 +189,14 @@ export const getControlGroupEmbeddableFactory = () => { }); }, serializeState: () => { - const { panelsJSON, references } = controlsManager.serializeControls(); + const { controls, references } = controlsManager.serializeControls(); return { rawState: { chainingSystem: chainingSystem$.getValue(), - controlStyle: labelPosition$.getValue(), - showApplySelections: !autoApplySelections$.getValue(), - ignoreParentSettingsJSON: JSON.stringify(ignoreParentSettings$.getValue()), - panelsJSON, + labelPosition: labelPosition$.getValue(), + autoApplySelections: autoApplySelections$.getValue(), + ignoreParentSettings: ignoreParentSettings$.getValue(), + controls, }, references, }; diff --git a/src/plugins/controls/public/control_group/init_controls_manager.ts b/src/plugins/controls/public/control_group/init_controls_manager.ts index ee020bf1fbd59..935845327131e 100644 --- a/src/plugins/controls/public/control_group/init_controls_manager.ts +++ b/src/plugins/controls/public/control_group/init_controls_manager.ts @@ -147,9 +147,8 @@ export function initControlsManager( }, serializeControls: () => { const references: Reference[] = []; - const explicitInputPanels: { - [panelId: string]: ControlPanelState & { explicitInput: object }; - } = {}; + + const controls: Array = []; controlsInOrder$.getValue().forEach(({ id }, index) => { const controlApi = getControlApi(id); @@ -166,18 +165,18 @@ export function initControlsManager( references.push(...controlReferences); } - explicitInputPanels[id] = { + controls.push({ grow, order: index, type: controlApi.type, width, - /** Re-add the `explicitInput` layer on serialize so control group saved object retains shape */ - explicitInput: { id, ...rest }, - }; + /** Re-add the `controlConfig` layer on serialize so control group saved object retains shape */ + controlConfig: { id, ...rest }, + }); }); return { - panelsJSON: JSON.stringify(explicitInputPanels), + controls, references, }; }, diff --git a/src/plugins/controls/public/control_group/utils/initialization_utils.ts b/src/plugins/controls/public/control_group/utils/initialization_utils.ts index ea785d05ac735..a35572387e1e1 100644 --- a/src/plugins/controls/public/control_group/utils/initialization_utils.ts +++ b/src/plugins/controls/public/control_group/utils/initialization_utils.ts @@ -7,17 +7,18 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { DEFAULT_CONTROL_LABEL_POSITION, type ControlGroupRuntimeState } from '../../../common'; +import { + type ControlGroupRuntimeState, + DEFAULT_CONTROL_CHAINING, + DEFAULT_CONTROL_LABEL_POSITION, + DEFAULT_AUTO_APPLY_SELECTIONS, + DEFAULT_IGNORE_PARENT_SETTINGS, +} from '../../../common'; export const getDefaultControlGroupRuntimeState = (): ControlGroupRuntimeState => ({ initialChildControlState: {}, labelPosition: DEFAULT_CONTROL_LABEL_POSITION, - chainingSystem: 'HIERARCHICAL', - autoApplySelections: true, - ignoreParentSettings: { - ignoreFilters: false, - ignoreQuery: false, - ignoreTimerange: false, - ignoreValidations: false, - }, + chainingSystem: DEFAULT_CONTROL_CHAINING, + autoApplySelections: DEFAULT_AUTO_APPLY_SELECTIONS, + ignoreParentSettings: DEFAULT_IGNORE_PARENT_SETTINGS, }); diff --git a/src/plugins/controls/public/control_group/utils/serialization_utils.ts b/src/plugins/controls/public/control_group/utils/serialization_utils.ts index ad7dea5827507..0a046244b732f 100644 --- a/src/plugins/controls/public/control_group/utils/serialization_utils.ts +++ b/src/plugins/controls/public/control_group/utils/serialization_utils.ts @@ -16,37 +16,31 @@ import { parseReferenceName } from '../../controls/data_controls/reference_name_ export const deserializeControlGroup = ( state: SerializedPanelState ): ControlGroupRuntimeState => { - const panels = JSON.parse(state.rawState.panelsJSON); - const ignoreParentSettings = JSON.parse(state.rawState.ignoreParentSettingsJSON); + const { controls } = state.rawState; + const controlsMap = Object.fromEntries(controls.map(({ id, ...rest }) => [id, rest])); /** Inject data view references into each individual control */ const references = state.references ?? []; references.forEach((reference) => { const referenceName = reference.name; const { controlId } = parseReferenceName(referenceName); - if (panels[controlId]) { - panels[controlId].dataViewId = reference.id; + if (controlsMap[controlId]) { + controlsMap[controlId].dataViewId = reference.id; } }); - /** Flatten the state of each panel by removing `explicitInput` */ - const flattenedPanels = Object.keys(panels).reduce((prev, panelId) => { - const currentPanel = panels[panelId]; - const currentPanelExplicitInput = panels[panelId].explicitInput; + /** Flatten the state of each control by removing `controlConfig` */ + const flattenedControls = Object.keys(controlsMap).reduce((prev, controlId) => { + const currentControl = controlsMap[controlId]; + const currentControlExplicitInput = controlsMap[controlId].controlConfig; return { ...prev, - [panelId]: { ...omit(currentPanel, 'explicitInput'), ...currentPanelExplicitInput }, + [controlId]: { ...omit(currentControl, 'controlConfig'), ...currentControlExplicitInput }, }; }, {}); return { - ...omit(state.rawState, ['panelsJSON', 'ignoreParentSettingsJSON']), - initialChildControlState: flattenedPanels, - ignoreParentSettings, - autoApplySelections: - typeof state.rawState.showApplySelections === 'boolean' - ? !state.rawState.showApplySelections - : true, // Rename "showApplySelections" to "autoApplySelections" - labelPosition: state.rawState.controlStyle, // Rename "controlStyle" to "labelPosition" + ...state.rawState, + initialChildControlState: flattenedControls, }; }; diff --git a/src/plugins/controls/server/control_group/control_group_migrations.test.ts b/src/plugins/controls/server/control_group/control_group_migrations.test.ts index 69b19225218e3..59643d3aa19c7 100644 --- a/src/plugins/controls/server/control_group/control_group_migrations.test.ts +++ b/src/plugins/controls/server/control_group/control_group_migrations.test.ts @@ -18,10 +18,8 @@ import { import { OptionsListControlState } from '../../common/options_list'; import { mockDataControlState, mockOptionsListControlState } from '../mocks'; import { removeHideExcludeAndHideExists } from './control_group_migrations'; -import { - SerializableControlGroupState, - getDefaultControlGroupState, -} from './control_group_persistence'; +import { getDefaultControlGroupState } from './control_group_persistence'; +import type { SerializableControlGroupState } from './types'; describe('migrate control group', () => { const getOptionsListControl = ( diff --git a/src/plugins/controls/server/control_group/control_group_migrations.ts b/src/plugins/controls/server/control_group/control_group_migrations.ts index a3d3d06aadafc..e737441cde717 100644 --- a/src/plugins/controls/server/control_group/control_group_migrations.ts +++ b/src/plugins/controls/server/control_group/control_group_migrations.ts @@ -14,7 +14,7 @@ import { type SerializedControlState, } from '../../common'; import { OptionsListControlState } from '../../common/options_list'; -import { SerializableControlGroupState } from './control_group_persistence'; +import { SerializableControlGroupState } from './types'; export const makeControlOrdersZeroBased = (state: SerializableControlGroupState) => { if ( diff --git a/src/plugins/controls/server/control_group/control_group_persistable_state.ts b/src/plugins/controls/server/control_group/control_group_persistable_state.ts index d59ffb2161934..9e880242df12b 100644 --- a/src/plugins/controls/server/control_group/control_group_persistable_state.ts +++ b/src/plugins/controls/server/control_group/control_group_persistable_state.ts @@ -20,7 +20,7 @@ import { makeControlOrdersZeroBased, removeHideExcludeAndHideExists, } from './control_group_migrations'; -import type { SerializableControlGroupState } from './control_group_persistence'; +import { SerializableControlGroupState } from './types'; const getPanelStatePrefix = (state: SerializedControlState) => `${state.explicitInput.id}:`; diff --git a/src/plugins/controls/server/control_group/control_group_persistence.ts b/src/plugins/controls/server/control_group/control_group_persistence.ts index e90aa850c6d1a..bcf61b3bcc1b2 100644 --- a/src/plugins/controls/server/control_group/control_group_persistence.ts +++ b/src/plugins/controls/server/control_group/control_group_persistence.ts @@ -9,37 +9,22 @@ import { SerializableRecord } from '@kbn/utility-types'; +import { ControlGroupSavedObjectState, SerializableControlGroupState } from './types'; import { + DEFAULT_CONTROL_CHAINING, DEFAULT_CONTROL_LABEL_POSITION, - type ControlGroupRuntimeState, - type ControlGroupSerializedState, - type ControlPanelState, - type SerializedControlState, + DEFAULT_IGNORE_PARENT_SETTINGS, + DEFAULT_AUTO_APPLY_SELECTIONS, } from '../../common'; export const getDefaultControlGroupState = (): SerializableControlGroupState => ({ panels: {}, labelPosition: DEFAULT_CONTROL_LABEL_POSITION, - chainingSystem: 'HIERARCHICAL', - autoApplySelections: true, - ignoreParentSettings: { - ignoreFilters: false, - ignoreQuery: false, - ignoreTimerange: false, - ignoreValidations: false, - }, + chainingSystem: DEFAULT_CONTROL_CHAINING, + autoApplySelections: DEFAULT_AUTO_APPLY_SELECTIONS, + ignoreParentSettings: DEFAULT_IGNORE_PARENT_SETTINGS, }); -// using SerializableRecord to force type to be read as serializable -export type SerializableControlGroupState = SerializableRecord & - Omit< - ControlGroupRuntimeState, - 'initialChildControlState' | 'ignoreParentSettings' | 'editorConfig' // editor config is not persisted - > & { - ignoreParentSettings: Record; - panels: Record> | {}; - }; - const safeJSONParse = (jsonString?: string): OutType | undefined => { if (!jsonString && typeof jsonString !== 'string') return; try { @@ -49,22 +34,26 @@ const safeJSONParse = (jsonString?: string): OutType | undefined => { } }; -export const controlGroupSerializedStateToSerializableRuntimeState = ( - serializedState: ControlGroupSerializedState +export const controlGroupSavedObjectStateToSerializableRuntimeState = ( + savedObjectState: ControlGroupSavedObjectState ): SerializableControlGroupState => { const defaultControlGroupInput = getDefaultControlGroupState(); return { - chainingSystem: serializedState?.chainingSystem, - labelPosition: serializedState?.controlStyle ?? defaultControlGroupInput.labelPosition, - autoApplySelections: !serializedState?.showApplySelections, - ignoreParentSettings: safeJSONParse(serializedState?.ignoreParentSettingsJSON) ?? {}, - panels: safeJSONParse(serializedState?.panelsJSON) ?? {}, + chainingSystem: + (savedObjectState?.chainingSystem as SerializableControlGroupState['chainingSystem']) ?? + defaultControlGroupInput.chainingSystem, + labelPosition: + (savedObjectState?.controlStyle as SerializableControlGroupState['labelPosition']) ?? + defaultControlGroupInput.labelPosition, + autoApplySelections: !savedObjectState?.showApplySelections, + ignoreParentSettings: safeJSONParse(savedObjectState?.ignoreParentSettingsJSON) ?? {}, + panels: safeJSONParse(savedObjectState?.panelsJSON) ?? {}, }; }; -export const serializableRuntimeStateToControlGroupSerializedState = ( +export const serializableRuntimeStateToControlGroupSavedObjectState = ( serializable: SerializableRecord // It is safe to treat this as SerializableControlGroupState -): ControlGroupSerializedState => { +): ControlGroupSavedObjectState => { return { controlStyle: serializable.labelPosition as SerializableControlGroupState['labelPosition'], chainingSystem: serializable.chainingSystem as SerializableControlGroupState['chainingSystem'], diff --git a/src/plugins/controls/server/control_group/control_group_telemetry.test.ts b/src/plugins/controls/server/control_group/control_group_telemetry.test.ts index da2800a7d744f..3647a23d36a17 100644 --- a/src/plugins/controls/server/control_group/control_group_telemetry.test.ts +++ b/src/plugins/controls/server/control_group/control_group_telemetry.test.ts @@ -7,16 +7,11 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { SerializableRecord } from '@kbn/utility-types'; -import { type ControlGroupSerializedState } from '../../common'; -import { - type ControlGroupTelemetry, - controlGroupTelemetry, - initializeControlGroupTelemetry, -} from './control_group_telemetry'; +import { controlGroupTelemetry, initializeControlGroupTelemetry } from './control_group_telemetry'; +import { ControlGroupSavedObjectState, ControlGroupTelemetry } from './types'; // controls attributes with all settings ignored + 3 options lists + hierarchical chaining + label above -const rawControlAttributes1: SerializableRecord & ControlGroupSerializedState = { +const rawControlAttributes1: ControlGroupSavedObjectState = { controlStyle: 'twoLine', chainingSystem: 'NONE', showApplySelections: true, @@ -27,7 +22,7 @@ const rawControlAttributes1: SerializableRecord & ControlGroupSerializedState = }; // controls attributes with some settings ignored + 2 range sliders, 1 time slider + No chaining + label inline -const rawControlAttributes2: SerializableRecord & ControlGroupSerializedState = { +const rawControlAttributes2: ControlGroupSavedObjectState = { controlStyle: 'oneLine', chainingSystem: 'NONE', showApplySelections: false, @@ -38,7 +33,7 @@ const rawControlAttributes2: SerializableRecord & ControlGroupSerializedState = }; // controls attributes with no settings ignored + 2 options lists, 1 range slider, 1 time slider + hierarchical chaining + label inline -const rawControlAttributes3: SerializableRecord & ControlGroupSerializedState = { +const rawControlAttributes3: ControlGroupSavedObjectState = { controlStyle: 'oneLine', chainingSystem: 'HIERARCHICAL', showApplySelections: false, diff --git a/src/plugins/controls/server/control_group/control_group_telemetry.ts b/src/plugins/controls/server/control_group/control_group_telemetry.ts index 21d1baf40116c..72944202b9550 100644 --- a/src/plugins/controls/server/control_group/control_group_telemetry.ts +++ b/src/plugins/controls/server/control_group/control_group_telemetry.ts @@ -9,31 +9,15 @@ import { PersistableStateService } from '@kbn/kibana-utils-plugin/common'; import { set } from '@kbn/safer-lodash-set'; -import type { ControlGroupSerializedState } from '../../common'; import { - type SerializableControlGroupState, - controlGroupSerializedStateToSerializableRuntimeState, + controlGroupSavedObjectStateToSerializableRuntimeState, getDefaultControlGroupState, } from './control_group_persistence'; - -export interface ControlGroupTelemetry { - total: number; - chaining_system: { - [key: string]: number; - }; - label_position: { - [key: string]: number; - }; - ignore_settings: { - [key: string]: number; - }; - by_type: { - [key: string]: { - total: number; - details: { [key: string]: number }; - }; - }; -} +import { + ControlGroupSavedObjectState, + ControlGroupTelemetry, + SerializableControlGroupState, +} from './types'; export const initializeControlGroupTelemetry = ( statsSoFar: Record @@ -113,8 +97,8 @@ export const controlGroupTelemetry: PersistableStateService['telemetry'] = ( const controlGroupStats = initializeControlGroupTelemetry(stats); const controlGroupState = { ...getDefaultControlGroupState(), - ...controlGroupSerializedStateToSerializableRuntimeState( - state as unknown as ControlGroupSerializedState + ...controlGroupSavedObjectStateToSerializableRuntimeState( + state as unknown as ControlGroupSavedObjectState ), }; if (!controlGroupState) return controlGroupStats; diff --git a/src/plugins/controls/server/control_group/types.ts b/src/plugins/controls/server/control_group/types.ts new file mode 100644 index 0000000000000..9aa0aaddc4a12 --- /dev/null +++ b/src/plugins/controls/server/control_group/types.ts @@ -0,0 +1,47 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { SerializableRecord } from '@kbn/utility-types'; +import { ControlGroupRuntimeState, ControlPanelState, SerializedControlState } from '../../common'; + +// using SerializableRecord to force type to be read as serializable +export type SerializableControlGroupState = SerializableRecord & + Omit< + ControlGroupRuntimeState, + 'initialChildControlState' | 'editorConfig' // editor config is not persisted + > & { + panels: Record> | {}; + }; + +export type ControlGroupSavedObjectState = SerializableRecord & { + chainingSystem: SerializableControlGroupState['chainingSystem']; + controlStyle: SerializableControlGroupState['labelPosition']; + showApplySelections: boolean; + ignoreParentSettingsJSON: string; + panelsJSON: string; +}; + +export interface ControlGroupTelemetry { + total: number; + chaining_system: { + [key: string]: number; + }; + label_position: { + [key: string]: number; + }; + ignore_settings: { + [key: string]: number; + }; + by_type: { + [key: string]: { + total: number; + details: { [key: string]: number }; + }; + }; +} diff --git a/src/plugins/controls/server/index.ts b/src/plugins/controls/server/index.ts index 541d9e2a46204..40261f8a3013e 100644 --- a/src/plugins/controls/server/index.ts +++ b/src/plugins/controls/server/index.ts @@ -13,10 +13,9 @@ export const plugin = async () => { }; export { - controlGroupSerializedStateToSerializableRuntimeState, - serializableRuntimeStateToControlGroupSerializedState, + controlGroupSavedObjectStateToSerializableRuntimeState, + serializableRuntimeStateToControlGroupSavedObjectState, } from './control_group/control_group_persistence'; -export { - type ControlGroupTelemetry, - initializeControlGroupTelemetry, -} from './control_group/control_group_telemetry'; +export { initializeControlGroupTelemetry } from './control_group/control_group_telemetry'; + +export type { ControlGroupTelemetry } from './control_group/types'; diff --git a/src/plugins/controls/tsconfig.json b/src/plugins/controls/tsconfig.json index e1040faecc1b0..41ab33dc18969 100644 --- a/src/plugins/controls/tsconfig.json +++ b/src/plugins/controls/tsconfig.json @@ -38,7 +38,7 @@ "@kbn/field-formats-plugin", "@kbn/presentation-panel-plugin", "@kbn/shared-ux-utility", - "@kbn/std" + "@kbn/std", ], "exclude": ["target/**/*"] } diff --git a/src/plugins/custom_integrations/kibana.jsonc b/src/plugins/custom_integrations/kibana.jsonc index fd8a429c0d666..b42bc6a932ea8 100644 --- a/src/plugins/custom_integrations/kibana.jsonc +++ b/src/plugins/custom_integrations/kibana.jsonc @@ -1,14 +1,18 @@ { "type": "plugin", "id": "@kbn/custom-integrations-plugin", - "owner": "@elastic/fleet", + "owner": [ + "@elastic/fleet" + ], + "group": "platform", + "visibility": "shared", "description": "Add custom data integrations so they can be displayed in the Fleet integrations app", "plugin": { "id": "customIntegrations", - "server": true, "browser": true, + "server": true, "extraPublicDirs": [ "common" ] } -} +} \ No newline at end of file diff --git a/src/plugins/dashboard/common/bwc/types.ts b/src/plugins/dashboard/common/bwc/types.ts index b1b97fa31485d..ae409d143656b 100644 --- a/src/plugins/dashboard/common/bwc/types.ts +++ b/src/plugins/dashboard/common/bwc/types.ts @@ -9,7 +9,7 @@ import type { SavedObjectReference } from '@kbn/core/public'; import type { Serializable } from '@kbn/utility-types'; -import { GridData } from '../content_management'; +import type { GridData } from '../../server/dashboard_saved_object'; interface KibanaAttributes { kibanaSavedObjectMeta: { diff --git a/src/plugins/dashboard/common/content_management/constants.ts b/src/plugins/dashboard/common/content_management/constants.ts index 29c679872a9e0..978271680af12 100644 --- a/src/plugins/dashboard/common/content_management/constants.ts +++ b/src/plugins/dashboard/common/content_management/constants.ts @@ -7,6 +7,18 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export const LATEST_VERSION = 2; +export const LATEST_VERSION = 3; export const CONTENT_ID = 'dashboard'; + +export const DASHBOARD_GRID_COLUMN_COUNT = 48; +export const DEFAULT_PANEL_WIDTH = DASHBOARD_GRID_COLUMN_COUNT / 2; +export const DEFAULT_PANEL_HEIGHT = 15; + +export const DEFAULT_DASHBOARD_OPTIONS = { + hidePanelTitles: false, + useMargins: true, + syncColors: true, + syncCursor: true, + syncTooltips: true, +} as const; diff --git a/src/plugins/dashboard/common/content_management/index.ts b/src/plugins/dashboard/common/content_management/index.ts index d87d65a61d4f0..b87b54520d7ab 100644 --- a/src/plugins/dashboard/common/content_management/index.ts +++ b/src/plugins/dashboard/common/content_management/index.ts @@ -7,14 +7,13 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export { LATEST_VERSION, CONTENT_ID } from './constants'; +export { + LATEST_VERSION, + CONTENT_ID, + DASHBOARD_GRID_COLUMN_COUNT, + DEFAULT_PANEL_HEIGHT, + DEFAULT_PANEL_WIDTH, + DEFAULT_DASHBOARD_OPTIONS, +} from './constants'; export type { DashboardContentType } from './types'; - -export type { - GridData, - DashboardItem, - DashboardCrudTypes, - DashboardAttributes, - SavedDashboardPanel, -} from './latest'; diff --git a/src/plugins/dashboard/common/content_management/v1/types.ts b/src/plugins/dashboard/common/content_management/v1/types.ts index 9b7c2973d9713..3b3317c0bd13e 100644 --- a/src/plugins/dashboard/common/content_management/v1/types.ts +++ b/src/plugins/dashboard/common/content_management/v1/types.ts @@ -14,7 +14,7 @@ import type { } from '@kbn/content-management-utils'; import { Serializable } from '@kbn/utility-types'; import { RefreshInterval } from '@kbn/data-plugin/common'; -import { ControlGroupSerializedState } from '@kbn/controls-plugin/common'; +import { ControlGroupChainingSystem, ControlLabelPosition } from '@kbn/controls-plugin/common'; import { DashboardContentType } from '../types'; @@ -62,10 +62,13 @@ export interface SavedDashboardPanel { version?: string; } -type ControlGroupAttributesV1 = Pick< - ControlGroupSerializedState, - 'panelsJSON' | 'chainingSystem' | 'controlStyle' | 'ignoreParentSettingsJSON' ->; +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +export type ControlGroupAttributesV1 = { + chainingSystem?: ControlGroupChainingSystem; + panelsJSON: string; // stringified version of ControlSerializedState + ignoreParentSettingsJSON: string; + controlStyle?: ControlLabelPosition; +}; /* eslint-disable-next-line @typescript-eslint/consistent-type-definitions */ export type DashboardAttributes = { @@ -77,7 +80,7 @@ export type DashboardAttributes = { description: string; panelsJSON: string; timeFrom?: string; - version: number; + version?: number; timeTo?: string; title: string; kibanaSavedObjectMeta: { diff --git a/src/plugins/dashboard/common/content_management/v2/index.ts b/src/plugins/dashboard/common/content_management/v2/index.ts index b0b10669699cf..bd687ff0dd609 100644 --- a/src/plugins/dashboard/common/content_management/v2/index.ts +++ b/src/plugins/dashboard/common/content_management/v2/index.ts @@ -8,4 +8,4 @@ */ export type { GridData, DashboardItem, SavedDashboardPanel } from '../v1/types'; // no changes made to types from v1 to v2 -export type { DashboardCrudTypes, DashboardAttributes } from './types'; +export type { ControlGroupAttributes, DashboardCrudTypes, DashboardAttributes } from './types'; diff --git a/src/plugins/dashboard/common/content_management/v2/types.ts b/src/plugins/dashboard/common/content_management/v2/types.ts index 3f009b749a2ab..ae2c2a798d813 100644 --- a/src/plugins/dashboard/common/content_management/v2/types.ts +++ b/src/plugins/dashboard/common/content_management/v2/types.ts @@ -12,21 +12,18 @@ import type { SavedObjectCreateOptions, SavedObjectUpdateOptions, } from '@kbn/content-management-utils'; -import { ControlGroupSerializedState } from '@kbn/controls-plugin/common'; import { DashboardContentType } from '../types'; -import { DashboardAttributes as DashboardAttributesV1 } from '../v1/types'; +import { + ControlGroupAttributesV1, + DashboardAttributes as DashboardAttributesV1, +} from '../v1/types'; -type ControlGroupAttributesV2 = Pick< - ControlGroupSerializedState, - | 'panelsJSON' - | 'chainingSystem' - | 'controlStyle' - | 'ignoreParentSettingsJSON' - | 'showApplySelections' ->; +export type ControlGroupAttributes = ControlGroupAttributesV1 & { + showApplySelections?: boolean; +}; export type DashboardAttributes = Omit & { - controlGroupInput?: ControlGroupAttributesV2; + controlGroupInput?: ControlGroupAttributes; }; export type DashboardCrudTypes = ContentManagementCrudTypes< diff --git a/src/plugins/dashboard/common/dashboard_container/types.ts b/src/plugins/dashboard/common/dashboard_container/types.ts index bcb7670f18e12..dd3f7302038c0 100644 --- a/src/plugins/dashboard/common/dashboard_container/types.ts +++ b/src/plugins/dashboard/common/dashboard_container/types.ts @@ -18,8 +18,7 @@ import type { Reference } from '@kbn/content-management-utils'; import { RefreshInterval } from '@kbn/data-plugin/common'; import { KibanaExecutionContext } from '@kbn/core-execution-context-common'; -import { DashboardOptions } from '../types'; -import { GridData } from '../content_management'; +import type { DashboardOptions, GridData } from '../../server/content_management'; export interface DashboardPanelMap { [key: string]: DashboardPanelState; diff --git a/src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.test.ts b/src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.test.ts index 689db61b0cb27..e9bd6aff0fe12 100644 --- a/src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.test.ts +++ b/src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.test.ts @@ -18,7 +18,8 @@ import { createInject, } from '../../dashboard_container/persistable_state/dashboard_container_references'; import { createEmbeddablePersistableStateServiceMock } from '@kbn/embeddable-plugin/common/mocks'; -import { DashboardAttributes } from '../../content_management'; +import type { DashboardAttributes, DashboardItem } from '../../../server/content_management'; +import { DashboardAttributesAndReferences } from '../../types'; const embeddablePersistableStateServiceMock = createEmbeddablePersistableStateServiceMock(); const dashboardInject = createInject(embeddablePersistableStateServiceMock); @@ -44,28 +45,37 @@ const deps: InjectExtractDeps = { }; const commonAttributes: DashboardAttributes = { - kibanaSavedObjectMeta: { searchSourceJSON: '' }, + kibanaSavedObjectMeta: { searchSource: {} }, timeRestore: false, - panelsJSON: '', version: 1, + options: { + hidePanelTitles: false, + useMargins: true, + syncColors: true, + syncCursor: true, + syncTooltips: true, + }, + panels: [], description: '', title: '', }; describe('extractReferences', () => { - test('extracts references from panelsJSON', () => { + test('extracts references from panels', () => { const doc = { id: '1', attributes: { ...commonAttributes, foo: true, - panelsJSON: JSON.stringify([ + panels: [ { panelIndex: 'panel-1', type: 'visualization', id: '1', title: 'Title 1', version: '7.9.1', + gridData: { x: 0, y: 0, w: 1, h: 1, i: 'panel-1' }, + panelConfig: {}, }, { panelIndex: 'panel-2', @@ -73,8 +83,10 @@ describe('extractReferences', () => { id: '2', title: 'Title 2', version: '7.9.1', + gridData: { x: 1, y: 1, w: 2, h: 2, i: 'panel-2' }, + panelConfig: {}, }, - ]), + ], }, references: [], }; @@ -86,9 +98,47 @@ describe('extractReferences', () => { "description": "", "foo": true, "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "", + "searchSource": Object {}, + }, + "options": Object { + "hidePanelTitles": false, + "syncColors": true, + "syncCursor": true, + "syncTooltips": true, + "useMargins": true, }, - "panelsJSON": "[{\\"version\\":\\"7.9.1\\",\\"type\\":\\"visualization\\",\\"panelIndex\\":\\"panel-1\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\",\\"panelRefName\\":\\"panel_panel-1\\"},{\\"version\\":\\"7.9.1\\",\\"type\\":\\"visualization\\",\\"panelIndex\\":\\"panel-2\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 2\\",\\"panelRefName\\":\\"panel_panel-2\\"}]", + "panels": Array [ + Object { + "gridData": Object { + "h": 1, + "i": "panel-1", + "w": 1, + "x": 0, + "y": 0, + }, + "panelConfig": Object {}, + "panelIndex": "panel-1", + "panelRefName": "panel_panel-1", + "title": "Title 1", + "type": "visualization", + "version": "7.9.1", + }, + Object { + "gridData": Object { + "h": 2, + "i": "panel-2", + "w": 2, + "x": 1, + "y": 1, + }, + "panelConfig": Object {}, + "panelIndex": "panel-2", + "panelRefName": "panel_panel-2", + "title": "Title 2", + "type": "visualization", + "version": "7.9.1", + }, + ], "timeRestore": false, "title": "", "version": 1, @@ -115,18 +165,18 @@ describe('extractReferences', () => { attributes: { ...commonAttributes, foo: true, - panelsJSON: JSON.stringify([ + panels: [ { id: '1', title: 'Title 1', version: '7.9.1', }, - ]), + ], }, references: [], - }; + } as unknown as DashboardAttributesAndReferences; expect(() => extractReferences(doc, deps)).toThrowErrorMatchingInlineSnapshot( - `"\\"type\\" attribute is missing from panel \\"undefined\\""` + `"\\"type\\" attribute is missing from panel \\"0\\""` ); }); @@ -136,25 +186,49 @@ describe('extractReferences', () => { attributes: { ...commonAttributes, foo: true, - panelsJSON: JSON.stringify([ + panels: [ { type: 'visualization', title: 'Title 1', version: '7.9.1', + gridData: { x: 0, y: 0, w: 1, h: 1, i: 'panel-1' }, + panelConfig: {}, }, - ]), + ], }, references: [], }; - expect(extractReferences(doc, deps)).toMatchInlineSnapshot(` + expect(extractReferences(doc as unknown as DashboardItem, deps)).toMatchInlineSnapshot(` Object { "attributes": Object { "description": "", "foo": true, "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "", + "searchSource": Object {}, + }, + "options": Object { + "hidePanelTitles": false, + "syncColors": true, + "syncCursor": true, + "syncTooltips": true, + "useMargins": true, }, - "panelsJSON": "[{\\"version\\":\\"7.9.1\\",\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\"}]", + "panels": Array [ + Object { + "gridData": Object { + "h": 1, + "i": "panel-1", + "w": 1, + "x": 0, + "y": 0, + }, + "panelConfig": Object {}, + "panelIndex": "0", + "title": "Title 1", + "type": "visualization", + "version": "7.9.1", + }, + ], "timeRestore": false, "title": "", "version": 1, @@ -171,18 +245,26 @@ describe('injectReferences', () => { ...commonAttributes, id: '1', title: 'test', - panelsJSON: JSON.stringify([ + panels: [ { + type: 'visualization', panelRefName: 'panel_0', + panelIndex: '0', title: 'Title 1', version: '7.9.0', + gridData: { x: 0, y: 0, w: 1, h: 1, i: '0' }, + panelConfig: {}, }, { + type: 'visualization', panelRefName: 'panel_1', + panelIndex: '1', title: 'Title 2', version: '7.9.0', + gridData: { x: 1, y: 1, w: 2, h: 2, i: '1' }, + panelConfig: {}, }, - ]), + ], }; const references = [ { @@ -203,9 +285,47 @@ describe('injectReferences', () => { "description": "", "id": "1", "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "", + "searchSource": Object {}, }, - "panelsJSON": "[{\\"version\\":\\"7.9.0\\",\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\",\\"id\\":\\"1\\"},{\\"version\\":\\"7.9.0\\",\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 2\\",\\"id\\":\\"2\\"}]", + "options": Object { + "hidePanelTitles": false, + "syncColors": true, + "syncCursor": true, + "syncTooltips": true, + "useMargins": true, + }, + "panels": Array [ + Object { + "gridData": Object { + "h": 1, + "i": "0", + "w": 1, + "x": 0, + "y": 0, + }, + "id": "1", + "panelConfig": Object {}, + "panelIndex": "0", + "title": "Title 1", + "type": "visualization", + "version": "7.9.0", + }, + Object { + "gridData": Object { + "h": 2, + "i": "1", + "w": 2, + "x": 1, + "y": 1, + }, + "id": "2", + "panelConfig": Object {}, + "panelIndex": "1", + "title": "Title 2", + "type": "visualization", + "version": "7.9.0", + }, + ], "timeRestore": false, "title": "test", "version": 1, @@ -213,7 +333,7 @@ describe('injectReferences', () => { `); }); - test('skips when panelsJSON is missing', () => { + test('skips when panels is missing', () => { const attributes = { id: '1', title: 'test', @@ -222,31 +342,8 @@ describe('injectReferences', () => { expect(newAttributes).toMatchInlineSnapshot(` Object { "id": "1", - "panelsJSON": "[]", - "title": "test", - } - `); - }); - - test('skips when panelsJSON is not an array', () => { - const attributes = { - ...commonAttributes, - id: '1', - panelsJSON: '{}', - title: 'test', - }; - const newAttributes = injectReferences({ attributes, references: [] }, deps); - expect(newAttributes).toMatchInlineSnapshot(` - Object { - "description": "", - "id": "1", - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "", - }, - "panelsJSON": "[]", - "timeRestore": false, + "panels": Array [], "title": "test", - "version": 1, } `); }); @@ -256,15 +353,23 @@ describe('injectReferences', () => { ...commonAttributes, id: '1', title: 'test', - panelsJSON: JSON.stringify([ + panels: [ { + type: 'visualization', panelRefName: 'panel_0', + panelIndex: '0', title: 'Title 1', + gridData: { x: 0, y: 0, w: 1, h: 1, i: '0' }, + panelConfig: {}, }, { + type: 'visualization', + panelIndex: '1', title: 'Title 2', + gridData: { x: 1, y: 1, w: 2, h: 2, i: '1' }, + panelConfig: {}, }, - ]), + ], }; const references = [ { @@ -279,9 +384,46 @@ describe('injectReferences', () => { "description": "", "id": "1", "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "", + "searchSource": Object {}, + }, + "options": Object { + "hidePanelTitles": false, + "syncColors": true, + "syncCursor": true, + "syncTooltips": true, + "useMargins": true, }, - "panelsJSON": "[{\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\",\\"id\\":\\"1\\"},{\\"embeddableConfig\\":{},\\"title\\":\\"Title 2\\"}]", + "panels": Array [ + Object { + "gridData": Object { + "h": 1, + "i": "0", + "w": 1, + "x": 0, + "y": 0, + }, + "id": "1", + "panelConfig": Object {}, + "panelIndex": "0", + "title": "Title 1", + "type": "visualization", + "version": undefined, + }, + Object { + "gridData": Object { + "h": 2, + "i": "1", + "w": 2, + "x": 1, + "y": 1, + }, + "panelConfig": Object {}, + "panelIndex": "1", + "title": "Title 2", + "type": "visualization", + "version": undefined, + }, + ], "timeRestore": false, "title": "test", "version": 1, @@ -294,12 +436,16 @@ describe('injectReferences', () => { ...commonAttributes, id: '1', title: 'test', - panelsJSON: JSON.stringify([ + panels: [ { + panelIndex: '0', panelRefName: 'panel_0', title: 'Title 1', + type: 'visualization', + gridData: { x: 0, y: 0, w: 1, h: 1, i: '0' }, + panelConfig: {}, }, - ]), + ], }; expect(() => injectReferences({ attributes, references: [] }, deps) diff --git a/src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts b/src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts index 1ede56a2b67a7..9b9290accb513 100644 --- a/src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts +++ b/src/plugins/dashboard/common/dashboard_saved_object/persistable_state/dashboard_saved_object_references.ts @@ -11,11 +11,11 @@ import type { Reference } from '@kbn/content-management-utils'; import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common/types'; import { - convertPanelMapToSavedPanels, - convertSavedPanelsToPanelMap, + convertPanelMapToPanelsArray, + convertPanelsArrayToPanelMap, } from '../../lib/dashboard_panel_converters'; import { DashboardAttributesAndReferences, ParsedDashboardAttributesWithType } from '../../types'; -import { DashboardAttributes, SavedDashboardPanel } from '../../content_management'; +import type { DashboardAttributes } from '../../../server/content_management'; import { createExtract, createInject, @@ -25,20 +25,12 @@ export interface InjectExtractDeps { embeddablePersistableStateService: EmbeddablePersistableStateService; } -function parseDashboardAttributesWithType( - attributes: DashboardAttributes -): ParsedDashboardAttributesWithType { - let parsedPanels = [] as SavedDashboardPanel[]; - if (typeof attributes.panelsJSON === 'string') { - const parsedJSON = JSON.parse(attributes.panelsJSON); - if (Array.isArray(parsedJSON)) { - parsedPanels = parsedJSON as SavedDashboardPanel[]; - } - } - +function parseDashboardAttributesWithType({ + panels, +}: DashboardAttributes): ParsedDashboardAttributesWithType { return { type: 'dashboard', - panels: convertSavedPanelsToPanelMap(parsedPanels), + panels: convertPanelsArrayToPanelMap(panels), } as ParsedDashboardAttributesWithType; } @@ -51,12 +43,12 @@ export function injectReferences( // inject references back into panels via the Embeddable persistable state service. const inject = createInject(deps.embeddablePersistableStateService); const injectedState = inject(parsedAttributes, references) as ParsedDashboardAttributesWithType; - const injectedPanels = convertPanelMapToSavedPanels(injectedState.panels); + const injectedPanels = convertPanelMapToPanelsArray(injectedState.panels); const newAttributes = { ...attributes, - panelsJSON: JSON.stringify(injectedPanels), - } as DashboardAttributes; + panels: injectedPanels, + }; return newAttributes; } @@ -81,12 +73,12 @@ export function extractReferences( references: Reference[]; state: ParsedDashboardAttributesWithType; }; - const extractedPanels = convertPanelMapToSavedPanels(extractedState.panels); + const extractedPanels = convertPanelMapToPanelsArray(extractedState.panels); const newAttributes = { ...attributes, - panelsJSON: JSON.stringify(extractedPanels), - } as DashboardAttributes; + panels: extractedPanels, + }; return { references: [...references, ...extractedReferences], diff --git a/src/plugins/dashboard/common/index.ts b/src/plugins/dashboard/common/index.ts index 8de0c49c41eec..be2cedf889e85 100644 --- a/src/plugins/dashboard/common/index.ts +++ b/src/plugins/dashboard/common/index.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export type { DashboardOptions, DashboardCapabilities, SharedDashboardState } from './types'; +export type { DashboardCapabilities, SharedDashboardState } from './types'; export type { DashboardPanelMap, @@ -16,9 +16,8 @@ export type { DashboardContainerByReferenceInput, } from './dashboard_container/types'; -export type { DashboardAttributes, SavedDashboardPanel } from './content_management'; - export { + type InjectExtractDeps, injectReferences, extractReferences, } from './dashboard_saved_object/persistable_state/dashboard_saved_object_references'; @@ -31,10 +30,8 @@ export { export { prefixReferencesFromPanel } from './dashboard_container/persistable_state/dashboard_container_references'; export { - convertPanelStateToSavedDashboardPanel, - convertSavedDashboardPanelToPanelState, - convertSavedPanelsToPanelMap, - convertPanelMapToSavedPanels, + convertPanelsArrayToPanelMap, + convertPanelMapToPanelsArray, } from './lib/dashboard_panel_converters'; export const UI_SETTINGS = { diff --git a/src/plugins/dashboard/common/lib/dashboard_panel_converters.ts b/src/plugins/dashboard/common/lib/dashboard_panel_converters.ts index a8c2d1f7c7b87..67317083b445d 100644 --- a/src/plugins/dashboard/common/lib/dashboard_panel_converters.ts +++ b/src/plugins/dashboard/common/lib/dashboard_panel_converters.ts @@ -9,77 +9,63 @@ import { v4 } from 'uuid'; import { omit } from 'lodash'; -import { EmbeddableInput, SavedObjectEmbeddableInput } from '@kbn/embeddable-plugin/common'; +import type { SavedObjectEmbeddableInput } from '@kbn/embeddable-plugin/common'; import type { Reference } from '@kbn/content-management-utils'; -import { DashboardPanelMap, DashboardPanelState } from '..'; -import { SavedDashboardPanel } from '../content_management'; +import type { DashboardPanelMap } from '..'; +import type { DashboardPanel } from '../../server/content_management'; + import { getReferencesForPanelId, prefixReferencesFromPanel, } from '../dashboard_container/persistable_state/dashboard_container_references'; -export function convertSavedDashboardPanelToPanelState< - TEmbeddableInput extends EmbeddableInput | SavedObjectEmbeddableInput = SavedObjectEmbeddableInput ->(savedDashboardPanel: SavedDashboardPanel): DashboardPanelState { - return { - type: savedDashboardPanel.type, - gridData: savedDashboardPanel.gridData, - panelRefName: savedDashboardPanel.panelRefName, - explicitInput: { - id: savedDashboardPanel.panelIndex, - ...(savedDashboardPanel.id !== undefined && { savedObjectId: savedDashboardPanel.id }), - ...(savedDashboardPanel.title !== undefined && { title: savedDashboardPanel.title }), - ...savedDashboardPanel.embeddableConfig, - } as TEmbeddableInput, - - /** - * Version information used to be stored in the panel until 8.11 when it was moved - * to live inside the explicit Embeddable Input. If version information is given here, we'd like to keep it. - * It will be removed on Dashboard save - */ - version: savedDashboardPanel.version, - }; -} - -export function convertPanelStateToSavedDashboardPanel( - panelState: DashboardPanelState, - removeLegacyVersion?: boolean -): SavedDashboardPanel { - const savedObjectId = (panelState.explicitInput as SavedObjectEmbeddableInput).savedObjectId; - return { - /** - * Version information used to be stored in the panel until 8.11 when it was moved to live inside the - * explicit Embeddable Input. If removeLegacyVersion is not passed, we'd like to keep this information for - * the time being. - */ - ...(!removeLegacyVersion ? { version: panelState.version } : {}), - - type: panelState.type, - gridData: panelState.gridData, - panelIndex: panelState.explicitInput.id, - embeddableConfig: omit(panelState.explicitInput, ['id', 'savedObjectId', 'title']), - ...(panelState.explicitInput.title !== undefined && { title: panelState.explicitInput.title }), - ...(savedObjectId !== undefined && { id: savedObjectId }), - ...(panelState.panelRefName !== undefined && { panelRefName: panelState.panelRefName }), - }; -} - -export const convertSavedPanelsToPanelMap = (panels?: SavedDashboardPanel[]): DashboardPanelMap => { +export const convertPanelsArrayToPanelMap = (panels?: DashboardPanel[]): DashboardPanelMap => { const panelsMap: DashboardPanelMap = {}; panels?.forEach((panel, idx) => { - panelsMap![panel.panelIndex ?? String(idx)] = convertSavedDashboardPanelToPanelState(panel); + const panelIndex = panel.panelIndex ?? String(idx); + panelsMap![panel.panelIndex ?? String(idx)] = { + type: panel.type, + gridData: panel.gridData, + panelRefName: panel.panelRefName, + explicitInput: { + id: panelIndex, + ...(panel.id !== undefined && { savedObjectId: panel.id }), + ...(panel.title !== undefined && { title: panel.title }), + ...panel.panelConfig, + }, + version: panel.version, + }; }); return panelsMap; }; -export const convertPanelMapToSavedPanels = ( +export const convertPanelMapToPanelsArray = ( panels: DashboardPanelMap, removeLegacyVersion?: boolean ) => { - return Object.values(panels).map((panel) => - convertPanelStateToSavedDashboardPanel(panel, removeLegacyVersion) - ); + return Object.values(panels).map((panelState) => { + const savedObjectId = (panelState.explicitInput as SavedObjectEmbeddableInput).savedObjectId; + const panelIndex = panelState.explicitInput.id; + return { + /** + * Version information used to be stored in the panel until 8.11 when it was moved to live inside the + * explicit Embeddable Input. If removeLegacyVersion is not passed, we'd like to keep this information for + * the time being. + */ + ...(!removeLegacyVersion ? { version: panelState.version } : {}), + + type: panelState.type, + gridData: panelState.gridData, + panelIndex, + panelConfig: omit(panelState.explicitInput, ['id', 'savedObjectId', 'title']), + ...(panelState.explicitInput.title !== undefined && { + title: panelState.explicitInput.title, + }), + ...(savedObjectId !== undefined && { id: savedObjectId }), + ...(panelState.panelRefName !== undefined && { panelRefName: panelState.panelRefName }), + }; + }); }; /** diff --git a/src/plugins/dashboard/common/types.ts b/src/plugins/dashboard/common/types.ts index b3b4b1e983b29..c8ecc237ed348 100644 --- a/src/plugins/dashboard/common/types.ts +++ b/src/plugins/dashboard/common/types.ts @@ -8,17 +8,9 @@ */ import type { Reference } from '@kbn/content-management-utils'; -import { EmbeddableStateWithType } from '@kbn/embeddable-plugin/common'; -import { DashboardAttributes, SavedDashboardPanel } from './content_management'; -import { DashboardContainerInput, DashboardPanelMap } from './dashboard_container/types'; - -export interface DashboardOptions { - hidePanelTitles: boolean; - useMargins: boolean; - syncColors: boolean; - syncTooltips: boolean; - syncCursor: boolean; -} +import type { EmbeddableStateWithType } from '@kbn/embeddable-plugin/common'; +import type { DashboardContainerInput, DashboardPanelMap } from './dashboard_container/types'; +import type { DashboardAttributes, DashboardPanel } from '../server/content_management'; export interface DashboardCapabilities { showWriteControls: boolean; @@ -32,7 +24,7 @@ export interface DashboardCapabilities { * For BWC reasons, dashboard state is stored with panels as an array instead of a map */ export type SharedDashboardState = Partial< - Omit & { panels: SavedDashboardPanel[] } + Omit & { panels: DashboardPanel[] } >; /** diff --git a/src/plugins/dashboard/kibana.jsonc b/src/plugins/dashboard/kibana.jsonc index 9be62b08597be..9d47ab95c8872 100644 --- a/src/plugins/dashboard/kibana.jsonc +++ b/src/plugins/dashboard/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/dashboard-plugin", - "owner": "@elastic/kibana-presentation", + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "shared", "description": "Adds the Dashboard app to Kibana", "plugin": { "id": "dashboard", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "data", "dataViews", @@ -47,4 +51,4 @@ "savedObjects" ] } -} +} \ No newline at end of file diff --git a/src/plugins/dashboard/public/dashboard_app/locator/load_dashboard_history_location_state.ts b/src/plugins/dashboard/public/dashboard_app/locator/load_dashboard_history_location_state.ts index 73832625cf11f..99aa14fe6225f 100644 --- a/src/plugins/dashboard/public/dashboard_app/locator/load_dashboard_history_location_state.ts +++ b/src/plugins/dashboard/public/dashboard_app/locator/load_dashboard_history_location_state.ts @@ -10,7 +10,7 @@ import { ScopedHistory } from '@kbn/core-application-browser'; import { ForwardedDashboardState } from './locator'; -import { convertSavedPanelsToPanelMap, DashboardContainerInput } from '../../../common'; +import { convertPanelsArrayToPanelMap, DashboardContainerInput } from '../../../common'; export const loadDashboardHistoryLocationState = ( getScopedHistory: () => ScopedHistory @@ -28,6 +28,6 @@ export const loadDashboardHistoryLocationState = ( return { ...restOfState, - ...{ panels: convertSavedPanelsToPanelMap(panels) }, + ...{ panels: convertPanelsArrayToPanelMap(panels) }, }; }; diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/share/show_share_modal.test.tsx b/src/plugins/dashboard/public/dashboard_app/top_nav/share/show_share_modal.test.tsx index 41c4a55f6ab8d..de7a1584dc9bf 100644 --- a/src/plugins/dashboard/public/dashboard_app/top_nav/share/show_share_modal.test.tsx +++ b/src/plugins/dashboard/public/dashboard_app/top_nav/share/show_share_modal.test.tsx @@ -8,7 +8,7 @@ */ import { Capabilities } from '@kbn/core/public'; -import { convertPanelMapToSavedPanels, DashboardContainerInput } from '../../../../common'; +import { convertPanelMapToPanelsArray, DashboardContainerInput } from '../../../../common'; import { DashboardLocatorParams } from '../../../dashboard_container'; import { shareService } from '../../../services/kibana_services'; @@ -143,7 +143,7 @@ describe('ShowShareModal', () => { ).locatorParams.params; const rawDashboardState = { ...unsavedDashboardState, - panels: convertPanelMapToSavedPanels(unsavedDashboardState.panels), + panels: convertPanelMapToPanelsArray(unsavedDashboardState.panels), }; unsavedStateKeys.forEach((key) => { expect(shareLocatorParams[key]).toStrictEqual( @@ -208,8 +208,8 @@ describe('ShowShareModal', () => { ).locatorParams.params; expect(shareLocatorParams.panels).toBeDefined(); - expect(shareLocatorParams.panels![0].embeddableConfig.changedKey1).toBe('changed'); - expect(shareLocatorParams.panels![1].embeddableConfig.changedKey2).toBe('definitely changed'); - expect(shareLocatorParams.panels![2].embeddableConfig.changedKey3).toBe('should still exist'); + expect(shareLocatorParams.panels![0].panelConfig.changedKey1).toBe('changed'); + expect(shareLocatorParams.panels![1].panelConfig.changedKey2).toBe('definitely changed'); + expect(shareLocatorParams.panels![2].panelConfig.changedKey3).toBe('should still exist'); }); }); diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/share/show_share_modal.tsx b/src/plugins/dashboard/public/dashboard_app/top_nav/share/show_share_modal.tsx index 5dd56465de920..2e3690e40d4ee 100644 --- a/src/plugins/dashboard/public/dashboard_app/top_nav/share/show_share_modal.tsx +++ b/src/plugins/dashboard/public/dashboard_app/top_nav/share/show_share_modal.tsx @@ -19,7 +19,7 @@ import { ViewMode } from '@kbn/embeddable-plugin/public'; import { i18n } from '@kbn/i18n'; import { getStateFromKbnUrl, setStateToKbnUrl, unhashUrl } from '@kbn/kibana-utils-plugin/public'; -import { convertPanelMapToSavedPanels, DashboardPanelMap } from '../../../../common'; +import { convertPanelMapToPanelsArray, DashboardPanelMap } from '../../../../common'; import { DashboardLocatorParams } from '../../../dashboard_container'; import { getDashboardBackupService, @@ -151,7 +151,7 @@ export function ShowShareModal({ ...latestPanels, ...modifiedPanels, }; - return convertPanelMapToSavedPanels(allUnsavedPanelsMap); + return convertPanelMapToPanelsArray(allUnsavedPanelsMap); })(); if (unsavedDashboardState) { diff --git a/src/plugins/dashboard/public/dashboard_app/url/search_sessions_integration.ts b/src/plugins/dashboard/public/dashboard_app/url/search_sessions_integration.ts index e9ae3d6a15050..0fc8ce7173e6f 100644 --- a/src/plugins/dashboard/public/dashboard_app/url/search_sessions_integration.ts +++ b/src/plugins/dashboard/public/dashboard_app/url/search_sessions_integration.ts @@ -22,7 +22,7 @@ import type { ViewMode } from '@kbn/embeddable-plugin/common'; import { DASHBOARD_APP_LOCATOR } from '@kbn/deeplinks-analytics'; import { SEARCH_SESSION_ID } from '../../dashboard_constants'; import { DashboardLocatorParams } from '../../dashboard_container'; -import { convertPanelMapToSavedPanels } from '../../../common'; +import { convertPanelMapToPanelsArray } from '../../../common'; import { dataService } from '../../services/kibana_services'; import { DashboardApi } from '../../dashboard_api/types'; @@ -93,7 +93,7 @@ function getLocatorParams({ : undefined, panels: savedObjectId ? undefined - : (convertPanelMapToSavedPanels( + : (convertPanelMapToPanelsArray( dashboardApi.panels$.value ) as DashboardLocatorParams['panels']), }; diff --git a/src/plugins/dashboard/public/dashboard_app/url/url_utils.ts b/src/plugins/dashboard/public/dashboard_app/url/url_utils.ts index b748909eac9ac..87faf87b026f8 100644 --- a/src/plugins/dashboard/public/dashboard_app/url/url_utils.ts +++ b/src/plugins/dashboard/public/dashboard_app/url/url_utils.ts @@ -19,24 +19,29 @@ import { DashboardContainerInput, DashboardPanelMap, SharedDashboardState, - convertSavedPanelsToPanelMap, + convertPanelsArrayToPanelMap, } from '../../../common'; -import { SavedDashboardPanel } from '../../../common/content_management'; +import type { DashboardPanel } from '../../../server/content_management'; +import type { SavedDashboardPanel } from '../../../server/dashboard_saved_object'; import { DashboardApi } from '../../dashboard_api/types'; import { DASHBOARD_STATE_STORAGE_KEY, createDashboardEditUrl } from '../../dashboard_constants'; import { migrateLegacyQuery } from '../../services/dashboard_content_management_service/lib/load_dashboard_state'; import { coreServices } from '../../services/kibana_services'; import { getPanelTooOldErrorString } from '../_dashboard_app_strings'; +const panelIsLegacy = (panel: unknown): panel is SavedDashboardPanel => { + return (panel as SavedDashboardPanel).embeddableConfig !== undefined; +}; + /** * We no longer support loading panels from a version older than 7.3 in the URL. * @returns whether or not there is a panel in the URL state saved with a version before 7.3 */ -export const isPanelVersionTooOld = (panels: SavedDashboardPanel[]) => { +export const isPanelVersionTooOld = (panels: DashboardPanel[] | SavedDashboardPanel[]) => { for (const panel of panels) { if ( !panel.gridData || - !panel.embeddableConfig || + !((panel as DashboardPanel).panelConfig || (panel as SavedDashboardPanel).embeddableConfig) || (panel.version && semverSatisfies(panel.version, '<7.3')) ) return true; @@ -58,7 +63,19 @@ function getPanelsMap(appStateInUrl: SharedDashboardState): DashboardPanelMap | return undefined; } - return convertSavedPanelsToPanelMap(appStateInUrl.panels); + // convert legacy embeddableConfig keys to panelConfig + const panels = appStateInUrl.panels.map((panel) => { + if (panelIsLegacy(panel)) { + const { embeddableConfig, ...rest } = panel; + return { + ...rest, + panelConfig: embeddableConfig, + }; + } + return panel; + }); + + return convertPanelsArrayToPanelMap(panels); } /** diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx index 444bac28c9e66..e5355bdb2988c 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx @@ -27,6 +27,7 @@ import { DashboardPanelMap, prefixReferencesFromPanel, } from '../../../../common'; +import type { DashboardAttributes } from '../../../../server/content_management'; import { DASHBOARD_CONTENT_ID, SAVED_OBJECT_POST_TIME } from '../../../dashboard_constants'; import { SaveDashboardReturn, @@ -95,7 +96,11 @@ export async function runQuickSave(this: DashboardContainer) { const { rawState: controlGroupSerializedState, references: extractedReferences } = await controlGroupApi.serializeState(); controlGroupReferences = extractedReferences; - stateToSave = { ...stateToSave, controlGroupInput: controlGroupSerializedState }; + stateToSave = { + ...stateToSave, + controlGroupInput: + controlGroupSerializedState as unknown as DashboardAttributes['controlGroupInput'], + }; } const saveResult = await getDashboardContentManagementService().saveDashboardState({ @@ -186,7 +191,8 @@ export async function runInteractiveSave(this: DashboardContainer, interactionMo controlGroupReferences = references; dashboardStateToSave = { ...dashboardStateToSave, - controlGroupInput: controlGroupSerializedState, + controlGroupInput: + controlGroupSerializedState as unknown as DashboardAttributes['controlGroupInput'], }; } diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx index a6765732c064c..35137075befe4 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx @@ -25,7 +25,7 @@ import { v4 } from 'uuid'; import { METRIC_TYPE } from '@kbn/analytics'; import type { Reference } from '@kbn/content-management-utils'; -import { ControlGroupApi, ControlGroupSerializedState } from '@kbn/controls-plugin/public'; +import { ControlGroupApi } from '@kbn/controls-plugin/public'; import type { KibanaExecutionContext, OverlayRef } from '@kbn/core/public'; import { RefreshInterval } from '@kbn/data-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; @@ -69,12 +69,8 @@ import { LocatorPublic } from '@kbn/share-plugin/common'; import { ExitFullScreenButtonKibanaProvider } from '@kbn/shared-ux-button-exit-full-screen'; import { DASHBOARD_CONTAINER_TYPE, DashboardApi, DashboardLocatorParams } from '../..'; -import { - DashboardAttributes, - DashboardContainerInput, - DashboardPanelMap, - DashboardPanelState, -} from '../../../common'; +import type { DashboardAttributes } from '../../../server/content_management'; +import { DashboardContainerInput, DashboardPanelMap, DashboardPanelState } from '../../../common'; import { getReferencesForControls, getReferencesForPanelId, @@ -887,15 +883,19 @@ export class DashboardContainer public getSerializedStateForControlGroup = () => { return { rawState: this.controlGroupInput - ? (this.controlGroupInput as ControlGroupSerializedState) - : ({ - controlStyle: 'oneLine', + ? this.controlGroupInput + : { + labelPosition: 'oneLine', chainingSystem: 'HIERARCHICAL', - showApplySelections: false, - panelsJSON: '{}', - ignoreParentSettingsJSON: - '{"ignoreFilters":false,"ignoreQuery":false,"ignoreTimerange":false,"ignoreValidations":false}', - } as ControlGroupSerializedState), + autoApplySelections: true, + controls: [], + ignoreParentSettings: { + ignoreFilters: false, + ignoreQuery: false, + ignoreTimerange: false, + ignoreValidations: false, + }, + }, references: getReferencesForControls(this.savedObjectReferences), }; }; diff --git a/src/plugins/dashboard/public/dashboard_container/panel_placement/place_clone_panel_strategy.ts b/src/plugins/dashboard/public/dashboard_container/panel_placement/place_clone_panel_strategy.ts index 8386df50717f3..bdf5a39df34b8 100644 --- a/src/plugins/dashboard/public/dashboard_container/panel_placement/place_clone_panel_strategy.ts +++ b/src/plugins/dashboard/public/dashboard_container/panel_placement/place_clone_panel_strategy.ts @@ -11,7 +11,7 @@ import { cloneDeep, forOwn } from 'lodash'; import { PanelNotFoundError } from '@kbn/embeddable-plugin/public'; import { DashboardPanelState } from '../../../common'; -import { GridData } from '../../../common/content_management'; +import type { GridData } from '../../../server/content_management'; import { PanelPlacementProps, PanelPlacementReturn } from './types'; import { DASHBOARD_GRID_COLUMN_COUNT } from '../../dashboard_constants'; @@ -109,9 +109,9 @@ export function placeClonePanel({ for (let j = position + 1; j < grid.length; j++) { originalPositionInTheGrid = grid[j].i; - const movedPanel = cloneDeep(otherPanels[originalPositionInTheGrid]); - movedPanel.gridData.y = movedPanel.gridData.y + diff; - otherPanels[originalPositionInTheGrid] = movedPanel; + const { gridData, ...movedPanel } = cloneDeep(otherPanels[originalPositionInTheGrid]); + const newGridData = { ...gridData, y: gridData.y + diff }; + otherPanels[originalPositionInTheGrid] = { ...movedPanel, gridData: newGridData }; } return { newPanelPlacement: bottomPlacement.grid, otherPanels }; } diff --git a/src/plugins/dashboard/public/dashboard_container/panel_placement/place_new_panel_strategies.ts b/src/plugins/dashboard/public/dashboard_container/panel_placement/place_new_panel_strategies.ts index 821a5e6eed1c3..a6c0aaba43467 100644 --- a/src/plugins/dashboard/public/dashboard_container/panel_placement/place_new_panel_strategies.ts +++ b/src/plugins/dashboard/public/dashboard_container/panel_placement/place_new_panel_strategies.ts @@ -20,9 +20,9 @@ export const runPanelPlacementStrategy = ( case PanelPlacementStrategy.placeAtTop: const otherPanels = { ...currentPanels }; for (const [id, panel] of Object.entries(currentPanels)) { - const currentPanel = cloneDeep(panel); - currentPanel.gridData.y = currentPanel.gridData.y + height; - otherPanels[id] = currentPanel; + const { gridData, ...currentPanel } = cloneDeep(panel); + const newGridData = { ...gridData, y: gridData.y + height }; + otherPanels[id] = { ...currentPanel, gridData: newGridData }; } return { newPanelPlacement: { x: 0, y: 0, w: width, h: height }, diff --git a/src/plugins/dashboard/public/dashboard_container/panel_placement/types.ts b/src/plugins/dashboard/public/dashboard_container/panel_placement/types.ts index df34a9158c11a..2dd826f9a5821 100644 --- a/src/plugins/dashboard/public/dashboard_container/panel_placement/types.ts +++ b/src/plugins/dashboard/public/dashboard_container/panel_placement/types.ts @@ -10,7 +10,7 @@ import { EmbeddableInput } from '@kbn/embeddable-plugin/public'; import { MaybePromise } from '@kbn/utility-types'; import { DashboardPanelState } from '../../../common'; -import { GridData } from '../../../common/content_management'; +import type { GridData } from '../../../server/content_management'; import { PanelPlacementStrategy } from '../../dashboard_constants'; export interface PanelPlacementSettings { diff --git a/src/plugins/dashboard/public/dashboard_container/types.ts b/src/plugins/dashboard/public/dashboard_container/types.ts index f0b6ea2621abd..cf307924e00fe 100644 --- a/src/plugins/dashboard/public/dashboard_container/types.ts +++ b/src/plugins/dashboard/public/dashboard_container/types.ts @@ -12,8 +12,8 @@ import type { ReduxEmbeddableState } from '@kbn/presentation-util-plugin/public' import { SerializableRecord } from '@kbn/utility-types'; import { ControlGroupRuntimeState } from '@kbn/controls-plugin/public'; -import type { DashboardContainerInput, DashboardOptions } from '../../common'; -import { SavedDashboardPanel } from '../../common/content_management'; +import type { DashboardContainerInput } from '../../common'; +import type { DashboardOptions, DashboardPanel } from '../../server/content_management'; export interface UnsavedPanelState { [key: string]: object | undefined; @@ -101,7 +101,7 @@ export type DashboardLocatorParams = Partial< /** * List of dashboard panels */ - panels?: Array; // used SerializableRecord here to force the GridData type to be read as serializable + panels?: Array; // used SerializableRecord here to force the GridData type to be read as serializable /** * Control group changes diff --git a/src/plugins/dashboard/public/dashboard_listing/dashboard_unsaved_listing.tsx b/src/plugins/dashboard/public/dashboard_listing/dashboard_unsaved_listing.tsx index 0e23583801309..04f40a199e83b 100644 --- a/src/plugins/dashboard/public/dashboard_listing/dashboard_unsaved_listing.tsx +++ b/src/plugins/dashboard/public/dashboard_listing/dashboard_unsaved_listing.tsx @@ -20,7 +20,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { ViewMode } from '@kbn/embeddable-plugin/public'; -import { DashboardAttributes } from '../../common/content_management'; +import type { DashboardAttributes } from '../../server/content_management'; import { DASHBOARD_PANELS_UNSAVED_ID, getDashboardBackupService, diff --git a/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx b/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx index 31bfa88120a5e..6c8c8f11d6a13 100644 --- a/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx +++ b/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx @@ -17,7 +17,7 @@ import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; import { ViewMode } from '@kbn/embeddable-plugin/public'; import { DashboardContainerInput } from '../../../common'; -import { DashboardItem } from '../../../common/content_management'; +import type { DashboardSearchOut } from '../../../server/content_management'; import { DASHBOARD_CONTENT_ID, SAVED_OBJECT_DELETE_TIME, @@ -42,7 +42,9 @@ type GetDetailViewLink = const SAVED_OBJECTS_LIMIT_SETTING = 'savedObjects:listingLimit'; const SAVED_OBJECTS_PER_PAGE_SETTING = 'savedObjects:perPage'; -const toTableListViewSavedObject = (hit: DashboardItem): DashboardSavedObjectUserContent => { +const toTableListViewSavedObject = ( + hit: DashboardSearchOut['hits'][number] +): DashboardSavedObjectUserContent => { const { title, description, timeRestore } = hit.attributes; return { type: 'dashboard', @@ -51,7 +53,7 @@ const toTableListViewSavedObject = (hit: DashboardItem): DashboardSavedObjectUse createdAt: hit.createdAt, createdBy: hit.createdBy, updatedBy: hit.updatedBy, - references: hit.references, + references: hit.references ?? [], managed: hit.managed, attributes: { title, diff --git a/src/plugins/dashboard/public/services/dashboard_content_management_service/dashboard_content_management_cache.ts b/src/plugins/dashboard/public/services/dashboard_content_management_service/dashboard_content_management_cache.ts index 1b54f9dda9eb4..e72e3f23fdaba 100644 --- a/src/plugins/dashboard/public/services/dashboard_content_management_service/dashboard_content_management_cache.ts +++ b/src/plugins/dashboard/public/services/dashboard_content_management_service/dashboard_content_management_cache.ts @@ -8,14 +8,14 @@ */ import LRUCache from 'lru-cache'; -import { DashboardCrudTypes } from '../../../common/content_management'; +import type { DashboardGetOut } from '../../../server/content_management'; import { DASHBOARD_CACHE_SIZE, DASHBOARD_CACHE_TTL } from '../../dashboard_constants'; export class DashboardContentManagementCache { - private cache: LRUCache; + private cache: LRUCache; constructor() { - this.cache = new LRUCache({ + this.cache = new LRUCache({ max: DASHBOARD_CACHE_SIZE, maxAge: DASHBOARD_CACHE_TTL, }); @@ -27,7 +27,7 @@ export class DashboardContentManagementCache { } /** Add the fetched dashboard to the cache */ - public addDashboard({ item: dashboard, meta }: DashboardCrudTypes['GetOut']) { + public addDashboard({ item: dashboard, meta }: DashboardGetOut) { this.cache.set(dashboard.id, { meta, item: dashboard, diff --git a/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/check_for_duplicate_dashboard_title.ts b/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/check_for_duplicate_dashboard_title.ts index 0d12cb446129b..2865663dec3c0 100644 --- a/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/check_for_duplicate_dashboard_title.ts +++ b/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/check_for_duplicate_dashboard_title.ts @@ -7,8 +7,8 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +import type { DashboardSearchIn, DashboardSearchOut } from '../../../../server/content_management'; import { DASHBOARD_CONTENT_ID } from '../../../dashboard_constants'; -import { DashboardCrudTypes } from '../../../../common/content_management'; import { extractTitleAndCount } from '../../../dashboard_container/embeddable/api/lib/extract_title_and_count'; import { contentManagementService } from '../../kibana_services'; @@ -54,8 +54,8 @@ export async function checkForDuplicateDashboardTitle({ const [baseDashboardName] = extractTitleAndCount(title); const { hits } = await contentManagementService.client.search< - DashboardCrudTypes['SearchIn'], - DashboardCrudTypes['SearchOut'] + DashboardSearchIn, + DashboardSearchOut >({ contentTypeId: DASHBOARD_CONTENT_ID, query: { diff --git a/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/delete_dashboards.ts b/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/delete_dashboards.ts index 0be9355ddb606..976a5579b1988 100644 --- a/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/delete_dashboards.ts +++ b/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/delete_dashboards.ts @@ -7,18 +7,15 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { getDashboardContentManagementCache } from '..'; -import { DashboardCrudTypes } from '../../../../common/content_management'; +import type { DeleteIn, DeleteResult } from '@kbn/content-management-plugin/common'; import { DASHBOARD_CONTENT_ID } from '../../../dashboard_constants'; +import { getDashboardContentManagementCache } from '..'; import { contentManagementService } from '../../kibana_services'; export const deleteDashboards = async (ids: string[]) => { const deletePromises = ids.map((id) => { getDashboardContentManagementCache().deleteDashboard(id); - return contentManagementService.client.delete< - DashboardCrudTypes['DeleteIn'], - DashboardCrudTypes['DeleteOut'] - >({ + return contentManagementService.client.delete({ contentTypeId: DASHBOARD_CONTENT_ID, id, }); diff --git a/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/find_dashboards.ts b/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/find_dashboards.ts index 2f9a2c2e9a033..4afdefb8d13e1 100644 --- a/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/find_dashboards.ts +++ b/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/find_dashboards.ts @@ -10,17 +10,20 @@ import type { Reference } from '@kbn/content-management-utils'; import { SavedObjectError, SavedObjectsFindOptionsReference } from '@kbn/core/public'; -import { getDashboardContentManagementCache } from '..'; -import { +import type { DashboardAttributes, - DashboardCrudTypes, - DashboardItem, -} from '../../../../common/content_management'; + DashboardGetIn, + DashboardGetOut, + DashboardSearchIn, + DashboardSearchOut, + DashboardSearchOptions, +} from '../../../../server/content_management'; +import { getDashboardContentManagementCache } from '..'; import { DASHBOARD_CONTENT_ID } from '../../../dashboard_constants'; import { contentManagementService } from '../../kibana_services'; export interface SearchDashboardsArgs { - options?: DashboardCrudTypes['SearchIn']['options']; + options?: DashboardSearchOptions; hasNoReference?: SavedObjectsFindOptionsReference[]; hasReference?: SavedObjectsFindOptionsReference[]; search: string; @@ -29,7 +32,7 @@ export interface SearchDashboardsArgs { export interface SearchDashboardsResponse { total: number; - hits: DashboardItem[]; + hits: DashboardSearchOut['hits']; } export async function searchDashboards({ @@ -42,10 +45,7 @@ export async function searchDashboards({ const { hits, pagination: { total }, - } = await contentManagementService.client.search< - DashboardCrudTypes['SearchIn'], - DashboardCrudTypes['SearchOut'] - >({ + } = await contentManagementService.client.search({ contentTypeId: DASHBOARD_CONTENT_ID, query: { text: search ? `${search}*` : undefined, @@ -84,10 +84,7 @@ export async function findDashboardById(id: string): Promise({ + const response = await contentManagementService.client.get({ contentTypeId: DASHBOARD_CONTENT_ID, id, }); @@ -119,8 +116,8 @@ export async function findDashboardsByIds(ids: string[]): Promise { const { hits } = await contentManagementService.client.search< - DashboardCrudTypes['SearchIn'], - DashboardCrudTypes['SearchOut'] + DashboardSearchIn, + DashboardSearchOut >({ contentTypeId: DASHBOARD_CONTENT_ID, query: { diff --git a/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/load_dashboard_state.ts b/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/load_dashboard_state.ts index 17102e2fe7d0a..2694411ed001a 100644 --- a/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/load_dashboard_state.ts +++ b/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/load_dashboard_state.ts @@ -10,19 +10,15 @@ import { has } from 'lodash'; import { v4 as uuidv4 } from 'uuid'; -import { injectSearchSourceReferences, parseSearchSourceJSON } from '@kbn/data-plugin/public'; +import { injectSearchSourceReferences } from '@kbn/data-plugin/public'; import { ViewMode } from '@kbn/embeddable-plugin/public'; import { Filter, Query } from '@kbn/es-query'; import { SavedObjectNotFound } from '@kbn/kibana-utils-plugin/public'; import { cleanFiltersForSerialize } from '@kbn/presentation-util-plugin/public'; import { getDashboardContentManagementCache } from '..'; -import { - convertSavedPanelsToPanelMap, - injectReferences, - type DashboardOptions, -} from '../../../../common'; -import { DashboardCrudTypes } from '../../../../common/content_management'; +import { convertPanelsArrayToPanelMap, injectReferences } from '../../../../common'; +import type { DashboardGetIn, DashboardGetOut } from '../../../../server/content_management'; import { DASHBOARD_CONTENT_ID, DEFAULT_DASHBOARD_INPUT } from '../../../dashboard_constants'; import { contentManagementService, @@ -30,7 +26,11 @@ import { embeddableService, savedObjectsTaggingService, } from '../../kibana_services'; -import type { LoadDashboardFromSavedObjectProps, LoadDashboardReturn } from '../types'; +import type { + DashboardSearchSource, + LoadDashboardFromSavedObjectProps, + LoadDashboardReturn, +} from '../types'; import { convertNumberToDashboardVersion } from './dashboard_versioning'; import { migrateDashboardInput } from './migrate_dashboard_input'; @@ -72,8 +72,8 @@ export const loadDashboardState = async ({ /** * Load the saved object from Content Management */ - let rawDashboardContent: DashboardCrudTypes['GetOut']['item']; - let resolveMeta: DashboardCrudTypes['GetOut']['meta']; + let rawDashboardContent: DashboardGetOut['item']; + let resolveMeta: DashboardGetOut['meta']; const cachedDashboard = dashboardContentManagementCache.fetchDashboard(id); if (cachedDashboard) { @@ -82,7 +82,7 @@ export const loadDashboardState = async ({ } else { /** Otherwise, fetch and load the dashboard from the content management client, and add it to the cache */ const result = await contentManagementService.client - .get({ + .get({ contentTypeId: DASHBOARD_CONTENT_ID, id, }) @@ -127,14 +127,16 @@ export const loadDashboardState = async ({ /** * Create search source and pull filters and query from it. */ - const searchSourceJSON = attributes.kibanaSavedObjectMeta.searchSourceJSON; + let searchSourceValues = attributes.kibanaSavedObjectMeta.searchSource; const searchSource = await (async () => { - if (!searchSourceJSON) { + if (!searchSourceValues) { return await dataSearchService.searchSource.create(); } try { - let searchSourceValues = parseSearchSourceJSON(searchSourceJSON); - searchSourceValues = injectSearchSourceReferences(searchSourceValues as any, references); + searchSourceValues = injectSearchSourceReferences( + searchSourceValues as any, + references + ) as DashboardSearchSource; return await dataSearchService.searchSource.create(searchSourceValues); } catch (error: any) { return await dataSearchService.searchSource.create(); @@ -151,8 +153,8 @@ export const loadDashboardState = async ({ refreshInterval, description, timeRestore, - optionsJSON, - panelsJSON, + options, + panels, timeFrom, version, timeTo, @@ -167,11 +169,7 @@ export const loadDashboardState = async ({ } : undefined; - /** - * Parse panels and options from JSON - */ - const options: DashboardOptions = optionsJSON ? JSON.parse(optionsJSON) : undefined; - const panels = convertSavedPanelsToPanelMap(panelsJSON ? JSON.parse(panelsJSON) : []); + const panelMap = convertPanelsArrayToPanelMap(panels ?? []); const { dashboardInput, anyMigrationRun } = migrateDashboardInput({ ...DEFAULT_DASHBOARD_INPUT, @@ -183,7 +181,7 @@ export const loadDashboardState = async ({ description, timeRange, filters, - panels, + panels: panelMap, query, title, @@ -192,7 +190,7 @@ export const loadDashboardState = async ({ controlGroupInput: attributes.controlGroupInput, - version: convertNumberToDashboardVersion(version), + ...(version && { version: convertNumberToDashboardVersion(version) }), }); return { diff --git a/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/save_dashboard_state.test.ts b/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/save_dashboard_state.test.ts index 8327397a66068..7e35b0ec1c163 100644 --- a/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/save_dashboard_state.test.ts +++ b/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/save_dashboard_state.test.ts @@ -95,7 +95,7 @@ describe('Save dashboard state', () => { currentState: { ...getSampleDashboardInput(), title: 'BooThree', - panels: { idOne: { type: 'boop' } }, + panels: { aVerySpecialVeryUniqueId: { type: 'boop' } }, } as unknown as DashboardContainerInput, lastSavedId: 'Boogatoonie', saveOptions: { saveAsCopy: true }, @@ -106,7 +106,11 @@ describe('Save dashboard state', () => { expect(contentManagementService.client.create).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ - panelsJSON: expect.not.stringContaining('neverGonnaGetThisId'), + panels: expect.arrayContaining([ + expect.objectContaining({ + panelIndex: expect.not.stringContaining('aVerySpecialVeryUniqueId'), + }), + ]), }), }) ); diff --git a/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/save_dashboard_state.ts b/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/save_dashboard_state.ts index 283ed5eed7f5b..27e6a53da1f9a 100644 --- a/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/save_dashboard_state.ts +++ b/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/save_dashboard_state.ts @@ -13,9 +13,16 @@ import moment, { Moment } from 'moment'; import { extractSearchSourceReferences, RefreshInterval } from '@kbn/data-plugin/public'; import { isFilterPinned } from '@kbn/es-query'; +import type { SavedObjectReference } from '@kbn/core/server'; import { getDashboardContentManagementCache } from '..'; -import { convertPanelMapToSavedPanels, extractReferences } from '../../../../common'; -import { DashboardAttributes, DashboardCrudTypes } from '../../../../common/content_management'; +import { convertPanelMapToPanelsArray, extractReferences } from '../../../../common'; +import type { + DashboardAttributes, + DashboardCreateIn, + DashboardCreateOut, + DashboardUpdateIn, + DashboardUpdateOut, +} from '../../../../server/content_management'; import { generateNewPanelIds } from '../../../../common/lib/dashboard_panel_converters'; import { DASHBOARD_CONTENT_ID } from '../../../dashboard_constants'; import { LATEST_DASHBOARD_CONTAINER_VERSION } from '../../../dashboard_container'; @@ -28,7 +35,7 @@ import { embeddableService, savedObjectsTaggingService, } from '../../kibana_services'; -import { SaveDashboardProps, SaveDashboardReturn } from '../types'; +import { DashboardSearchSource, SaveDashboardProps, SaveDashboardReturn } from '../types'; import { convertDashboardVersionToNumber } from './dashboard_versioning'; export const convertTimeToUTCString = (time?: string | Moment): undefined | string => { @@ -88,33 +95,30 @@ export const saveDashboardState = async ({ // } - /** - * Stringify filters and query into search source JSON - */ - const { searchSourceJSON, searchSourceReferences } = await (async () => { - const searchSource = await dataSearchService.searchSource.create(); - searchSource.setField( + const { searchSource, searchSourceReferences } = await (async () => { + const searchSourceFields = await dataSearchService.searchSource.create(); + searchSourceFields.setField( 'filter', // save only unpinned filters filters.filter((filter) => !isFilterPinned(filter)) ); - searchSource.setField('query', query); - - const rawSearchSourceFields = searchSource.getSerializedFields(); - const [fields, references] = extractSearchSourceReferences(rawSearchSourceFields); - return { searchSourceReferences: references, searchSourceJSON: JSON.stringify(fields) }; + searchSourceFields.setField('query', query); + + const rawSearchSourceFields = searchSourceFields.getSerializedFields(); + const [fields, references] = extractSearchSourceReferences(rawSearchSourceFields) as [ + DashboardSearchSource, + SavedObjectReference[] + ]; + return { searchSourceReferences: references, searchSource: fields }; })(); - /** - * Stringify options and panels - */ - const optionsJSON = JSON.stringify({ + const options = { useMargins, syncColors, syncCursor, syncTooltips, hidePanelTitles, - }); - const panelsJSON = JSON.stringify(convertPanelMapToSavedPanels(panels, true)); + }; + const savedPanels = convertPanelMapToPanelsArray(panels, true); /** * Parse global time filter settings @@ -134,12 +138,12 @@ export const saveDashboardState = async ({ const rawDashboardAttributes: DashboardAttributes = { version: convertDashboardVersionToNumber(LATEST_DASHBOARD_CONTAINER_VERSION), controlGroupInput, - kibanaSavedObjectMeta: { searchSourceJSON }, + kibanaSavedObjectMeta: { searchSource }, description: description ?? '', refreshInterval, timeRestore, - optionsJSON, - panelsJSON, + options, + panels: savedPanels, timeFrom, title, timeTo, @@ -174,10 +178,7 @@ export const saveDashboardState = async ({ try { const result = idToSaveTo - ? await contentManagementService.client.update< - DashboardCrudTypes['UpdateIn'], - DashboardCrudTypes['UpdateOut'] - >({ + ? await contentManagementService.client.update({ id: idToSaveTo, contentTypeId: DASHBOARD_CONTENT_ID, data: attributes, @@ -187,10 +188,7 @@ export const saveDashboardState = async ({ mergeAttributes: false, }, }) - : await contentManagementService.client.create< - DashboardCrudTypes['CreateIn'], - DashboardCrudTypes['CreateOut'] - >({ + : await contentManagementService.client.create({ contentTypeId: DASHBOARD_CONTENT_ID, data: attributes, options: { diff --git a/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/update_dashboard_meta.ts b/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/update_dashboard_meta.ts index 2fd57738f17aa..90f31cfdc05c6 100644 --- a/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/update_dashboard_meta.ts +++ b/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/update_dashboard_meta.ts @@ -9,7 +9,7 @@ import { DashboardContainerInput } from '../../../../common'; import { DASHBOARD_CONTENT_ID } from '../../../dashboard_constants'; -import { DashboardCrudTypes } from '../../../../common/content_management'; +import type { DashboardUpdateIn, DashboardUpdateOut } from '../../../../server/content_management'; import { findDashboardsByIds } from './find_dashboards'; import { contentManagementService, savedObjectsTaggingService } from '../../kibana_services'; @@ -35,10 +35,7 @@ export const updateDashboardMeta = async ({ ? savedObjectsTaggingApi.ui.updateTagsReferences(dashboard.references, tags) : dashboard.references; - await contentManagementService.client.update< - DashboardCrudTypes['UpdateIn'], - DashboardCrudTypes['UpdateOut'] - >({ + await contentManagementService.client.update({ contentTypeId: DASHBOARD_CONTENT_ID, id, data: { title, description }, diff --git a/src/plugins/dashboard/public/services/dashboard_content_management_service/types.ts b/src/plugins/dashboard/public/services/dashboard_content_management_service/types.ts index 3294bb06c0d42..0f4fe1c86a56d 100644 --- a/src/plugins/dashboard/public/services/dashboard_content_management_service/types.ts +++ b/src/plugins/dashboard/public/services/dashboard_content_management_service/types.ts @@ -8,11 +8,12 @@ */ import type { Reference } from '@kbn/content-management-utils'; +import type { Query, SerializedSearchSourceFields } from '@kbn/data-plugin/common'; import { ControlGroupRuntimeState } from '@kbn/controls-plugin/public'; import { SavedObjectSaveOpts } from '@kbn/saved-objects-plugin/public'; import { DashboardContainerInput } from '../../../common'; -import { DashboardAttributes, DashboardCrudTypes } from '../../../common/content_management'; +import type { DashboardAttributes, DashboardGetOut } from '../../../server/content_management'; import { DashboardDuplicateTitleCheckProps } from './lib/check_for_duplicate_dashboard_title'; import { FindDashboardsByIdResponse, @@ -38,7 +39,7 @@ export interface LoadDashboardFromSavedObjectProps { id?: string; } -type DashboardResolveMeta = DashboardCrudTypes['GetOut']['meta']; +type DashboardResolveMeta = DashboardGetOut['meta']; export type SavedDashboardInput = DashboardContainerInput & { /** @@ -54,6 +55,10 @@ export type SavedDashboardInput = DashboardContainerInput & { controlGroupState?: Partial; }; +export type DashboardSearchSource = Omit & { + query?: Query; +}; + export interface LoadDashboardReturn { dashboardFound: boolean; newDashboardCreated?: boolean; diff --git a/src/plugins/dashboard/public/services/mocks.ts b/src/plugins/dashboard/public/services/mocks.ts index 255098ecd8196..c39c665ed55da 100644 --- a/src/plugins/dashboard/public/services/mocks.ts +++ b/src/plugins/dashboard/public/services/mocks.ts @@ -32,7 +32,8 @@ import { urlForwardingPluginMock } from '@kbn/url-forwarding-plugin/public/mocks import { visualizationsPluginMock } from '@kbn/visualizations-plugin/public/mocks'; import { setKibanaServices } from './kibana_services'; -import { DashboardAttributes, DashboardCapabilities } from '../../common'; +import { DashboardAttributes } from '../../server/content_management'; +import { DashboardCapabilities } from '../../common'; import { LoadDashboardReturn } from './dashboard_content_management_service/types'; import { SearchDashboardsResponse } from './dashboard_content_management_service/lib/find_dashboards'; diff --git a/src/plugins/dashboard/server/api/constants.ts b/src/plugins/dashboard/server/api/constants.ts new file mode 100644 index 0000000000000..458165d797869 --- /dev/null +++ b/src/plugins/dashboard/server/api/constants.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export const PUBLIC_API_VERSION = '2023-10-31'; +export const PUBLIC_API_CONTENT_MANAGEMENT_VERSION = 3; +export const PUBLIC_API_PATH = '/api/dashboards/dashboard'; diff --git a/src/plugins/dashboard/server/content_management/schema/v2/index.ts b/src/plugins/dashboard/server/api/index.ts similarity index 80% rename from src/plugins/dashboard/server/content_management/schema/v2/index.ts rename to src/plugins/dashboard/server/api/index.ts index 66beda1385d00..ccf84609b2b10 100644 --- a/src/plugins/dashboard/server/content_management/schema/v2/index.ts +++ b/src/plugins/dashboard/server/api/index.ts @@ -7,8 +7,4 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export { - serviceDefinition, - dashboardSavedObjectSchema, - dashboardAttributesSchema, -} from './cm_services'; +export { registerAPIRoutes } from './register_routes'; diff --git a/src/plugins/dashboard/server/api/register_routes.ts b/src/plugins/dashboard/server/api/register_routes.ts new file mode 100644 index 0000000000000..692942e1bd1bb --- /dev/null +++ b/src/plugins/dashboard/server/api/register_routes.ts @@ -0,0 +1,327 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { schema } from '@kbn/config-schema'; +import type { ContentManagementServerSetup } from '@kbn/content-management-plugin/server'; +import type { HttpServiceSetup } from '@kbn/core/server'; +import type { UsageCounter } from '@kbn/usage-collection-plugin/server'; +import type { Logger } from '@kbn/logging'; + +import { CONTENT_ID } from '../../common/content_management'; +import { + PUBLIC_API_PATH, + PUBLIC_API_VERSION, + PUBLIC_API_CONTENT_MANAGEMENT_VERSION, +} from './constants'; +import { + dashboardAttributesSchema, + dashboardGetResultSchema, + dashboardCreateResultSchema, + dashboardSearchResultsSchema, + referenceSchema, +} from '../content_management/v3'; + +interface RegisterAPIRoutesArgs { + http: HttpServiceSetup; + contentManagement: ContentManagementServerSetup; + restCounter?: UsageCounter; + logger: Logger; +} + +const TECHNICAL_PREVIEW_WARNING = + 'This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.'; + +export function registerAPIRoutes({ + http, + contentManagement, + restCounter, + logger, +}: RegisterAPIRoutesArgs) { + const { versioned: versionedRouter } = http.createRouter(); + + // Create API route + const createRoute = versionedRouter.post({ + path: `${PUBLIC_API_PATH}/{id?}`, + access: 'public', + summary: 'Create a dashboard', + description: TECHNICAL_PREVIEW_WARNING, + options: { + tags: ['oas-tag:Dashboards'], + }, + }); + + createRoute.addVersion( + { + version: PUBLIC_API_VERSION, + validate: { + request: { + params: schema.object({ + id: schema.maybe(schema.string()), + }), + body: schema.object({ + attributes: dashboardAttributesSchema, + references: schema.maybe(schema.arrayOf(referenceSchema)), + spaces: schema.maybe(schema.arrayOf(schema.string())), + }), + }, + response: { + 200: { + body: () => dashboardCreateResultSchema, + }, + }, + }, + }, + async (ctx, req, res) => { + const { id } = req.params; + const { attributes, references, spaces: initialNamespaces } = req.body; + const client = contentManagement.contentClient + .getForRequest({ request: req, requestHandlerContext: ctx }) + .for(CONTENT_ID, PUBLIC_API_CONTENT_MANAGEMENT_VERSION); + let result; + try { + ({ result } = await client.create(attributes, { + id, + references, + initialNamespaces, + })); + } catch (e) { + if (e.isBoom && e.output.statusCode === 409) { + return res.conflict({ + body: { + message: `A dashboard with saved object ID ${id} already exists.`, + }, + }); + } + + if (e.isBoom && e.output.statusCode === 403) { + return res.forbidden(); + } + + return res.badRequest(); + } + + return res.ok({ body: result }); + } + ); + + // Update API route + + const updateRoute = versionedRouter.put({ + path: `${PUBLIC_API_PATH}/{id}`, + access: 'public', + summary: `Update an existing dashboard.`, + description: TECHNICAL_PREVIEW_WARNING, + options: { + tags: ['oas-tag:Dashboards'], + }, + }); + + updateRoute.addVersion( + { + version: PUBLIC_API_VERSION, + validate: { + request: { + params: schema.object({ + id: schema.string(), + }), + body: schema.object({ + attributes: dashboardAttributesSchema, + references: schema.maybe(schema.arrayOf(referenceSchema)), + }), + }, + response: { + 200: { + body: () => dashboardCreateResultSchema, + }, + }, + }, + }, + async (ctx, req, res) => { + const { attributes, references } = req.body; + const client = contentManagement.contentClient + .getForRequest({ request: req, requestHandlerContext: ctx }) + .for(CONTENT_ID, PUBLIC_API_CONTENT_MANAGEMENT_VERSION); + let result; + try { + ({ result } = await client.update(req.params.id, attributes, { references })); + } catch (e) { + if (e.isBoom && e.output.statusCode === 404) { + return res.notFound({ + body: { + message: `A dashboard with saved object ID ${req.params.id} was not found.`, + }, + }); + } + if (e.isBoom && e.output.statusCode === 403) { + return res.forbidden(); + } + return res.badRequest(e.message); + } + + return res.created({ body: result }); + } + ); + + // List API route + const listRoute = versionedRouter.get({ + path: `${PUBLIC_API_PATH}`, + access: 'public', + summary: `Get a list of dashboards.`, + description: TECHNICAL_PREVIEW_WARNING, + options: { + tags: ['oas-tag:Dashboards'], + }, + }); + + listRoute.addVersion( + { + version: PUBLIC_API_VERSION, + validate: { + request: { + query: schema.object({ + page: schema.number({ defaultValue: 1 }), + perPage: schema.maybe(schema.number()), + }), + }, + response: { + 200: { + body: () => + schema.object({ + items: schema.arrayOf(dashboardSearchResultsSchema), + total: schema.number(), + }), + }, + }, + }, + }, + async (ctx, req, res) => { + const { page, perPage: limit } = req.query; + const client = contentManagement.contentClient + .getForRequest({ request: req, requestHandlerContext: ctx }) + .for(CONTENT_ID, PUBLIC_API_CONTENT_MANAGEMENT_VERSION); + let result; + try { + // TODO add filtering + ({ result } = await client.search({ cursor: page.toString(), limit })); + } catch (e) { + if (e.isBoom && e.output.statusCode === 403) { + return res.forbidden(); + } + + return res.badRequest(); + } + + const body = { + items: result.hits, + total: result.pagination.total, + }; + return res.ok({ body }); + } + ); + + // Get API route + const getRoute = versionedRouter.get({ + path: `${PUBLIC_API_PATH}/{id}`, + access: 'public', + summary: `Get a dashboard.`, + description: TECHNICAL_PREVIEW_WARNING, + options: { + tags: ['oas-tag:Dashboards'], + }, + }); + + getRoute.addVersion( + { + version: PUBLIC_API_VERSION, + validate: { + request: { + params: schema.object({ + id: schema.string(), + }), + }, + response: { + 200: { + body: () => dashboardGetResultSchema, + }, + }, + }, + }, + async (ctx, req, res) => { + const client = contentManagement.contentClient + .getForRequest({ request: req, requestHandlerContext: ctx }) + .for(CONTENT_ID, PUBLIC_API_CONTENT_MANAGEMENT_VERSION); + let result; + try { + ({ result } = await client.get(req.params.id)); + } catch (e) { + if (e.isBoom && e.output.statusCode === 404) { + return res.notFound({ + body: { + message: `A dashboard with saved object ID ${req.params.id}] was not found.`, + }, + }); + } + + if (e.isBoom && e.output.statusCode === 403) { + return res.forbidden(); + } + + return res.badRequest(e.message); + } + + return res.ok({ body: result }); + } + ); + + // Delete API route + const deleteRoute = versionedRouter.delete({ + path: `${PUBLIC_API_PATH}/{id}`, + access: 'public', + summary: `Delete a dashboard.`, + description: TECHNICAL_PREVIEW_WARNING, + options: { + tags: ['oas-tag:Dashboards'], + }, + }); + + deleteRoute.addVersion( + { + version: PUBLIC_API_VERSION, + validate: { + request: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + }, + async (ctx, req, res) => { + const client = contentManagement.contentClient + .getForRequest({ request: req, requestHandlerContext: ctx }) + .for(CONTENT_ID, PUBLIC_API_CONTENT_MANAGEMENT_VERSION); + try { + await client.delete(req.params.id); + } catch (e) { + if (e.isBoom && e.output.statusCode === 404) { + return res.notFound({ + body: { + message: `A dashboard with saved object ID ${req.params.id} was not found.`, + }, + }); + } + if (e.isBoom && e.output.statusCode === 403) { + return res.forbidden(); + } + return res.badRequest(); + } + + return res.ok(); + } + ); +} diff --git a/src/plugins/dashboard/server/content_management/schema/cm_services.ts b/src/plugins/dashboard/server/content_management/cm_services.ts similarity index 94% rename from src/plugins/dashboard/server/content_management/schema/cm_services.ts rename to src/plugins/dashboard/server/content_management/cm_services.ts index 10fbbd7f44ba8..081d7ad8a39d4 100644 --- a/src/plugins/dashboard/server/content_management/schema/cm_services.ts +++ b/src/plugins/dashboard/server/content_management/cm_services.ts @@ -17,8 +17,10 @@ import type { import { serviceDefinition as v1 } from './v1'; import { serviceDefinition as v2 } from './v2'; +import { serviceDefinition as v3 } from './v3'; export const cmServicesDefinition: { [version: Version]: ServicesDefinition } = { 1: v1, 2: v2, + 3: v3, }; diff --git a/src/plugins/dashboard/server/content_management/dashboard_storage.ts b/src/plugins/dashboard/server/content_management/dashboard_storage.ts index 248979032132a..e65002802989f 100644 --- a/src/plugins/dashboard/server/content_management/dashboard_storage.ts +++ b/src/plugins/dashboard/server/content_management/dashboard_storage.ts @@ -7,23 +7,40 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { SOContentStorage, tagsToFindOptions } from '@kbn/content-management-utils'; -import { SavedObjectsFindOptions } from '@kbn/core-saved-objects-api-server'; +import Boom from '@hapi/boom'; +import { tagsToFindOptions } from '@kbn/content-management-utils'; +import { + SavedObjectsFindOptions, + SavedObjectsFindResult, +} from '@kbn/core-saved-objects-api-server'; import type { Logger } from '@kbn/logging'; -import { CONTENT_ID } from '../../common/content_management'; -import { cmServicesDefinition } from './schema/cm_services'; -import type { DashboardCrudTypes } from '../../common/content_management'; +import { CreateResult, DeleteResult, SearchQuery } from '@kbn/content-management-plugin/common'; +import { StorageContext } from '@kbn/content-management-plugin/server'; +import { DASHBOARD_SAVED_OBJECT_TYPE } from '../dashboard_saved_object'; +import { cmServicesDefinition } from './cm_services'; +import { DashboardSavedObjectAttributes } from '../dashboard_saved_object'; +import { itemAttrsToSavedObjectAttrs, savedObjectToItem } from './latest'; +import type { + DashboardAttributes, + DashboardItem, + DashboardCreateOut, + DashboardCreateOptions, + DashboardGetOut, + DashboardSearchOut, + DashboardUpdateOptions, + DashboardUpdateOut, + DashboardSearchOptions, +} from './latest'; const searchArgsToSOFindOptions = ( - args: DashboardCrudTypes['SearchIn'] + query: SearchQuery, + options: DashboardSearchOptions ): SavedObjectsFindOptions => { - const { query, contentTypeId, options } = args; - return { - type: contentTypeId, + type: DASHBOARD_SAVED_OBJECT_TYPE, searchFields: options?.onlyTitle ? ['title'] : ['title^3', 'description'], - fields: ['description', 'title', 'timeRestore'], + fields: options?.fields ?? ['title', 'description', 'timeRestore'], search: query.text, perPage: query.limit, page: query.cursor ? +query.cursor : undefined, @@ -32,7 +49,16 @@ const searchArgsToSOFindOptions = ( }; }; -export class DashboardStorage extends SOContentStorage { +const savedObjectClientFromRequest = async (ctx: StorageContext) => { + if (!ctx.requestHandlerContext) { + throw new Error('Storage context.requestHandlerContext missing.'); + } + + const { savedObjects } = await ctx.requestHandlerContext.core; + return savedObjects.client; +}; + +export class DashboardStorage { constructor({ logger, throwOnResultValidationError, @@ -40,26 +66,316 @@ export class DashboardStorage extends SOContentStorage { logger: Logger; throwOnResultValidationError: boolean; }) { - super({ - savedObjectType: CONTENT_ID, - cmServicesDefinition, - searchArgsToSOFindOptions, - enableMSearch: true, - allowedSavedObjectAttributes: [ - 'kibanaSavedObjectMeta', - 'controlGroupInput', - 'refreshInterval', - 'description', - 'timeRestore', - 'optionsJSON', - 'panelsJSON', - 'timeFrom', - 'version', - 'timeTo', - 'title', - ], - logger, - throwOnResultValidationError, - }); + this.logger = logger; + this.throwOnResultValidationError = throwOnResultValidationError ?? false; + this.mSearch = { + savedObjectType: DASHBOARD_SAVED_OBJECT_TYPE, + additionalSearchFields: [], + toItemResult: (ctx: StorageContext, savedObject: SavedObjectsFindResult): DashboardItem => { + const transforms = ctx.utils.getTransforms(cmServicesDefinition); + + const { item, error: itemError } = savedObjectToItem( + savedObject as SavedObjectsFindResult, + false + ); + if (itemError) { + throw Boom.badRequest(`Invalid response. ${itemError.message}`); + } + + const validationError = transforms.mSearch.out.result.validate(item); + if (validationError) { + if (this.throwOnResultValidationError) { + throw Boom.badRequest(`Invalid response. ${validationError.message}`); + } else { + this.logger.warn(`Invalid response. ${validationError.message}`); + } + } + + // Validate DB response and DOWN transform to the request version + const { value, error: resultError } = transforms.mSearch.out.result.down< + DashboardItem, + DashboardItem + >( + item, + undefined, // do not override version + { validate: false } // validation is done above + ); + + if (resultError) { + throw Boom.badRequest(`Invalid response. ${resultError.message}`); + } + + return value; + }, + }; + } + + private logger: Logger; + private throwOnResultValidationError: boolean; + + mSearch: { + savedObjectType: string; + toItemResult: (ctx: StorageContext, savedObject: SavedObjectsFindResult) => DashboardItem; + additionalSearchFields?: string[]; + }; + + async get(ctx: StorageContext, id: string): Promise { + const transforms = ctx.utils.getTransforms(cmServicesDefinition); + const soClient = await savedObjectClientFromRequest(ctx); + + // Save data in DB + const { + saved_object: savedObject, + alias_purpose: aliasPurpose, + alias_target_id: aliasTargetId, + outcome, + } = await soClient.resolve(DASHBOARD_SAVED_OBJECT_TYPE, id); + + const { item, error: itemError } = savedObjectToItem(savedObject, false); + if (itemError) { + throw Boom.badRequest(`Invalid response. ${itemError.message}`); + } + + const response = { item, meta: { aliasPurpose, aliasTargetId, outcome } }; + + const validationError = transforms.get.out.result.validate(response); + if (validationError) { + if (this.throwOnResultValidationError) { + throw Boom.badRequest(`Invalid response. ${validationError.message}`); + } else { + this.logger.warn(`Invalid response. ${validationError.message}`); + } + } + + // Validate response and DOWN transform to the request version + const { value, error: resultError } = transforms.get.out.result.down< + DashboardGetOut, + DashboardGetOut + >( + response, + undefined, // do not override version + { validate: false } // validation is done above + ); + + if (resultError) { + throw Boom.badRequest(`Invalid response. ${resultError.message}`); + } + + return value; + } + + async bulkGet(): Promise { + // Not implemented + throw new Error(`[bulkGet] has not been implemented. See DashboardStorage class.`); + } + + async create( + ctx: StorageContext, + data: DashboardAttributes, + options: DashboardCreateOptions + ): Promise { + const transforms = ctx.utils.getTransforms(cmServicesDefinition); + const soClient = await savedObjectClientFromRequest(ctx); + + // Validate input (data & options) & UP transform them to the latest version + const { value: dataToLatest, error: dataError } = transforms.create.in.data.up< + DashboardAttributes, + DashboardAttributes + >(data); + if (dataError) { + throw Boom.badRequest(`Invalid data. ${dataError.message}`); + } + + const { value: optionsToLatest, error: optionsError } = transforms.create.in.options.up< + DashboardCreateOptions, + DashboardCreateOptions + >(options); + if (optionsError) { + throw Boom.badRequest(`Invalid options. ${optionsError.message}`); + } + + const { attributes: soAttributes, error: attributesError } = + itemAttrsToSavedObjectAttrs(dataToLatest); + if (attributesError) { + throw Boom.badRequest(`Invalid data. ${attributesError.message}`); + } + + // Save data in DB + const savedObject = await soClient.create( + DASHBOARD_SAVED_OBJECT_TYPE, + soAttributes, + optionsToLatest + ); + + const { item, error: itemError } = savedObjectToItem(savedObject, false); + if (itemError) { + throw Boom.badRequest(`Invalid response. ${itemError.message}`); + } + + const validationError = transforms.create.out.result.validate({ item }); + if (validationError) { + if (this.throwOnResultValidationError) { + throw Boom.badRequest(`Invalid response. ${validationError.message}`); + } else { + this.logger.warn(`Invalid response. ${validationError.message}`); + } + } + + // Validate DB response and DOWN transform to the request version + const { value, error: resultError } = transforms.create.out.result.down< + CreateResult + >( + { item }, + undefined, // do not override version + { validate: false } // validation is done above + ); + + if (resultError) { + throw Boom.badRequest(`Invalid response. ${resultError.message}`); + } + + return value; + } + + async update( + ctx: StorageContext, + id: string, + data: DashboardAttributes, + options: DashboardUpdateOptions + ): Promise { + const transforms = ctx.utils.getTransforms(cmServicesDefinition); + const soClient = await savedObjectClientFromRequest(ctx); + + // Validate input (data & options) & UP transform them to the latest version + const { value: dataToLatest, error: dataError } = transforms.update.in.data.up< + DashboardAttributes, + DashboardAttributes + >(data); + if (dataError) { + throw Boom.badRequest(`Invalid data. ${dataError.message}`); + } + + const { value: optionsToLatest, error: optionsError } = transforms.update.in.options.up< + DashboardUpdateOptions, + DashboardUpdateOptions + >(options); + if (optionsError) { + throw Boom.badRequest(`Invalid options. ${optionsError.message}`); + } + + const { attributes: soAttributes, error: attributesError } = + itemAttrsToSavedObjectAttrs(dataToLatest); + if (attributesError) { + throw Boom.badRequest(`Invalid data. ${attributesError.message}`); + } + + // Save data in DB + const partialSavedObject = await soClient.update( + DASHBOARD_SAVED_OBJECT_TYPE, + id, + soAttributes, + optionsToLatest + ); + + const { item, error: itemError } = savedObjectToItem(partialSavedObject, true); + if (itemError) { + throw Boom.badRequest(`Invalid response. ${itemError.message}`); + } + + const validationError = transforms.update.out.result.validate({ item }); + if (validationError) { + if (this.throwOnResultValidationError) { + throw Boom.badRequest(`Invalid response. ${validationError.message}`); + } else { + this.logger.warn(`Invalid response. ${validationError.message}`); + } + } + + // Validate DB response and DOWN transform to the request version + const { value, error: resultError } = transforms.update.out.result.down< + DashboardUpdateOut, + DashboardUpdateOut + >( + { item }, + undefined, // do not override version + { validate: false } // validation is done above + ); + + if (resultError) { + throw Boom.badRequest(`Invalid response. ${resultError.message}`); + } + + return value; + } + + async delete( + ctx: StorageContext, + id: string, + // force is necessary to delete saved objects that exist in multiple namespaces + options?: { force: boolean } + ): Promise { + const soClient = await savedObjectClientFromRequest(ctx); + await soClient.delete(DASHBOARD_SAVED_OBJECT_TYPE, id, { force: options?.force ?? false }); + return { success: true }; + } + + async search( + ctx: StorageContext, + query: SearchQuery, + options: DashboardSearchOptions + ): Promise { + const transforms = ctx.utils.getTransforms(cmServicesDefinition); + const soClient = await savedObjectClientFromRequest(ctx); + + // Validate and UP transform the options + const { value: optionsToLatest, error: optionsError } = transforms.search.in.options.up< + DashboardSearchOptions, + DashboardSearchOptions + >(options); + if (optionsError) { + throw Boom.badRequest(`Invalid payload. ${optionsError.message}`); + } + + const soQuery = searchArgsToSOFindOptions(query, optionsToLatest); + // Execute the query in the DB + const soResponse = await soClient.find(soQuery); + const hits = soResponse.saved_objects + .map((so) => { + const { item } = savedObjectToItem(so, false, soQuery.fields); + return item; + }) + // Ignore any saved objects that failed to convert to items. + .filter((item) => item !== null); + const response = { + hits, + pagination: { + total: soResponse.total, + }, + }; + + const validationError = transforms.search.out.result.validate(response); + if (validationError) { + if (this.throwOnResultValidationError) { + throw Boom.badRequest(`Invalid response. ${validationError.message}`); + } else { + this.logger.warn(`Invalid response. ${validationError.message}`); + } + } + + // Validate the response and DOWN transform to the request version + const { value, error: resultError } = transforms.search.out.result.down< + DashboardSearchOut, + DashboardSearchOut + >( + response, + undefined, // do not override version + { validate: false } // validation is done above + ); + + if (resultError) { + throw Boom.badRequest(`Invalid response. ${resultError.message}`); + } + + return value; } } diff --git a/src/plugins/dashboard/server/content_management/index.ts b/src/plugins/dashboard/server/content_management/index.ts index 6539241912671..8ff43345aa9ce 100644 --- a/src/plugins/dashboard/server/content_management/index.ts +++ b/src/plugins/dashboard/server/content_management/index.ts @@ -7,4 +7,24 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +export type { + ControlGroupAttributes, + GridData, + DashboardPanel, + DashboardAttributes, + DashboardItem, + DashboardGetIn, + DashboardGetOut, + DashboardCreateIn, + DashboardCreateOut, + DashboardCreateOptions, + DashboardSearchIn, + DashboardSearchOut, + DashboardSearchOptions, + DashboardUpdateIn, + DashboardUpdateOut, + DashboardUpdateOptions, + DashboardOptions, +} from './latest'; + export { DashboardStorage } from './dashboard_storage'; diff --git a/src/plugins/dashboard/common/content_management/latest.ts b/src/plugins/dashboard/server/content_management/latest.ts similarity index 91% rename from src/plugins/dashboard/common/content_management/latest.ts rename to src/plugins/dashboard/server/content_management/latest.ts index 82b84de84f8bf..e35d4011f84f0 100644 --- a/src/plugins/dashboard/common/content_management/latest.ts +++ b/src/plugins/dashboard/server/content_management/latest.ts @@ -7,5 +7,5 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -// Latest version is 2 -export * from './v2'; +// Latest version is 3 +export * from './v3'; diff --git a/src/plugins/dashboard/server/content_management/schema/v1/cm_services.ts b/src/plugins/dashboard/server/content_management/v1/cm_services.ts similarity index 61% rename from src/plugins/dashboard/server/content_management/schema/v1/cm_services.ts rename to src/plugins/dashboard/server/content_management/v1/cm_services.ts index f54cf0add822a..0cee0bb23f450 100644 --- a/src/plugins/dashboard/server/content_management/schema/v1/cm_services.ts +++ b/src/plugins/dashboard/server/content_management/v1/cm_services.ts @@ -16,51 +16,7 @@ import { updateOptionsSchema, createResultSchema, } from '@kbn/content-management-utils'; - -export const controlGroupInputSchema = schema - .object({ - panelsJSON: schema.maybe(schema.string()), - controlStyle: schema.maybe(schema.string()), - chainingSystem: schema.maybe(schema.string()), - ignoreParentSettingsJSON: schema.maybe(schema.string()), - }) - .extends({}, { unknowns: 'ignore' }); - -export const dashboardAttributesSchema = schema.object( - { - // General - title: schema.string(), - description: schema.string({ defaultValue: '' }), - - // Search - kibanaSavedObjectMeta: schema.object({ - searchSourceJSON: schema.maybe(schema.string()), - }), - - // Time - timeRestore: schema.maybe(schema.boolean()), - timeFrom: schema.maybe(schema.string()), - timeTo: schema.maybe(schema.string()), - refreshInterval: schema.maybe( - schema.object({ - pause: schema.boolean(), - value: schema.number(), - display: schema.maybe(schema.string()), - section: schema.maybe(schema.number()), - }) - ), - - // Dashboard Content - controlGroupInput: schema.maybe(controlGroupInputSchema), - panelsJSON: schema.string({ defaultValue: '[]' }), - optionsJSON: schema.string({ defaultValue: '{}' }), - - // Legacy - hits: schema.maybe(schema.number()), - version: schema.maybe(schema.number()), - }, - { unknowns: 'forbid' } -); +import { dashboardAttributesSchema } from '../../dashboard_saved_object/schema/v1'; export const dashboardSavedObjectSchema = savedObjectSchema(dashboardAttributesSchema); @@ -84,8 +40,10 @@ const dashboardUpdateOptionsSchema = schema.object({ mergeAttributes: schema.maybe(updateOptionsSchema.mergeAttributes), }); -// Content management service definition. -// We need it for BWC support between different versions of the content +/** + * Content management service definition v1. + * Dashboard attributes in content management version v1 are tightly coupled with the v1 model version saved object schema. + */ export const serviceDefinition: ServicesDefinition = { get: { out: { diff --git a/src/plugins/dashboard/server/content_management/schema/v1/index.ts b/src/plugins/dashboard/server/content_management/v1/index.ts similarity index 77% rename from src/plugins/dashboard/server/content_management/schema/v1/index.ts rename to src/plugins/dashboard/server/content_management/v1/index.ts index c26552457e5f9..163b952218bc8 100644 --- a/src/plugins/dashboard/server/content_management/schema/v1/index.ts +++ b/src/plugins/dashboard/server/content_management/v1/index.ts @@ -7,9 +7,4 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export { - serviceDefinition, - dashboardSavedObjectSchema, - controlGroupInputSchema, - dashboardAttributesSchema, -} from './cm_services'; +export { serviceDefinition } from './cm_services'; diff --git a/src/plugins/dashboard/server/content_management/schema/v2/cm_services.ts b/src/plugins/dashboard/server/content_management/v2/cm_services.ts similarity index 70% rename from src/plugins/dashboard/server/content_management/schema/v2/cm_services.ts rename to src/plugins/dashboard/server/content_management/v2/cm_services.ts index 9e81945e4c718..3b560b8416731 100644 --- a/src/plugins/dashboard/server/content_management/schema/v2/cm_services.ts +++ b/src/plugins/dashboard/server/content_management/v2/cm_services.ts @@ -7,36 +7,23 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { schema } from '@kbn/config-schema'; import { createResultSchema, objectTypeToGetResultSchema, savedObjectSchema, } from '@kbn/content-management-utils'; import type { ContentManagementServicesDefinition as ServicesDefinition } from '@kbn/object-versioning'; -import { - controlGroupInputSchema as controlGroupInputSchemaV1, - dashboardAttributesSchema as dashboardAttributesSchemaV1, - serviceDefinition as serviceDefinitionV1, -} from '../v1'; - -export const dashboardAttributesSchema = dashboardAttributesSchemaV1.extends( - { - controlGroupInput: schema.maybe( - controlGroupInputSchemaV1.extends( - { - showApplySelections: schema.maybe(schema.boolean()), - }, - { unknowns: 'ignore' } - ) - ), - }, - { unknowns: 'ignore' } -); +import type { DashboardCrudTypes } from '../../../common/content_management/v2'; +import { serviceDefinition as serviceDefinitionV1 } from '../v1'; +import { dashboardAttributesOut as attributesTov3 } from '../v3'; +import { dashboardAttributesSchema } from '../../dashboard_saved_object/schema/v2'; export const dashboardSavedObjectSchema = savedObjectSchema(dashboardAttributesSchema); -// Content management service definition. +/** + * Content management service definition v2. + * Dashboard attributes in content management version v2 are tightly coupled with the v2 model version saved object schema. + */ export const serviceDefinition: ServicesDefinition = { get: { out: { @@ -50,6 +37,7 @@ export const serviceDefinition: ServicesDefinition = { ...serviceDefinitionV1?.create?.in, data: { schema: dashboardAttributesSchema, + up: (data: DashboardCrudTypes['CreateIn']['data']) => attributesTov3(data), }, }, out: { @@ -63,6 +51,7 @@ export const serviceDefinition: ServicesDefinition = { ...serviceDefinitionV1.update?.in, data: { schema: dashboardAttributesSchema, + up: (data: DashboardCrudTypes['UpdateIn']['data']) => attributesTov3(data), }, }, }, diff --git a/src/plugins/dashboard/server/content_management/v2/index.ts b/src/plugins/dashboard/server/content_management/v2/index.ts new file mode 100644 index 0000000000000..163b952218bc8 --- /dev/null +++ b/src/plugins/dashboard/server/content_management/v2/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export { serviceDefinition } from './cm_services'; diff --git a/src/plugins/dashboard/server/content_management/v3/cm_services.ts b/src/plugins/dashboard/server/content_management/v3/cm_services.ts new file mode 100644 index 0000000000000..e086d1cc1460a --- /dev/null +++ b/src/plugins/dashboard/server/content_management/v3/cm_services.ts @@ -0,0 +1,539 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { v4 as uuidv4 } from 'uuid'; +import { schema, Type } from '@kbn/config-schema'; +import { createOptionsSchemas, updateOptionsSchema } from '@kbn/content-management-utils'; +import type { ContentManagementServicesDefinition as ServicesDefinition } from '@kbn/object-versioning'; +import { + type ControlGroupChainingSystem, + type ControlLabelPosition, + type ControlWidth, + CONTROL_CHAINING_OPTIONS, + CONTROL_LABEL_POSITION_OPTIONS, + CONTROL_WIDTH_OPTIONS, + DEFAULT_CONTROL_CHAINING, + DEFAULT_CONTROL_GROW, + DEFAULT_CONTROL_LABEL_POSITION, + DEFAULT_CONTROL_WIDTH, + DEFAULT_IGNORE_PARENT_SETTINGS, + DEFAULT_AUTO_APPLY_SELECTIONS, +} from '@kbn/controls-plugin/common'; +import { FilterStateStore } from '@kbn/es-query'; +import { SortDirection } from '@kbn/data-plugin/common/search'; +import { + DASHBOARD_GRID_COLUMN_COUNT, + DEFAULT_PANEL_HEIGHT, + DEFAULT_PANEL_WIDTH, + DEFAULT_DASHBOARD_OPTIONS, +} from '../../../common/content_management'; +import { getResultV3ToV2 } from './transform_utils'; + +const apiError = schema.object({ + error: schema.string(), + message: schema.string(), + statusCode: schema.number(), + metadata: schema.maybe(schema.object({}, { unknowns: 'allow' })), +}); + +// This schema should be provided by the controls plugin. Perhaps we can resolve this with the embeddable registry. +// See https://github.com/elastic/kibana/issues/192622 +export const controlGroupInputSchema = schema.object({ + controls: schema.arrayOf( + schema.object( + { + type: schema.string({ meta: { description: 'The type of the control panel.' } }), + controlConfig: schema.maybe(schema.recordOf(schema.string(), schema.any())), + id: schema.string({ + defaultValue: uuidv4(), + meta: { description: 'The unique ID of the control.' }, + }), + order: schema.number({ + meta: { + description: 'The order of the control panel in the control group.', + }, + }), + width: schema.oneOf( + Object.values(CONTROL_WIDTH_OPTIONS).map((value) => schema.literal(value)) as [ + Type + ], + { + defaultValue: DEFAULT_CONTROL_WIDTH, + meta: { description: 'Minimum width of the control panel in the control group.' }, + } + ), + grow: schema.boolean({ + defaultValue: DEFAULT_CONTROL_GROW, + meta: { description: 'Expand width of the control panel to fit available space.' }, + }), + }, + { unknowns: 'allow' } + ), + { + defaultValue: [], + meta: { description: 'An array of control panels and their state in the control group.' }, + } + ), + labelPosition: schema.oneOf( + Object.values(CONTROL_LABEL_POSITION_OPTIONS).map((value) => schema.literal(value)) as [ + Type + ], + { + defaultValue: DEFAULT_CONTROL_LABEL_POSITION, + meta: { + description: 'Position of the labels for controls. For example, "oneLine", "twoLine".', + }, + } + ), + chainingSystem: schema.oneOf( + Object.values(CONTROL_CHAINING_OPTIONS).map((value) => schema.literal(value)) as [ + Type + ], + { + defaultValue: DEFAULT_CONTROL_CHAINING, + meta: { + description: + 'The chaining strategy for multiple controls. For example, "HIERARCHICAL" or "NONE".', + }, + } + ), + enhancements: schema.maybe(schema.recordOf(schema.string(), schema.any())), + ignoreParentSettings: schema.object({ + ignoreFilters: schema.boolean({ + meta: { description: 'Ignore global filters in controls.' }, + defaultValue: DEFAULT_IGNORE_PARENT_SETTINGS.ignoreFilters, + }), + ignoreQuery: schema.boolean({ + meta: { description: 'Ignore the global query bar in controls.' }, + defaultValue: DEFAULT_IGNORE_PARENT_SETTINGS.ignoreQuery, + }), + ignoreTimerange: schema.boolean({ + meta: { description: 'Ignore the global time range in controls.' }, + defaultValue: DEFAULT_IGNORE_PARENT_SETTINGS.ignoreTimerange, + }), + ignoreValidations: schema.boolean({ + meta: { description: 'Ignore validations in controls.' }, + defaultValue: DEFAULT_IGNORE_PARENT_SETTINGS.ignoreValidations, + }), + }), + autoApplySelections: schema.boolean({ + meta: { description: 'Show apply selections button in controls.' }, + defaultValue: DEFAULT_AUTO_APPLY_SELECTIONS, + }), +}); + +const searchSourceSchema = schema.object( + { + type: schema.maybe(schema.string()), + query: schema.maybe( + schema.object({ + query: schema.oneOf([ + schema.string({ + meta: { + description: + 'A text-based query such as Kibana Query Language (KQL) or Lucene query language.', + }, + }), + schema.recordOf(schema.string(), schema.any()), + ]), + language: schema.string({ + meta: { description: 'The query language such as KQL or Lucene.' }, + }), + }) + ), + filter: schema.maybe( + schema.arrayOf( + schema.object( + { + meta: schema.object( + { + alias: schema.maybe(schema.nullable(schema.string())), + disabled: schema.maybe(schema.boolean()), + negate: schema.maybe(schema.boolean()), + controlledBy: schema.maybe(schema.string()), + group: schema.maybe(schema.string()), + index: schema.maybe(schema.string()), + isMultiIndex: schema.maybe(schema.boolean()), + type: schema.maybe(schema.string()), + key: schema.maybe(schema.string()), + params: schema.maybe(schema.any()), + value: schema.maybe(schema.string()), + field: schema.maybe(schema.string()), + }, + { unknowns: 'allow' } + ), + query: schema.maybe(schema.recordOf(schema.string(), schema.any())), + $state: schema.maybe( + schema.object({ + store: schema.oneOf( + [ + schema.literal(FilterStateStore.APP_STATE), + schema.literal(FilterStateStore.GLOBAL_STATE), + ], + { + meta: { + description: + "Denote whether a filter is specific to an application's context (e.g. 'appState') or whether it should be applied globally (e.g. 'globalState').", + }, + } + ), + }) + ), + }, + { meta: { description: 'A filter for the search source.' } } + ) + ) + ), + sort: schema.maybe( + schema.arrayOf( + schema.recordOf( + schema.string(), + schema.oneOf([ + schema.oneOf([schema.literal(SortDirection.asc), schema.literal(SortDirection.desc)]), + schema.object({ + order: schema.oneOf([ + schema.literal(SortDirection.asc), + schema.literal(SortDirection.desc), + ]), + format: schema.maybe(schema.string()), + }), + schema.object({ + order: schema.oneOf([ + schema.literal(SortDirection.asc), + schema.literal(SortDirection.desc), + ]), + numeric_type: schema.maybe( + schema.oneOf([ + schema.literal('double'), + schema.literal('long'), + schema.literal('date'), + schema.literal('date_nanos'), + ]) + ), + }), + ]) + ) + ) + ), + }, + /** + The Dashboard _should_ only ever uses the query and filters fields on the search + source. But we should be liberal in what we accept, so we allow unknowns. + */ + { defaultValue: {}, unknowns: 'allow' } +); + +export const gridDataSchema = schema.object({ + x: schema.number({ meta: { description: 'The x coordinate of the panel in grid units' } }), + y: schema.number({ meta: { description: 'The y coordinate of the panel in grid units' } }), + w: schema.number({ + defaultValue: DEFAULT_PANEL_WIDTH, + min: 1, + max: DASHBOARD_GRID_COLUMN_COUNT, + meta: { description: 'The width of the panel in grid units' }, + }), + h: schema.number({ + defaultValue: DEFAULT_PANEL_HEIGHT, + min: 1, + meta: { description: 'The height of the panel in grid units' }, + }), + i: schema.string({ + meta: { description: 'The unique identifier of the panel' }, + defaultValue: uuidv4(), + }), +}); + +export const panelSchema = schema.object({ + panelConfig: schema.object( + { + version: schema.maybe( + schema.string({ + meta: { description: 'The version of the embeddable in the panel.' }, + }) + ), + title: schema.maybe(schema.string({ meta: { description: 'The title of the panel' } })), + description: schema.maybe( + schema.string({ meta: { description: 'The description of the panel' } }) + ), + savedObjectId: schema.maybe( + schema.string({ + meta: { description: 'The unique id of the library item to construct the embeddable.' }, + }) + ), + hidePanelTitles: schema.maybe( + schema.boolean({ + defaultValue: false, + meta: { description: 'Set to true to hide the panel title in its container.' }, + }) + ), + enhancements: schema.maybe(schema.recordOf(schema.string(), schema.any())), + }, + { + unknowns: 'allow', + } + ), + id: schema.maybe( + schema.string({ meta: { description: 'The saved object id for by reference panels' } }) + ), + type: schema.string({ meta: { description: 'The embeddable type' } }), + panelRefName: schema.maybe(schema.string()), + gridData: gridDataSchema, + panelIndex: schema.string({ + meta: { description: 'The unique ID of the panel.' }, + defaultValue: schema.siblingRef('gridData.i'), + }), + title: schema.maybe(schema.string({ meta: { description: 'The title of the panel' } })), + version: schema.maybe( + schema.string({ + meta: { + description: + "The version was used to store Kibana version information from versions 7.3.0 -> 8.11.0. As of version 8.11.0, the versioning information is now per-embeddable-type and is stored on the embeddable's input. (panelConfig in this type).", + deprecated: true, + }, + }) + ), +}); + +export const optionsSchema = schema.object({ + hidePanelTitles: schema.boolean({ + defaultValue: DEFAULT_DASHBOARD_OPTIONS.hidePanelTitles, + meta: { description: 'Hide the panel titles in the dashboard.' }, + }), + useMargins: schema.boolean({ + defaultValue: DEFAULT_DASHBOARD_OPTIONS.useMargins, + meta: { description: 'Show margins between panels in the dashboard layout.' }, + }), + syncColors: schema.boolean({ + defaultValue: DEFAULT_DASHBOARD_OPTIONS.syncColors, + meta: { description: 'Synchronize colors between related panels in the dashboard.' }, + }), + syncTooltips: schema.boolean({ + defaultValue: DEFAULT_DASHBOARD_OPTIONS.syncTooltips, + meta: { description: 'Synchronize tooltips between related panels in the dashboard.' }, + }), + syncCursor: schema.boolean({ + defaultValue: DEFAULT_DASHBOARD_OPTIONS.syncCursor, + meta: { description: 'Synchronize cursor position between related panels in the dashboard.' }, + }), +}); + +// These are the attributes that are returned in search results +export const searchResultsAttributesSchema = schema.object({ + title: schema.string({ meta: { description: 'A human-readable title for the dashboard' } }), + description: schema.string({ defaultValue: '', meta: { description: 'A short description.' } }), + timeRestore: schema.boolean({ + defaultValue: false, + meta: { description: 'Whether to restore time upon viewing this dashboard' }, + }), +}); + +export const dashboardAttributesSchema = searchResultsAttributesSchema.extends({ + // Search + kibanaSavedObjectMeta: schema.object( + { + searchSource: schema.maybe(searchSourceSchema), + }, + { + meta: { + description: 'A container for various metadata', + }, + defaultValue: {}, + } + ), + // Time + timeFrom: schema.maybe( + schema.string({ meta: { description: 'An ISO string indicating when to restore time from' } }) + ), + timeTo: schema.maybe( + schema.string({ meta: { description: 'An ISO string indicating when to restore time from' } }) + ), + refreshInterval: schema.maybe( + schema.object( + { + pause: schema.boolean({ + meta: { + description: + 'Whether the refresh interval is set to be paused while viewing the dashboard.', + }, + }), + value: schema.number({ + meta: { + description: 'A numeric value indicating refresh frequency in milliseconds.', + }, + }), + display: schema.maybe( + schema.string({ + meta: { + description: + 'A human-readable string indicating the refresh frequency. No longer used.', + deprecated: true, + }, + }) + ), + section: schema.maybe( + schema.number({ + meta: { + description: 'No longer used.', // TODO what is this legacy property? + deprecated: true, + }, + }) + ), + }, + { + meta: { + description: 'A container for various refresh interval settings', + }, + } + ) + ), + + // Dashboard Content + controlGroupInput: schema.maybe(controlGroupInputSchema), + panels: schema.arrayOf(panelSchema, { defaultValue: [] }), + options: optionsSchema, + version: schema.maybe(schema.number({ meta: { deprecated: true } })), +}); + +export const referenceSchema = schema.object( + { + name: schema.string(), + type: schema.string(), + id: schema.string(), + }, + { unknowns: 'forbid' } +); + +export const dashboardItemSchema = schema.object( + { + id: schema.string(), + type: schema.string(), + version: schema.maybe(schema.string()), + createdAt: schema.maybe(schema.string()), + updatedAt: schema.maybe(schema.string()), + createdBy: schema.maybe(schema.string()), + updatedBy: schema.maybe(schema.string()), + managed: schema.maybe(schema.boolean()), + error: schema.maybe(apiError), + attributes: dashboardAttributesSchema, + references: schema.arrayOf(referenceSchema), + namespaces: schema.maybe(schema.arrayOf(schema.string())), + originId: schema.maybe(schema.string()), + }, + { unknowns: 'allow' } +); + +export const dashboardSearchResultsSchema = dashboardItemSchema.extends({ + attributes: searchResultsAttributesSchema, +}); + +export const dashboardSearchOptionsSchema = schema.maybe( + schema.object( + { + onlyTitle: schema.maybe(schema.boolean()), + fields: schema.maybe(schema.arrayOf(schema.string())), + kuery: schema.maybe(schema.string()), + cursor: schema.maybe(schema.number()), + limit: schema.maybe(schema.number()), + }, + { unknowns: 'forbid' } + ) +); + +export const dashboardCreateOptionsSchema = schema.object({ + id: schema.maybe(createOptionsSchemas.id), + overwrite: schema.maybe(createOptionsSchemas.overwrite), + references: schema.maybe(schema.arrayOf(referenceSchema)), + initialNamespaces: schema.maybe(createOptionsSchemas.initialNamespaces), +}); + +export const dashboardUpdateOptionsSchema = schema.object({ + references: schema.maybe(schema.arrayOf(referenceSchema)), + mergeAttributes: schema.maybe(updateOptionsSchema.mergeAttributes), +}); + +export const dashboardGetResultSchema = schema.object( + { + item: dashboardItemSchema, + meta: schema.object( + { + outcome: schema.oneOf([ + schema.literal('exactMatch'), + schema.literal('aliasMatch'), + schema.literal('conflict'), + ]), + aliasTargetId: schema.maybe(schema.string()), + aliasPurpose: schema.maybe( + schema.oneOf([ + schema.literal('savedObjectConversion'), + schema.literal('savedObjectImport'), + ]) + ), + }, + { unknowns: 'forbid' } + ), + }, + { unknowns: 'forbid' } +); + +export const dashboardCreateResultSchema = schema.object( + { + item: dashboardItemSchema, + }, + { unknowns: 'forbid' } +); + +export const serviceDefinition: ServicesDefinition = { + get: { + out: { + result: { + schema: dashboardGetResultSchema, + down: getResultV3ToV2, + }, + }, + }, + create: { + in: { + options: { + schema: dashboardCreateOptionsSchema, + }, + data: { + schema: dashboardAttributesSchema, + }, + }, + out: { + result: { + schema: dashboardCreateResultSchema, + }, + }, + }, + update: { + in: { + options: { + schema: dashboardUpdateOptionsSchema, + }, + data: { + schema: dashboardAttributesSchema, + }, + }, + }, + search: { + in: { + options: { + schema: dashboardSearchOptionsSchema, + }, + }, + }, + mSearch: { + out: { + result: { + schema: dashboardItemSchema, + }, + }, + }, +}; diff --git a/src/plugins/dashboard/server/content_management/v3/index.ts b/src/plugins/dashboard/server/content_management/v3/index.ts new file mode 100644 index 0000000000000..7be9313c3210e --- /dev/null +++ b/src/plugins/dashboard/server/content_management/v3/index.ts @@ -0,0 +1,42 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export type { + ControlGroupAttributes, + GridData, + DashboardPanel, + DashboardAttributes, + DashboardItem, + DashboardGetIn, + DashboardGetOut, + DashboardCreateIn, + DashboardCreateOut, + DashboardCreateOptions, + DashboardSearchIn, + DashboardSearchOut, + DashboardSearchOptions, + DashboardUpdateIn, + DashboardUpdateOut, + DashboardUpdateOptions, + DashboardOptions, +} from './types'; +export { + serviceDefinition, + dashboardAttributesSchema, + dashboardGetResultSchema, + dashboardCreateResultSchema, + dashboardItemSchema, + dashboardSearchResultsSchema, + referenceSchema, +} from './cm_services'; +export { + dashboardAttributesOut, + itemAttrsToSavedObjectAttrs, + savedObjectToItem, +} from './transform_utils'; diff --git a/src/plugins/dashboard/server/content_management/v3/transform_utils.test.ts b/src/plugins/dashboard/server/content_management/v3/transform_utils.test.ts new file mode 100644 index 0000000000000..627f1c4211033 --- /dev/null +++ b/src/plugins/dashboard/server/content_management/v3/transform_utils.test.ts @@ -0,0 +1,551 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import type { SavedObject } from '@kbn/core-saved-objects-api-server'; +import type { + DashboardSavedObjectAttributes, + SavedDashboardPanel, +} from '../../dashboard_saved_object'; +import type { DashboardAttributes, DashboardItem } from './types'; +import { + dashboardAttributesOut, + getResultV3ToV2, + itemAttrsToSavedObjectAttrs, + savedObjectToItem, +} from './transform_utils'; +import { + DEFAULT_AUTO_APPLY_SELECTIONS, + DEFAULT_CONTROL_CHAINING, + DEFAULT_CONTROL_GROW, + DEFAULT_CONTROL_LABEL_POSITION, + DEFAULT_CONTROL_WIDTH, + DEFAULT_IGNORE_PARENT_SETTINGS, + ControlLabelPosition, + ControlGroupChainingSystem, + ControlWidth, +} from '@kbn/controls-plugin/common'; +import { DEFAULT_DASHBOARD_OPTIONS } from '../../../common/content_management'; + +describe('dashboardAttributesOut', () => { + const controlGroupInputControlsSo = { + explicitInput: { anyKey: 'some value' }, + type: 'type1', + order: 0, + }; + + const panelsSo: SavedDashboardPanel[] = [ + { + embeddableConfig: { enhancements: {} }, + gridData: { x: 0, y: 0, w: 10, h: 10, i: '1' }, + id: '1', + panelIndex: '1', + panelRefName: 'ref1', + title: 'title1', + type: 'type1', + version: '2', + }, + ]; + + it('should set default values if not provided', () => { + const input: DashboardSavedObjectAttributes = { + controlGroupInput: { + panelsJSON: JSON.stringify({ foo: controlGroupInputControlsSo }), + }, + panelsJSON: JSON.stringify(panelsSo), + optionsJSON: JSON.stringify({ + hidePanelTitles: false, + }), + kibanaSavedObjectMeta: {}, + title: 'my title', + description: 'my description', + }; + expect(dashboardAttributesOut(input)).toEqual({ + controlGroupInput: { + chainingSystem: DEFAULT_CONTROL_CHAINING, + labelPosition: DEFAULT_CONTROL_LABEL_POSITION, + ignoreParentSettings: DEFAULT_IGNORE_PARENT_SETTINGS, + autoApplySelections: DEFAULT_AUTO_APPLY_SELECTIONS, + controls: [ + { + controlConfig: { anyKey: 'some value' }, + grow: DEFAULT_CONTROL_GROW, + id: 'foo', + order: 0, + type: 'type1', + width: DEFAULT_CONTROL_WIDTH, + }, + ], + }, + description: 'my description', + kibanaSavedObjectMeta: {}, + options: { + ...DEFAULT_DASHBOARD_OPTIONS, + hidePanelTitles: false, + }, + panels: [ + { + panelConfig: { enhancements: {} }, + gridData: { x: 0, y: 0, w: 10, h: 10, i: '1' }, + id: '1', + panelIndex: '1', + panelRefName: 'ref1', + title: 'title1', + type: 'type1', + version: '2', + }, + ], + timeRestore: false, + title: 'my title', + }); + }); + + it('should transform full attributes correctly', () => { + const input: DashboardSavedObjectAttributes = { + controlGroupInput: { + panelsJSON: JSON.stringify({ + foo: { + ...controlGroupInputControlsSo, + grow: false, + width: 'small', + }, + }), + ignoreParentSettingsJSON: JSON.stringify({ ignoreFilters: true }), + controlStyle: 'twoLine', + chainingSystem: 'NONE', + showApplySelections: true, + }, + description: 'description', + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ query: { query: 'test', language: 'KQL' } }), + }, + optionsJSON: JSON.stringify({ + hidePanelTitles: true, + useMargins: false, + syncColors: false, + syncTooltips: false, + syncCursor: false, + }), + panelsJSON: JSON.stringify(panelsSo), + refreshInterval: { pause: true, value: 1000 }, + timeFrom: 'now-15m', + timeRestore: true, + timeTo: 'now', + title: 'title', + }; + expect(dashboardAttributesOut(input)).toEqual({ + controlGroupInput: { + chainingSystem: 'NONE', + labelPosition: 'twoLine', + ignoreParentSettings: { + ignoreFilters: true, + ignoreQuery: false, + ignoreTimerange: false, + ignoreValidations: false, + }, + autoApplySelections: false, + controls: [ + { + controlConfig: { + anyKey: 'some value', + }, + id: 'foo', + grow: false, + width: 'small', + order: 0, + type: 'type1', + }, + ], + }, + description: 'description', + kibanaSavedObjectMeta: { + searchSource: { query: { query: 'test', language: 'KQL' } }, + }, + options: { + hidePanelTitles: true, + useMargins: false, + syncColors: false, + syncTooltips: false, + syncCursor: false, + }, + panels: [ + { + panelConfig: { + enhancements: {}, + }, + gridData: { + x: 0, + y: 0, + w: 10, + h: 10, + i: '1', + }, + id: '1', + panelIndex: '1', + panelRefName: 'ref1', + title: 'title1', + type: 'type1', + version: '2', + }, + ], + refreshInterval: { + pause: true, + value: 1000, + }, + timeFrom: 'now-15m', + timeRestore: true, + timeTo: 'now', + title: 'title', + }); + }); +}); + +describe('itemAttrsToSavedObjectAttrs', () => { + it('should transform item attributes to saved object attributes correctly', () => { + const input: DashboardAttributes = { + controlGroupInput: { + chainingSystem: 'NONE', + labelPosition: 'twoLine', + controls: [ + { + controlConfig: { anyKey: 'some value' }, + grow: false, + id: 'foo', + order: 0, + type: 'type1', + width: 'small', + }, + ], + ignoreParentSettings: { + ignoreFilters: true, + ignoreQuery: true, + ignoreTimerange: true, + ignoreValidations: true, + }, + autoApplySelections: false, + }, + description: 'description', + kibanaSavedObjectMeta: { searchSource: { query: { query: 'test', language: 'KQL' } } }, + options: { + hidePanelTitles: true, + useMargins: false, + syncColors: false, + syncTooltips: false, + syncCursor: false, + }, + panels: [ + { + gridData: { x: 0, y: 0, w: 10, h: 10, i: '1' }, + id: '1', + panelConfig: { enhancements: {} }, + panelIndex: '1', + panelRefName: 'ref1', + title: 'title1', + type: 'type1', + version: '2', + }, + ], + timeRestore: true, + title: 'title', + refreshInterval: { pause: true, value: 1000 }, + timeFrom: 'now-15m', + timeTo: 'now', + }; + + const output = itemAttrsToSavedObjectAttrs(input); + expect(output).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "controlGroupInput": Object { + "chainingSystem": "NONE", + "controlStyle": "twoLine", + "ignoreParentSettingsJSON": "{\\"ignoreFilters\\":true,\\"ignoreQuery\\":true,\\"ignoreTimerange\\":true,\\"ignoreValidations\\":true}", + "panelsJSON": "{\\"foo\\":{\\"grow\\":false,\\"order\\":0,\\"type\\":\\"type1\\",\\"width\\":\\"small\\",\\"explicitInput\\":{\\"anyKey\\":\\"some value\\",\\"id\\":\\"foo\\"}}}", + "showApplySelections": true, + }, + "description": "description", + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"query\\":{\\"query\\":\\"test\\",\\"language\\":\\"KQL\\"}}", + }, + "optionsJSON": "{\\"hidePanelTitles\\":true,\\"useMargins\\":false,\\"syncColors\\":false,\\"syncTooltips\\":false,\\"syncCursor\\":false}", + "panelsJSON": "[{\\"id\\":\\"1\\",\\"panelRefName\\":\\"ref1\\",\\"title\\":\\"title1\\",\\"type\\":\\"type1\\",\\"version\\":\\"2\\",\\"embeddableConfig\\":{\\"enhancements\\":{}},\\"panelIndex\\":\\"1\\",\\"gridData\\":{\\"x\\":0,\\"y\\":0,\\"w\\":10,\\"h\\":10,\\"i\\":\\"1\\"}}]", + "refreshInterval": Object { + "pause": true, + "value": 1000, + }, + "timeFrom": "now-15m", + "timeRestore": true, + "timeTo": "now", + "title": "title", + }, + "error": null, + } + `); + }); + + it('should handle missing optional attributes', () => { + const input: DashboardAttributes = { + title: 'title', + description: 'my description', + timeRestore: false, + panels: [], + options: DEFAULT_DASHBOARD_OPTIONS, + kibanaSavedObjectMeta: {}, + }; + + const output = itemAttrsToSavedObjectAttrs(input); + expect(output).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "description": "my description", + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{}", + }, + "optionsJSON": "{\\"hidePanelTitles\\":false,\\"useMargins\\":true,\\"syncColors\\":true,\\"syncCursor\\":true,\\"syncTooltips\\":true}", + "panelsJSON": "[]", + "timeRestore": false, + "title": "title", + }, + "error": null, + } + `); + }); +}); + +describe('savedObjectToItem', () => { + const commonSavedObject: SavedObject = { + references: [], + id: '3d8459d9-0f1a-403d-aa82-6d93713a54b5', + type: 'dashboard', + attributes: {}, + }; + + const getSavedObjectForAttributes = ( + attributes: DashboardSavedObjectAttributes + ): SavedObject => { + return { + ...commonSavedObject, + attributes, + }; + }; + it('should convert saved object to item with all attributes', () => { + const input = getSavedObjectForAttributes({ + title: 'title', + description: 'description', + timeRestore: true, + panelsJSON: JSON.stringify([ + { + embeddableConfig: { enhancements: {} }, + gridData: { x: 0, y: 0, w: 10, h: 10, i: '1' }, + id: '1', + panelIndex: '1', + panelRefName: 'ref1', + title: 'title1', + type: 'type1', + version: '2', + }, + ]), + optionsJSON: JSON.stringify({ + hidePanelTitles: true, + useMargins: false, + syncColors: false, + syncTooltips: false, + syncCursor: false, + }), + kibanaSavedObjectMeta: { + searchSourceJSON: '{"query":{"query":"test","language":"KQL"}}', + }, + }); + + const { item, error } = savedObjectToItem(input, false); + expect(error).toBeNull(); + expect(item).toEqual({ + ...commonSavedObject, + attributes: { + title: 'title', + description: 'description', + timeRestore: true, + panels: [ + { + panelConfig: { enhancements: {} }, + gridData: { x: 0, y: 0, w: 10, h: 10, i: '1' }, + id: '1', + panelIndex: '1', + panelRefName: 'ref1', + title: 'title1', + type: 'type1', + version: '2', + }, + ], + options: { + hidePanelTitles: true, + useMargins: false, + syncColors: false, + syncTooltips: false, + syncCursor: false, + }, + kibanaSavedObjectMeta: { + searchSource: { query: { query: 'test', language: 'KQL' } }, + }, + }, + }); + }); + + it('should handle missing optional attributes', () => { + const input = getSavedObjectForAttributes({ + title: 'title', + description: 'description', + timeRestore: false, + panelsJSON: '[]', + optionsJSON: '{}', + kibanaSavedObjectMeta: {}, + }); + + const { item, error } = savedObjectToItem(input, false); + expect(error).toBeNull(); + expect(item).toEqual({ + ...commonSavedObject, + attributes: { + title: 'title', + description: 'description', + timeRestore: false, + panels: [], + options: DEFAULT_DASHBOARD_OPTIONS, + kibanaSavedObjectMeta: {}, + }, + }); + }); + + it('should handle partial saved object', () => { + const input = { + ...commonSavedObject, + references: undefined, + attributes: { + title: 'title', + description: 'my description', + timeRestore: false, + }, + }; + + const { item, error } = savedObjectToItem(input, true, ['title', 'description']); + expect(error).toBeNull(); + expect(item).toEqual({ + ...commonSavedObject, + references: undefined, + attributes: { + title: 'title', + description: 'my description', + }, + }); + }); + + it('should return an error if attributes can not be parsed', () => { + const input = { + ...commonSavedObject, + references: undefined, + attributes: { + title: 'title', + panelsJSON: 'not stringified json', + }, + }; + const { item, error } = savedObjectToItem(input, true); + expect(item).toBeNull(); + expect(error).not.toBe(null); + }); +}); + +describe('getResultV3ToV2', () => { + const commonAttributes = { + description: 'description', + refreshInterval: { pause: true, value: 1000 }, + timeFrom: 'now-15m', + timeRestore: true, + timeTo: 'now', + title: 'title', + }; + it('should transform a v3 result to a v2 result with all attributes', () => { + const v3Result = { + meta: { outcome: 'exactMatch' as const }, + item: { + id: '1', + type: 'dashboard', + attributes: { + ...commonAttributes, + controlGroupInput: { + chainingSystem: 'NONE' as ControlGroupChainingSystem, + labelPosition: 'twoLine' as ControlLabelPosition, + controls: [ + { + controlConfig: { bizz: 'buzz' }, + grow: false, + order: 0, + id: 'foo', + type: 'type1', + width: 'small' as ControlWidth, + }, + ], + ignoreParentSettings: { + ignoreFilters: true, + ignoreQuery: true, + ignoreTimerange: true, + ignoreValidations: true, + }, + autoApplySelections: false, + }, + kibanaSavedObjectMeta: { searchSource: { query: { query: 'test', language: 'KQL' } } }, + options: { + hidePanelTitles: true, + useMargins: false, + syncColors: false, + syncCursor: false, + syncTooltips: false, + }, + panels: [ + { + id: '1', + type: 'visualization', + panelConfig: { title: 'my panel' }, + gridData: { x: 0, y: 0, w: 15, h: 15, i: 'foo' }, + panelIndex: 'foo', + }, + ], + }, + references: [], + }, + }; + + const output = getResultV3ToV2(v3Result); + + // Common attributes should match between v2 and v3 + expect(output.item.attributes).toMatchObject(commonAttributes); + expect(output.item.attributes.controlGroupInput).toMatchObject({ + chainingSystem: 'NONE', + controlStyle: 'twoLine', + showApplySelections: true, + }); + + // Check transformed attributes + expect(output.item.attributes.controlGroupInput!.panelsJSON).toMatchInlineSnapshot( + `"{\\"foo\\":{\\"grow\\":false,\\"order\\":0,\\"type\\":\\"type1\\",\\"width\\":\\"small\\",\\"explicitInput\\":{\\"bizz\\":\\"buzz\\",\\"id\\":\\"foo\\"}}}"` + ); + expect( + output.item.attributes.controlGroupInput!.ignoreParentSettingsJSON + ).toMatchInlineSnapshot( + `"{\\"ignoreFilters\\":true,\\"ignoreQuery\\":true,\\"ignoreTimerange\\":true,\\"ignoreValidations\\":true}"` + ); + expect(output.item.attributes.kibanaSavedObjectMeta.searchSourceJSON).toMatchInlineSnapshot( + `"{\\"query\\":{\\"query\\":\\"test\\",\\"language\\":\\"KQL\\"}}"` + ); + expect(output.item.attributes.optionsJSON).toMatchInlineSnapshot( + `"{\\"hidePanelTitles\\":true,\\"useMargins\\":false,\\"syncColors\\":false,\\"syncCursor\\":false,\\"syncTooltips\\":false}"` + ); + expect(output.item.attributes.panelsJSON).toMatchInlineSnapshot( + `"[{\\"id\\":\\"1\\",\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{\\"title\\":\\"my panel\\"},\\"panelIndex\\":\\"foo\\",\\"gridData\\":{\\"x\\":0,\\"y\\":0,\\"w\\":15,\\"h\\":15,\\"i\\":\\"foo\\"}}]"` + ); + }); +}); diff --git a/src/plugins/dashboard/server/content_management/v3/transform_utils.ts b/src/plugins/dashboard/server/content_management/v3/transform_utils.ts new file mode 100644 index 0000000000000..843dd59f849f3 --- /dev/null +++ b/src/plugins/dashboard/server/content_management/v3/transform_utils.ts @@ -0,0 +1,365 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { v4 as uuidv4 } from 'uuid'; +import { pick } from 'lodash'; + +import type { Query } from '@kbn/es-query'; +import { + type ControlGroupChainingSystem, + type ControlLabelPosition, + type ControlPanelsState, + type SerializedControlState, + DEFAULT_AUTO_APPLY_SELECTIONS, + DEFAULT_CONTROL_CHAINING, + DEFAULT_CONTROL_GROW, + DEFAULT_CONTROL_LABEL_POSITION, + DEFAULT_CONTROL_WIDTH, + DEFAULT_IGNORE_PARENT_SETTINGS, +} from '@kbn/controls-plugin/common'; +import { SerializedSearchSourceFields, parseSearchSourceJSON } from '@kbn/data-plugin/common'; + +import type { SavedObject, SavedObjectReference } from '@kbn/core-saved-objects-api-server'; +import type { + ControlGroupAttributes, + DashboardAttributes, + DashboardGetOut, + DashboardItem, + DashboardOptions, + ItemAttrsToSavedObjectAttrsReturn, + PartialDashboardItem, + SavedObjectToItemReturn, +} from './types'; +import type { + DashboardSavedObjectAttributes, + SavedDashboardPanel, +} from '../../dashboard_saved_object'; +import type { + ControlGroupAttributes as ControlGroupAttributesV2, + DashboardCrudTypes as DashboardCrudTypesV2, +} from '../../../common/content_management/v2'; +import { DEFAULT_DASHBOARD_OPTIONS } from '../../../common/content_management'; + +function controlGroupInputOut( + controlGroupInput?: DashboardSavedObjectAttributes['controlGroupInput'] +): ControlGroupAttributes | undefined { + if (!controlGroupInput) { + return; + } + const { + panelsJSON, + ignoreParentSettingsJSON, + controlStyle = DEFAULT_CONTROL_LABEL_POSITION, + chainingSystem = DEFAULT_CONTROL_CHAINING, + showApplySelections = !DEFAULT_AUTO_APPLY_SELECTIONS, + } = controlGroupInput; + const controls = panelsJSON + ? Object.entries(JSON.parse(panelsJSON) as ControlPanelsState).map( + ([ + id, + { + explicitInput, + type, + grow = DEFAULT_CONTROL_GROW, + width = DEFAULT_CONTROL_WIDTH, + order, + }, + ]) => ({ + controlConfig: explicitInput, + id, + grow, + order, + type, + width, + }) + ) + : []; + + const { + ignoreFilters = DEFAULT_IGNORE_PARENT_SETTINGS.ignoreFilters, + ignoreQuery = DEFAULT_IGNORE_PARENT_SETTINGS.ignoreQuery, + ignoreTimerange = DEFAULT_IGNORE_PARENT_SETTINGS.ignoreTimerange, + ignoreValidations = DEFAULT_IGNORE_PARENT_SETTINGS.ignoreValidations, + } = ignoreParentSettingsJSON ? JSON.parse(ignoreParentSettingsJSON) : {}; + + // try to maintain a consistent (alphabetical) order of keys + return { + autoApplySelections: !showApplySelections, + chainingSystem: chainingSystem as ControlGroupChainingSystem, + controls, + labelPosition: controlStyle as ControlLabelPosition, + ignoreParentSettings: { ignoreFilters, ignoreQuery, ignoreTimerange, ignoreValidations }, + }; +} + +function kibanaSavedObjectMetaOut( + kibanaSavedObjectMeta: DashboardSavedObjectAttributes['kibanaSavedObjectMeta'] +): DashboardAttributes['kibanaSavedObjectMeta'] { + const { searchSourceJSON } = kibanaSavedObjectMeta; + if (!searchSourceJSON) { + return {}; + } + // Dashboards do not yet support ES|QL (AggregateQuery) in the search source + return { + searchSource: parseSearchSourceJSON(searchSourceJSON) as Omit< + SerializedSearchSourceFields, + 'query' + > & { query?: Query }, + }; +} + +function optionsOut(optionsJSON: string): DashboardAttributes['options'] { + const { + hidePanelTitles = DEFAULT_DASHBOARD_OPTIONS.hidePanelTitles, + useMargins = DEFAULT_DASHBOARD_OPTIONS.useMargins, + syncColors = DEFAULT_DASHBOARD_OPTIONS.syncColors, + syncCursor = DEFAULT_DASHBOARD_OPTIONS.syncCursor, + syncTooltips = DEFAULT_DASHBOARD_OPTIONS.syncTooltips, + } = JSON.parse(optionsJSON) as DashboardOptions; + return { + hidePanelTitles, + useMargins, + syncColors, + syncCursor, + syncTooltips, + }; +} + +function panelsOut(panelsJSON: string): DashboardAttributes['panels'] { + const panels = JSON.parse(panelsJSON) as SavedDashboardPanel[]; + return panels.map( + ({ embeddableConfig, gridData, id, panelIndex, panelRefName, title, type, version }) => ({ + gridData, + id, + panelConfig: embeddableConfig, + panelIndex, + panelRefName, + title, + type, + version, + }) + ); +} + +export function dashboardAttributesOut( + attributes: DashboardSavedObjectAttributes | Partial +): DashboardAttributes | Partial { + const { + controlGroupInput, + description, + kibanaSavedObjectMeta, + optionsJSON, + panelsJSON, + refreshInterval, + timeFrom, + timeRestore, + timeTo, + title, + version, + } = attributes; + // try to maintain a consistent (alphabetical) order of keys + return { + ...(controlGroupInput && { controlGroupInput: controlGroupInputOut(controlGroupInput) }), + ...(description && { description }), + ...(kibanaSavedObjectMeta && { + kibanaSavedObjectMeta: kibanaSavedObjectMetaOut(kibanaSavedObjectMeta), + }), + ...(optionsJSON && { options: optionsOut(optionsJSON) }), + ...(panelsJSON && { panels: panelsOut(panelsJSON) }), + ...(refreshInterval && { + refreshInterval: { pause: refreshInterval.pause, value: refreshInterval.value }, + }), + ...(timeFrom && { timeFrom }), + timeRestore: timeRestore ?? false, + ...(timeTo && { timeTo }), + title, + ...(version && { version }), + }; +} + +function controlGroupInputIn( + controlGroupInput?: ControlGroupAttributes +): DashboardSavedObjectAttributes['controlGroupInput'] | undefined { + if (!controlGroupInput) { + return; + } + const { controls, ignoreParentSettings, labelPosition, chainingSystem, autoApplySelections } = + controlGroupInput; + const updatedControls = Object.fromEntries( + controls.map(({ controlConfig, id = uuidv4(), ...restOfControl }) => { + return [id, { ...restOfControl, explicitInput: { ...controlConfig, id } }]; + }) + ); + return { + chainingSystem, + controlStyle: labelPosition, + ignoreParentSettingsJSON: JSON.stringify(ignoreParentSettings), + panelsJSON: JSON.stringify(updatedControls), + showApplySelections: !autoApplySelections, + }; +} + +function panelsIn( + panels: DashboardAttributes['panels'] +): DashboardSavedObjectAttributes['panelsJSON'] { + const updatedPanels = panels.map(({ panelIndex, gridData, panelConfig, ...restPanel }) => { + const idx = panelIndex ?? uuidv4(); + return { + ...restPanel, + embeddableConfig: panelConfig, + panelIndex: idx, + gridData: { + ...gridData, + i: idx, + }, + }; + }); + + return JSON.stringify(updatedPanels); +} + +function kibanaSavedObjectMetaIn( + kibanaSavedObjectMeta: DashboardAttributes['kibanaSavedObjectMeta'] +) { + const { searchSource } = kibanaSavedObjectMeta; + return { searchSourceJSON: JSON.stringify(searchSource ?? {}) }; +} + +export const getResultV3ToV2 = (result: DashboardGetOut): DashboardCrudTypesV2['GetOut'] => { + const { meta, item } = result; + const { attributes, ...rest } = item; + const { + controlGroupInput, + description, + kibanaSavedObjectMeta, + options, + panels, + refreshInterval, + timeFrom, + timeRestore, + timeTo, + title, + version, + } = attributes; + + const v2Attributes = { + ...(controlGroupInput && { + controlGroupInput: controlGroupInputIn(controlGroupInput) as ControlGroupAttributesV2, + }), + description, + ...(kibanaSavedObjectMeta && { + kibanaSavedObjectMeta: kibanaSavedObjectMetaIn(kibanaSavedObjectMeta), + }), + ...(options && { optionsJSON: JSON.stringify(options) }), + panelsJSON: panels ? panelsIn(panels) : '[]', + refreshInterval, + ...(timeFrom && { timeFrom }), + timeRestore, + ...(timeTo && { timeTo }), + title, + ...(version && { version }), + }; + return { + meta, + item: { + ...rest, + attributes: v2Attributes, + }, + }; +}; + +export const itemAttrsToSavedObjectAttrs = ( + attributes: DashboardAttributes +): ItemAttrsToSavedObjectAttrsReturn => { + try { + const { controlGroupInput, kibanaSavedObjectMeta, options, panels, ...rest } = attributes; + const soAttributes = { + ...rest, + ...(controlGroupInput && { + controlGroupInput: controlGroupInputIn(controlGroupInput), + }), + ...(options && { + optionsJSON: JSON.stringify(options), + }), + ...(panels && { + panelsJSON: panelsIn(panels), + }), + ...(kibanaSavedObjectMeta && { + kibanaSavedObjectMeta: kibanaSavedObjectMetaIn(kibanaSavedObjectMeta), + }), + }; + return { attributes: soAttributes, error: null }; + } catch (e) { + return { attributes: null, error: e }; + } +}; + +type PartialSavedObject = Omit>, 'references'> & { + references: SavedObjectReference[] | undefined; +}; + +export function savedObjectToItem( + savedObject: SavedObject, + partial: false, + allowedAttributes?: string[] +): SavedObjectToItemReturn; + +export function savedObjectToItem( + savedObject: PartialSavedObject, + partial: true, + allowedAttributes?: string[] +): SavedObjectToItemReturn; + +export function savedObjectToItem( + savedObject: + | SavedObject + | PartialSavedObject, + partial: boolean, + allowedAttributes?: string[] +): SavedObjectToItemReturn { + const { + id, + type, + updated_at: updatedAt, + updated_by: updatedBy, + created_at: createdAt, + created_by: createdBy, + attributes, + error, + namespaces, + references, + version, + managed, + } = savedObject; + + try { + const attributesOut = allowedAttributes + ? pick(dashboardAttributesOut(attributes), allowedAttributes) + : dashboardAttributesOut(attributes); + return { + item: { + id, + type, + updatedAt, + updatedBy, + createdAt, + createdBy, + attributes: attributesOut, + error, + namespaces, + references, + version, + managed, + }, + error: null, + }; + } catch (e) { + return { item: null, error: e }; + } +} diff --git a/src/plugins/dashboard/server/content_management/v3/types.ts b/src/plugins/dashboard/server/content_management/v3/types.ts new file mode 100644 index 0000000000000..36f277ff3b268 --- /dev/null +++ b/src/plugins/dashboard/server/content_management/v3/types.ts @@ -0,0 +1,90 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { TypeOf } from '@kbn/config-schema'; +import { + CreateIn, + GetIn, + SearchIn, + SearchResult, + UpdateIn, +} from '@kbn/content-management-plugin/common'; +import { SavedObjectReference } from '@kbn/core-saved-objects-api-server'; +import { + dashboardItemSchema, + controlGroupInputSchema, + gridDataSchema, + panelSchema, + dashboardAttributesSchema, + dashboardCreateOptionsSchema, + dashboardCreateResultSchema, + dashboardGetResultSchema, + dashboardSearchOptionsSchema, + dashboardSearchResultsSchema, + dashboardUpdateOptionsSchema, + optionsSchema, +} from './cm_services'; +import { CONTENT_ID } from '../../../common/content_management'; +import { DashboardSavedObjectAttributes } from '../../dashboard_saved_object'; + +export type DashboardOptions = TypeOf; + +// Panel config has some defined types but also allows for custom keys added by embeddables +// The schema uses "unknowns: 'allow'" to permit any other keys, but the TypeOf helper does not +// recognize this, so we need to manually extend the type here. +export type DashboardPanel = Omit, 'panelConfig'> & { + panelConfig: TypeOf['panelConfig'] & { [key: string]: any }; +}; +export type DashboardAttributes = Omit, 'panels'> & { + panels: DashboardPanel[]; +}; + +export type DashboardItem = TypeOf; +export type PartialDashboardItem = Omit & { + attributes: Partial; + references: SavedObjectReference[] | undefined; +}; + +export type ControlGroupAttributes = TypeOf; +export type GridData = TypeOf; + +export type DashboardGetIn = GetIn; +export type DashboardGetOut = TypeOf; + +export type DashboardCreateIn = CreateIn; +export type DashboardCreateOut = TypeOf; +export type DashboardCreateOptions = TypeOf; + +export type DashboardUpdateIn = UpdateIn>; +export type DashboardUpdateOut = TypeOf; +export type DashboardUpdateOptions = TypeOf; + +export type DashboardSearchIn = SearchIn; +export type DashboardSearchOptions = TypeOf; +export type DashboardSearchOut = SearchResult>; + +export type SavedObjectToItemReturn = + | { + item: T; + error: null; + } + | { + item: null; + error: Error; + }; + +export type ItemAttrsToSavedObjectAttrsReturn = + | { + attributes: DashboardSavedObjectAttributes; + error: null; + } + | { + attributes: null; + error: Error; + }; diff --git a/src/plugins/dashboard/server/dashboard_saved_object/dashboard_saved_object.ts b/src/plugins/dashboard/server/dashboard_saved_object/dashboard_saved_object.ts index fc551d823377c..3b7f137cc1d96 100644 --- a/src/plugins/dashboard/server/dashboard_saved_object/dashboard_saved_object.ts +++ b/src/plugins/dashboard/server/dashboard_saved_object/dashboard_saved_object.ts @@ -10,19 +10,21 @@ import { ANALYTICS_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { SavedObjectsType } from '@kbn/core/server'; -import { dashboardAttributesSchema as dashboardAttributesSchemaV1 } from '../content_management/schema/v1'; -import { dashboardAttributesSchema as dashboardAttributesSchemaV2 } from '../content_management/schema/v2'; +import { dashboardAttributesSchema as dashboardAttributesSchemaV1 } from './schema/v1'; +import { dashboardAttributesSchema as dashboardAttributesSchemaV2 } from './schema/v2'; import { createDashboardSavedObjectTypeMigrations, DashboardSavedObjectTypeMigrationsDeps, } from './migrations/dashboard_saved_object_migrations'; +export const DASHBOARD_SAVED_OBJECT_TYPE = 'dashboard'; + export const createDashboardSavedObjectType = ({ migrationDeps, }: { migrationDeps: DashboardSavedObjectTypeMigrationsDeps; }): SavedObjectsType => ({ - name: 'dashboard', + name: DASHBOARD_SAVED_OBJECT_TYPE, indexPattern: ANALYTICS_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'multiple-isolated', diff --git a/src/plugins/dashboard/server/dashboard_saved_object/index.ts b/src/plugins/dashboard/server/dashboard_saved_object/index.ts index 912c508f0cedf..23c91f2c6ea35 100644 --- a/src/plugins/dashboard/server/dashboard_saved_object/index.ts +++ b/src/plugins/dashboard/server/dashboard_saved_object/index.ts @@ -7,4 +7,8 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export { createDashboardSavedObjectType } from './dashboard_saved_object'; +export { + createDashboardSavedObjectType, + DASHBOARD_SAVED_OBJECT_TYPE, +} from './dashboard_saved_object'; +export type { DashboardSavedObjectAttributes, GridData, SavedDashboardPanel } from './schema'; diff --git a/src/plugins/dashboard/server/dashboard_saved_object/migrations/dashboard_saved_object_migrations.test.ts b/src/plugins/dashboard/server/dashboard_saved_object/migrations/dashboard_saved_object_migrations.test.ts index 7abb1523e3611..b5f5b6b20b312 100644 --- a/src/plugins/dashboard/server/dashboard_saved_object/migrations/dashboard_saved_object_migrations.test.ts +++ b/src/plugins/dashboard/server/dashboard_saved_object/migrations/dashboard_saved_object_migrations.test.ts @@ -613,12 +613,11 @@ describe('dashboard', () => { expect(newDoc).toMatchInlineSnapshot(` Object { "attributes": Object { - "description": "", "kibanaSavedObjectMeta": Object { "searchSourceJSON": "{\\"query\\":{\\"language\\":\\"kuery\\",\\"query\\":\\"\\"},\\"filter\\":[{\\"query\\":{\\"match_phrase\\":{\\"machine.os.keyword\\":\\"osx\\"}},\\"$state\\":{\\"store\\":\\"appState\\"},\\"meta\\":{\\"type\\":\\"phrase\\",\\"key\\":\\"machine.os.keyword\\",\\"params\\":{\\"query\\":\\"osx\\"},\\"disabled\\":false,\\"negate\\":false,\\"alias\\":null,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\\"}}]}", }, - "optionsJSON": "{\\"useMargins\\":true,\\"hidePanelTitles\\":false}", - "panelsJSON": "[{\\"version\\":\\"7.9.3\\",\\"type\\":\\"visualization\\",\\"gridData\\":{\\"x\\":0,\\"y\\":0,\\"w\\":24,\\"h\\":15,\\"i\\":\\"82fa0882-9f9e-476a-bbb9-03555e5ced91\\"},\\"panelIndex\\":\\"82fa0882-9f9e-476a-bbb9-03555e5ced91\\",\\"embeddableConfig\\":{\\"enhancements\\":{\\"dynamicActions\\":{\\"events\\":[]}}},\\"panelRefName\\":\\"panel_82fa0882-9f9e-476a-bbb9-03555e5ced91\\"}]", + "optionsJSON": "{\\"hidePanelTitles\\":false,\\"useMargins\\":true,\\"syncColors\\":true,\\"syncCursor\\":true,\\"syncTooltips\\":true}", + "panelsJSON": "[{\\"version\\":\\"7.9.3\\",\\"type\\":\\"visualization\\",\\"panelRefName\\":\\"panel_82fa0882-9f9e-476a-bbb9-03555e5ced91\\",\\"embeddableConfig\\":{\\"enhancements\\":{\\"dynamicActions\\":{\\"events\\":[]}}},\\"panelIndex\\":\\"82fa0882-9f9e-476a-bbb9-03555e5ced91\\",\\"gridData\\":{\\"x\\":0,\\"y\\":0,\\"w\\":24,\\"h\\":15,\\"i\\":\\"82fa0882-9f9e-476a-bbb9-03555e5ced91\\"}}]", "timeRestore": false, "title": "Dashboard A", "version": 1, @@ -710,7 +709,7 @@ describe('dashboard', () => { contextMock ); expect(migratedDoc.attributes.panelsJSON).toMatchInlineSnapshot( - `"[{\\"version\\":\\"7.9.3\\",\\"gridData\\":{\\"x\\":0,\\"y\\":0,\\"w\\":24,\\"h\\":15,\\"i\\":\\"0\\"},\\"panelIndex\\":\\"0\\",\\"embeddableConfig\\":{}},{\\"version\\":\\"7.13.0\\",\\"gridData\\":{\\"x\\":24,\\"y\\":0,\\"w\\":24,\\"h\\":15,\\"i\\":\\"1\\"},\\"panelIndex\\":\\"1\\",\\"embeddableConfig\\":{\\"attributes\\":{\\"byValueThing\\":\\"ThisIsByValue\\"},\\"superCoolKey\\":\\"ONLY 4 BY VALUE EMBEDDABLES THANK YOU VERY MUCH\\"}}]"` + `"[{\\"version\\":\\"7.9.3\\",\\"gridData\\":{\\"x\\":0,\\"y\\":0,\\"w\\":24,\\"h\\":15,\\"i\\":\\"0\\"},\\"panelIndex\\":\\"0\\",\\"embeddableConfig\\":{}},{\\"gridData\\":{\\"x\\":24,\\"y\\":0,\\"w\\":24,\\"h\\":15,\\"i\\":\\"1\\"},\\"panelIndex\\":\\"1\\",\\"embeddableConfig\\":{\\"attributes\\":{\\"byValueThing\\":\\"ThisIsByValue\\"},\\"superCoolKey\\":\\"ONLY 4 BY VALUE EMBEDDABLES THANK YOU VERY MUCH\\"},\\"version\\":\\"7.13.0\\"}]"` ); }); }); diff --git a/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_by_value_dashboard_panels.ts b/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_by_value_dashboard_panels.ts index 1b1d04cdebf77..0e32e2feec300 100644 --- a/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_by_value_dashboard_panels.ts +++ b/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_by_value_dashboard_panels.ts @@ -9,8 +9,8 @@ import { CONTROL_GROUP_TYPE } from '@kbn/controls-plugin/common'; import { - controlGroupSerializedStateToSerializableRuntimeState, - serializableRuntimeStateToControlGroupSerializedState, + controlGroupSavedObjectStateToSerializableRuntimeState, + serializableRuntimeStateToControlGroupSavedObjectState, } from '@kbn/controls-plugin/server'; import { Serializable, SerializableRecord } from '@kbn/utility-types'; import { SavedObjectMigrationFn } from '@kbn/core/server'; @@ -20,8 +20,8 @@ import { SavedObjectEmbeddableInput } from '@kbn/embeddable-plugin/common'; import { convertPanelStateToSavedDashboardPanel, convertSavedDashboardPanelToPanelState, -} from '../../../common'; -import { SavedDashboardPanel } from '../../../common/content_management'; +} from './utils'; +import type { SavedDashboardPanel } from '..'; type ValueOrReferenceInput = SavedObjectEmbeddableInput & { attributes?: Serializable; @@ -35,7 +35,7 @@ export const migrateByValueDashboardPanels = const { attributes } = doc; if (attributes?.controlGroupInput) { - const controlGroupState = controlGroupSerializedStateToSerializableRuntimeState( + const controlGroupState = controlGroupSavedObjectStateToSerializableRuntimeState( attributes.controlGroupInput ); const migratedControlGroupInput = migrate({ @@ -43,7 +43,7 @@ export const migrateByValueDashboardPanels = type: CONTROL_GROUP_TYPE, } as SerializableRecord); attributes.controlGroupInput = - serializableRuntimeStateToControlGroupSerializedState(migratedControlGroupInput); + serializableRuntimeStateToControlGroupSavedObjectState(migratedControlGroupInput); } // Skip if panelsJSON is missing otherwise this will cause saved object import to fail when diff --git a/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_extract_panel_references.ts b/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_extract_panel_references.ts index 1782a63beda5d..091ef21322671 100644 --- a/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_extract_panel_references.ts +++ b/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_extract_panel_references.ts @@ -7,11 +7,12 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { SavedObjectMigrationFn } from '@kbn/core/server'; +import { SavedObject, SavedObjectMigrationFn } from '@kbn/core/server'; import { extractReferences, injectReferences } from '../../../common'; -import { DashboardAttributes } from '../../../common/content_management'; -import { DashboardSavedObjectTypeMigrationsDeps } from './dashboard_saved_object_migrations'; +import type { DashboardSavedObjectTypeMigrationsDeps } from './dashboard_saved_object_migrations'; +import type { DashboardSavedObjectAttributes } from '../schema'; +import { itemAttrsToSavedObjectAttrs, savedObjectToItem } from '../../content_management/latest'; /** * In 7.8.0 we introduced dashboard drilldowns which are stored inside dashboard saved object as part of embeddable state @@ -26,7 +27,7 @@ import { DashboardSavedObjectTypeMigrationsDeps } from './dashboard_saved_object */ export function createExtractPanelReferencesMigration( deps: DashboardSavedObjectTypeMigrationsDeps -): SavedObjectMigrationFn { +): SavedObjectMigrationFn { return (doc) => { const references = doc.references ?? []; @@ -36,19 +37,32 @@ export function createExtractPanelReferencesMigration( */ const oldNonPanelReferences = references.filter((ref) => !ref.name.startsWith('panel_')); + // Use Content Management to convert the saved object to the DashboardAttributes + // expected by injectReferences + const { item, error: itemError } = savedObjectToItem( + doc as unknown as SavedObject, + false + ); + + if (itemError) throw itemError; + + const parsedAttributes = item.attributes; const injectedAttributes = injectReferences( { - attributes: doc.attributes, + attributes: parsedAttributes, references, }, { embeddablePersistableStateService: deps.embeddable } ); - const { attributes, references: newPanelReferences } = extractReferences( + const { attributes: extractedAttributes, references: newPanelReferences } = extractReferences( { attributes: injectedAttributes, references: [] }, { embeddablePersistableStateService: deps.embeddable } ); + const { attributes, error: attributesError } = itemAttrsToSavedObjectAttrs(extractedAttributes); + if (attributesError) throw attributesError; + return { ...doc, references: [...oldNonPanelReferences, ...newPanelReferences], diff --git a/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_hidden_titles.ts b/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_hidden_titles.ts index c223ff0bc32a3..77c114315ba1f 100644 --- a/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_hidden_titles.ts +++ b/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_hidden_titles.ts @@ -7,14 +7,14 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { SavedObjectMigrationFn } from '@kbn/core/server'; -import { EmbeddableInput } from '@kbn/embeddable-plugin/common'; +import type { SavedObjectMigrationFn } from '@kbn/core/server'; +import type { EmbeddableInput } from '@kbn/embeddable-plugin/common'; +import type { SavedDashboardPanel } from '../schema'; import { convertSavedDashboardPanelToPanelState, convertPanelStateToSavedDashboardPanel, -} from '../../../common'; -import { SavedDashboardPanel } from '../../../common/content_management'; +} from './utils'; /** * Before 7.10, hidden panel titles were stored as a blank string on the title attribute. In 7.10, this was replaced diff --git a/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_to_730/migrate_to_730_panels.ts b/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_to_730/migrate_to_730_panels.ts index ab05f64a2d711..e23ccfd00153d 100644 --- a/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_to_730/migrate_to_730_panels.ts +++ b/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_to_730/migrate_to_730_panels.ts @@ -13,7 +13,7 @@ import semverSatisfies from 'semver/functions/satisfies'; import { i18n } from '@kbn/i18n'; import type { SerializableRecord } from '@kbn/utility-types'; -import { +import type { SavedDashboardPanel620, SavedDashboardPanel630, SavedDashboardPanel610, @@ -25,7 +25,7 @@ import { RawSavedDashboardPanel640To720, RawSavedDashboardPanel730ToLatest, } from './types'; -import { GridData } from '../../../../common/content_management'; +import type { GridData } from '../../../content_management'; const PANEL_HEIGHT_SCALE_FACTOR = 5; const PANEL_HEIGHT_SCALE_FACTOR_WITH_MARGINS = 4; diff --git a/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_to_730/migrations_730.ts b/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_to_730/migrations_730.ts index 6af69882b0774..6da3c1510530c 100644 --- a/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_to_730/migrations_730.ts +++ b/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_to_730/migrations_730.ts @@ -49,7 +49,7 @@ export const migrations730 = (doc: DashboardDoc700To720, { log }: SavedObjectMig } try { - const searchSource = JSON.parse(doc.attributes.kibanaSavedObjectMeta.searchSourceJSON); + const searchSource = JSON.parse(doc.attributes.kibanaSavedObjectMeta.searchSourceJSON!); doc.attributes.kibanaSavedObjectMeta.searchSourceJSON = JSON.stringify( moveFiltersToQuery(searchSource) ); diff --git a/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_to_730/types.ts b/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_to_730/types.ts index 750fd736c9660..585b9c55d5012 100644 --- a/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_to_730/types.ts +++ b/src/plugins/dashboard/server/dashboard_saved_object/migrations/migrate_to_730/types.ts @@ -10,14 +10,12 @@ import type { Serializable } from '@kbn/utility-types'; import { SavedObjectReference } from '@kbn/core/server'; -import type { - GridData, - DashboardAttributes as CurrentDashboardAttributes, // Dashboard attributes from common are the source of truth for the current version. -} from '../../../../common/content_management'; +import type { GridData } from '../../../content_management'; +import type { DashboardSavedObjectAttributes } from '../../schema'; interface KibanaAttributes { kibanaSavedObjectMeta: { - searchSourceJSON: string; + searchSourceJSON?: string; }; } @@ -45,7 +43,7 @@ interface DashboardAttributesTo720 extends KibanaAttributes { optionsJSON?: string; } -export type DashboardDoc730ToLatest = Doc; +export type DashboardDoc730ToLatest = Doc; export type DashboardDoc700To720 = Doc; diff --git a/src/plugins/dashboard/common/lib/dashboard_panel_converters.test.ts b/src/plugins/dashboard/server/dashboard_saved_object/migrations/utils.test.ts similarity index 82% rename from src/plugins/dashboard/common/lib/dashboard_panel_converters.test.ts rename to src/plugins/dashboard/server/dashboard_saved_object/migrations/utils.test.ts index f750d95ca2efa..17aca8fef68ce 100644 --- a/src/plugins/dashboard/common/lib/dashboard_panel_converters.test.ts +++ b/src/plugins/dashboard/server/dashboard_saved_object/migrations/utils.test.ts @@ -7,13 +7,14 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +import type { EmbeddableInput } from '@kbn/embeddable-plugin/common/types'; +import type { SavedDashboardPanel } from '../schema'; +import type { DashboardPanelState } from '../../../common'; + import { convertSavedDashboardPanelToPanelState, convertPanelStateToSavedDashboardPanel, -} from './dashboard_panel_converters'; -import { SavedDashboardPanel } from '../content_management'; -import { DashboardPanelState } from '../dashboard_container/types'; -import { EmbeddableInput } from '@kbn/embeddable-plugin/common/types'; +} from './utils'; test('convertSavedDashboardPanelToPanelState', () => { const savedDashboardPanel: SavedDashboardPanel = { @@ -148,7 +149,7 @@ test('convertPanelStateToSavedDashboardPanel will not leave title as part of emb expect(converted.title).toBe('title'); }); -test('convertPanelStateToSavedDashboardPanel retains legacy version info when not passed removeLegacyVersion', () => { +test('convertPanelStateToSavedDashboardPanel retains legacy version info', () => { const dashboardPanel: DashboardPanelState = { gridData: { x: 0, @@ -168,24 +169,3 @@ test('convertPanelStateToSavedDashboardPanel retains legacy version info when no const converted = convertPanelStateToSavedDashboardPanel(dashboardPanel); expect(converted.version).toBe('8.10.0'); }); - -test('convertPanelStateToSavedDashboardPanel removes legacy version info when passed removeLegacyVersion', () => { - const dashboardPanel: DashboardPanelState = { - gridData: { - x: 0, - y: 0, - h: 15, - w: 15, - i: '123', - }, - explicitInput: { - id: '123', - title: 'title', - } as EmbeddableInput, - type: 'search', - version: '8.10.0', - }; - - const converted = convertPanelStateToSavedDashboardPanel(dashboardPanel, true); - expect(converted.version).not.toBeDefined(); -}); diff --git a/src/plugins/dashboard/server/dashboard_saved_object/migrations/utils.ts b/src/plugins/dashboard/server/dashboard_saved_object/migrations/utils.ts new file mode 100644 index 0000000000000..4ed8ec5b8e977 --- /dev/null +++ b/src/plugins/dashboard/server/dashboard_saved_object/migrations/utils.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { omit } from 'lodash'; +import type { EmbeddableInput, SavedObjectEmbeddableInput } from '@kbn/embeddable-plugin/common'; +import type { SavedDashboardPanel } from '../schema'; +import type { DashboardPanelState } from '../../../common'; + +export function convertSavedDashboardPanelToPanelState< + TEmbeddableInput extends EmbeddableInput | SavedObjectEmbeddableInput = SavedObjectEmbeddableInput +>(savedDashboardPanel: SavedDashboardPanel): DashboardPanelState { + return { + type: savedDashboardPanel.type, + gridData: savedDashboardPanel.gridData, + panelRefName: savedDashboardPanel.panelRefName, + explicitInput: { + id: savedDashboardPanel.panelIndex, + ...(savedDashboardPanel.id !== undefined && { savedObjectId: savedDashboardPanel.id }), + ...(savedDashboardPanel.title !== undefined && { title: savedDashboardPanel.title }), + ...savedDashboardPanel.embeddableConfig, + } as TEmbeddableInput, + version: savedDashboardPanel.version, + }; +} + +export function convertPanelStateToSavedDashboardPanel( + panelState: DashboardPanelState +): SavedDashboardPanel { + const savedObjectId = (panelState.explicitInput as SavedObjectEmbeddableInput).savedObjectId; + const panelIndex = panelState.explicitInput.id; + return { + type: panelState.type, + gridData: { + ...panelState.gridData, + i: panelIndex, + }, + panelIndex, + embeddableConfig: omit(panelState.explicitInput, ['id', 'savedObjectId', 'title']), + ...(panelState.explicitInput.title !== undefined && { title: panelState.explicitInput.title }), + ...(savedObjectId !== undefined && { id: savedObjectId }), + ...(panelState.panelRefName !== undefined && { panelRefName: panelState.panelRefName }), + ...(panelState.version !== undefined && { version: panelState.version }), + }; +} diff --git a/src/plugins/dashboard/server/dashboard_saved_object/schema/index.ts b/src/plugins/dashboard/server/dashboard_saved_object/schema/index.ts new file mode 100644 index 0000000000000..4c50de472f53e --- /dev/null +++ b/src/plugins/dashboard/server/dashboard_saved_object/schema/index.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export type { DashboardSavedObjectAttributes, GridData, SavedDashboardPanel } from './latest'; +export { dashboardSavedObjectSchema } from './latest'; diff --git a/src/plugins/dashboard/server/dashboard_saved_object/schema/latest.ts b/src/plugins/dashboard/server/dashboard_saved_object/schema/latest.ts new file mode 100644 index 0000000000000..a40e476abe793 --- /dev/null +++ b/src/plugins/dashboard/server/dashboard_saved_object/schema/latest.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +// Latest model version for dashboard saved objects is v2 +export { + dashboardAttributesSchema as dashboardSavedObjectSchema, + type DashboardAttributes as DashboardSavedObjectAttributes, + type GridData, + type SavedDashboardPanel, +} from './v2'; diff --git a/src/plugins/dashboard/server/dashboard_saved_object/schema/v1/index.ts b/src/plugins/dashboard/server/dashboard_saved_object/schema/v1/index.ts new file mode 100644 index 0000000000000..e52a6ca4075ac --- /dev/null +++ b/src/plugins/dashboard/server/dashboard_saved_object/schema/v1/index.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export type { DashboardAttributes } from './types'; +export { controlGroupInputSchema, dashboardAttributesSchema } from './v1'; diff --git a/src/plugins/dashboard/server/dashboard_saved_object/schema/v1/types.ts b/src/plugins/dashboard/server/dashboard_saved_object/schema/v1/types.ts new file mode 100644 index 0000000000000..8717851845cf7 --- /dev/null +++ b/src/plugins/dashboard/server/dashboard_saved_object/schema/v1/types.ts @@ -0,0 +1,13 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { TypeOf } from '@kbn/config-schema'; +import { dashboardAttributesSchema } from './v1'; + +export type DashboardAttributes = TypeOf; diff --git a/src/plugins/dashboard/server/dashboard_saved_object/schema/v1/v1.ts b/src/plugins/dashboard/server/dashboard_saved_object/schema/v1/v1.ts new file mode 100644 index 0000000000000..63b4cd3c2c10b --- /dev/null +++ b/src/plugins/dashboard/server/dashboard_saved_object/schema/v1/v1.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { schema } from '@kbn/config-schema'; + +export const controlGroupInputSchema = schema + .object({ + panelsJSON: schema.maybe(schema.string()), + controlStyle: schema.maybe(schema.string()), + chainingSystem: schema.maybe(schema.string()), + ignoreParentSettingsJSON: schema.maybe(schema.string()), + }) + .extends({}, { unknowns: 'ignore' }); + +export const dashboardAttributesSchema = schema.object( + { + // General + title: schema.string(), + description: schema.string({ defaultValue: '' }), + + // Search + kibanaSavedObjectMeta: schema.object({ + searchSourceJSON: schema.maybe(schema.string()), + }), + + // Time + timeRestore: schema.maybe(schema.boolean()), + timeFrom: schema.maybe(schema.string()), + timeTo: schema.maybe(schema.string()), + refreshInterval: schema.maybe( + schema.object({ + pause: schema.boolean(), + value: schema.number(), + display: schema.maybe(schema.string()), + section: schema.maybe(schema.number()), + }) + ), + + // Dashboard Content + controlGroupInput: schema.maybe(controlGroupInputSchema), + panelsJSON: schema.string({ defaultValue: '[]' }), + optionsJSON: schema.maybe(schema.string()), + + // Legacy + hits: schema.maybe(schema.number()), + version: schema.maybe(schema.number()), + }, + { unknowns: 'forbid' } +); diff --git a/src/plugins/dashboard/server/dashboard_saved_object/schema/v2/index.ts b/src/plugins/dashboard/server/dashboard_saved_object/schema/v2/index.ts new file mode 100644 index 0000000000000..2fda02230ed69 --- /dev/null +++ b/src/plugins/dashboard/server/dashboard_saved_object/schema/v2/index.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export type { DashboardAttributes, GridData, SavedDashboardPanel } from './types'; +export { controlGroupInputSchema, dashboardAttributesSchema } from './v2'; diff --git a/src/plugins/dashboard/server/dashboard_saved_object/schema/v2/types.ts b/src/plugins/dashboard/server/dashboard_saved_object/schema/v2/types.ts new file mode 100644 index 0000000000000..e50a27efe2b3b --- /dev/null +++ b/src/plugins/dashboard/server/dashboard_saved_object/schema/v2/types.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { Serializable } from '@kbn/utility-types'; +import { TypeOf } from '@kbn/config-schema'; +import { dashboardAttributesSchema, gridDataSchema } from './v2'; + +export type DashboardAttributes = TypeOf; +export type GridData = TypeOf; + +/** + * A saved dashboard panel parsed directly from the Dashboard Attributes panels JSON + */ +export interface SavedDashboardPanel { + embeddableConfig: { [key: string]: Serializable }; // parsed into the panel's explicitInput + id?: string; // the saved object id for by reference panels + type: string; // the embeddable type + panelRefName?: string; + gridData: GridData; + panelIndex: string; + title?: string; + + /** + * This version key was used to store Kibana version information from versions 7.3.0 -> 8.11.0. + * As of version 8.11.0, the versioning information is now per-embeddable-type and is stored on the + * embeddable's input. (embeddableConfig in this type). + */ + version?: string; +} diff --git a/src/plugins/dashboard/server/dashboard_saved_object/schema/v2/v2.ts b/src/plugins/dashboard/server/dashboard_saved_object/schema/v2/v2.ts new file mode 100644 index 0000000000000..dc0ed3eb84cbb --- /dev/null +++ b/src/plugins/dashboard/server/dashboard_saved_object/schema/v2/v2.ts @@ -0,0 +1,36 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { schema } from '@kbn/config-schema'; +import { + controlGroupInputSchema as controlGroupInputSchemaV1, + dashboardAttributesSchema as dashboardAttributesSchemaV1, +} from '../v1'; + +export const controlGroupInputSchema = controlGroupInputSchemaV1.extends( + { + showApplySelections: schema.maybe(schema.boolean()), + }, + { unknowns: 'ignore' } +); + +export const dashboardAttributesSchema = dashboardAttributesSchemaV1.extends( + { + controlGroupInput: schema.maybe(controlGroupInputSchema), + }, + { unknowns: 'ignore' } +); + +export const gridDataSchema = schema.object({ + x: schema.number(), + y: schema.number(), + w: schema.number(), + h: schema.number(), + i: schema.string(), +}); diff --git a/src/plugins/dashboard/server/index.ts b/src/plugins/dashboard/server/index.ts index f76a75cb837b3..94e7ed14378c1 100644 --- a/src/plugins/dashboard/server/index.ts +++ b/src/plugins/dashboard/server/index.ts @@ -26,3 +26,7 @@ export async function plugin(initializerContext: PluginInitializerContext) { } export type { DashboardPluginSetup, DashboardPluginStart } from './types'; +export type { DashboardAttributes } from './content_management'; +export type { DashboardSavedObjectAttributes } from './dashboard_saved_object'; + +export { PUBLIC_API_PATH } from './api/constants'; diff --git a/src/plugins/dashboard/server/plugin.ts b/src/plugins/dashboard/server/plugin.ts index 3218ee85ef383..e3d67ca10716b 100644 --- a/src/plugins/dashboard/server/plugin.ts +++ b/src/plugins/dashboard/server/plugin.ts @@ -30,6 +30,7 @@ import { createDashboardSavedObjectType } from './dashboard_saved_object'; import { CONTENT_ID, LATEST_VERSION } from '../common/content_management'; import { registerDashboardUsageCollector } from './usage/register_collector'; import { dashboardPersistableStateServiceFactory } from './dashboard_container/dashboard_container_embeddable_factory'; +import { registerAPIRoutes } from './api'; interface SetupDeps { embeddable: EmbeddableSetup; @@ -111,6 +112,12 @@ export class DashboardPlugin core.uiSettings.register(getUISettings()); + registerAPIRoutes({ + http: core.http, + contentManagement: plugins.contentManagement, + logger: this.logger, + }); + return {}; } diff --git a/src/plugins/dashboard/server/usage/dashboard_telemetry.test.ts b/src/plugins/dashboard/server/usage/dashboard_telemetry.test.ts index 225ac7743d23c..8f4f94d3621e2 100644 --- a/src/plugins/dashboard/server/usage/dashboard_telemetry.test.ts +++ b/src/plugins/dashboard/server/usage/dashboard_telemetry.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { SavedDashboardPanel } from '../../common/content_management'; +import { SavedDashboardPanel } from '../dashboard_saved_object'; import { getEmptyDashboardData, collectPanelsByType } from './dashboard_telemetry'; import { EmbeddableStateWithType } from '@kbn/embeddable-plugin/common'; import { createEmbeddablePersistableStateServiceMock } from '@kbn/embeddable-plugin/common/mocks'; diff --git a/src/plugins/dashboard/server/usage/dashboard_telemetry.ts b/src/plugins/dashboard/server/usage/dashboard_telemetry.ts index 0048e081e6f61..f26de753c12e2 100644 --- a/src/plugins/dashboard/server/usage/dashboard_telemetry.ts +++ b/src/plugins/dashboard/server/usage/dashboard_telemetry.ts @@ -17,7 +17,7 @@ import { import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common'; import { TaskManagerStartContract } from '@kbn/task-manager-plugin/server'; -import { DashboardAttributes, SavedDashboardPanel } from '../../common/content_management'; +import { DashboardSavedObjectAttributes, SavedDashboardPanel } from '../dashboard_saved_object'; import { TASK_ID } from './dashboard_telemetry_collection_task'; import { emptyState, type LatestTaskStateSchema } from './task_state'; @@ -95,7 +95,7 @@ export const collectPanelsByType = ( export const controlsCollectorFactory = (embeddableService: EmbeddablePersistableStateService) => - (attributes: DashboardAttributes, collectorData: DashboardCollectorData) => { + (attributes: DashboardSavedObjectAttributes, collectorData: DashboardCollectorData) => { if (!isEmpty(attributes.controlGroupInput)) { collectorData.controls = embeddableService.telemetry( { diff --git a/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts b/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts index d660af962db57..7eb4cebc39e49 100644 --- a/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts +++ b/src/plugins/dashboard/server/usage/dashboard_telemetry_collection_task.ts @@ -23,9 +23,15 @@ import { collectPanelsByType, getEmptyDashboardData, } from './dashboard_telemetry'; -import { injectReferences } from '../../common'; -import { DashboardAttributesAndReferences } from '../../common/types'; -import { DashboardAttributes, SavedDashboardPanel } from '../../common/content_management'; +import type { + DashboardSavedObjectAttributes, + SavedDashboardPanel, +} from '../dashboard_saved_object'; + +interface DashboardSavedObjectAttributesAndReferences { + attributes: DashboardSavedObjectAttributes; + references: SavedObjectReference[]; +} // This task is responsible for running daily and aggregating all the Dashboard telemerty data // into a single document. This is an effort to make sure the load of fetching/parsing all of the @@ -88,17 +94,18 @@ export function dashboardTaskRunner(logger: Logger, core: CoreSetup, embeddable: async run() { let dashboardData = getEmptyDashboardData(); const controlsCollector = controlsCollectorFactory(embeddable); - const processDashboards = (dashboards: DashboardAttributesAndReferences[]) => { + const processDashboards = (dashboards: DashboardSavedObjectAttributesAndReferences[]) => { for (const dashboard of dashboards) { - const attributes = injectReferences(dashboard, { - embeddablePersistableStateService: embeddable, - }); + // TODO is this injecting references really necessary? + // const attributes = injectReferences(dashboard, { + // embeddablePersistableStateService: embeddable, + // }); - dashboardData = controlsCollector(attributes, dashboardData); + dashboardData = controlsCollector(dashboard.attributes, dashboardData); try { const panels = JSON.parse( - attributes.panelsJSON as string + dashboard.attributes.panelsJSON as string ) as unknown as SavedDashboardPanel[]; collectPanelsByType(panels, dashboardData, embeddable); @@ -129,7 +136,7 @@ export function dashboardTaskRunner(logger: Logger, core: CoreSetup, embeddable: const esClient = await getEsClient(); let result = await esClient.search<{ - dashboard: DashboardAttributes; + dashboard: DashboardSavedObjectAttributes; references: SavedObjectReference[]; }>(searchParams); @@ -144,8 +151,8 @@ export function dashboardTaskRunner(logger: Logger, core: CoreSetup, embeddable: } return undefined; }) - .filter( - (s): s is DashboardAttributesAndReferences => s !== undefined + .filter( + (s): s is DashboardSavedObjectAttributesAndReferences => s !== undefined ) ); @@ -163,8 +170,8 @@ export function dashboardTaskRunner(logger: Logger, core: CoreSetup, embeddable: } return undefined; }) - .filter( - (s): s is DashboardAttributesAndReferences => s !== undefined + .filter( + (s): s is DashboardSavedObjectAttributesAndReferences => s !== undefined ) ); } diff --git a/src/plugins/data/kibana.jsonc b/src/plugins/data/kibana.jsonc index c109bde374680..84e692c42648a 100644 --- a/src/plugins/data/kibana.jsonc +++ b/src/plugins/data/kibana.jsonc @@ -5,6 +5,8 @@ "@elastic/kibana-visualizations", "@elastic/kibana-data-discovery" ], + "group": "platform", + "visibility": "shared", "description": "Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters.", "serviceFolders": [ "search", @@ -13,8 +15,8 @@ ], "plugin": { "id": "data", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "bfetch", "expressions", @@ -38,4 +40,4 @@ "common" ] } -} +} \ No newline at end of file diff --git a/src/plugins/data_view_editor/kibana.jsonc b/src/plugins/data_view_editor/kibana.jsonc index bdec3b4f4943d..04d543bdd47ec 100644 --- a/src/plugins/data_view_editor/kibana.jsonc +++ b/src/plugins/data_view_editor/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/data-view-editor-plugin", - "owner": "@elastic/kibana-data-discovery", + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared", "description": "This plugin provides the ability to create data views via a modal flyout inside Kibana apps", "plugin": { "id": "dataViewEditor", - "server": false, "browser": true, + "server": false, "requiredPlugins": [ "data", "dataViews" @@ -17,4 +21,4 @@ "esUiShared" ] } -} +} \ No newline at end of file diff --git a/src/plugins/data_view_field_editor/kibana.jsonc b/src/plugins/data_view_field_editor/kibana.jsonc index 50a336cfe0c9e..9c3e453d1b796 100644 --- a/src/plugins/data_view_field_editor/kibana.jsonc +++ b/src/plugins/data_view_field_editor/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/data-view-field-editor-plugin", - "owner": "@elastic/kibana-data-discovery", + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared", "description": "Reusable data view field editor across Kibana", "plugin": { "id": "dataViewFieldEditor", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "data", "fieldFormats", @@ -20,4 +24,4 @@ "esUiShared" ] } -} +} \ No newline at end of file diff --git a/src/plugins/data_view_management/kibana.jsonc b/src/plugins/data_view_management/kibana.jsonc index 5b827868ee1e8..c679c3b9ad964 100644 --- a/src/plugins/data_view_management/kibana.jsonc +++ b/src/plugins/data_view_management/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/data-view-management-plugin", - "owner": "@elastic/kibana-data-discovery", + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared", "description": "Data view management app", "plugin": { "id": "dataViewManagement", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "management", "data", @@ -28,4 +32,4 @@ "kibanaUtils" ] } -} +} \ No newline at end of file diff --git a/src/plugins/data_views/kibana.jsonc b/src/plugins/data_views/kibana.jsonc index 7789383b48ba4..00df1941eaa37 100644 --- a/src/plugins/data_views/kibana.jsonc +++ b/src/plugins/data_views/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/data-views-plugin", - "owner": "@elastic/kibana-data-discovery", + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared", "description": "Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters.", "plugin": { "id": "dataViews", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "fieldFormats", "expressions", @@ -18,9 +22,11 @@ "requiredBundles": [ "kibanaUtils" ], - "runtimePluginDependencies" : ["security"], + "runtimePluginDependencies": [ + "security" + ], "extraPublicDirs": [ "common" ] } -} +} \ No newline at end of file diff --git a/src/plugins/dev_tools/kibana.jsonc b/src/plugins/dev_tools/kibana.jsonc index c269b74918619..45efd4af09fa9 100644 --- a/src/plugins/dev_tools/kibana.jsonc +++ b/src/plugins/dev_tools/kibana.jsonc @@ -1,14 +1,18 @@ { "type": "plugin", "id": "@kbn/dev-tools-plugin", - "owner": "@elastic/kibana-management", + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "devTools", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "urlForwarding" ], "requiredBundles": [] } -} +} \ No newline at end of file diff --git a/src/plugins/discover/kibana.jsonc b/src/plugins/discover/kibana.jsonc index 1f5e25229df02..87837a38ed834 100644 --- a/src/plugins/discover/kibana.jsonc +++ b/src/plugins/discover/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/discover-plugin", - "owner": "@elastic/kibana-data-discovery", + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared", "description": "This plugin contains the Discover application and the saved search embeddable.", "plugin": { "id": "discover", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "charts", "data", @@ -44,7 +48,14 @@ "fieldsMetadata", "logsDataAccess" ], - "requiredBundles": ["kibanaUtils", "kibanaReact", "unifiedSearch", "savedObjects"], - "extraPublicDirs": ["common"] + "requiredBundles": [ + "kibanaUtils", + "kibanaReact", + "unifiedSearch", + "savedObjects" + ], + "extraPublicDirs": [ + "common" + ] } -} +} \ No newline at end of file diff --git a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx index 20ab07e1155cd..26f0563215536 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx @@ -8,8 +8,8 @@ */ import React, { useCallback, useEffect, useMemo, useRef } from 'react'; -import { type DataView, DataViewType } from '@kbn/data-views-plugin/public'; -import { DataViewPickerProps } from '@kbn/unified-search-plugin/public'; +import { DataViewType } from '@kbn/data-views-plugin/public'; +import type { DataViewPickerProps } from '@kbn/unified-search-plugin/public'; import { ENABLE_ESQL } from '@kbn/esql-utils'; import { TextBasedLanguages } from '@kbn/esql-utils'; import { DiscoverFlyouts, dismissAllFlyoutsExceptFor } from '@kbn/discover-utils'; @@ -20,7 +20,6 @@ import { useDiscoverServices } from '../../../../hooks/use_discover_services'; import type { DiscoverStateContainer } from '../../state_management/discover_state'; import { onSaveSearch } from './on_save_search'; import { useDiscoverCustomization } from '../../../../customizations'; -import { addLog } from '../../../../utils/add_log'; import { useAppStateSelector } from '../../state_management/discover_app_state_container'; import { useDiscoverTopNav } from './use_discover_topnav'; import { useIsEsqlMode } from '../../hooks/use_is_esql_mode'; @@ -47,15 +46,8 @@ export const DiscoverTopNav = ({ onCancelClick, }: DiscoverTopNavProps) => { const services = useDiscoverServices(); - const { - dataViewEditor, - navigation, - dataViewFieldEditor, - data, - uiSettings, - dataViews, - setHeaderActionMenu, - } = services; + const { dataViewEditor, navigation, dataViewFieldEditor, data, uiSettings, setHeaderActionMenu } = + services; const query = useAppStateSelector((state) => state.query); const adHocDataViews = useInternalStateSelector((state) => state.adHocDataViews); const dataView = useInternalStateSelector((state) => state.dataView!); @@ -93,7 +85,7 @@ export const DiscoverTopNav = ({ const editField = useMemo( () => canEditDataView - ? async (fieldName?: string, uiAction: 'edit' | 'add' = 'edit') => { + ? async (fieldName?: string) => { if (dataView?.id) { const dataViewInstance = await data.dataViews.get(dataView.id); closeFieldEditor.current = await dataViewFieldEditor.openEditor({ @@ -112,7 +104,7 @@ export const DiscoverTopNav = ({ ); const addField = useMemo( - () => (canEditDataView && editField ? () => editField(undefined, 'add') : undefined), + () => (canEditDataView && editField ? () => editField() : undefined), [editField, canEditDataView] ); @@ -123,23 +115,6 @@ export const DiscoverTopNav = ({ }); }, [dataViewEditor, stateContainer]); - const onEditDataView = useCallback( - async (editedDataView: DataView) => { - if (editedDataView.isPersisted()) { - // Clear the current data view from the cache and create a new instance - // of it, ensuring we have a new object reference to trigger a re-render - dataViews.clearInstanceCache(editedDataView.id); - stateContainer.actions.setDataView(await dataViews.create(editedDataView.toSpec(), true)); - } else { - await stateContainer.actions.updateAdHocDataViewId(); - } - stateContainer.actions.loadDataViewList(); - addLog('[DiscoverTopNav] onEditDataView triggers data fetching'); - stateContainer.dataState.fetch(); - }, - [dataViews, stateContainer.actions, stateContainer.dataState] - ); - const updateSavedQueryId = (newSavedQueryId: string | undefined) => { const { appState } = stateContainer; if (newSavedQueryId) { @@ -223,14 +198,13 @@ export const DiscoverTopNav = ({ textBasedLanguages: supportedTextBasedLanguages, adHocDataViews, savedDataViews, - onEditDataView, + onEditDataView: stateContainer.actions.onDataViewEdited, }; }, [ adHocDataViews, addField, createNewDataView, dataView, - onEditDataView, savedDataViews, stateContainer, uiSettings, diff --git a/src/plugins/discover/public/context_awareness/profile_providers/example/example_data_source_profile/profile.tsx b/src/plugins/discover/public/context_awareness/profile_providers/example/example_data_source_profile/profile.tsx index 1fe833ba99afe..46ecce387e877 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/example/example_data_source_profile/profile.tsx +++ b/src/plugins/discover/public/context_awareness/profile_providers/example/example_data_source_profile/profile.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { EuiBadge, EuiLink, EuiFlyout, EuiFlyoutBody } from '@elastic/eui'; +import { EuiBadge, EuiLink, EuiFlyout, EuiPanel } from '@elastic/eui'; import { AppMenuActionId, AppMenuActionType, @@ -89,12 +89,12 @@ export const createExampleDataSourceProfileProvider = (): DataSourceProfileProvi title: 'Example', order: 0, component: () => ( - +
Example Doc View
                     {context.formatRecord(params.record.flattened)}
                   
-
+ ), }); diff --git a/src/plugins/embeddable/kibana.jsonc b/src/plugins/embeddable/kibana.jsonc index 0af5411af56dd..b617114f9fa59 100644 --- a/src/plugins/embeddable/kibana.jsonc +++ b/src/plugins/embeddable/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/embeddable-plugin", - "owner": "@elastic/kibana-presentation", + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "shared", "description": "Adds embeddables service to Kibana", "plugin": { "id": "embeddable", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "data", "inspector", @@ -15,8 +19,17 @@ "savedObjectsManagement", "contentManagement" ], - "optionalPlugins": ["savedObjectsTaggingOss", "usageCollection"], - "requiredBundles": ["savedObjects", "kibanaUtils", "presentationPanel"], - "extraPublicDirs": ["common"] + "optionalPlugins": [ + "savedObjectsTaggingOss", + "usageCollection" + ], + "requiredBundles": [ + "savedObjects", + "kibanaUtils", + "presentationPanel" + ], + "extraPublicDirs": [ + "common" + ] } -} +} \ No newline at end of file diff --git a/src/plugins/es_ui_shared/kibana.jsonc b/src/plugins/es_ui_shared/kibana.jsonc index 3c738268e7034..927939ae4af63 100644 --- a/src/plugins/es_ui_shared/kibana.jsonc +++ b/src/plugins/es_ui_shared/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/es-ui-shared-plugin", - "owner": "@elastic/kibana-management", + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "esUiShared", - "server": true, "browser": true, + "server": true, "requiredBundles": [ "dataViews" ], @@ -17,4 +21,4 @@ "static/forms/helpers/field_validators/types" ] } -} +} \ No newline at end of file diff --git a/src/plugins/esql/kibana.jsonc b/src/plugins/esql/kibana.jsonc index 2bb2b759dc429..6ee732ef79f5a 100644 --- a/src/plugins/esql/kibana.jsonc +++ b/src/plugins/esql/kibana.jsonc @@ -2,6 +2,8 @@ "type": "plugin", "id": "@kbn/esql", "owner": "@elastic/kibana-esql", + "group": "platform", + "visibility": "shared", "plugin": { "id": "esql", "server": true, diff --git a/src/plugins/esql_datagrid/kibana.jsonc b/src/plugins/esql_datagrid/kibana.jsonc index e2596ccb9fc8b..f8f880b2d4313 100644 --- a/src/plugins/esql_datagrid/kibana.jsonc +++ b/src/plugins/esql_datagrid/kibana.jsonc @@ -2,6 +2,8 @@ "type": "plugin", "id": "@kbn/esql-datagrid", "owner": "@elastic/kibana-esql", + "group": "platform", + "visibility": "shared", "plugin": { "id": "esqlDataGrid", "server": false, diff --git a/src/plugins/event_annotation/kibana.jsonc b/src/plugins/event_annotation/kibana.jsonc index 79a2dfb105820..24fb6bfde6223 100644 --- a/src/plugins/event_annotation/kibana.jsonc +++ b/src/plugins/event_annotation/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/event-annotation-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "private", "description": "The Event Annotation service contains expressions for event annotations", "plugin": { "id": "eventAnnotation", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "expressions", "data", @@ -18,15 +22,15 @@ "contentManagement" ], "optionalPlugins": [ - "savedObjectsTagging", + "savedObjectsTagging" ], "requiredBundles": [ "data", "savedObjectsFinder", - "dataViews", + "dataViews" ], "extraPublicDirs": [ "common" ] } -} +} \ No newline at end of file diff --git a/src/plugins/event_annotation_listing/kibana.jsonc b/src/plugins/event_annotation_listing/kibana.jsonc index 1ae3534fc98df..7c6aaf4939d33 100644 --- a/src/plugins/event_annotation_listing/kibana.jsonc +++ b/src/plugins/event_annotation_listing/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/event-annotation-listing-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "private", "description": "The listing page for event annotations.", "plugin": { "id": "eventAnnotationListing", - "server": false, "browser": true, + "server": false, "requiredPlugins": [ "savedObjectsManagement", "eventAnnotation", @@ -16,13 +20,13 @@ "dataViews", "unifiedSearch", "kibanaUtils", - "contentManagement", + "contentManagement" ], "optionalPlugins": [ "savedObjectsTagging", - "lens", + "lens" ], "requiredBundles": [], "extraPublicDirs": [] } -} +} \ No newline at end of file diff --git a/src/plugins/expression_error/kibana.jsonc b/src/plugins/expression_error/kibana.jsonc index 28d389ce5a315..29fb16ff0d744 100644 --- a/src/plugins/expression_error/kibana.jsonc +++ b/src/plugins/expression_error/kibana.jsonc @@ -1,16 +1,20 @@ { "type": "plugin", "id": "@kbn/expression-error-plugin", - "owner": "@elastic/kibana-presentation", + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "shared", "description": "Adds 'error' renderer to expressions", "plugin": { "id": "expressionError", - "server": false, "browser": true, + "server": false, "requiredPlugins": [ "expressions", "presentationUtil" ], "requiredBundles": [] } -} +} \ No newline at end of file diff --git a/src/plugins/expression_image/kibana.jsonc b/src/plugins/expression_image/kibana.jsonc index b6a05d8b051c5..3aca2b43f0e7f 100644 --- a/src/plugins/expression_image/kibana.jsonc +++ b/src/plugins/expression_image/kibana.jsonc @@ -1,16 +1,20 @@ { "type": "plugin", "id": "@kbn/expression-image-plugin", - "owner": "@elastic/kibana-presentation", + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "shared", "description": "Adds 'image' function and renderer to expressions", "plugin": { "id": "expressionImage", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "expressions", "presentationUtil" ], "requiredBundles": [] } -} +} \ No newline at end of file diff --git a/src/plugins/expression_metric/kibana.jsonc b/src/plugins/expression_metric/kibana.jsonc index 298e7046bf7f8..19070c800cefc 100644 --- a/src/plugins/expression_metric/kibana.jsonc +++ b/src/plugins/expression_metric/kibana.jsonc @@ -1,16 +1,20 @@ { "type": "plugin", "id": "@kbn/expression-metric-plugin", - "owner": "@elastic/kibana-presentation", + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "shared", "description": "Adds 'metric' function and renderer to expressions", "plugin": { "id": "expressionMetric", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "expressions", "presentationUtil" ], "requiredBundles": [] } -} +} \ No newline at end of file diff --git a/src/plugins/expression_repeat_image/kibana.jsonc b/src/plugins/expression_repeat_image/kibana.jsonc index 13e88e1970fd8..17936c2203f3d 100644 --- a/src/plugins/expression_repeat_image/kibana.jsonc +++ b/src/plugins/expression_repeat_image/kibana.jsonc @@ -1,16 +1,20 @@ { "type": "plugin", "id": "@kbn/expression-repeat-image-plugin", - "owner": "@elastic/kibana-presentation", + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "shared", "description": "Adds 'repeatImage' function and renderer to expressions", "plugin": { "id": "expressionRepeatImage", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "expressions", "presentationUtil" ], "requiredBundles": [] } -} +} \ No newline at end of file diff --git a/src/plugins/expression_reveal_image/kibana.jsonc b/src/plugins/expression_reveal_image/kibana.jsonc index 7b13ef28f3088..5d936b2a29a0b 100644 --- a/src/plugins/expression_reveal_image/kibana.jsonc +++ b/src/plugins/expression_reveal_image/kibana.jsonc @@ -1,16 +1,20 @@ { "type": "plugin", "id": "@kbn/expression-reveal-image-plugin", - "owner": "@elastic/kibana-presentation", + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "shared", "description": "Adds 'revealImage' function and renderer to expressions", "plugin": { "id": "expressionRevealImage", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "expressions", "presentationUtil" ], "requiredBundles": [] } -} +} \ No newline at end of file diff --git a/src/plugins/expression_shape/kibana.jsonc b/src/plugins/expression_shape/kibana.jsonc index 85e6fca310d66..96fa1295b32c0 100644 --- a/src/plugins/expression_shape/kibana.jsonc +++ b/src/plugins/expression_shape/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/expression-shape-plugin", - "owner": "@elastic/kibana-presentation", + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "shared", "description": "Adds 'shape' function and renderer to expressions", "plugin": { "id": "expressionShape", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "expressions", "presentationUtil" @@ -16,4 +20,4 @@ "common" ] } -} +} \ No newline at end of file diff --git a/src/plugins/expressions/kibana.jsonc b/src/plugins/expressions/kibana.jsonc index 7ed96b7020deb..5c5588fc9e9be 100644 --- a/src/plugins/expressions/kibana.jsonc +++ b/src/plugins/expressions/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/expressions-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "shared", "description": "Adds expression runtime to Kibana", "plugin": { "id": "expressions", - "server": true, "browser": true, + "server": true, "requiredBundles": [ "kibanaUtils", "inspector" @@ -16,4 +20,4 @@ "common/fonts" ] } -} +} \ No newline at end of file diff --git a/src/plugins/field_formats/kibana.jsonc b/src/plugins/field_formats/kibana.jsonc index 49347ac8131b1..4289dd5fe017a 100644 --- a/src/plugins/field_formats/kibana.jsonc +++ b/src/plugins/field_formats/kibana.jsonc @@ -1,14 +1,18 @@ { "type": "plugin", "id": "@kbn/field-formats-plugin", - "owner": "@elastic/kibana-data-discovery", + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared", "description": "Index pattern fields and ambiguous values formatters", "plugin": { "id": "fieldFormats", - "server": true, "browser": true, + "server": true, "extraPublicDirs": [ "common" ] } -} +} \ No newline at end of file diff --git a/src/plugins/files/kibana.jsonc b/src/plugins/files/kibana.jsonc index 5f59c5d09b3d7..6a10ac67ebda3 100644 --- a/src/plugins/files/kibana.jsonc +++ b/src/plugins/files/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/files-plugin", - "owner": "@elastic/appex-sharedux", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared", "description": "File upload, download, sharing, and serving over HTTP implementation in Kibana.", "plugin": { "id": "files", - "server": true, "browser": true, + "server": true, "optionalPlugins": [ "security", "usageCollection" @@ -18,4 +22,4 @@ "common" ] } -} +} \ No newline at end of file diff --git a/src/plugins/files_management/kibana.jsonc b/src/plugins/files_management/kibana.jsonc index aef8736c6c1f9..5df36d08cd9a9 100644 --- a/src/plugins/files_management/kibana.jsonc +++ b/src/plugins/files_management/kibana.jsonc @@ -1,16 +1,20 @@ { "type": "plugin", "id": "@kbn/files-management-plugin", - "owner": "@elastic/appex-sharedux", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private", "description": "Simple UI for managing files in Kibana", "plugin": { "id": "filesManagement", - "server": false, "browser": true, + "server": false, "requiredPlugins": [ "files", "management" ], "requiredBundles": [] } -} +} \ No newline at end of file diff --git a/src/plugins/ftr_apis/kibana.jsonc b/src/plugins/ftr_apis/kibana.jsonc index f40ab911a104b..75663274a1f3a 100644 --- a/src/plugins/ftr_apis/kibana.jsonc +++ b/src/plugins/ftr_apis/kibana.jsonc @@ -1,13 +1,17 @@ { "type": "plugin", "id": "@kbn/ftr-apis-plugin", - "owner": "@elastic/kibana-core", + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private", "plugin": { "id": "ftrApis", - "server": true, "browser": false, + "server": true, "configPath": [ "ftr_apis" ] } -} +} \ No newline at end of file diff --git a/src/plugins/ftr_apis/server/routes/kbn_client_so/bulk_delete.ts b/src/plugins/ftr_apis/server/routes/kbn_client_so/bulk_delete.ts index b1667bfed4b99..7ba0a504fa530 100644 --- a/src/plugins/ftr_apis/server/routes/kbn_client_so/bulk_delete.ts +++ b/src/plugins/ftr_apis/server/routes/kbn_client_so/bulk_delete.ts @@ -15,8 +15,10 @@ export const registerBulkDeleteRoute = (router: IRouter) => { router.post( { path: `${KBN_CLIENT_API_PREFIX}/_bulk_delete`, - options: { - tags: ['access:ftrApis'], + security: { + authz: { + requiredPrivileges: ['ftrApis'], + }, }, validate: { body: schema.arrayOf( diff --git a/src/plugins/ftr_apis/server/routes/kbn_client_so/clean.ts b/src/plugins/ftr_apis/server/routes/kbn_client_so/clean.ts index 86be3af46348f..2f2edc66fdc4a 100644 --- a/src/plugins/ftr_apis/server/routes/kbn_client_so/clean.ts +++ b/src/plugins/ftr_apis/server/routes/kbn_client_so/clean.ts @@ -15,8 +15,10 @@ export const registerCleanRoute = (router: IRouter) => { router.post( { path: `${KBN_CLIENT_API_PREFIX}/_clean`, - options: { - tags: ['access:ftrApis'], + security: { + authz: { + requiredPrivileges: ['ftrApis'], + }, }, validate: { body: schema.object({ diff --git a/src/plugins/ftr_apis/server/routes/kbn_client_so/create.ts b/src/plugins/ftr_apis/server/routes/kbn_client_so/create.ts index 528e271de1d4f..fdf93e2d517b8 100644 --- a/src/plugins/ftr_apis/server/routes/kbn_client_so/create.ts +++ b/src/plugins/ftr_apis/server/routes/kbn_client_so/create.ts @@ -15,8 +15,10 @@ export const registerCreateRoute = (router: IRouter) => { router.post( { path: `${KBN_CLIENT_API_PREFIX}/{type}/{id?}`, - options: { - tags: ['access:ftrApis'], + security: { + authz: { + requiredPrivileges: ['ftrApis'], + }, }, validate: { params: schema.object({ diff --git a/src/plugins/ftr_apis/server/routes/kbn_client_so/delete.ts b/src/plugins/ftr_apis/server/routes/kbn_client_so/delete.ts index 77cec6243711c..69bc5f51db118 100644 --- a/src/plugins/ftr_apis/server/routes/kbn_client_so/delete.ts +++ b/src/plugins/ftr_apis/server/routes/kbn_client_so/delete.ts @@ -15,8 +15,10 @@ export const registerDeleteRoute = (router: IRouter) => { router.delete( { path: `${KBN_CLIENT_API_PREFIX}/{type}/{id}`, - options: { - tags: ['access:ftrApis'], + security: { + authz: { + requiredPrivileges: ['ftrApis'], + }, }, validate: { params: schema.object({ diff --git a/src/plugins/ftr_apis/server/routes/kbn_client_so/find.ts b/src/plugins/ftr_apis/server/routes/kbn_client_so/find.ts index 2aefd0f87d334..ecacba6b782cd 100644 --- a/src/plugins/ftr_apis/server/routes/kbn_client_so/find.ts +++ b/src/plugins/ftr_apis/server/routes/kbn_client_so/find.ts @@ -15,8 +15,10 @@ export const registerFindRoute = (router: IRouter) => { router.get( { path: `${KBN_CLIENT_API_PREFIX}/_find`, - options: { - tags: ['access:ftrApis'], + security: { + authz: { + requiredPrivileges: ['ftrApis'], + }, }, validate: { query: schema.object({ diff --git a/src/plugins/ftr_apis/server/routes/kbn_client_so/get.ts b/src/plugins/ftr_apis/server/routes/kbn_client_so/get.ts index bcfcd906ffc4c..88685608aee1a 100644 --- a/src/plugins/ftr_apis/server/routes/kbn_client_so/get.ts +++ b/src/plugins/ftr_apis/server/routes/kbn_client_so/get.ts @@ -15,8 +15,10 @@ export const registerGetRoute = (router: IRouter) => { router.get( { path: `${KBN_CLIENT_API_PREFIX}/{type}/{id}`, - options: { - tags: ['access:ftrApis'], + security: { + authz: { + requiredPrivileges: ['ftrApis'], + }, }, validate: { params: schema.object({ diff --git a/src/plugins/ftr_apis/server/routes/kbn_client_so/update.ts b/src/plugins/ftr_apis/server/routes/kbn_client_so/update.ts index ee5b90e2897e0..e2eef65c0ec26 100644 --- a/src/plugins/ftr_apis/server/routes/kbn_client_so/update.ts +++ b/src/plugins/ftr_apis/server/routes/kbn_client_so/update.ts @@ -15,8 +15,10 @@ export const registerUpdateRoute = (router: IRouter) => { router.put( { path: `${KBN_CLIENT_API_PREFIX}/{type}/{id}`, - options: { - tags: ['access:ftrApis'], + security: { + authz: { + requiredPrivileges: ['ftrApis'], + }, }, validate: { params: schema.object({ diff --git a/src/plugins/guided_onboarding/kibana.jsonc b/src/plugins/guided_onboarding/kibana.jsonc index 1bbdc9d1003c1..5a31cd6986c6b 100644 --- a/src/plugins/guided_onboarding/kibana.jsonc +++ b/src/plugins/guided_onboarding/kibana.jsonc @@ -1,13 +1,20 @@ { "type": "plugin", "id": "@kbn/guided-onboarding-plugin", - "owner": "@elastic/appex-sharedux", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared", "description": "Guided onboarding framework", "plugin": { "id": "guidedOnboarding", - "server": true, "browser": true, - "optionalPlugins": ["cloud", "features"], + "server": true, + "optionalPlugins": [ + "cloud", + "features" + ], "requiredBundles": [] } -} +} \ No newline at end of file diff --git a/src/plugins/home/kibana.jsonc b/src/plugins/home/kibana.jsonc index 8c0a7884ce8ee..deef8ba61fd73 100644 --- a/src/plugins/home/kibana.jsonc +++ b/src/plugins/home/kibana.jsonc @@ -1,18 +1,28 @@ { "type": "plugin", "id": "@kbn/home-plugin", - "owner": "@elastic/kibana-core", + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "home", - "server": true, "browser": true, - "requiredPlugins": ["dataViews", "share", "urlForwarding"], - "requiredBundles": ["kibanaReact"], + "server": true, + "requiredPlugins": [ + "dataViews", + "share", + "urlForwarding" + ], "optionalPlugins": [ "usageCollection", "customIntegrations", "cloud", "guidedOnboarding" + ], + "requiredBundles": [ + "kibanaReact" ] } -} +} \ No newline at end of file diff --git a/src/plugins/image_embeddable/kibana.jsonc b/src/plugins/image_embeddable/kibana.jsonc index 4dbf82a16d962..ec09c71345736 100644 --- a/src/plugins/image_embeddable/kibana.jsonc +++ b/src/plugins/image_embeddable/kibana.jsonc @@ -1,14 +1,27 @@ { "type": "plugin", "id": "@kbn/image-embeddable-plugin", - "owner": "@elastic/appex-sharedux", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private", "description": "Image embeddable", "plugin": { "id": "imageEmbeddable", - "server": false, "browser": true, - "requiredPlugins": ["embeddable", "files", "uiActions", "kibanaReact"], - "optionalPlugins": ["security", "screenshotMode", "embeddableEnhanced"], + "server": false, + "requiredPlugins": [ + "embeddable", + "files", + "uiActions", + "kibanaReact" + ], + "optionalPlugins": [ + "security", + "screenshotMode", + "embeddableEnhanced" + ], "requiredBundles": [] } -} +} \ No newline at end of file diff --git a/src/plugins/input_control_vis/kibana.jsonc b/src/plugins/input_control_vis/kibana.jsonc index 129396f15ace6..0dce906132726 100644 --- a/src/plugins/input_control_vis/kibana.jsonc +++ b/src/plugins/input_control_vis/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/input-control-vis-plugin", - "owner": "@elastic/kibana-presentation", + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "private", "description": "Adds Input Control visualization to Kibana", "plugin": { "id": "inputControlVis", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "data", "expressions", @@ -16,7 +20,8 @@ "uiActions" ], "requiredBundles": [ - "kibanaReact", "embeddable" + "kibanaReact", + "embeddable" ] } -} +} \ No newline at end of file diff --git a/src/plugins/inspector/kibana.jsonc b/src/plugins/inspector/kibana.jsonc index 8ff572bc21f67..b77688378a55b 100644 --- a/src/plugins/inspector/kibana.jsonc +++ b/src/plugins/inspector/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/inspector-plugin", - "owner": "@elastic/kibana-presentation", + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "inspector", - "server": false, "browser": true, + "server": false, "requiredPlugins": [ "share" ], @@ -17,4 +21,4 @@ "common/adapters/request" ] } -} +} \ No newline at end of file diff --git a/src/plugins/interactive_setup/kibana.jsonc b/src/plugins/interactive_setup/kibana.jsonc index 5a6c8dace60b9..f313dc6304ac3 100644 --- a/src/plugins/interactive_setup/kibana.jsonc +++ b/src/plugins/interactive_setup/kibana.jsonc @@ -1,15 +1,19 @@ { "type": "plugin", "id": "@kbn/interactive-setup-plugin", - "owner": "@elastic/kibana-security", + "owner": [ + "@elastic/kibana-security" + ], + "group": "platform", + "visibility": "private", "description": "This plugin provides UI and APIs for the interactive setup mode.", "plugin": { "id": "interactiveSetup", - "type": "preboot", - "server": true, "browser": true, + "server": true, + "type": "preboot", "configPath": [ "interactiveSetup" ] } -} +} \ No newline at end of file diff --git a/src/plugins/interactive_setup/server/routes/configure.ts b/src/plugins/interactive_setup/server/routes/configure.ts index 1cdaf588a6cd9..bb5a85800e03b 100644 --- a/src/plugins/interactive_setup/server/routes/configure.ts +++ b/src/plugins/interactive_setup/server/routes/configure.ts @@ -37,6 +37,13 @@ export function defineConfigureRoute({ router.post( { path: '/internal/interactive_setup/configure', + security: { + authz: { + enabled: false, + reason: + 'Interactive setup is strictly a "pre-boot" feature which cannot leverage conventional authorization.', + }, + }, validate: { body: schema.object({ host: schema.uri({ scheme: ['http', 'https'] }), diff --git a/src/plugins/interactive_setup/server/routes/enroll.ts b/src/plugins/interactive_setup/server/routes/enroll.ts index 1cd0362d2790b..7ee97db592ac5 100644 --- a/src/plugins/interactive_setup/server/routes/enroll.ts +++ b/src/plugins/interactive_setup/server/routes/enroll.ts @@ -40,6 +40,13 @@ export function defineEnrollRoutes({ router.post( { path: '/internal/interactive_setup/enroll', + security: { + authz: { + enabled: false, + reason: + 'Interactive setup is strictly a "pre-boot" feature which cannot leverage conventional authorization.', + }, + }, validate: { body: schema.object({ hosts: schema.arrayOf(schema.uri({ scheme: 'https' }), { diff --git a/src/plugins/interactive_setup/server/routes/ping.ts b/src/plugins/interactive_setup/server/routes/ping.ts index 4deaeee675404..4c71d9f05bd1b 100644 --- a/src/plugins/interactive_setup/server/routes/ping.ts +++ b/src/plugins/interactive_setup/server/routes/ping.ts @@ -17,6 +17,13 @@ export function definePingRoute({ router, logger, elasticsearch, preboot }: Rout router.post( { path: '/internal/interactive_setup/ping', + security: { + authz: { + enabled: false, + reason: + 'Interactive setup is strictly a "pre-boot" feature which cannot leverage conventional authorization.', + }, + }, validate: { body: schema.object({ host: schema.uri({ scheme: ['http', 'https'] }), diff --git a/src/plugins/interactive_setup/server/routes/status.ts b/src/plugins/interactive_setup/server/routes/status.ts index 78a97ac862317..14c94411ded53 100644 --- a/src/plugins/interactive_setup/server/routes/status.ts +++ b/src/plugins/interactive_setup/server/routes/status.ts @@ -15,6 +15,13 @@ export function defineStatusRoute({ router, elasticsearch, preboot }: RouteDefin router.get( { path: '/internal/interactive_setup/status', + security: { + authz: { + enabled: false, + reason: + 'Interactive setup is strictly a "pre-boot" feature which cannot leverage conventional authorization.', + }, + }, validate: false, options: { authRequired: false }, }, diff --git a/src/plugins/interactive_setup/server/routes/verify.ts b/src/plugins/interactive_setup/server/routes/verify.ts index a40e35794fb9e..7fb5bb2e70c18 100644 --- a/src/plugins/interactive_setup/server/routes/verify.ts +++ b/src/plugins/interactive_setup/server/routes/verify.ts @@ -15,6 +15,13 @@ export function defineVerifyRoute({ router, verificationCode }: RouteDefinitionP router.post( { path: '/internal/interactive_setup/verify', + security: { + authz: { + enabled: false, + reason: + 'Interactive setup is strictly a "pre-boot" feature which cannot leverage conventional authorization.', + }, + }, validate: { body: schema.object({ code: schema.string(), diff --git a/src/plugins/kibana_overview/kibana.jsonc b/src/plugins/kibana_overview/kibana.jsonc index 3fa85f51fce76..6ea60e6a4007b 100644 --- a/src/plugins/kibana_overview/kibana.jsonc +++ b/src/plugins/kibana_overview/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/kibana-overview-plugin", - "owner": "@elastic/appex-sharedux", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private", "plugin": { "id": "kibanaOverview", - "server": false, "browser": true, + "server": false, "requiredPlugins": [ "navigation", "dataViews", @@ -22,4 +26,4 @@ "newsfeed" ] } -} +} \ No newline at end of file diff --git a/src/plugins/kibana_react/kibana.jsonc b/src/plugins/kibana_react/kibana.jsonc index 445442e7dc72d..f18c848be2dfe 100644 --- a/src/plugins/kibana_react/kibana.jsonc +++ b/src/plugins/kibana_react/kibana.jsonc @@ -1,13 +1,17 @@ { "type": "plugin", "id": "@kbn/kibana-react-plugin", - "owner": "@elastic/appex-sharedux", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "kibanaReact", - "server": false, "browser": true, + "server": false, "extraPublicDirs": [ "common" ] } -} +} \ No newline at end of file diff --git a/src/plugins/kibana_usage_collection/kibana.jsonc b/src/plugins/kibana_usage_collection/kibana.jsonc index df2eee17b0638..00a92e964cbba 100644 --- a/src/plugins/kibana_usage_collection/kibana.jsonc +++ b/src/plugins/kibana_usage_collection/kibana.jsonc @@ -1,13 +1,17 @@ { "type": "plugin", "id": "@kbn/kibana-usage-collection-plugin", - "owner": "@elastic/kibana-core", + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private", "plugin": { "id": "kibanaUsageCollection", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "usageCollection" ] } -} +} \ No newline at end of file diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts index 6b3db9460eb7c..000f0bc9ace24 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -578,10 +578,6 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, - 'metrics:allowCheckingForFailedShards': { - type: 'boolean', - _meta: { description: 'Non-default value of setting.' }, - }, 'observability:apmDefaultServiceEnvironment': { type: 'keyword', _meta: { description: 'Default value of the setting was changed.' }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index 92076ebc302e2..bcbf2d573dec4 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -154,7 +154,6 @@ export interface UsageStats { 'discover:rowHeightOption': number; hideAnnouncements: boolean; isDefaultIndexMigrated: boolean; - 'metrics:allowCheckingForFailedShards': boolean; 'observability:syntheticsThrottlingEnabled': boolean; 'observability:enableLegacyUptimeApp': boolean; 'observability:apmLabsButton': boolean; diff --git a/src/plugins/kibana_utils/kibana.jsonc b/src/plugins/kibana_utils/kibana.jsonc index 7e12fba0e2b86..f58ee6d1a404a 100644 --- a/src/plugins/kibana_utils/kibana.jsonc +++ b/src/plugins/kibana_utils/kibana.jsonc @@ -1,15 +1,19 @@ { "type": "plugin", "id": "@kbn/kibana-utils-plugin", - "owner": "@elastic/appex-sharedux", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "kibanaUtils", - "server": false, "browser": true, + "server": false, "extraPublicDirs": [ "common", "demos/state_containers/todomvc", "common/state_containers" ] } -} +} \ No newline at end of file diff --git a/src/plugins/links/kibana.jsonc b/src/plugins/links/kibana.jsonc index 4aed94ab56751..7eaac4ad878e9 100644 --- a/src/plugins/links/kibana.jsonc +++ b/src/plugins/links/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/links-plugin", - "owner": "@elastic/kibana-presentation", + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "private", "description": "A dashboard panel for creating links to dashboards or external links.", "plugin": { "id": "links", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "contentManagement", "dashboard", @@ -18,7 +22,12 @@ "uiActionsEnhanced", "visualizations" ], - "optionalPlugins": ["triggersActionsUi", "usageCollection"], - "requiredBundles": ["savedObjects"] + "optionalPlugins": [ + "triggersActionsUi", + "usageCollection" + ], + "requiredBundles": [ + "savedObjects" + ] } -} +} \ No newline at end of file diff --git a/src/plugins/links/public/types.ts b/src/plugins/links/public/types.ts index 97b1f0254f4ea..df3eb7fc2b514 100644 --- a/src/plugins/links/public/types.ts +++ b/src/plugins/links/public/types.ts @@ -22,7 +22,7 @@ import { DynamicActionsSerializedState } from '@kbn/embeddable-enhanced-plugin/p import { HasSerializedChildState, PresentationContainer } from '@kbn/presentation-containers'; import { LocatorPublic } from '@kbn/share-plugin/common'; import { DashboardLocatorParams, DASHBOARD_CONTAINER_TYPE } from '@kbn/dashboard-plugin/public'; -import { DashboardAttributes } from '@kbn/dashboard-plugin/common'; +import type { DashboardAttributes } from '@kbn/dashboard-plugin/server'; import { CONTENT_ID } from '../common'; import { Link, LinksAttributes, LinksLayoutType } from '../common/content_management'; @@ -73,5 +73,5 @@ export type ResolvedLink = Link & { export interface DashboardItem { id: string; - attributes: DashboardAttributes; + attributes: Pick; } diff --git a/src/plugins/management/kibana.jsonc b/src/plugins/management/kibana.jsonc index c6e6b59206306..a70b1cce153a3 100644 --- a/src/plugins/management/kibana.jsonc +++ b/src/plugins/management/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/management-plugin", - "owner": "@elastic/kibana-management", + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "management", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "share" ], @@ -18,4 +22,4 @@ "kibanaUtils" ] } -} +} \ No newline at end of file diff --git a/src/plugins/maps_ems/kibana.jsonc b/src/plugins/maps_ems/kibana.jsonc index f71542e94ae71..9b86f2f45b0c8 100644 --- a/src/plugins/maps_ems/kibana.jsonc +++ b/src/plugins/maps_ems/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/maps-ems-plugin", - "owner": "@elastic/kibana-gis", + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "private", "plugin": { "id": "mapsEms", - "server": true, "browser": true, + "server": true, "configPath": [ "map" ], diff --git a/src/plugins/navigation/kibana.jsonc b/src/plugins/navigation/kibana.jsonc index 92cb0d492572d..04328fa2217f8 100644 --- a/src/plugins/navigation/kibana.jsonc +++ b/src/plugins/navigation/kibana.jsonc @@ -1,13 +1,22 @@ { "type": "plugin", "id": "@kbn/navigation-plugin", - "owner": "@elastic/appex-sharedux", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "navigation", - "server": true, "browser": true, - "optionalPlugins": ["cloud", "spaces"], - "requiredPlugins": ["unifiedSearch"], + "server": true, + "requiredPlugins": [ + "unifiedSearch" + ], + "optionalPlugins": [ + "cloud", + "spaces" + ], "requiredBundles": [] } -} +} \ No newline at end of file diff --git a/src/plugins/newsfeed/kibana.jsonc b/src/plugins/newsfeed/kibana.jsonc index b0c9e21b8fa56..113aa7667e582 100644 --- a/src/plugins/newsfeed/kibana.jsonc +++ b/src/plugins/newsfeed/kibana.jsonc @@ -1,14 +1,18 @@ { "type": "plugin", "id": "@kbn/newsfeed-plugin", - "owner": "@elastic/kibana-core", + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "newsfeed", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "screenshotMode" ], "requiredBundles": [] } -} +} \ No newline at end of file diff --git a/src/plugins/no_data_page/kibana.jsonc b/src/plugins/no_data_page/kibana.jsonc index 202917173b7a4..0ebe065d5b5ab 100644 --- a/src/plugins/no_data_page/kibana.jsonc +++ b/src/plugins/no_data_page/kibana.jsonc @@ -1,10 +1,14 @@ { "type": "plugin", "id": "@kbn/no-data-page-plugin", - "owner": "@elastic/appex-sharedux", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private", "plugin": { "id": "noDataPage", - "server": true, - "browser": true + "browser": true, + "server": true } -} +} \ No newline at end of file diff --git a/src/plugins/presentation_panel/kibana.jsonc b/src/plugins/presentation_panel/kibana.jsonc index cbcda3501f40f..dd8a20694ac5d 100644 --- a/src/plugins/presentation_panel/kibana.jsonc +++ b/src/plugins/presentation_panel/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/presentation-panel-plugin", - "owner": "@elastic/kibana-presentation", + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "private", "description": "Adds a standardized Presentation panel which allows any forward ref component to interface with various Kibana systems.", "plugin": { "id": "presentationPanel", - "server": false, "browser": true, + "server": false, "requiredPlugins": [ "data", "inspector", @@ -16,6 +20,9 @@ "savedObjectsManagement", "savedObjectsTaggingOss" ], - "requiredBundles": ["kibanaReact", "unifiedSearch"] + "requiredBundles": [ + "kibanaReact", + "unifiedSearch" + ] } -} +} \ No newline at end of file diff --git a/src/plugins/presentation_util/kibana.jsonc b/src/plugins/presentation_util/kibana.jsonc index f9b659fa61630..681877d132b7d 100644 --- a/src/plugins/presentation_util/kibana.jsonc +++ b/src/plugins/presentation_util/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/presentation-util-plugin", - "owner": "@elastic/kibana-presentation", + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "shared", "description": "The Presentation Utility Plugin is a set of common, shared components and toolkits for solutions within the Presentation space, (e.g. Dashboards, Canvas).", "plugin": { "id": "presentationUtil", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "kibanaReact", "contentManagement", @@ -15,7 +19,11 @@ "dataViews", "uiActions" ], - "extraPublicDirs": ["common"], - "requiredBundles": ["savedObjects"], + "requiredBundles": [ + "savedObjects" + ], + "extraPublicDirs": [ + "common" + ] } -} +} \ No newline at end of file diff --git a/src/plugins/saved_objects/kibana.jsonc b/src/plugins/saved_objects/kibana.jsonc index 1f063a7cdfa59..86aa1ab920725 100644 --- a/src/plugins/saved_objects/kibana.jsonc +++ b/src/plugins/saved_objects/kibana.jsonc @@ -1,14 +1,18 @@ { "type": "plugin", "id": "@kbn/saved-objects-plugin", - "owner": "@elastic/kibana-core", + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "savedObjects", - "server": false, "browser": true, + "server": false, "requiredPlugins": [ "data", "dataViews" ] } -} +} \ No newline at end of file diff --git a/src/plugins/saved_objects_finder/kibana.jsonc b/src/plugins/saved_objects_finder/kibana.jsonc index ad53ee32ae369..5e2b45f0271bc 100644 --- a/src/plugins/saved_objects_finder/kibana.jsonc +++ b/src/plugins/saved_objects_finder/kibana.jsonc @@ -1,11 +1,17 @@ { "type": "plugin", "id": "@kbn/saved-objects-finder-plugin", - "owner": "@elastic/kibana-data-discovery", + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "savedObjectsFinder", - "server": true, "browser": true, - "requiredBundles": ["savedObjectsManagement"] + "server": true, + "requiredBundles": [ + "savedObjectsManagement" + ] } -} +} \ No newline at end of file diff --git a/src/plugins/saved_objects_management/kibana.jsonc b/src/plugins/saved_objects_management/kibana.jsonc index ec6004dca617d..7fab51bcb49ac 100644 --- a/src/plugins/saved_objects_management/kibana.jsonc +++ b/src/plugins/saved_objects_management/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/saved-objects-management-plugin", - "owner": "@elastic/kibana-core", + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "savedObjectsManagement", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "management", "data", @@ -23,4 +27,4 @@ "public/lib" ] } -} +} \ No newline at end of file diff --git a/src/plugins/saved_objects_tagging_oss/kibana.jsonc b/src/plugins/saved_objects_tagging_oss/kibana.jsonc index 823dd9c074e9e..2d9ebcc42dbb4 100644 --- a/src/plugins/saved_objects_tagging_oss/kibana.jsonc +++ b/src/plugins/saved_objects_tagging_oss/kibana.jsonc @@ -1,13 +1,17 @@ { "type": "plugin", "id": "@kbn/saved-objects-tagging-oss-plugin", - "owner": "@elastic/appex-sharedux", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "savedObjectsTaggingOss", - "server": false, "browser": true, + "server": false, "optionalPlugins": [ "savedObjects" ] } -} +} \ No newline at end of file diff --git a/src/plugins/saved_search/kibana.jsonc b/src/plugins/saved_search/kibana.jsonc index da389103a5f78..820d42662ff1c 100644 --- a/src/plugins/saved_search/kibana.jsonc +++ b/src/plugins/saved_search/kibana.jsonc @@ -1,15 +1,29 @@ { "type": "plugin", "id": "@kbn/saved-search-plugin", - "owner": "@elastic/kibana-data-discovery", + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared", "description": "This plugin contains the definition and helper methods around saved searches, used by discover and visualizations.", "plugin": { "id": "savedSearch", - "server": true, "browser": true, - "requiredPlugins": ["data", "contentManagement", "embeddable", "expressions"], - "optionalPlugins": ["spaces", "savedObjectsTaggingOss"], + "server": true, + "requiredPlugins": [ + "data", + "contentManagement", + "embeddable", + "expressions" + ], + "optionalPlugins": [ + "spaces", + "savedObjectsTaggingOss" + ], "requiredBundles": [], - "extraPublicDirs": ["common"] + "extraPublicDirs": [ + "common" + ] } -} +} \ No newline at end of file diff --git a/src/plugins/screenshot_mode/kibana.jsonc b/src/plugins/screenshot_mode/kibana.jsonc index 3b57e37801a15..e00d2f49d6334 100644 --- a/src/plugins/screenshot_mode/kibana.jsonc +++ b/src/plugins/screenshot_mode/kibana.jsonc @@ -1,10 +1,14 @@ { "type": "plugin", "id": "@kbn/screenshot-mode-plugin", - "owner": "@elastic/appex-sharedux", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "screenshotMode", - "server": true, - "browser": true + "browser": true, + "server": true } -} +} \ No newline at end of file diff --git a/src/plugins/share/kibana.jsonc b/src/plugins/share/kibana.jsonc index a705a73709730..d402d595c1a9b 100644 --- a/src/plugins/share/kibana.jsonc +++ b/src/plugins/share/kibana.jsonc @@ -1,14 +1,18 @@ { "type": "plugin", "id": "@kbn/share-plugin", - "owner": "@elastic/appex-sharedux", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared", "description": "Adds URL Service and sharing capabilities to Kibana", "plugin": { "id": "share", - "server": true, "browser": true, + "server": true, "requiredBundles": [ "kibanaUtils" ] } -} +} \ No newline at end of file diff --git a/src/plugins/telemetry/kibana.jsonc b/src/plugins/telemetry/kibana.jsonc index a5edcdde85d99..15b87d686c15a 100644 --- a/src/plugins/telemetry/kibana.jsonc +++ b/src/plugins/telemetry/kibana.jsonc @@ -1,12 +1,15 @@ { "type": "plugin", "id": "@kbn/telemetry-plugin", - "owner": "@elastic/kibana-core", + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "telemetry", - "server": true, "browser": true, - "enabledOnAnonymousPages": true, + "server": true, "requiredPlugins": [ "telemetryCollectionManager", "usageCollection", @@ -19,8 +22,9 @@ "requiredBundles": [ "kibanaUtils" ], + "enabledOnAnonymousPages": true, "extraPublicDirs": [ "common/constants" ] } -} +} \ No newline at end of file diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 4e533249c7346..1d23d73f356e4 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -10728,12 +10728,6 @@ "description": "Non-default value of setting." } }, - "metrics:allowCheckingForFailedShards": { - "type": "boolean", - "_meta": { - "description": "Non-default value of setting." - } - }, "observability:apmDefaultServiceEnvironment": { "type": "keyword", "_meta": { diff --git a/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts b/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts index 843bf67e7863c..f19ec804ac6e9 100644 --- a/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts +++ b/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts @@ -14,6 +14,7 @@ import type { StatsGetterConfig, } from '@kbn/telemetry-collection-manager-plugin/server'; import type { SecurityPluginStart } from '@kbn/security-plugin/server'; +import { ApiOperation } from '@kbn/security-plugin-types-server'; import { RequestHandler } from '@kbn/core-http-server'; import { FetchSnapshotTelemetry } from '../../common/routes'; import { UsageStatsBody, v2 } from '../../common/types'; @@ -50,7 +51,7 @@ export function registerTelemetryUsageStatsRoutes( // security API directly to check privileges for this action. Note that the 'decryptedTelemetry' API privilege string is only // granted to users that have "Global All" or "Global Read" privileges in Kibana. const { checkPrivilegesWithRequest, actions } = security.authz; - const privileges = { kibana: actions.api.get('decryptedTelemetry') }; + const privileges = { kibana: actions.api.get(ApiOperation.Read, 'decryptedTelemetry') }; const { hasAllRequested } = await checkPrivilegesWithRequest(req).globally(privileges); if (!hasAllRequested) { return res.forbidden(); diff --git a/src/plugins/telemetry/tsconfig.json b/src/plugins/telemetry/tsconfig.json index 09d5aa25c914b..a8538b4a0b18a 100644 --- a/src/plugins/telemetry/tsconfig.json +++ b/src/plugins/telemetry/tsconfig.json @@ -36,6 +36,7 @@ "@kbn/analytics-collection-utils", "@kbn/react-kibana-mount", "@kbn/core-node-server", + "@kbn/security-plugin-types-server", ], "exclude": [ "target/**/*", diff --git a/src/plugins/telemetry_collection_manager/kibana.jsonc b/src/plugins/telemetry_collection_manager/kibana.jsonc index cbf63b44c6115..3c96594530acc 100644 --- a/src/plugins/telemetry_collection_manager/kibana.jsonc +++ b/src/plugins/telemetry_collection_manager/kibana.jsonc @@ -1,13 +1,17 @@ { "type": "plugin", "id": "@kbn/telemetry-collection-manager-plugin", - "owner": "@elastic/kibana-core", + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "telemetryCollectionManager", - "server": true, "browser": false, + "server": true, "requiredPlugins": [ "usageCollection" ] } -} +} \ No newline at end of file diff --git a/src/plugins/telemetry_management_section/kibana.jsonc b/src/plugins/telemetry_management_section/kibana.jsonc index 0cd94f9d23234..e86f9f8901d14 100644 --- a/src/plugins/telemetry_management_section/kibana.jsonc +++ b/src/plugins/telemetry_management_section/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/telemetry-management-section-plugin", - "owner": "@elastic/kibana-core", + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "telemetryManagementSection", - "server": false, "browser": true, + "server": false, "requiredPlugins": [ "telemetry", "advancedSettings" diff --git a/src/plugins/ui_actions/kibana.jsonc b/src/plugins/ui_actions/kibana.jsonc index e63c80190c074..1af247645ba49 100644 --- a/src/plugins/ui_actions/kibana.jsonc +++ b/src/plugins/ui_actions/kibana.jsonc @@ -1,15 +1,19 @@ { "type": "plugin", "id": "@kbn/ui-actions-plugin", - "owner": "@elastic/appex-sharedux", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared", "description": "Adds UI Actions service to Kibana", "plugin": { "id": "uiActions", - "server": false, "browser": true, + "server": false, "requiredPlugins": [], "requiredBundles": [ "kibanaUtils" ] } -} +} \ No newline at end of file diff --git a/src/plugins/ui_actions_enhanced/kibana.jsonc b/src/plugins/ui_actions_enhanced/kibana.jsonc index 595c483a66505..7575e79bf6fe7 100644 --- a/src/plugins/ui_actions_enhanced/kibana.jsonc +++ b/src/plugins/ui_actions_enhanced/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/ui-actions-enhanced-plugin", - "owner": "@elastic/appex-sharedux", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared", "description": "Extends UI Actions plugin with more functionality", "plugin": { "id": "uiActionsEnhanced", - "server": true, "browser": true, + "server": true, "configPath": [ "src", "ui_actions_enhanced" @@ -23,4 +27,4 @@ "kibanaReact" ] } -} +} \ No newline at end of file diff --git a/src/plugins/unified_doc_viewer/kibana.jsonc b/src/plugins/unified_doc_viewer/kibana.jsonc index 6bd1b738c0ccb..a741cd93472b3 100644 --- a/src/plugins/unified_doc_viewer/kibana.jsonc +++ b/src/plugins/unified_doc_viewer/kibana.jsonc @@ -1,15 +1,26 @@ { "type": "plugin", "id": "@kbn/unified-doc-viewer-plugin", - "owner": "@elastic/kibana-data-discovery", + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared", "description": "This plugin contains services reliant on the plugin lifecycle for the unified doc viewer component (see @kbn/unified-doc-viewer).", "plugin": { "id": "unifiedDocViewer", - "server": false, "browser": true, - "requiredBundles": ["kibanaUtils"], - "requiredPlugins": ["data", "fieldFormats", "share"], - "optionalPlugins": ["fieldsMetadata"] + "server": false, + "requiredPlugins": [ + "data", + "fieldFormats", + "share" + ], + "optionalPlugins": [ + "fieldsMetadata" + ], + "requiredBundles": [ + "kibanaUtils" + ] } -} - \ No newline at end of file +} \ No newline at end of file diff --git a/src/plugins/unified_histogram/kibana.jsonc b/src/plugins/unified_histogram/kibana.jsonc index 54e749a89f3e4..4af13ca5d6996 100644 --- a/src/plugins/unified_histogram/kibana.jsonc +++ b/src/plugins/unified_histogram/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/unified-histogram-plugin", - "owner": "@elastic/kibana-data-discovery", + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared", "description": "The `unifiedHistogram` plugin provides UI components to create a layout including a resizable histogram and a main display.", "plugin": { "id": "unifiedHistogram", - "server": false, "browser": true, + "server": false, "requiredBundles": [ "data", "dataViews", @@ -15,4 +19,4 @@ "visualizations" ] } -} +} \ No newline at end of file diff --git a/src/plugins/unified_search/kibana.jsonc b/src/plugins/unified_search/kibana.jsonc index ad7f73a608857..b3e71c12af28a 100644 --- a/src/plugins/unified_search/kibana.jsonc +++ b/src/plugins/unified_search/kibana.jsonc @@ -1,15 +1,19 @@ { "type": "plugin", "id": "@kbn/unified-search-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "shared", "description": "Contains all the key functionality of Kibana's unified search experience.Contains all the key functionality of Kibana's unified search experience.", "serviceFolders": [ "autocomplete" ], "plugin": { "id": "unifiedSearch", - "server": true, "browser": true, + "server": true, "configPath": [ "unifiedSearch" ], @@ -30,4 +34,4 @@ "esql" ] } -} +} \ No newline at end of file diff --git a/src/plugins/url_forwarding/kibana.jsonc b/src/plugins/url_forwarding/kibana.jsonc index 3eede9eb9effc..4089ad10fc7b9 100644 --- a/src/plugins/url_forwarding/kibana.jsonc +++ b/src/plugins/url_forwarding/kibana.jsonc @@ -1,10 +1,14 @@ { "type": "plugin", "id": "@kbn/url-forwarding-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "private", "plugin": { "id": "urlForwarding", - "server": false, - "browser": true + "browser": true, + "server": false } -} +} \ No newline at end of file diff --git a/src/plugins/usage_collection/kibana.jsonc b/src/plugins/usage_collection/kibana.jsonc index 78d54f302a327..3e9ccac82974b 100644 --- a/src/plugins/usage_collection/kibana.jsonc +++ b/src/plugins/usage_collection/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/usage-collection-plugin", - "owner": "@elastic/kibana-core", + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "usageCollection", - "server": true, "browser": true, + "server": true, "configPath": [ "usageCollection" ], @@ -16,4 +20,4 @@ "kibanaUtils" ] } -} +} \ No newline at end of file diff --git a/src/plugins/vis_default_editor/kibana.jsonc b/src/plugins/vis_default_editor/kibana.jsonc index 15db2338f2ca7..1e096b8be6950 100644 --- a/src/plugins/vis_default_editor/kibana.jsonc +++ b/src/plugins/vis_default_editor/kibana.jsonc @@ -1,15 +1,19 @@ { "type": "plugin", "id": "@kbn/vis-default-editor-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "private", "description": "The default editor used in most aggregation-based visualizations.", "plugin": { "id": "visDefaultEditor", - "server": false, "browser": true, + "server": false, "requiredPlugins": [ "dataViews", - "unifiedSearch", + "unifiedSearch" ], "optionalPlugins": [ "visualizations" @@ -24,4 +28,4 @@ "savedSearch" ] } -} +} \ No newline at end of file diff --git a/src/plugins/vis_type_markdown/kibana.jsonc b/src/plugins/vis_type_markdown/kibana.jsonc index 476dcc0605ad4..49a9f0d00b0bf 100644 --- a/src/plugins/vis_type_markdown/kibana.jsonc +++ b/src/plugins/vis_type_markdown/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/vis-type-markdown-plugin", - "owner": "@elastic/kibana-presentation", + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "private", "description": "Adds a markdown visualization type", "plugin": { "id": "visTypeMarkdown", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "expressions", "visualizations" @@ -18,4 +22,4 @@ "visualizations" ] } -} +} \ No newline at end of file diff --git a/src/plugins/vis_types/gauge/kibana.jsonc b/src/plugins/vis_types/gauge/kibana.jsonc index 7897ac6a6a3b1..634fe39685cbc 100644 --- a/src/plugins/vis_types/gauge/kibana.jsonc +++ b/src/plugins/vis_types/gauge/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/vis-type-gauge-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "private", "description": "Contains the gauge chart implementation using the elastic-charts library. The goal is to eventually deprecate the old implementation and keep only this. Until then, the library used is defined by the Legacy charts library advanced setting.", "plugin": { "id": "visTypeGauge", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "charts", "data", @@ -26,4 +30,4 @@ "common" ] } -} +} \ No newline at end of file diff --git a/src/plugins/vis_types/heatmap/kibana.jsonc b/src/plugins/vis_types/heatmap/kibana.jsonc index f658f744526dd..79b426ed2d7bd 100644 --- a/src/plugins/vis_types/heatmap/kibana.jsonc +++ b/src/plugins/vis_types/heatmap/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/vis-type-heatmap-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "private", "description": "Contains the heatmap implementation using the elastic-charts library. The goal is to eventually deprecate the old implementation and keep only this. Until then, the library used is defined by the Legacy heatmap charts library advanced setting.", "plugin": { "id": "visTypeHeatmap", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "charts", "data", @@ -24,4 +28,4 @@ "common" ] } -} +} \ No newline at end of file diff --git a/src/plugins/vis_types/metric/kibana.jsonc b/src/plugins/vis_types/metric/kibana.jsonc index 539e4318a9362..88a3f469e3485 100644 --- a/src/plugins/vis_types/metric/kibana.jsonc +++ b/src/plugins/vis_types/metric/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/vis-type-metric-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "private", "description": "Registers the Metric aggregation-based visualization.", "plugin": { "id": "visTypeMetric", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "data", "visualizations", @@ -19,4 +23,4 @@ "kibanaUtils" ] } -} +} \ No newline at end of file diff --git a/src/plugins/vis_types/pie/kibana.jsonc b/src/plugins/vis_types/pie/kibana.jsonc index 85364316fc19d..79beaa143cbe1 100644 --- a/src/plugins/vis_types/pie/kibana.jsonc +++ b/src/plugins/vis_types/pie/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/vis-type-pie-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "private", "description": "Contains the pie chart implementation using the elastic-charts library. The goal is to eventually deprecate the old implementation and keep only this. Until then, the library used is defined by the Legacy charts library advanced setting.", "plugin": { "id": "visTypePie", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "charts", "data", @@ -24,4 +28,4 @@ "common" ] } -} +} \ No newline at end of file diff --git a/src/plugins/vis_types/table/kibana.jsonc b/src/plugins/vis_types/table/kibana.jsonc index de4712484d07d..5cdc5da34d871 100644 --- a/src/plugins/vis_types/table/kibana.jsonc +++ b/src/plugins/vis_types/table/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/vis-type-table-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "private", "description": "Registers the datatable aggregation-based visualization.", "plugin": { "id": "visTypeTable", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "expressions", "visualizations", @@ -22,4 +26,4 @@ "visDefaultEditor" ] } -} +} \ No newline at end of file diff --git a/src/plugins/vis_types/tagcloud/kibana.jsonc b/src/plugins/vis_types/tagcloud/kibana.jsonc index 8694f3f27c2f5..71d9d91d7becf 100644 --- a/src/plugins/vis_types/tagcloud/kibana.jsonc +++ b/src/plugins/vis_types/tagcloud/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/vis-type-tagcloud-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "private", "description": "Registers the tagcloud visualization. It is based on elastic-charts wordcloud.", "plugin": { "id": "visTypeTagcloud", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "data", "expressions", @@ -20,4 +24,4 @@ "kibanaUtils" ] } -} +} \ No newline at end of file diff --git a/src/plugins/vis_types/timelion/kibana.jsonc b/src/plugins/vis_types/timelion/kibana.jsonc index aa11b92e58874..041d1248cfe1e 100644 --- a/src/plugins/vis_types/timelion/kibana.jsonc +++ b/src/plugins/vis_types/timelion/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/vis-type-timelion-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "private", "description": "Registers the timelion visualization. Also contains the backend for both timelion app and timelion visualization.", "plugin": { "id": "visTypeTimelion", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "visualizations", "data", @@ -24,4 +28,4 @@ "visDefaultEditor" ] } -} +} \ No newline at end of file diff --git a/src/plugins/vis_types/timeseries/common/constants.ts b/src/plugins/vis_types/timeseries/common/constants.ts index e881fb767f0d0..4734d25d191ce 100644 --- a/src/plugins/vis_types/timeseries/common/constants.ts +++ b/src/plugins/vis_types/timeseries/common/constants.ts @@ -10,7 +10,6 @@ export const UI_SETTINGS = { MAX_BUCKETS_SETTING: 'metrics:max_buckets', ALLOW_STRING_INDICES: 'metrics:allowStringIndices', - ALLOW_CHECKING_FOR_FAILED_SHARDS: 'metrics:allowCheckingForFailedShards', }; export const SERIES_SEPARATOR = '╰┄►'; export const INDEXES_SEPARATOR = ','; diff --git a/src/plugins/vis_types/timeseries/kibana.jsonc b/src/plugins/vis_types/timeseries/kibana.jsonc index 9ba099aef6321..bd0e4ac352daa 100644 --- a/src/plugins/vis_types/timeseries/kibana.jsonc +++ b/src/plugins/vis_types/timeseries/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/vis-type-timeseries-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "private", "description": "Registers the TSVB visualization. TSVB has its one editor, works with index patterns and index strings and contains 6 types of charts: timeseries, topN, table. markdown, metric and gauge.", "plugin": { "id": "visTypeTimeseries", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "charts", "data", @@ -28,4 +32,4 @@ "fieldFormats" ] } -} +} \ No newline at end of file diff --git a/src/plugins/vis_types/timeseries/public/metrics_type.ts b/src/plugins/vis_types/timeseries/public/metrics_type.ts index eec28bb6cf47c..65150ab9eabea 100644 --- a/src/plugins/vis_types/timeseries/public/metrics_type.ts +++ b/src/plugins/vis_types/timeseries/public/metrics_type.ts @@ -24,9 +24,9 @@ import { extractIndexPatternValues, isStringTypeIndexPattern, } from '../common/index_patterns_utils'; -import { TSVB_DEFAULT_COLOR, UI_SETTINGS, VIS_TYPE } from '../common/constants'; +import { TSVB_DEFAULT_COLOR, VIS_TYPE } from '../common/constants'; import { toExpressionAst } from './to_ast'; -import { getDataViewsStart, getUISettings } from './services'; +import { getDataViewsStart } from './services'; import type { TimeseriesVisDefaultParams, TimeseriesVisParams } from './types'; import type { IndexPatternValue, Panel } from '../common/types'; @@ -188,6 +188,5 @@ export const metricsVisDefinition: VisTypeDefinition< requests: new RequestAdapter(), }), requiresSearch: true, - suppressWarnings: () => !getUISettings().get(UI_SETTINGS.ALLOW_CHECKING_FOR_FAILED_SHARDS), getUsedIndexPattern: getUsedIndexPatterns, }; diff --git a/src/plugins/vis_types/timeseries/server/ui_settings.ts b/src/plugins/vis_types/timeseries/server/ui_settings.ts index 0d3dcc681110c..9d6ac0f0856d5 100644 --- a/src/plugins/vis_types/timeseries/server/ui_settings.ts +++ b/src/plugins/vis_types/timeseries/server/ui_settings.ts @@ -38,18 +38,4 @@ export const getUiSettings: () => Record = () => ({ }), schema: schema.boolean(), }, - [UI_SETTINGS.ALLOW_CHECKING_FOR_FAILED_SHARDS]: { - name: i18n.translate('visTypeTimeseries.advancedSettings.allowCheckingForFailedShardsTitle', { - defaultMessage: 'Show TSVB request shard failures', - }), - value: true, - description: i18n.translate( - 'visTypeTimeseries.advancedSettings.allowCheckingForFailedShardsText', - { - defaultMessage: - 'Show warning message for partial data in TSVB charts if the request succeeds for some shards but fails for others.', - } - ), - schema: schema.boolean(), - }, }); diff --git a/src/plugins/vis_types/vega/kibana.jsonc b/src/plugins/vis_types/vega/kibana.jsonc index b3a2c28afa698..25c4320825288 100644 --- a/src/plugins/vis_types/vega/kibana.jsonc +++ b/src/plugins/vis_types/vega/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/vis-type-vega-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "private", "description": "Registers the vega visualization. Is the elastic version of vega and vega-lite libraries.", "plugin": { "id": "visTypeVega", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "data", "visualizations", @@ -25,4 +29,4 @@ "visDefaultEditor" ] } -} +} \ No newline at end of file diff --git a/src/plugins/vis_types/vislib/kibana.jsonc b/src/plugins/vis_types/vislib/kibana.jsonc index bb1882e4d860d..8521b198c2b6a 100644 --- a/src/plugins/vis_types/vislib/kibana.jsonc +++ b/src/plugins/vis_types/vislib/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/vis-type-vislib-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "private", "description": "Contains the vislib visualizations. These are the classical area/line/bar, gauge/goal and heatmap charts. We want to replace them with elastic-charts.", "plugin": { "id": "visTypeVislib", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "charts", "data", @@ -23,4 +27,4 @@ "visTypeGauge" ] } -} +} \ No newline at end of file diff --git a/src/plugins/vis_types/xy/kibana.jsonc b/src/plugins/vis_types/xy/kibana.jsonc index bc0f162537fd5..ff8da57388377 100644 --- a/src/plugins/vis_types/xy/kibana.jsonc +++ b/src/plugins/vis_types/xy/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/vis-type-xy-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "private", "description": "Contains the new xy-axis chart using the elastic-charts library, which will eventually replace the vislib xy-axis charts including bar, area, and line.", "plugin": { "id": "visTypeXy", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "charts", "visualizations", @@ -22,4 +26,4 @@ "common" ] } -} +} \ No newline at end of file diff --git a/src/plugins/visualizations/kibana.jsonc b/src/plugins/visualizations/kibana.jsonc index 95a2999611bd4..8e8b492e99a0b 100644 --- a/src/plugins/visualizations/kibana.jsonc +++ b/src/plugins/visualizations/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/visualizations-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "shared", "description": "Contains the shared architecture among all the legacy visualizations, e.g. the visualization type registry or the visualization embeddable.", "plugin": { "id": "visualizations", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "data", "charts", @@ -37,7 +41,12 @@ "noDataPage", "embeddableEnhanced" ], - "requiredBundles": ["kibanaUtils", "kibanaReact", "charts", "savedObjects"], + "requiredBundles": [ + "kibanaUtils", + "kibanaReact", + "charts", + "savedObjects" + ], "extraPublicDirs": [ "common/constants", "common/utils", @@ -45,4 +54,4 @@ "common/convert_to_lens" ] } -} +} \ No newline at end of file diff --git a/src/plugins/visualizations/public/legacy/embeddable/visualize_embeddable.tsx b/src/plugins/visualizations/public/legacy/embeddable/visualize_embeddable.tsx index 4f6bfa344a0a3..196753d73b28c 100644 --- a/src/plugins/visualizations/public/legacy/embeddable/visualize_embeddable.tsx +++ b/src/plugins/visualizations/public/legacy/embeddable/visualize_embeddable.tsx @@ -353,10 +353,6 @@ export class VisualizeEmbeddable ); return true; } - if (this.vis.type.suppressWarnings?.()) { - // if the vis type wishes to supress all warnings, return true so the default logic won't pick it up - return true; - } }); } diff --git a/src/plugins/visualizations/public/vis_types/base_vis_type.ts b/src/plugins/visualizations/public/vis_types/base_vis_type.ts index f3a88245008e3..e7519729eaa03 100644 --- a/src/plugins/visualizations/public/vis_types/base_vis_type.ts +++ b/src/plugins/visualizations/public/vis_types/base_vis_type.ts @@ -43,7 +43,6 @@ export class BaseVisType { public readonly disableCreate; public readonly disableEdit; public readonly requiresSearch; - public readonly suppressWarnings; public readonly hasPartialRows; public readonly hierarchicalData; public readonly setup; @@ -70,7 +69,6 @@ export class BaseVisType { this.icon = opts.icon; this.image = opts.image; this.order = opts.order ?? 0; - this.suppressWarnings = opts.suppressWarnings; this.visConfig = defaultsDeep({}, opts.visConfig, { defaults: {} }); this.editorConfig = defaultsDeep({}, opts.editorConfig, { collections: {} }); this.options = defaultsDeep({}, opts.options, defaultOptions); diff --git a/test/api_integration/apis/dashboards/create_dashboard/index.ts b/test/api_integration/apis/dashboards/create_dashboard/index.ts new file mode 100644 index 0000000000000..c9c2f63dd3b8c --- /dev/null +++ b/test/api_integration/apis/dashboards/create_dashboard/index.ts @@ -0,0 +1,30 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, loadTestFile }: FtrProviderContext) { + const kibanaServer = getService('kibanaServer'); + describe('dashboards - create', () => { + before(async () => { + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); + + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); + loadTestFile(require.resolve('./main')); + loadTestFile(require.resolve('./validation')); + }); +} diff --git a/test/api_integration/apis/dashboards/create_dashboard/main.ts b/test/api_integration/apis/dashboards/create_dashboard/main.ts new file mode 100644 index 0000000000000..3b8b71f827deb --- /dev/null +++ b/test/api_integration/apis/dashboards/create_dashboard/main.ts @@ -0,0 +1,216 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import expect from '@kbn/expect'; +import { PUBLIC_API_PATH } from '@kbn/dashboard-plugin/server'; +import { DEFAULT_IGNORE_PARENT_SETTINGS } from '@kbn/controls-plugin/common'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + describe('main', () => { + it('sets top level default values', async () => { + const title = `foo-${Date.now()}-${Math.random()}`; + + const response = await supertest + .post(PUBLIC_API_PATH) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({ + attributes: { + title, + }, + }); + + expect(response.status).to.be(200); + expect(response.body.item.attributes.kibanaSavedObjectMeta.searchSource).to.eql({}); + expect(response.body.item.attributes.panels).to.eql([]); + expect(response.body.item.attributes.timeRestore).to.be(false); + expect(response.body.item.attributes.options).to.eql({ + hidePanelTitles: false, + useMargins: true, + syncColors: true, + syncTooltips: true, + syncCursor: true, + }); + }); + + it('sets panels default values', async () => { + const title = `foo-${Date.now()}-${Math.random()}`; + + const response = await supertest + .post(PUBLIC_API_PATH) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({ + attributes: { + title, + panels: [ + { + type: 'visualization', + gridData: { + x: 0, + y: 0, + w: 24, + h: 15, + }, + panelConfig: {}, + }, + ], + }, + }); + + expect(response.status).to.be(200); + expect(response.body.item.attributes.panels).to.be.an('array'); + // panel index is a random uuid when not provided + expect(response.body.item.attributes.panels[0].panelIndex).match(/^[0-9a-f-]{36}$/); + expect(response.body.item.attributes.panels[0].panelIndex).to.eql( + response.body.item.attributes.panels[0].gridData.i + ); + }); + + it('sets controls default values', async () => { + const title = `foo-${Date.now()}-${Math.random()}`; + + const response = await supertest + .post(PUBLIC_API_PATH) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({ + attributes: { + title, + controlGroupInput: { + controls: [ + { + type: 'optionsListControl', + order: 0, + width: 'medium', + grow: true, + controlConfig: { + title: 'Origin City', + fieldName: 'OriginCityName', + dataViewId: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', + selectedOptions: [], + enhancements: {}, + }, + }, + ], + }, + }, + }); + + expect(response.status).to.be(200); + // generates a random saved object id + expect(response.body.item.id).match(/^[0-9a-f-]{36}$/); + // saved object stores controls panels as an object, but the API should return as an array + expect(response.body.item.attributes.controlGroupInput.controls).to.be.an('array'); + + expect(response.body.item.attributes.controlGroupInput.ignoreParentSettings).to.eql( + DEFAULT_IGNORE_PARENT_SETTINGS + ); + }); + + it('can create a dashboard with a specific id', async () => { + const title = `foo-${Date.now()}-${Math.random()}`; + const id = `bar-${Date.now()}-${Math.random()}`; + + const response = await supertest + .post(`${PUBLIC_API_PATH}/${id}`) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({ + attributes: { title }, + }); + + expect(response.status).to.be(200); + expect(response.body.item.id).to.be(id); + }); + + it('creates a dashboard with references', async () => { + const title = `foo-${Date.now()}-${Math.random()}`; + + const response = await supertest + .post(PUBLIC_API_PATH) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({ + attributes: { + title, + panels: [ + { + type: 'visualization', + gridData: { + x: 0, + y: 0, + w: 24, + h: 15, + i: 'bizz', + }, + panelConfig: {}, + panelIndex: 'bizz', + panelRefName: 'panel_bizz', + }, + ], + }, + references: [ + { + name: 'bizz:panel_bizz', + type: 'visualization', + id: 'my-saved-object', + }, + ], + }); + + expect(response.status).to.be(200); + expect(response.body.item.attributes.panels).to.be.an('array'); + }); + + // TODO Maybe move this test to x-pack/test/api_integration/dashboards + it('can create a dashboard in a defined space', async () => { + const title = `foo-${Date.now()}-${Math.random()}`; + + const spaceId = 'space-1'; + + const response = await supertest + .post(`/s/${spaceId}${PUBLIC_API_PATH}`) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({ + attributes: { + title, + }, + spaces: [spaceId], + }); + + expect(response.status).to.be(200); + expect(response.body.item.namespaces).to.eql([spaceId]); + }); + + it('return error if provided id already exists', async () => { + const title = `foo-${Date.now()}-${Math.random()}`; + // id is a saved object loaded by the kbn_archiver + const id = 'be3733a0-9efe-11e7-acb3-3dab96693fab'; + + const response = await supertest + .post(`${PUBLIC_API_PATH}/${id}`) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({ + attributes: { + title, + }, + }); + + expect(response.status).to.be(409); + expect(response.body.message).to.be( + 'A dashboard with saved object ID be3733a0-9efe-11e7-acb3-3dab96693fab already exists.' + ); + }); + }); +} diff --git a/test/api_integration/apis/dashboards/create_dashboard/validation.ts b/test/api_integration/apis/dashboards/create_dashboard/validation.ts new file mode 100644 index 0000000000000..c7f0917a7180c --- /dev/null +++ b/test/api_integration/apis/dashboards/create_dashboard/validation.ts @@ -0,0 +1,63 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import expect from '@kbn/expect'; +import { PUBLIC_API_PATH } from '@kbn/dashboard-plugin/server'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + describe('validation', () => { + it('returns error when attributes object is not provided', async () => { + const response = await supertest + .post(PUBLIC_API_PATH) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({}); + expect(response.status).to.be(400); + expect(response.body.statusCode).to.be(400); + expect(response.body.message).to.be( + '[request body.attributes.title]: expected value of type [string] but got [undefined]' + ); + }); + + it('returns error when title is not provided', async () => { + const response = await supertest + .post(PUBLIC_API_PATH) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({ + attributes: {}, + }); + expect(response.status).to.be(400); + expect(response.body.statusCode).to.be(400); + expect(response.body.message).to.be( + '[request body.attributes.title]: expected value of type [string] but got [undefined]' + ); + }); + + it('returns error if panels is not an array', async () => { + const response = await supertest + .post(PUBLIC_API_PATH) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({ + attributes: { + title: 'foo', + panels: {}, + }, + }); + expect(response.status).to.be(400); + expect(response.body.statusCode).to.be(400); + expect(response.body.message).to.be( + '[request body.attributes.panels]: expected value of type [array] but got [Object]' + ); + }); + }); +} diff --git a/test/api_integration/apis/dashboards/delete_dashboard/index.ts b/test/api_integration/apis/dashboards/delete_dashboard/index.ts new file mode 100644 index 0000000000000..41494dfd986d2 --- /dev/null +++ b/test/api_integration/apis/dashboards/delete_dashboard/index.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, loadTestFile }: FtrProviderContext) { + const kibanaServer = getService('kibanaServer'); + describe('dashboards - delete', () => { + before(async () => { + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); + + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); + loadTestFile(require.resolve('./main')); + }); +} diff --git a/test/api_integration/apis/dashboards/delete_dashboard/main.ts b/test/api_integration/apis/dashboards/delete_dashboard/main.ts new file mode 100644 index 0000000000000..19ed2b2e1c051 --- /dev/null +++ b/test/api_integration/apis/dashboards/delete_dashboard/main.ts @@ -0,0 +1,42 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import expect from '@kbn/expect'; +import { PUBLIC_API_PATH } from '@kbn/dashboard-plugin/server'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + describe('main', () => { + it('should return 404 for a non-existent dashboard', async () => { + const response = await supertest + .delete(`${PUBLIC_API_PATH}/non-existent-dashboard`) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send(); + + expect(response.status).to.be(404); + expect(response.body).to.eql({ + statusCode: 404, + error: 'Not Found', + message: 'A dashboard with saved object ID non-existent-dashboard was not found.', + }); + }); + + it('should return 200 if the dashboard is deleted', async () => { + const response = await supertest + .delete(`${PUBLIC_API_PATH}/be3733a0-9efe-11e7-acb3-3dab96693fab`) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send(); + + expect(response.status).to.be(200); + }); + }); +} diff --git a/test/api_integration/apis/dashboards/get_dashboard/index.ts b/test/api_integration/apis/dashboards/get_dashboard/index.ts new file mode 100644 index 0000000000000..82ac6f1903cb7 --- /dev/null +++ b/test/api_integration/apis/dashboards/get_dashboard/index.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, loadTestFile }: FtrProviderContext) { + const kibanaServer = getService('kibanaServer'); + describe('dashboards - get', () => { + before(async () => { + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); + + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); + loadTestFile(require.resolve('./main')); + }); +} diff --git a/test/api_integration/apis/dashboards/get_dashboard/main.ts b/test/api_integration/apis/dashboards/get_dashboard/main.ts new file mode 100644 index 0000000000000..b6585c0c4f48a --- /dev/null +++ b/test/api_integration/apis/dashboards/get_dashboard/main.ts @@ -0,0 +1,34 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import expect from '@kbn/expect'; +import { PUBLIC_API_PATH } from '@kbn/dashboard-plugin/server'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + describe('main', () => { + it('should return 200 with an existing dashboard', async () => { + const response = await supertest + .get(`${PUBLIC_API_PATH}/be3733a0-9efe-11e7-acb3-3dab96693fab`) + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send(); + + expect(response.status).to.be(200); + + expect(response.body.item.id).to.be('be3733a0-9efe-11e7-acb3-3dab96693fab'); + expect(response.body.item.type).to.be('dashboard'); + expect(response.body.item.attributes.title).to.be('Requests'); + + // Does not return unsupported options from the saved object + expect(response.body.item.attributes.options).to.not.have.keys(['darkTheme']); + expect(response.body.item.attributes.refreshInterval).to.not.have.keys(['display']); + }); + }); +} diff --git a/test/api_integration/apis/dashboards/index.ts b/test/api_integration/apis/dashboards/index.ts new file mode 100644 index 0000000000000..f844c02168922 --- /dev/null +++ b/test/api_integration/apis/dashboards/index.ts @@ -0,0 +1,20 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('dashboards', () => { + loadTestFile(require.resolve('./create_dashboard')); + loadTestFile(require.resolve('./delete_dashboard')); + loadTestFile(require.resolve('./get_dashboard')); + loadTestFile(require.resolve('./update_dashboard')); + loadTestFile(require.resolve('./list_dashboards')); + }); +} diff --git a/test/api_integration/apis/dashboards/list_dashboards/index.ts b/test/api_integration/apis/dashboards/list_dashboards/index.ts new file mode 100644 index 0000000000000..10f77ad3fee5a --- /dev/null +++ b/test/api_integration/apis/dashboards/list_dashboards/index.ts @@ -0,0 +1,47 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, loadTestFile }: FtrProviderContext) { + const kibanaServer = getService('kibanaServer'); + const supertest = getService('supertest'); + describe('dashboards - list', () => { + const createManyDashboards = async (count: number) => { + const fileChunks: string[] = []; + for (let i = 0; i < count; i++) { + const id = `test-dashboard-${i}`; + fileChunks.push( + JSON.stringify({ + type: 'dashboard', + id, + attributes: { + title: `My dashboard (${i})`, + kibanaSavedObjectMeta: { searchSourceJSON: '{}' }, + }, + references: [], + }) + ); + } + + await supertest + .post(`/api/saved_objects/_import`) + .attach('file', Buffer.from(fileChunks.join('\n'), 'utf8'), 'export.ndjson') + .expect(200); + }; + before(async () => { + await createManyDashboards(100); + }); + + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + }); + loadTestFile(require.resolve('./main')); + }); +} diff --git a/test/api_integration/apis/dashboards/list_dashboards/main.ts b/test/api_integration/apis/dashboards/list_dashboards/main.ts new file mode 100644 index 0000000000000..c0ef1059169ef --- /dev/null +++ b/test/api_integration/apis/dashboards/list_dashboards/main.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import expect from '@kbn/expect'; +import { PUBLIC_API_PATH } from '@kbn/dashboard-plugin/server'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + describe('main', () => { + it('should retrieve a paginated list of dashboards', async () => { + const response = await supertest + .get(`${PUBLIC_API_PATH}`) + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send(); + + expect(response.status).to.be(200); + expect(response.body.total).to.be(100); + expect(response.body.items[0].id).to.be('test-dashboard-0'); + expect(response.body.items.length).to.be(20); + }); + + it('should allow users to set a per page limit', async () => { + const response = await supertest + .get(`${PUBLIC_API_PATH}?perPage=10`) + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send(); + + expect(response.status).to.be(200); + expect(response.body.total).to.be(100); + expect(response.body.items.length).to.be(10); + }); + + it('should allow users to paginate through the list of dashboards', async () => { + const response = await supertest + .get(`${PUBLIC_API_PATH}?page=5&perPage=10`) + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send(); + + expect(response.status).to.be(200); + expect(response.body.total).to.be(100); + expect(response.body.items.length).to.be(10); + expect(response.body.items[0].id).to.be('test-dashboard-40'); + }); + }); +} diff --git a/test/api_integration/apis/dashboards/update_dashboard/index.ts b/test/api_integration/apis/dashboards/update_dashboard/index.ts new file mode 100644 index 0000000000000..c2a8d7d16cb27 --- /dev/null +++ b/test/api_integration/apis/dashboards/update_dashboard/index.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, loadTestFile }: FtrProviderContext) { + const kibanaServer = getService('kibanaServer'); + describe('dashboards - update', () => { + before(async () => { + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); + + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); + loadTestFile(require.resolve('./main')); + loadTestFile(require.resolve('./validation')); + }); +} diff --git a/test/api_integration/apis/dashboards/update_dashboard/main.ts b/test/api_integration/apis/dashboards/update_dashboard/main.ts new file mode 100644 index 0000000000000..18a7d5ca2d3fe --- /dev/null +++ b/test/api_integration/apis/dashboards/update_dashboard/main.ts @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import expect from '@kbn/expect'; +import { PUBLIC_API_PATH } from '@kbn/dashboard-plugin/server'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + describe('main', () => { + it('should return 201 with an updated dashboard', async () => { + const response = await supertest + .put(`${PUBLIC_API_PATH}/be3733a0-9efe-11e7-acb3-3dab96693fab`) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({ + attributes: { + title: 'Refresh Requests (Updated)', + options: { useMargins: false }, + panels: [ + { + type: 'visualization', + gridData: { x: 0, y: 0, w: 48, h: 60, i: '1' }, + panelIndex: '1', + panelRefName: 'panel_1', + version: '7.3.0', + }, + ], + timeFrom: 'Wed Sep 16 2015 22:52:17 GMT-0700', + timeRestore: true, + timeTo: 'Fri Sep 18 2015 12:24:38 GMT-0700', + }, + references: [ + { + id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', + name: '1:panel_1', + type: 'visualization', + }, + ], + }); + + expect(response.status).to.be(201); + + expect(response.body.item.id).to.be('be3733a0-9efe-11e7-acb3-3dab96693fab'); + expect(response.body.item.type).to.be('dashboard'); + expect(response.body.item.attributes.title).to.be('Refresh Requests (Updated)'); + }); + + it('should return 404 when updating a non-existent dashboard', async () => { + const response = await supertest + .put(`${PUBLIC_API_PATH}/not-an-id`) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({ + attributes: { + title: 'Some other dashboard (updated)', + }, + }); + + expect(response.status).to.be(404); + expect(response.body).to.eql({ + statusCode: 404, + error: 'Not Found', + message: 'A dashboard with saved object ID not-an-id was not found.', + }); + }); + }); +} diff --git a/test/api_integration/apis/dashboards/update_dashboard/validation.ts b/test/api_integration/apis/dashboards/update_dashboard/validation.ts new file mode 100644 index 0000000000000..4a7a069e24617 --- /dev/null +++ b/test/api_integration/apis/dashboards/update_dashboard/validation.ts @@ -0,0 +1,63 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import expect from '@kbn/expect'; +import { PUBLIC_API_PATH } from '@kbn/dashboard-plugin/server'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + describe('validation', () => { + it('returns error when attributes object is not provided', async () => { + const response = await supertest + .put(`${PUBLIC_API_PATH}/be3733a0-9efe-11e7-acb3-3dab96693fab`) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({}); + expect(response.status).to.be(400); + expect(response.body.statusCode).to.be(400); + expect(response.body.message).to.be( + '[request body.attributes.title]: expected value of type [string] but got [undefined]' + ); + }); + + it('returns error when title is not provided', async () => { + const response = await supertest + .put(`${PUBLIC_API_PATH}/be3733a0-9efe-11e7-acb3-3dab96693fab`) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({ + attributes: {}, + }); + expect(response.status).to.be(400); + expect(response.body.statusCode).to.be(400); + expect(response.body.message).to.be( + '[request body.attributes.title]: expected value of type [string] but got [undefined]' + ); + }); + + it('returns error if panels is not an array', async () => { + const response = await supertest + .put(`${PUBLIC_API_PATH}/be3733a0-9efe-11e7-acb3-3dab96693fab`) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({ + attributes: { + title: 'foo', + panels: {}, + }, + }); + expect(response.status).to.be(400); + expect(response.body.statusCode).to.be(400); + expect(response.body.message).to.be( + '[request body.attributes.panels]: expected value of type [array] but got [Object]' + ); + }); + }); +} diff --git a/test/api_integration/apis/index.ts b/test/api_integration/apis/index.ts index bbd7c3abf8649..af1cbf2464fa9 100644 --- a/test/api_integration/apis/index.ts +++ b/test/api_integration/apis/index.ts @@ -14,6 +14,7 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./console')); loadTestFile(require.resolve('./core')); loadTestFile(require.resolve('./custom_integration')); + loadTestFile(require.resolve('./dashboards')); loadTestFile(require.resolve('./general')); loadTestFile(require.resolve('./home')); loadTestFile(require.resolve('./data_view_field_editor')); diff --git a/test/common/plugins/otel_metrics/kibana.jsonc b/test/common/plugins/otel_metrics/kibana.jsonc index e64546f446052..dea9f97260c7a 100644 --- a/test/common/plugins/otel_metrics/kibana.jsonc +++ b/test/common/plugins/otel_metrics/kibana.jsonc @@ -2,6 +2,8 @@ "type": "plugin", "id": "@kbn/open-telemetry-instrumented-plugin", "owner": "@elastic/obs-ux-infra_services-team", + "group": "platform", + "visibility": "shared", "plugin": { "id": "openTelemetryInstrumentedPlugin", "server": true, diff --git a/test/functional/apps/dashboard/group3/dashboard_state.ts b/test/functional/apps/dashboard/group3/dashboard_state.ts index 40022c155f456..9822c2ce361a1 100644 --- a/test/functional/apps/dashboard/group3/dashboard_state.ts +++ b/test/functional/apps/dashboard/group3/dashboard_state.ts @@ -309,12 +309,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { panels: (appState.panels ?? []).map((panel) => { return { ...panel, - embeddableConfig: { - ...(panel.embeddableConfig ?? {}), + panelConfig: { + ...(panel.panelConfig ?? {}), vis: { - ...((panel.embeddableConfig?.vis as object) ?? {}), + ...((panel.panelConfig?.vis as object) ?? {}), colors: { - ...((panel.embeddableConfig?.vis as { colors: object })?.colors ?? {}), + ...((panel.panelConfig?.vis as { colors: object })?.colors ?? {}), ['80000']: 'FFFFFF', }, }, @@ -353,10 +353,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { panels: (appState.panels ?? []).map((panel) => { return { ...panel, - embeddableConfig: { - ...(panel.embeddableConfig ?? {}), + panelConfig: { + ...(panel.panelConfig ?? {}), vis: { - ...((panel.embeddableConfig?.vis as object) ?? {}), + ...((panel.panelConfig?.vis as object) ?? {}), colors: {}, }, }, diff --git a/test/functional/apps/discover/group6/_sidebar_field_stats.ts b/test/functional/apps/discover/group6/_sidebar_field_stats.ts index 3cfa2c1da20af..d9fb2798c0f2c 100644 --- a/test/functional/apps/discover/group6/_sidebar_field_stats.ts +++ b/test/functional/apps/discover/group6/_sidebar_field_stats.ts @@ -155,77 +155,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await unifiedFieldList.waitUntilSidebarHasLoaded(); }); - it('should show top values popover for numeric field', async () => { + it('should not show top values popover for numeric field', async () => { await unifiedFieldList.clickFieldListItem('bytes'); - await testSubjects.existOrFail('dscFieldStats-topValues'); - expect(await testSubjects.getVisibleText('dscFieldStats-title')).to.be('Top values'); - const topValuesRows = await testSubjects.findAll('dscFieldStats-topValues-bucket'); - expect(topValuesRows.length).to.eql(10); - expect(await testSubjects.getVisibleText('dscFieldStats-statsFooter')).to.contain( - '42 sample values' - ); - - await unifiedFieldList.clickFieldListPlusFilter('bytes', '0'); - const editorValue = await monacoEditor.getCodeEditorValue(); - expect(editorValue).to.eql( - `from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| WHERE \`bytes\`==0` - ); + await testSubjects.missingOrFail('dscFieldStats-statsFooter'); await unifiedFieldList.closeFieldPopover(); }); - it('should show a top values popover for a keyword field', async () => { + it('should not show a top values popover for a keyword field', async () => { await unifiedFieldList.clickFieldListItem('extension.raw'); - await testSubjects.existOrFail('dscFieldStats-topValues'); - expect(await testSubjects.getVisibleText('dscFieldStats-title')).to.be('Top values'); - const topValuesRows = await testSubjects.findAll('dscFieldStats-topValues-bucket'); - expect(topValuesRows.length).to.eql(5); - await testSubjects.missingOrFail('unifiedFieldStats-buttonGroup'); - await testSubjects.missingOrFail('unifiedFieldStats-histogram'); - expect(await testSubjects.getVisibleText('dscFieldStats-statsFooter')).to.contain( - '500 sample values' - ); - - await unifiedFieldList.clickFieldListPlusFilter('extension.raw', 'css'); - const editorValue = await monacoEditor.getCodeEditorValue(); - expect(editorValue).to.eql( - `from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| WHERE \`extension.raw\`=="css"` - ); - - await unifiedFieldList.closeFieldPopover(); - }); - - it('should show a top values popover for an ip field', async () => { - await unifiedFieldList.clickFieldListItem('clientip'); - await testSubjects.existOrFail('dscFieldStats-topValues'); - expect(await testSubjects.getVisibleText('dscFieldStats-title')).to.be('Top values'); - const topValuesRows = await testSubjects.findAll('dscFieldStats-topValues-bucket'); - expect(topValuesRows.length).to.eql(10); - await testSubjects.missingOrFail('unifiedFieldStats-buttonGroup'); - await testSubjects.missingOrFail('unifiedFieldStats-histogram'); - expect(await testSubjects.getVisibleText('dscFieldStats-statsFooter')).to.contain( - '32 sample values' - ); - - await unifiedFieldList.clickFieldListPlusFilter('clientip', '216.126.255.31'); - const editorValue = await monacoEditor.getCodeEditorValue(); - expect(editorValue).to.eql( - `from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| WHERE \`clientip\`::string=="216.126.255.31"` - ); - - await unifiedFieldList.closeFieldPopover(); - }); - - it('should show a top values popover for _index field', async () => { - await unifiedFieldList.clickFieldListItem('_index'); - await testSubjects.existOrFail('dscFieldStats-topValues'); - expect(await testSubjects.getVisibleText('dscFieldStats-title')).to.be('Top values'); - const topValuesRows = await testSubjects.findAll('dscFieldStats-topValues-bucket'); - expect(topValuesRows.length).to.eql(1); - await testSubjects.missingOrFail('unifiedFieldStats-buttonGroup'); - await testSubjects.missingOrFail('unifiedFieldStats-histogram'); - expect(await testSubjects.getVisibleText('dscFieldStats-statsFooter')).to.contain( - '500 sample values' - ); + await testSubjects.missingOrFail('dscFieldStats-statsFooter'); await unifiedFieldList.closeFieldPopover(); }); @@ -240,102 +178,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await unifiedFieldList.closeFieldPopover(); }); - it('should show examples for geo points field', async () => { - await unifiedFieldList.clickFieldListItem('geo.coordinates'); - await testSubjects.existOrFail('dscFieldStats-topValues'); - expect(await testSubjects.getVisibleText('dscFieldStats-title')).to.be('Examples'); - const topValuesRows = await testSubjects.findAll('dscFieldStats-topValues-bucket'); - expect(topValuesRows.length).to.eql(11); - await testSubjects.missingOrFail('unifiedFieldStats-buttonGroup'); - await testSubjects.missingOrFail('unifiedFieldStats-histogram'); - expect(await testSubjects.getVisibleText('dscFieldStats-statsFooter')).to.contain( - '100 sample records' - ); - await unifiedFieldList.closeFieldPopover(); - }); - - it('should show examples for text field', async () => { + it('should not show examples for text field', async () => { await unifiedFieldList.clickFieldListItem('extension'); - await testSubjects.existOrFail('dscFieldStats-topValues'); - expect(await testSubjects.getVisibleText('dscFieldStats-title')).to.be('Examples'); - const topValuesRows = await testSubjects.findAll('dscFieldStats-topValues-bucket'); - expect(topValuesRows.length).to.eql(5); - await testSubjects.missingOrFail('unifiedFieldStats-buttonGroup'); - await testSubjects.missingOrFail('unifiedFieldStats-histogram'); - expect(await testSubjects.getVisibleText('dscFieldStats-statsFooter')).to.contain( - '100 sample records' - ); - - await unifiedFieldList.clickFieldListPlusFilter('extension', 'css'); - const editorValue = await monacoEditor.getCodeEditorValue(); - expect(editorValue).to.eql( - `from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| WHERE \`extension\`=="css"` - ); - - await unifiedFieldList.closeFieldPopover(); - }); - - it('should show examples for _id field', async () => { - await unifiedFieldList.clickFieldListItem('_id'); - await testSubjects.existOrFail('dscFieldStats-topValues'); - expect(await testSubjects.getVisibleText('dscFieldStats-title')).to.be('Examples'); - const topValuesRows = await testSubjects.findAll('dscFieldStats-topValues-bucket'); - expect(topValuesRows.length).to.eql(11); - await testSubjects.missingOrFail('unifiedFieldStats-buttonGroup'); - await testSubjects.missingOrFail('unifiedFieldStats-histogram'); - expect(await testSubjects.getVisibleText('dscFieldStats-statsFooter')).to.contain( - '100 sample records' - ); - await unifiedFieldList.closeFieldPopover(); - }); - - it('should show a top values popover for a more complex query', async () => { - const testQuery = `from logstash-* | sort @timestamp desc | limit 50 | stats avg(bytes) by geo.dest | limit 3`; - await monacoEditor.setCodeEditorValue(testQuery); - await testSubjects.click('querySubmitButton'); - await header.waitUntilLoadingHasFinished(); - await unifiedFieldList.waitUntilSidebarHasLoaded(); - - await unifiedFieldList.clickFieldListItem('avg(bytes)'); - await testSubjects.existOrFail('dscFieldStats-topValues'); - expect(await testSubjects.getVisibleText('dscFieldStats-title')).to.be('Top values'); - const topValuesRows = await testSubjects.findAll('dscFieldStats-topValues-bucket'); - expect(topValuesRows.length).to.eql(3); - expect(await testSubjects.getVisibleText('dscFieldStats-statsFooter')).to.contain( - '3 sample values' - ); - - await unifiedFieldList.clickFieldListPlusFilter('avg(bytes)', '5453'); - const editorValue = await monacoEditor.getCodeEditorValue(); - expect(editorValue).to.eql( - `from logstash-* | sort @timestamp desc | limit 50 | stats avg(bytes) by geo.dest | limit 3\n| WHERE \`avg(bytes)\`==5453` - ); - - await unifiedFieldList.closeFieldPopover(); - }); - - it('should show a top values popover for a boolean field', async () => { - const testQuery = `row enabled = true`; - await monacoEditor.setCodeEditorValue(testQuery); - await testSubjects.click('querySubmitButton'); - await header.waitUntilLoadingHasFinished(); - await unifiedFieldList.waitUntilSidebarHasLoaded(); - - await unifiedFieldList.clickFieldListItem('enabled'); - await testSubjects.existOrFail('dscFieldStats-topValues'); - expect(await testSubjects.getVisibleText('dscFieldStats-title')).to.be('Top values'); - const topValuesRows = await testSubjects.findAll('dscFieldStats-topValues-bucket'); - expect(topValuesRows.length).to.eql(1); - expect(await unifiedFieldList.getFieldStatsTopValueBucketsVisibleText()).to.be( - 'true\n100%' - ); - expect(await testSubjects.getVisibleText('dscFieldStats-statsFooter')).to.contain( - '1 sample value' - ); - - await unifiedFieldList.clickFieldListMinusFilter('enabled', 'true'); - const editorValue = await monacoEditor.getCodeEditorValue(); - expect(editorValue).to.eql(`row enabled = true\n| WHERE \`enabled\`!=true`); + await testSubjects.missingOrFail('dscFieldStats-statsFooter'); await unifiedFieldList.closeFieldPopover(); }); }); diff --git a/x-pack/examples/exploratory_view_example/kibana.jsonc b/x-pack/examples/exploratory_view_example/kibana.jsonc index 6cf8fa64983ac..cf077336b0f90 100644 --- a/x-pack/examples/exploratory_view_example/kibana.jsonc +++ b/x-pack/examples/exploratory_view_example/kibana.jsonc @@ -2,6 +2,8 @@ "type": "plugin", "id": "@kbn/exploratory-view-example-plugin", "owner": "@elastic/obs-ux-infra_services-team", + "group": "observability", + "visibility": "private", "plugin": { "id": "exploratoryViewExample", "server": false, diff --git a/x-pack/examples/screenshotting_example/kibana.jsonc b/x-pack/examples/screenshotting_example/kibana.jsonc index 127706ad42e3d..3519bc91caa66 100644 --- a/x-pack/examples/screenshotting_example/kibana.jsonc +++ b/x-pack/examples/screenshotting_example/kibana.jsonc @@ -2,6 +2,10 @@ "type": "plugin", "id": "@kbn/screenshotting-example-plugin", "owner": "@elastic/appex-sharedux", + // This plugin is not meant to be referenced or imported + "visibility": "private", + // If cloned / used as an inspiration, please bear in mind that your plugin might belong to a specific solution group + "group": "platform", "description": "An example integration with the screenshotting plugin.", "plugin": { "id": "screenshottingExample", diff --git a/x-pack/examples/ui_actions_enhanced_examples/kibana.jsonc b/x-pack/examples/ui_actions_enhanced_examples/kibana.jsonc index 1da3e4f182875..25211ae2063bd 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/kibana.jsonc +++ b/x-pack/examples/ui_actions_enhanced_examples/kibana.jsonc @@ -2,6 +2,10 @@ "type": "plugin", "id": "@kbn/ui-actions-enhanced-examples-plugin", "owner": "@elastic/appex-sharedux", + // This plugin is not meant to be referenced or imported + "visibility": "private", + // If cloned / used as an inspiration, please bear in mind that your plugin might belong to a specific solution group + "group": "platform", "plugin": { "id": "uiActionsEnhancedExamples", "server": false, diff --git a/x-pack/packages/kbn-ai-assistant/kibana.jsonc b/x-pack/packages/kbn-ai-assistant/kibana.jsonc index 4cddd90431e39..625dedc6c99f4 100644 --- a/x-pack/packages/kbn-ai-assistant/kibana.jsonc +++ b/x-pack/packages/kbn-ai-assistant/kibana.jsonc @@ -1,5 +1,7 @@ { "id": "@kbn/ai-assistant", "owner": "@elastic/search-kibana", - "type": "shared-browser" + "type": "shared-browser", + "group": "platform", + "visibility": "shared" } diff --git a/x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.test.tsx b/x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.test.tsx index 337c11419209e..6a304430103ab 100644 --- a/x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.test.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.test.tsx @@ -18,7 +18,7 @@ const mockChatService = createMockChatService(); let items: ReturnType; -function Providers({ children }: { children: React.ReactElement }) { +function Providers({ children }: { children: React.ReactNode }) { return ( > = ({ children }) => ( }, ]} setSelectedIlmPhaseOptions={jest.fn()} + defaultStartTime="now-7d" + defaultEndTime="now" > {children} diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_context/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_context/index.tsx index 762efef424a10..876ff528e75ff 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_context/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_context/index.tsx @@ -41,6 +41,8 @@ export interface DataQualityProviderProps { ilmPhases: string[]; selectedIlmPhaseOptions: EuiComboBoxOptionOption[]; setSelectedIlmPhaseOptions: (options: EuiComboBoxOptionOption[]) => void; + defaultStartTime: string; + defaultEndTime: string; } const DataQualityContext = React.createContext(undefined); @@ -67,6 +69,8 @@ export const DataQualityProvider: React.FC { const value = useMemo( () => ({ @@ -90,6 +94,8 @@ export const DataQualityProvider: React.FC {children} @@ -159,6 +161,8 @@ describe('useIlmExplain', () => { }, ]} setSelectedIlmPhaseOptions={jest.fn()} + defaultStartTime={'now-7d'} + defaultEndTime={'now'} > {children} diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/hooks/use_stats/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/hooks/use_stats/index.test.tsx index 061bbb5aa6824..ae4ee9a7bd2c4 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/hooks/use_stats/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/hooks/use_stats/index.test.tsx @@ -69,6 +69,8 @@ const ContextWrapper: FC> = ({ children }) => ( }, ]} setSelectedIlmPhaseOptions={jest.fn()} + defaultStartTime={'now-7d'} + defaultEndTime={'now'} > {children} @@ -119,6 +121,8 @@ const ContextWrapperILMNotAvailable: FC> = ({ childre }, ]} setSelectedIlmPhaseOptions={jest.fn()} + defaultStartTime={'now-7d'} + defaultEndTime={'now'} > {children} diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_all/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_all/index.test.tsx index f303d614bce00..cd9ddec6dffb7 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_all/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_all/index.test.tsx @@ -403,10 +403,9 @@ describe('CheckAll', () => { // simulate the wall clock advancing for (let i = 0; i < totalIndexNames + 1; i++) { - act(() => { + await act(async () => { jest.advanceTimersByTime(1000 * 10); }); - await waitFor(() => {}); } }); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/hooks/use_stored_pattern_results/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/hooks/use_stored_pattern_results/index.test.tsx new file mode 100644 index 0000000000000..5f90890eea693 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/hooks/use_stored_pattern_results/index.test.tsx @@ -0,0 +1,186 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; + +import { getHistoricalResultStub } from '../../../../stub/get_historical_result_stub'; +import { useStoredPatternResults } from '.'; + +const startTime = 'now-7d'; +const endTime = 'now'; +const isILMAvailable = true; + +describe('useStoredPatternResults', () => { + const httpFetch = jest.fn(); + const mockToasts = notificationServiceMock.createStartContract().toasts; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('when patterns are empty', () => { + it('should return an empty array and not call getStorageResults', () => { + const { result } = renderHook(() => + useStoredPatternResults({ + patterns: [], + toasts: mockToasts, + httpFetch, + isILMAvailable, + startTime, + endTime, + }) + ); + + expect(result.current).toEqual([]); + expect(httpFetch).not.toHaveBeenCalled(); + }); + }); + + describe('when patterns are provided', () => { + it('should fetch and return stored pattern results correctly', async () => { + const patterns = ['pattern1-*', 'pattern2-*']; + + httpFetch.mockImplementation((path: string) => { + if (path === '/internal/ecs_data_quality_dashboard/results_latest/pattern1-*') { + return Promise.resolve([getHistoricalResultStub('pattern1-index1')]); + } + + if (path === '/internal/ecs_data_quality_dashboard/results_latest/pattern2-*') { + return Promise.resolve([getHistoricalResultStub('pattern2-index1')]); + } + + return Promise.reject(new Error('Invalid path')); + }); + + const { result, waitFor } = renderHook(() => + useStoredPatternResults({ + patterns, + toasts: mockToasts, + httpFetch, + isILMAvailable, + startTime, + endTime, + }) + ); + + await waitFor(() => result.current.length > 0); + + expect(httpFetch).toHaveBeenCalledTimes(2); + + expect(httpFetch).toHaveBeenCalledWith( + '/internal/ecs_data_quality_dashboard/results_latest/pattern1-*', + { + method: 'GET', + signal: expect.any(AbortSignal), + version: '1', + } + ); + expect(httpFetch).toHaveBeenCalledWith( + '/internal/ecs_data_quality_dashboard/results_latest/pattern2-*', + { + method: 'GET', + signal: expect.any(AbortSignal), + version: '1', + } + ); + + expect(result.current).toEqual([ + { + pattern: 'pattern1-*', + results: { + 'pattern1-index1': { + docsCount: expect.any(Number), + error: null, + ilmPhase: expect.any(String), + incompatible: expect.any(Number), + indexName: 'pattern1-index1', + pattern: 'pattern1-*', + markdownComments: expect.any(Array), + sameFamily: expect.any(Number), + checkedAt: expect.any(Number), + }, + }, + }, + { + pattern: 'pattern2-*', + results: { + 'pattern2-index1': { + docsCount: expect.any(Number), + error: null, + ilmPhase: expect.any(String), + incompatible: expect.any(Number), + indexName: 'pattern2-index1', + pattern: 'pattern2-*', + markdownComments: expect.any(Array), + sameFamily: expect.any(Number), + checkedAt: expect.any(Number), + }, + }, + }, + ]); + }); + + describe('when isILMAvailable is false', () => { + it('should call getStorageResults with startDate and endDate', async () => { + const patterns = ['pattern1-*', 'pattern2-*']; + + httpFetch.mockImplementation((path: string) => { + if (path === '/internal/ecs_data_quality_dashboard/results_latest/pattern1-*') { + return Promise.resolve([getHistoricalResultStub('pattern1-index1')]); + } + + if (path === '/internal/ecs_data_quality_dashboard/results_latest/pattern2-*') { + return Promise.resolve([getHistoricalResultStub('pattern2-index1')]); + } + + return Promise.reject(new Error('Invalid path')); + }); + + const { result, waitFor } = renderHook(() => + useStoredPatternResults({ + patterns, + toasts: mockToasts, + httpFetch, + isILMAvailable: false, + startTime, + endTime, + }) + ); + + await waitFor(() => result.current.length > 0); + + expect(httpFetch).toHaveBeenCalledTimes(2); + + expect(httpFetch).toHaveBeenCalledWith( + '/internal/ecs_data_quality_dashboard/results_latest/pattern1-*', + { + method: 'GET', + signal: expect.any(AbortSignal), + version: '1', + query: { + startDate: startTime, + endDate: endTime, + }, + } + ); + expect(httpFetch).toHaveBeenCalledWith( + '/internal/ecs_data_quality_dashboard/results_latest/pattern2-*', + { + method: 'GET', + signal: expect.any(AbortSignal), + version: '1', + query: { + startDate: startTime, + endDate: endTime, + }, + } + ); + }); + }); + }); +}); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/hooks/use_stored_pattern_results/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/hooks/use_stored_pattern_results/index.tsx new file mode 100644 index 0000000000000..b92b36218c07a --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/hooks/use_stored_pattern_results/index.tsx @@ -0,0 +1,86 @@ +/* + * 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 { useEffect, useState } from 'react'; +import { IToasts } from '@kbn/core-notifications-browser'; +import { HttpHandler } from '@kbn/core-http-browser'; +import { isEmpty } from 'lodash/fp'; + +import { DataQualityCheckResult } from '../../../../types'; +import { + GetStorageResultsOpts, + formatResultFromStorage, + getStorageResults, +} from '../../utils/storage'; + +export interface UseStoredPatternResultsOpts { + patterns: string[]; + toasts: IToasts; + httpFetch: HttpHandler; + isILMAvailable: boolean; + startTime: string; + endTime: string; +} + +export type UseStoredPatternResultsReturnValue = Array<{ + pattern: string; + results: Record; +}>; + +export const useStoredPatternResults = ({ + patterns, + toasts, + httpFetch, + isILMAvailable, + startTime, + endTime, +}: UseStoredPatternResultsOpts): UseStoredPatternResultsReturnValue => { + const [storedPatternResults, setStoredPatternResults] = useState< + Array<{ pattern: string; results: Record }> + >([]); + + useEffect(() => { + if (isEmpty(patterns)) { + return; + } + + const abortController = new AbortController(); + const fetchStoredPatternResults = async () => { + const requests = patterns.map(async (pattern) => { + const getStorageResultsOpts: GetStorageResultsOpts = { + pattern, + httpFetch, + abortController, + toasts, + }; + + if (!isILMAvailable) { + getStorageResultsOpts.startTime = startTime; + getStorageResultsOpts.endTime = endTime; + } + + return getStorageResults(getStorageResultsOpts).then((results) => ({ + pattern, + results: Object.fromEntries( + results.map((storageResult) => [ + storageResult.indexName, + formatResultFromStorage({ storageResult, pattern }), + ]) + ), + })); + }); + + const patternResults = await Promise.all(requests); + if (patternResults?.length) { + setStoredPatternResults(patternResults); + } + }; + + fetchStoredPatternResults(); + }, [endTime, httpFetch, isILMAvailable, patterns, startTime, toasts]); + + return storedPatternResults; +}; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/index.test.tsx new file mode 100644 index 0000000000000..7dc74731d66dd --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/index.test.tsx @@ -0,0 +1,712 @@ +/* + * 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. + */ + +// fixing timezone for Date +// so when tests are run in different timezones, the results are consistent +process.env.TZ = 'UTC'; + +import { renderHook, act } from '@testing-library/react-hooks'; +import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; + +import type { TelemetryEvents } from '../../types'; +import { useStoredPatternResults } from './hooks/use_stored_pattern_results'; +import { mockPartitionedFieldMetadata } from '../../mock/partitioned_field_metadata/mock_partitioned_field_metadata'; +import { useResultsRollup } from '.'; +import { getPatternRollupStub } from '../../stub/get_pattern_rollup_stub'; +import { formatBytes, formatNumber } from '../../mock/test_providers/utils/format'; + +jest.mock('./hooks/use_stored_pattern_results', () => ({ + ...jest.requireActual('./hooks/use_stored_pattern_results'), + useStoredPatternResults: jest.fn().mockReturnValue([]), +})); + +describe('useResultsRollup', () => { + const httpFetch = jest.fn(); + const toasts = notificationServiceMock.createStartContract().toasts; + + const mockTelemetryEvents: TelemetryEvents = { + reportDataQualityIndexChecked: jest.fn(), + reportDataQualityCheckAllCompleted: jest.fn(), + }; + + const patterns = ['auditbeat-*', 'packetbeat-*']; + const isILMAvailable = true; + const startTime = 'now-7d'; + const endTime = 'now'; + + const useStoredPatternResultsMock = useStoredPatternResults as jest.Mock; + + beforeEach(() => { + jest.clearAllMocks(); + useStoredPatternResultsMock.mockReturnValue([]); + }); + + describe('initialization', () => { + it('should initialize with default values', () => { + const { result } = renderHook(() => + useResultsRollup({ + httpFetch, + toasts, + patterns, + isILMAvailable, + telemetryEvents: mockTelemetryEvents, + startTime, + endTime, + }) + ); + + expect(result.current.patternIndexNames).toEqual({}); + expect(result.current.patternRollups).toEqual({}); + expect(result.current.totalDocsCount).toBe(0); + expect(result.current.totalIncompatible).toBeUndefined(); + expect(result.current.totalIndices).toBe(0); + expect(result.current.totalIndicesChecked).toBe(0); + expect(result.current.totalSameFamily).toBeUndefined(); + expect(result.current.totalSizeInBytes).toBe(0); + }); + + it('should fetch stored pattern results and update patternRollups from it', () => { + const mockStoredResults = [ + { + pattern: 'auditbeat-*', + results: { + 'auditbeat-7.11.0-2021.01.01': { + indexName: 'auditbeat-7.11.0-2021.01.01', + pattern: 'auditbeat-*', + docsCount: 500, + incompatible: 0, + error: null, + ilmPhase: 'hot', + sameFamily: 0, + markdownComments: [], + checkedAt: Date.now(), + }, + }, + }, + ]; + + useStoredPatternResultsMock.mockReturnValue(mockStoredResults); + + const { result } = renderHook(() => + useResultsRollup({ + httpFetch, + toasts, + patterns: ['auditbeat-*'], + isILMAvailable, + telemetryEvents: mockTelemetryEvents, + startTime, + endTime, + }) + ); + + expect(useStoredPatternResultsMock).toHaveBeenCalledWith({ + patterns: ['auditbeat-*'], + toasts, + httpFetch, + isILMAvailable, + startTime, + endTime, + }); + + expect(result.current.patternRollups).toEqual({ + 'auditbeat-*': { + pattern: 'auditbeat-*', + results: { + 'auditbeat-7.11.0-2021.01.01': expect.any(Object), + }, + }, + }); + }); + }); + + describe('updatePatternIndexNames', () => { + it('should update pattern index names', () => { + const { result } = renderHook(() => + useResultsRollup({ + httpFetch, + toasts, + patterns, + isILMAvailable, + telemetryEvents: mockTelemetryEvents, + startTime, + endTime, + }) + ); + + act(() => { + result.current.updatePatternIndexNames({ + pattern: 'packetbeat-*', + indexNames: ['packetbeat-7.10.0-2021.01.01'], + }); + }); + + expect(result.current.patternIndexNames).toEqual({ + 'packetbeat-*': ['packetbeat-7.10.0-2021.01.01'], + }); + }); + }); + + describe('updatePatternRollup', () => { + it('should update pattern rollup when called', () => { + const { result } = renderHook(() => + useResultsRollup({ + httpFetch, + toasts, + patterns, + isILMAvailable, + telemetryEvents: mockTelemetryEvents, + startTime, + endTime, + }) + ); + + const patternRollup = getPatternRollupStub('packetbeat-*', 1); + + expect(result.current.patternRollups).toEqual({}); + + act(() => { + result.current.updatePatternRollup(patternRollup); + }); + + expect(result.current.patternRollups).toEqual({ + 'packetbeat-*': patternRollup, + }); + }); + }); + + describe('onCheckCompleted', () => { + describe('when invoked with successful check data', () => { + beforeEach(() => { + jest.useFakeTimers(); + jest.setSystemTime(new Date('2021-10-07T00:00:00Z').getTime()); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('should update patternRollup with said data, report to telemetry and persist it in storage', () => { + const { result } = renderHook(() => + useResultsRollup({ + httpFetch, + toasts, + patterns, + isILMAvailable, + telemetryEvents: mockTelemetryEvents, + startTime, + endTime, + }) + ); + + const patternRollup = getPatternRollupStub('packetbeat-*', 1); + + act(() => { + result.current.updatePatternRollup(patternRollup); + }); + + expect(result.current.patternRollups['packetbeat-*'].results?.['.ds-packetbeat-1']).toEqual( + { + checkedAt: new Date('2021-10-07T00:00:00Z').getTime(), + docsCount: 1000000, + error: null, + ilmPhase: 'hot', + incompatible: 0, + indexName: '.ds-packetbeat-1', + markdownComments: ['foo', 'bar', 'baz'], + pattern: 'packetbeat-*', + sameFamily: 0, + } + ); + + jest.advanceTimersByTime(1000); + + const mockOnCheckCompletedOpts = { + batchId: 'test-batch', + checkAllStartTime: Date.now(), + error: null, + formatBytes, + formatNumber, + indexName: '.ds-packetbeat-1', + partitionedFieldMetadata: mockPartitionedFieldMetadata, + pattern: 'packetbeat-*', + requestTime: 1500, + isLastCheck: true, + isCheckAll: true, + }; + + jest.advanceTimersByTime(1000); + + act(() => { + result.current.onCheckCompleted(mockOnCheckCompletedOpts); + }); + + expect(result.current.patternRollups['packetbeat-*'].results?.['.ds-packetbeat-1']).toEqual( + { + checkedAt: new Date('2021-10-07T00:00:02Z').getTime(), + docsCount: 1000000, + error: null, + ilmPhase: 'hot', + incompatible: 3, + indexName: '.ds-packetbeat-1', + markdownComments: expect.any(Array), + pattern: 'packetbeat-*', + sameFamily: 0, + } + ); + + expect(mockTelemetryEvents.reportDataQualityIndexChecked).toHaveBeenCalledWith({ + batchId: 'test-batch', + ecsVersion: '8.11.0', + errorCount: 0, + ilmPhase: 'hot', + indexId: 'uuid-1', + indexName: '.ds-packetbeat-1', + isCheckAll: true, + numberOfCustomFields: 4, + numberOfDocuments: 1000000, + numberOfEcsFields: 2, + numberOfFields: 9, + numberOfIncompatibleFields: 3, + numberOfIndices: 1, + numberOfIndicesChecked: 1, + numberOfSameFamily: 0, + sameFamilyFields: [], + sizeInBytes: 500000000, + timeConsumedMs: 1500, + unallowedMappingFields: ['host.name', 'source.ip'], + unallowedValueFields: ['event.category'], + }); + expect(mockTelemetryEvents.reportDataQualityCheckAllCompleted).toHaveBeenCalledWith({ + batchId: 'test-batch', + ecsVersion: '8.11.0', + isCheckAll: true, + numberOfDocuments: 1000000, + numberOfIncompatibleFields: 3, + numberOfIndices: 1, + numberOfIndicesChecked: 1, + numberOfSameFamily: 0, + sizeInBytes: 500000000, + timeConsumedMs: 1000, + }); + + expect(httpFetch).toHaveBeenCalledWith('/internal/ecs_data_quality_dashboard/results', { + method: 'POST', + version: '1', + signal: expect.any(AbortSignal), + body: expect.any(String), + }); + + const body = JSON.parse(httpFetch.mock.calls[0][1].body); + + expect(body).toEqual({ + batchId: 'test-batch', + indexName: '.ds-packetbeat-1', + indexPattern: 'packetbeat-*', + isCheckAll: true, + checkedAt: new Date('2021-10-07T00:00:02Z').getTime(), + docsCount: 1000000, + totalFieldCount: 9, + ecsFieldCount: 2, + customFieldCount: 4, + incompatibleFieldCount: 3, + incompatibleFieldMappingItems: [ + { + fieldName: 'host.name', + expectedValue: 'keyword', + actualValue: 'text', + description: + 'Name of the host.\nIt can contain what `hostname` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.', + }, + { + fieldName: 'source.ip', + expectedValue: 'ip', + actualValue: 'text', + description: 'IP address of the source (IPv4 or IPv6).', + }, + ], + incompatibleFieldValueItems: [ + { + fieldName: 'event.category', + expectedValues: [ + 'authentication', + 'configuration', + 'database', + 'driver', + 'email', + 'file', + 'host', + 'iam', + 'intrusion_detection', + 'malware', + 'network', + 'package', + 'process', + 'registry', + 'session', + 'threat', + 'vulnerability', + 'web', + ], + actualValues: [ + { name: 'an_invalid_category', count: 2 }, + { name: 'theory', count: 1 }, + ], + description: + 'This is one of four ECS Categorization Fields, and indicates the second level in the ECS category hierarchy.\n`event.category` represents the "big buckets" of ECS categories. For example, filtering on `event.category:process` yields all events relating to process activity. This field is closely related to `event.type`, which is used as a subcategory.\nThis field is an array. This will allow proper categorization of some events that fall in multiple categories.', + }, + ], + sameFamilyFieldCount: 0, + sameFamilyFields: [], + sameFamilyFieldItems: [], + unallowedMappingFields: ['host.name', 'source.ip'], + unallowedValueFields: ['event.category'], + sizeInBytes: 500000000, + ilmPhase: 'hot', + markdownComments: [ + '### .ds-packetbeat-1\n', + '| Result | Index | Docs | Incompatible fields | ILM Phase | Size |\n|--------|-------|------|---------------------|-----------|------|\n| ❌ | .ds-packetbeat-1 | 1,000,000 (100.0%) | 3 | `hot` | 476.8MB |\n\n', + '### **Incompatible fields** `3` **Same family** `0` **Custom fields** `4` **ECS compliant fields** `2` **All fields** `9`\n', + "#### 3 incompatible fields\n\nFields are incompatible with ECS when index mappings, or the values of the fields in the index, don't conform to the Elastic Common Schema (ECS), version 8.11.0.\n\n❌ Detection engine rules referencing these fields may not match them correctly\n❌ Pages may not display some events or fields due to unexpected field mappings or values\n❌ Mappings or field values that don't comply with ECS are not supported\n", + '\n#### Incompatible field mappings - .ds-packetbeat-1\n\n\n| Field | ECS mapping type (expected) | Index mapping type (actual) | \n|-------|-----------------------------|-----------------------------|\n| host.name | `keyword` | `text` |\n| source.ip | `ip` | `text` |\n\n#### Incompatible field values - .ds-packetbeat-1\n\n\n| Field | ECS values (expected) | Document values (actual) | \n|-------|-----------------------|--------------------------|\n| event.category | `authentication`, `configuration`, `database`, `driver`, `email`, `file`, `host`, `iam`, `intrusion_detection`, `malware`, `network`, `package`, `process`, `registry`, `session`, `threat`, `vulnerability`, `web` | `an_invalid_category` (2), `theory` (1) |\n\n', + ], + ecsVersion: '8.11.0', + indexId: 'uuid-1', + error: null, + }); + }); + + describe('when isILMAvailable is false', () => { + it('should omit ilmPhase and nullify sizeInBytes when storing payload', () => { + const { result } = renderHook(() => + useResultsRollup({ + httpFetch, + toasts, + patterns, + isILMAvailable: false, + telemetryEvents: mockTelemetryEvents, + startTime, + endTime, + }) + ); + + const patternRollup = getPatternRollupStub('packetbeat-*', 1, false); + + act(() => { + result.current.updatePatternRollup(patternRollup); + }); + + jest.advanceTimersByTime(1000); + + const mockOnCheckCompletedOpts = { + batchId: 'test-batch', + checkAllStartTime: Date.now(), + error: null, + formatBytes, + formatNumber, + indexName: '.ds-packetbeat-1', + partitionedFieldMetadata: mockPartitionedFieldMetadata, + pattern: 'packetbeat-*', + requestTime: 1500, + isLastCheck: true, + isCheckAll: true, + }; + + jest.advanceTimersByTime(1000); + + act(() => { + result.current.onCheckCompleted(mockOnCheckCompletedOpts); + }); + + expect(mockTelemetryEvents.reportDataQualityIndexChecked).toHaveBeenCalledWith({ + batchId: 'test-batch', + ecsVersion: '8.11.0', + errorCount: 0, + ilmPhase: undefined, + indexId: 'uuid-1', + indexName: '.ds-packetbeat-1', + isCheckAll: true, + numberOfCustomFields: 4, + numberOfDocuments: 1000000, + numberOfEcsFields: 2, + numberOfFields: 9, + numberOfIncompatibleFields: 3, + numberOfIndices: 1, + numberOfIndicesChecked: 1, + numberOfSameFamily: 0, + sameFamilyFields: [], + sizeInBytes: undefined, + timeConsumedMs: 1500, + unallowedMappingFields: ['host.name', 'source.ip'], + unallowedValueFields: ['event.category'], + }); + expect(mockTelemetryEvents.reportDataQualityCheckAllCompleted).toHaveBeenCalledWith({ + batchId: 'test-batch', + ecsVersion: '8.11.0', + isCheckAll: true, + numberOfDocuments: 1000000, + numberOfIncompatibleFields: 3, + numberOfIndices: 1, + numberOfIndicesChecked: 1, + numberOfSameFamily: 0, + sizeInBytes: undefined, + timeConsumedMs: 1000, + }); + + expect(httpFetch).toHaveBeenCalledWith('/internal/ecs_data_quality_dashboard/results', { + method: 'POST', + version: '1', + signal: expect.any(AbortSignal), + body: expect.any(String), + }); + + const body = JSON.parse(httpFetch.mock.calls[0][1].body); + + expect(body).toEqual({ + batchId: 'test-batch', + indexName: '.ds-packetbeat-1', + indexPattern: 'packetbeat-*', + isCheckAll: true, + checkedAt: new Date('2021-10-07T00:00:02Z').getTime(), + docsCount: 1000000, + totalFieldCount: 9, + ecsFieldCount: 2, + customFieldCount: 4, + incompatibleFieldCount: 3, + incompatibleFieldMappingItems: [ + { + fieldName: 'host.name', + expectedValue: 'keyword', + actualValue: 'text', + description: + 'Name of the host.\nIt can contain what `hostname` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.', + }, + { + fieldName: 'source.ip', + expectedValue: 'ip', + actualValue: 'text', + description: 'IP address of the source (IPv4 or IPv6).', + }, + ], + incompatibleFieldValueItems: [ + { + fieldName: 'event.category', + expectedValues: [ + 'authentication', + 'configuration', + 'database', + 'driver', + 'email', + 'file', + 'host', + 'iam', + 'intrusion_detection', + 'malware', + 'network', + 'package', + 'process', + 'registry', + 'session', + 'threat', + 'vulnerability', + 'web', + ], + actualValues: [ + { name: 'an_invalid_category', count: 2 }, + { name: 'theory', count: 1 }, + ], + description: + 'This is one of four ECS Categorization Fields, and indicates the second level in the ECS category hierarchy.\n`event.category` represents the "big buckets" of ECS categories. For example, filtering on `event.category:process` yields all events relating to process activity. This field is closely related to `event.type`, which is used as a subcategory.\nThis field is an array. This will allow proper categorization of some events that fall in multiple categories.', + }, + ], + sameFamilyFieldCount: 0, + sameFamilyFields: [], + sameFamilyFieldItems: [], + unallowedMappingFields: ['host.name', 'source.ip'], + unallowedValueFields: ['event.category'], + ilmPhase: undefined, + sizeInBytes: 0, + markdownComments: [ + '### .ds-packetbeat-1\n', + '| Result | Index | Docs | Incompatible fields |\n|--------|-------|------|---------------------|\n| ❌ | .ds-packetbeat-1 | 1,000,000 (100.0%) | 3 |\n\n', + '### **Incompatible fields** `3` **Same family** `0` **Custom fields** `4` **ECS compliant fields** `2` **All fields** `9`\n', + "#### 3 incompatible fields\n\nFields are incompatible with ECS when index mappings, or the values of the fields in the index, don't conform to the Elastic Common Schema (ECS), version 8.11.0.\n\n❌ Detection engine rules referencing these fields may not match them correctly\n❌ Pages may not display some events or fields due to unexpected field mappings or values\n❌ Mappings or field values that don't comply with ECS are not supported\n", + '\n#### Incompatible field mappings - .ds-packetbeat-1\n\n\n| Field | ECS mapping type (expected) | Index mapping type (actual) | \n|-------|-----------------------------|-----------------------------|\n| host.name | `keyword` | `text` |\n| source.ip | `ip` | `text` |\n\n#### Incompatible field values - .ds-packetbeat-1\n\n\n| Field | ECS values (expected) | Document values (actual) | \n|-------|-----------------------|--------------------------|\n| event.category | `authentication`, `configuration`, `database`, `driver`, `email`, `file`, `host`, `iam`, `intrusion_detection`, `malware`, `network`, `package`, `process`, `registry`, `session`, `threat`, `vulnerability`, `web` | `an_invalid_category` (2), `theory` (1) |\n\n', + ], + ecsVersion: '8.11.0', + indexId: 'uuid-1', + error: null, + }); + }); + }); + }); + + describe('when check fails with error message and no partitionedFieldMetadata', () => { + it('should update patternRollup with error message, reset state without persisting in storage', () => { + const { result } = renderHook(() => + useResultsRollup({ + httpFetch, + toasts, + patterns, + isILMAvailable, + telemetryEvents: mockTelemetryEvents, + startTime, + endTime, + }) + ); + + const patternRollup = getPatternRollupStub('packetbeat-*', 1); + + act(() => { + result.current.updatePatternRollup(patternRollup); + }); + + const mockOnCheckCompletedOpts = { + batchId: 'test-batch', + checkAllStartTime: Date.now(), + error: 'Something went wrong', + formatBytes, + formatNumber, + indexName: '.ds-packetbeat-1', + partitionedFieldMetadata: null, + pattern: 'packetbeat-*', + requestTime: 1500, + isLastCheck: true, + isCheckAll: true, + }; + + act(() => { + result.current.onCheckCompleted(mockOnCheckCompletedOpts); + }); + + expect(result.current.patternRollups['packetbeat-*'].results?.['.ds-packetbeat-1']).toEqual( + { + checkedAt: undefined, + docsCount: 1000000, + error: 'Something went wrong', + ilmPhase: 'hot', + incompatible: undefined, + indexName: '.ds-packetbeat-1', + markdownComments: expect.any(Array), + pattern: 'packetbeat-*', + sameFamily: undefined, + } + ); + + expect(mockTelemetryEvents.reportDataQualityIndexChecked).not.toHaveBeenCalled(); + + expect(httpFetch).not.toHaveBeenCalledWith( + '/internal/ecs_data_quality_dashboard/results', + expect.any(Object) + ); + }); + }); + + describe('edge cases', () => { + describe('given no error nor partitionedFieldMetadata', () => { + it('should reset result state accordingly and not invoke telemetry report nor persist in storage', () => { + const { result } = renderHook(() => + useResultsRollup({ + httpFetch, + toasts, + patterns, + isILMAvailable, + telemetryEvents: mockTelemetryEvents, + startTime, + endTime, + }) + ); + + const patternRollup = getPatternRollupStub('packetbeat-*', 1); + + act(() => { + result.current.updatePatternRollup(patternRollup); + }); + + const mockOnCheckCompletedOpts = { + batchId: 'test-batch', + checkAllStartTime: Date.now(), + error: null, + formatBytes, + formatNumber, + indexName: '.ds-packetbeat-1', + partitionedFieldMetadata: null, + pattern: 'packetbeat-*', + requestTime: 1500, + isLastCheck: true, + isCheckAll: true, + }; + + act(() => { + result.current.onCheckCompleted(mockOnCheckCompletedOpts); + }); + + expect( + result.current.patternRollups['packetbeat-*'].results?.['.ds-packetbeat-1'] + ).toEqual({ + checkedAt: undefined, + docsCount: 1000000, + error: null, + ilmPhase: 'hot', + incompatible: undefined, + indexName: '.ds-packetbeat-1', + markdownComments: expect.any(Array), + pattern: 'packetbeat-*', + sameFamily: undefined, + }); + + expect(mockTelemetryEvents.reportDataQualityIndexChecked).not.toHaveBeenCalled(); + + expect(httpFetch).not.toHaveBeenCalledWith( + '/internal/ecs_data_quality_dashboard/results', + expect.any(Object) + ); + }); + }); + }); + }); + + describe('calculating totals', () => { + describe('when patternRollups change', () => { + it('should update totals', () => { + const { result } = renderHook(() => + useResultsRollup({ + httpFetch, + toasts, + patterns: ['packetbeat-*', 'auditbeat-*'], + isILMAvailable, + telemetryEvents: mockTelemetryEvents, + startTime, + endTime, + }) + ); + + const patternRollup1 = getPatternRollupStub('packetbeat-*', 1); + const patternRollup2 = getPatternRollupStub('auditbeat-*', 1); + + expect(result.current.totalIndices).toBe(0); + expect(result.current.totalDocsCount).toBe(0); + expect(result.current.totalSizeInBytes).toBe(0); + + act(() => { + result.current.updatePatternRollup(patternRollup1); + }); + + expect(result.current.totalIndices).toEqual(1); + expect(result.current.totalDocsCount).toEqual(1000000); + expect(result.current.totalSizeInBytes).toEqual(500000000); + + act(() => { + result.current.updatePatternRollup(patternRollup2); + }); + + expect(result.current.totalIndices).toEqual(2); + expect(result.current.totalDocsCount).toEqual(2000000); + expect(result.current.totalSizeInBytes).toEqual(1000000000); + }); + }); + }); +}); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/index.tsx index 28b36765a245b..bfed849e373d3 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/index.tsx @@ -21,91 +21,48 @@ import { getTotalPatternSameFamily, getIndexId, } from './utils/stats'; -import { - getStorageResults, - postStorageResult, - formatStorageResult, - formatResultFromStorage, -} from './utils/storage'; +import { postStorageResult, formatStorageResult } from './utils/storage'; import { getPatternRollupsWithLatestCheckResult } from './utils/get_pattern_rollups_with_latest_check_result'; -import type { - DataQualityCheckResult, - OnCheckCompleted, - PatternRollup, - TelemetryEvents, -} from '../../types'; +import type { OnCheckCompleted, PatternRollup, TelemetryEvents } from '../../types'; import { getEscapedIncompatibleMappingsFields, getEscapedIncompatibleValuesFields, getEscapedSameFamilyFields, } from './utils/metadata'; import { UseResultsRollupReturnValue } from './types'; -import { useIsMountedRef } from '../use_is_mounted_ref'; import { getDocsCount, getIndexIncompatible, getSizeInBytes } from '../../utils/stats'; import { getIlmPhase } from '../../utils/get_ilm_phase'; +import { useStoredPatternResults } from './hooks/use_stored_pattern_results'; interface Props { - ilmPhases: string[]; patterns: string[]; toasts: IToasts; httpFetch: HttpHandler; telemetryEvents: TelemetryEvents; isILMAvailable: boolean; + startTime: string; + endTime: string; } -const useStoredPatternResults = (patterns: string[], toasts: IToasts, httpFetch: HttpHandler) => { - const { isMountedRef } = useIsMountedRef(); - const [storedPatternResults, setStoredPatternResults] = useState< - Array<{ pattern: string; results: Record }> - >([]); - - useEffect(() => { - if (isEmpty(patterns)) { - return; - } - - let ignore = false; - const abortController = new AbortController(); - const fetchStoredPatternResults = async () => { - const requests = patterns.map((pattern) => - getStorageResults({ pattern, httpFetch, abortController, toasts }).then((results = []) => ({ - pattern, - results: Object.fromEntries( - results.map((storageResult) => [ - storageResult.indexName, - formatResultFromStorage({ storageResult, pattern }), - ]) - ), - })) - ); - const patternResults = await Promise.all(requests); - if (patternResults?.length && !ignore) { - if (isMountedRef.current) { - setStoredPatternResults(patternResults); - } - } - }; - - fetchStoredPatternResults(); - return () => { - ignore = true; - }; - }, [httpFetch, isMountedRef, patterns, toasts]); - - return storedPatternResults; -}; - export const useResultsRollup = ({ httpFetch, toasts, - ilmPhases, patterns, isILMAvailable, telemetryEvents, + startTime, + endTime, }: Props): UseResultsRollupReturnValue => { const [patternIndexNames, setPatternIndexNames] = useState>({}); const [patternRollups, setPatternRollups] = useState>({}); - const storedPatternsResults = useStoredPatternResults(patterns, toasts, httpFetch); + const storedPatternsResults = useStoredPatternResults({ + httpFetch, + patterns, + toasts, + isILMAvailable, + startTime, + endTime, + }); useEffect(() => { if (!isEmpty(storedPatternsResults)) { @@ -247,12 +204,6 @@ export const useResultsRollup = ({ [httpFetch, isILMAvailable, telemetryEvents, toasts] ); - useEffect(() => { - // reset all state - setPatternRollups({}); - setPatternIndexNames({}); - }, [ilmPhases, patterns]); - const useResultsRollupReturnValue = useMemo( () => ({ onCheckCompleted, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/storage.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/storage.test.ts index 9f315d65c01d5..b43954e73f6fd 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/storage.test.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/storage.test.ts @@ -200,4 +200,26 @@ describe('getStorageResults', () => { expect(toasts.addError).toHaveBeenCalledWith('test-error', { title: expect.any(String) }); expect(results).toEqual([]); }); + + it('should provide stad and end date', async () => { + await getStorageResults({ + httpFetch: fetch, + abortController: new AbortController(), + pattern: 'auditbeat-*', + toasts, + startTime: 'now-7d', + endTime: 'now', + }); + + expect(fetch).toHaveBeenCalledWith( + '/internal/ecs_data_quality_dashboard/results_latest/auditbeat-*', + expect.objectContaining({ + method: 'GET', + query: { + startDate: 'now-7d', + endDate: 'now', + }, + }) + ); + }); }); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/storage.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/storage.ts index e4a5c43d5b4a5..7fc339c085bea 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/storage.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/storage.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { HttpHandler } from '@kbn/core-http-browser'; +import { HttpFetchQuery, HttpHandler } from '@kbn/core-http-browser'; import { IToasts } from '@kbn/core-notifications-browser'; import { @@ -131,23 +131,40 @@ export async function postStorageResult({ } } +export interface GetStorageResultsOpts { + pattern: string; + httpFetch: HttpHandler; + toasts: IToasts; + abortController: AbortController; + startTime?: string; + endTime?: string; +} + export async function getStorageResults({ pattern, httpFetch, toasts, abortController, -}: { - pattern: string; - httpFetch: HttpHandler; - toasts: IToasts; - abortController: AbortController; -}): Promise { + startTime, + endTime, +}: GetStorageResultsOpts): Promise { try { const route = GET_INDEX_RESULTS_LATEST.replace('{pattern}', pattern); + + const query: HttpFetchQuery = {}; + + if (startTime) { + query.startDate = startTime; + } + if (endTime) { + query.endDate = endTime; + } + const results = await httpFetch(route, { method: 'GET', signal: abortController.signal, version: INTERNAL_API_VERSION, + ...(Object.keys(query).length > 0 ? { query } : {}), }); return results; } catch (err) { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/index.test.tsx index 90e5dba08d4dc..f925a67ea3d32 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/index.test.tsx @@ -67,6 +67,8 @@ describe('DataQualityPanel', () => { setLastChecked={jest.fn()} baseTheme={DARK_THEME} toasts={toasts} + defaultStartTime={'now-7d'} + defaultEndTime={'now'} /> ); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/index.tsx index 7d1a106d83570..9b9cbdefb6670 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/index.tsx @@ -46,6 +46,8 @@ interface Props { setLastChecked: (lastChecked: string) => void; startDate?: string | null; theme?: PartialTheme; + defaultStartTime: string; + defaultEndTime: string; } const defaultSelectedIlmPhaseOptions: EuiComboBoxOptionOption[] = ilmPhaseOptionsStatic.filter( @@ -71,6 +73,8 @@ const DataQualityPanelComponent: React.FC = ({ setLastChecked, startDate, theme, + defaultStartTime, + defaultEndTime, }) => { const [selectedIlmPhaseOptions, setSelectedIlmPhaseOptions] = useState( defaultSelectedIlmPhaseOptions @@ -104,12 +108,13 @@ const DataQualityPanelComponent: React.FC = ({ ); const resultsRollupHookReturnValue = useResultsRollup({ - ilmPhases, patterns, httpFetch, toasts, isILMAvailable, telemetryEvents, + startTime: defaultStartTime, + endTime: defaultEndTime, }); const indicesCheckHookReturnValue = useIndicesCheck({ @@ -138,6 +143,8 @@ const DataQualityPanelComponent: React.FC = ({ ilmPhases={ilmPhases} selectedIlmPhaseOptions={selectedIlmPhaseOptions} setSelectedIlmPhaseOptions={setSelectedIlmPhaseOptions} + defaultStartTime={defaultStartTime} + defaultEndTime={defaultEndTime} > diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx index 17b73f1e6dcd0..e0220d26e8690 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx @@ -135,6 +135,8 @@ const TestDataQualityProvidersComponent: React.FC ilmPhases, selectedIlmPhaseOptions, setSelectedIlmPhaseOptions, + defaultStartTime, + defaultEndTime, } = getMergedDataQualityContextProps(dataQualityContextProps); const mergedResultsRollupContextProps = @@ -162,6 +164,8 @@ const TestDataQualityProvidersComponent: React.FC ilmPhases={ilmPhases} selectedIlmPhaseOptions={selectedIlmPhaseOptions} setSelectedIlmPhaseOptions={setSelectedIlmPhaseOptions} + defaultStartTime={defaultStartTime} + defaultEndTime={defaultEndTime} > + value != null ? numeral(value).format(defaultBytesFormat) : EMPTY_STAT; + +const defaultNumberFormat = '0,0.[000]'; +export const formatNumber = (value: number | undefined) => + value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/utils/get_merged_data_quality_context_props.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/utils/get_merged_data_quality_context_props.ts index 264198e510b5e..be12434fc6c85 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/utils/get_merged_data_quality_context_props.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/utils/get_merged_data_quality_context_props.ts @@ -5,10 +5,9 @@ * 2.0. */ -import numeral from '@elastic/numeral'; - import { DataQualityProviderProps } from '../../../data_quality_context'; -import { EMPTY_STAT } from '../../../constants'; + +import { formatBytes as formatBytesMock, formatNumber as formatNumberMock } from './format'; export const getMergedDataQualityContextProps = ( dataQualityContextProps?: Partial @@ -31,15 +30,15 @@ export const getMergedDataQualityContextProps = ( ilmPhases, selectedIlmPhaseOptions, setSelectedIlmPhaseOptions, + defaultStartTime, + defaultEndTime, } = { isILMAvailable: true, addSuccessToast: jest.fn(), canUserCreateAndReadCases: jest.fn(() => true), endDate: null, - formatBytes: (value: number | undefined) => - value != null ? numeral(value).format('0,0.[0]b') : EMPTY_STAT, - formatNumber: (value: number | undefined) => - value != null ? numeral(value).format('0,0.[000]') : EMPTY_STAT, + formatBytes: formatBytesMock, + formatNumber: formatNumberMock, isAssistantEnabled: true, lastChecked: '2023-03-28T22:27:28.159Z', openCreateCaseFlyout: jest.fn(), @@ -72,6 +71,8 @@ export const getMergedDataQualityContextProps = ( }, ], setSelectedIlmPhaseOptions: jest.fn(), + defaultStartTime: 'now-7d/d', + defaultEndTime: 'now/d', ...dataQualityContextProps, }; @@ -93,5 +94,7 @@ export const getMergedDataQualityContextProps = ( ilmPhases, selectedIlmPhaseOptions, setSelectedIlmPhaseOptions, + defaultStartTime, + defaultEndTime, }; }; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/stub/get_pattern_rollup_stub/index.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/stub/get_pattern_rollup_stub/index.ts new file mode 100644 index 0000000000000..38aa129a6ec9a --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/stub/get_pattern_rollup_stub/index.ts @@ -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 { PatternRollup } from '../../types'; + +const phases = ['hot', 'warm', 'cold', 'frozen'] as const; + +/** + * + * This function derives ilmExplain, results, stats and ilmExplainPhaseCounts + * from the provided pattern and indicesCount for the purpose of simplifying + * stubbing of resultsRollup in tests. + * + * @param pattern - The index pattern to simulate. Defaults to `'packetbeat-*'`. + * @param indicesCount - The number of indices to generate. Defaults to `2`. + * @param isILMAvailable - Whether ILM is available. Defaults to `true`. + * @returns An object containing stubbed pattern rollup data + */ +export const getPatternRollupStub = ( + pattern = 'packetbeat-*', + indicesCount = 2, + isILMAvailable = true +): PatternRollup => { + // Derive ilmExplain from isILMAvailable, pattern and indicesCount + const ilmExplain = isILMAvailable + ? Object.fromEntries( + Array.from({ length: indicesCount }).map((_, i) => { + const indexName = pattern.replace('*', `${i + 1}`); + const dsIndexName = `.ds-${indexName}`; + // Cycle through phases + const phase = phases[i % phases.length]; + return [ + dsIndexName, + { + index: dsIndexName, + managed: true, + policy: pattern, + phase, + }, + ]; + }) + ) + : null; + + // Derive ilmExplainPhaseCounts from ilmExplain + const ilmExplainPhaseCounts = ilmExplain + ? phases.reduce( + (counts, phase) => ({ + ...counts, + [phase]: Object.values(ilmExplain).filter((explain) => explain.phase === phase).length, + }), + { hot: 0, warm: 0, cold: 0, frozen: 0, unmanaged: 0 } + ) + : undefined; + + // Derive results from pattern and indicesCount + const results = Object.fromEntries( + Array.from({ length: indicesCount }, (_, i) => { + const indexName = pattern.replace('*', `${i + 1}`); + const dsIndexName = `.ds-${indexName}`; + return [ + dsIndexName, + { + docsCount: 1000000 + i * 100000, // Example doc count + error: null, + ilmPhase: ilmExplain?.[dsIndexName].phase, + incompatible: i, + indexName: dsIndexName, + markdownComments: ['foo', 'bar', 'baz'], + pattern, + sameFamily: i, + checkedAt: Date.now(), + }, + ]; + }) + ); + + // Derive stats from isILMAvailable, pattern and indicesCount + const stats = Object.fromEntries( + Array.from({ length: indicesCount }, (_, i) => { + const indexName = pattern.replace('*', `${i + 1}`); + const dsIndexName = `.ds-${indexName}`; + return [ + dsIndexName, + { + uuid: `uuid-${i + 1}`, + size_in_bytes: isILMAvailable ? 500000000 + i * 10000000 : null, + name: dsIndexName, + num_docs: results[dsIndexName].docsCount, + }, + ]; + }) + ); + + // Derive total docsCount and sizeInBytes from stats + const totalDocsCount = Object.values(stats).reduce((sum, stat) => sum + stat.num_docs, 0); + const totalSizeInBytes = isILMAvailable + ? Object.values(stats).reduce((sum, stat) => sum + (stat.size_in_bytes ?? 0), 0) + : undefined; + + return { + docsCount: totalDocsCount, + error: null, + pattern, + ilmExplain, + ilmExplainPhaseCounts, + indices: indicesCount, + results, + sizeInBytes: totalSizeInBytes, + stats, + }; +}; diff --git a/x-pack/packages/security/authorization_core/src/actions/api.ts b/x-pack/packages/security/authorization_core/src/actions/api.ts index fec6296d8f63f..d91bc1bd89669 100644 --- a/x-pack/packages/security/authorization_core/src/actions/api.ts +++ b/x-pack/packages/security/authorization_core/src/actions/api.ts @@ -8,6 +8,7 @@ import { isString } from 'lodash'; import type { ApiActions as ApiActionsType } from '@kbn/security-plugin-types-server'; +import { ApiOperation } from '@kbn/security-plugin-types-server'; export class ApiActions implements ApiActionsType { private readonly prefix: string; @@ -16,11 +17,33 @@ export class ApiActions implements ApiActionsType { this.prefix = `api:`; } - public get(operation: string) { - if (!operation || !isString(operation)) { - throw new Error('operation is required and must be a string'); + private isValidOperation(operation: string): operation is ApiOperation { + return Object.values(ApiOperation).includes(operation as ApiOperation); + } + public actionFromRouteTag(routeTag: string) { + const [operation, subject] = routeTag.split('_'); + if (!this.isValidOperation(operation)) { + throw new Error('operation is required and must be a valid ApiOperation'); + } + return this.get(operation, subject); + } + + public get(operation: string | ApiOperation, subject?: string) { + if (arguments.length === 1) { + if (!isString(operation) || !operation) { + throw new Error('operation is required and must be a string'); + } + return `${this.prefix}${operation}`; + } + + if (!isString(subject) || !subject) { + throw new Error('subject is required and must be a string'); + } + + if (!this.isValidOperation(operation)) { + throw new Error('operation is required and must be a valid ApiOperation'); } - return `${this.prefix}${operation}`; + return `${this.prefix}${operation}_${subject}`; } } diff --git a/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts b/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts index f9d490bfcb09b..6af21d5357a72 100644 --- a/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts +++ b/x-pack/packages/security/authorization_core/src/privileges/privileges.test.ts @@ -7,6 +7,7 @@ import { KibanaFeature } from '@kbn/features-plugin/server'; import { featuresPluginMock } from '@kbn/features-plugin/server/mocks'; +import { ApiOperation } from '@kbn/security-plugin-types-server'; import { getReplacedByForPrivilege, privilegesFactory } from './privileges'; import { licenseMock } from '../__fixtures__/licensing.mock'; @@ -793,10 +794,12 @@ describe('features', () => { const actual = privileges.get(); expect(actual).toHaveProperty(`${group}.all`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), - ...(expectGetFeatures ? [actions.api.get('features')] : []), - ...(expectGetFeatures ? [actions.api.get('taskManager')] : []), - ...(expectGetFeatures ? [actions.api.get('manageSpaces')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Read, 'features')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'taskManager')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'spaces')] : []), ...(expectManageSpaces ? [ actions.space.manage, @@ -965,10 +968,12 @@ describe('features', () => { const expectedActions = [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), - ...(expectGetFeatures ? [actions.api.get('features')] : []), - ...(expectGetFeatures ? [actions.api.get('taskManager')] : []), - ...(expectGetFeatures ? [actions.api.get('manageSpaces')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Read, 'features')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'taskManager')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'spaces')] : []), ...(expectManageSpaces ? [ actions.space.manage, @@ -1124,7 +1129,9 @@ describe('features', () => { const actual = privileges.get(); expect(actual).toHaveProperty(`${group}.read`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), ...(expectGlobalSettings ? [actions.ui.get('globalSettings', 'show')] : []), actions.ui.get('catalogue', 'read-catalogue-1'), actions.ui.get('catalogue', 'read-catalogue-2'), @@ -1243,7 +1250,9 @@ describe('features', () => { const expectedActions = [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), ...(expectGlobalSettings ? [actions.ui.get('globalSettings', 'show')] : []), actions.ui.get('catalogue', 'read-catalogue-2'), actions.ui.get('management', 'read-management', 'read-management-2'), @@ -1341,10 +1350,12 @@ describe('features', () => { const actual = privileges.get(); expect(actual).toHaveProperty(`${group}.all`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), - ...(expectGetFeatures ? [actions.api.get('features')] : []), - ...(expectGetFeatures ? [actions.api.get('taskManager')] : []), - ...(expectGetFeatures ? [actions.api.get('manageSpaces')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Read, 'features')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'taskManager')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'spaces')] : []), ...(expectManageSpaces ? [ actions.space.manage, @@ -1359,7 +1370,9 @@ describe('features', () => { ]); expect(actual).toHaveProperty(`${group}.read`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), ...(expectGlobalSettings ? [actions.ui.get('globalSettings', 'show')] : []), ]); }); @@ -1410,10 +1423,12 @@ describe('features', () => { const actual = privileges.get(); expect(actual).toHaveProperty(`${group}.all`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), - ...(expectGetFeatures ? [actions.api.get('features')] : []), - ...(expectGetFeatures ? [actions.api.get('taskManager')] : []), - ...(expectGetFeatures ? [actions.api.get('manageSpaces')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Read, 'features')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'taskManager')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'spaces')] : []), ...(expectManageSpaces ? [ actions.space.manage, @@ -1428,7 +1443,9 @@ describe('features', () => { ]); expect(actual).toHaveProperty(`${group}.read`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), ...(expectGlobalSettings ? [actions.ui.get('globalSettings', 'show')] : []), ]); }); @@ -1508,10 +1525,12 @@ describe('features', () => { const actual = privileges.get(); expect(actual).toHaveProperty(`${group}.all`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), - ...(expectGetFeatures ? [actions.api.get('features')] : []), - ...(expectGetFeatures ? [actions.api.get('taskManager')] : []), - ...(expectGetFeatures ? [actions.api.get('manageSpaces')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Read, 'features')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'taskManager')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'spaces')] : []), ...(expectManageSpaces ? [ actions.space.manage, @@ -1526,7 +1545,9 @@ describe('features', () => { ]); expect(actual).toHaveProperty(`${group}.read`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), ...(expectGlobalSettings ? [actions.ui.get('globalSettings', 'show')] : []), ]); }); @@ -1578,10 +1599,12 @@ describe('features', () => { const actual = privileges.get(); expect(actual).toHaveProperty(`${group}.all`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), - ...(expectGetFeatures ? [actions.api.get('features')] : []), - ...(expectGetFeatures ? [actions.api.get('taskManager')] : []), - ...(expectGetFeatures ? [actions.api.get('manageSpaces')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Read, 'features')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'taskManager')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'spaces')] : []), ...(expectManageSpaces ? [ actions.space.manage, @@ -1596,7 +1619,9 @@ describe('features', () => { ]); expect(actual).toHaveProperty(`${group}.read`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), ...(expectGlobalSettings ? [actions.ui.get('globalSettings', 'show')] : []), ]); }); @@ -1677,10 +1702,12 @@ describe('features', () => { const actual = privileges.get(); expect(actual).toHaveProperty(`${group}.all`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), - ...(expectGetFeatures ? [actions.api.get('features')] : []), - ...(expectGetFeatures ? [actions.api.get('taskManager')] : []), - ...(expectGetFeatures ? [actions.api.get('manageSpaces')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Read, 'features')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'taskManager')] : []), + ...(expectGetFeatures ? [actions.api.get(ApiOperation.Manage, 'spaces')] : []), ...(expectManageSpaces ? [ actions.space.manage, @@ -1695,7 +1722,9 @@ describe('features', () => { ]); expect(actual).toHaveProperty(`${group}.read`, [ actions.login, - ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), + ...(expectDecryptedTelemetry + ? [actions.api.get(ApiOperation.Read, 'decryptedTelemetry')] + : []), ...(expectGlobalSettings ? [actions.ui.get('globalSettings', 'show')] : []), ]); }); @@ -1945,10 +1974,10 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, - actions.api.get('decryptedTelemetry'), - actions.api.get('features'), - actions.api.get('taskManager'), - actions.api.get('manageSpaces'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'features'), + actions.api.get(ApiOperation.Manage, 'taskManager'), + actions.api.get(ApiOperation.Manage, 'spaces'), actions.space.manage, actions.ui.get('spaces', 'manage'), actions.ui.get('management', 'kibana', 'spaces'), @@ -1960,7 +1989,7 @@ describe('subFeatures', () => { ]); expect(actual).toHaveProperty('global.read', [ actions.login, - actions.api.get('decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), actions.ui.get('globalSettings', 'show'), actions.ui.get('foo', 'foo'), ]); @@ -2104,10 +2133,10 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, - actions.api.get('decryptedTelemetry'), - actions.api.get('features'), - actions.api.get('taskManager'), - actions.api.get('manageSpaces'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'features'), + actions.api.get(ApiOperation.Manage, 'taskManager'), + actions.api.get(ApiOperation.Manage, 'spaces'), actions.space.manage, actions.ui.get('spaces', 'manage'), actions.ui.get('management', 'kibana', 'spaces'), @@ -2137,7 +2166,7 @@ describe('subFeatures', () => { ]); expect(actual).toHaveProperty('global.read', [ actions.login, - actions.api.get('decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), actions.ui.get('globalSettings', 'show'), actions.savedObject.get('all-sub-feature-type', 'bulk_get'), actions.savedObject.get('all-sub-feature-type', 'get'), @@ -2340,10 +2369,10 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, - actions.api.get('decryptedTelemetry'), - actions.api.get('features'), - actions.api.get('taskManager'), - actions.api.get('manageSpaces'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'features'), + actions.api.get(ApiOperation.Manage, 'taskManager'), + actions.api.get(ApiOperation.Manage, 'spaces'), actions.space.manage, actions.ui.get('spaces', 'manage'), actions.ui.get('management', 'kibana', 'spaces'), @@ -2354,7 +2383,7 @@ describe('subFeatures', () => { ]); expect(actual).toHaveProperty('global.read', [ actions.login, - actions.api.get('decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), actions.ui.get('globalSettings', 'show'), ]); @@ -2479,10 +2508,10 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, - actions.api.get('decryptedTelemetry'), - actions.api.get('features'), - actions.api.get('taskManager'), - actions.api.get('manageSpaces'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'features'), + actions.api.get(ApiOperation.Manage, 'taskManager'), + actions.api.get(ApiOperation.Manage, 'spaces'), actions.space.manage, actions.ui.get('spaces', 'manage'), actions.ui.get('management', 'kibana', 'spaces'), @@ -2512,7 +2541,7 @@ describe('subFeatures', () => { ]); expect(actual).toHaveProperty('global.read', [ actions.login, - actions.api.get('decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), actions.ui.get('globalSettings', 'show'), actions.ui.get('foo', 'foo'), ]); @@ -2658,10 +2687,10 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, - actions.api.get('decryptedTelemetry'), - actions.api.get('features'), - actions.api.get('taskManager'), - actions.api.get('manageSpaces'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'features'), + actions.api.get(ApiOperation.Manage, 'taskManager'), + actions.api.get(ApiOperation.Manage, 'spaces'), actions.space.manage, actions.ui.get('spaces', 'manage'), actions.ui.get('management', 'kibana', 'spaces'), @@ -2672,7 +2701,7 @@ describe('subFeatures', () => { ]); expect(actual).toHaveProperty('global.read', [ actions.login, - actions.api.get('decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), actions.ui.get('globalSettings', 'show'), ]); @@ -2795,10 +2824,10 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, - actions.api.get('decryptedTelemetry'), - actions.api.get('features'), - actions.api.get('taskManager'), - actions.api.get('manageSpaces'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'features'), + actions.api.get(ApiOperation.Manage, 'taskManager'), + actions.api.get(ApiOperation.Manage, 'spaces'), actions.space.manage, actions.ui.get('spaces', 'manage'), actions.ui.get('management', 'kibana', 'spaces'), @@ -2828,7 +2857,7 @@ describe('subFeatures', () => { ]); expect(actual).toHaveProperty('global.read', [ actions.login, - actions.api.get('decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), actions.ui.get('globalSettings', 'show'), actions.savedObject.get('all-sub-feature-type', 'bulk_get'), actions.savedObject.get('all-sub-feature-type', 'get'), @@ -3010,10 +3039,10 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, - actions.api.get('decryptedTelemetry'), - actions.api.get('features'), - actions.api.get('taskManager'), - actions.api.get('manageSpaces'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'features'), + actions.api.get(ApiOperation.Manage, 'taskManager'), + actions.api.get(ApiOperation.Manage, 'spaces'), actions.space.manage, actions.ui.get('spaces', 'manage'), actions.ui.get('management', 'kibana', 'spaces'), @@ -3043,7 +3072,7 @@ describe('subFeatures', () => { ]); expect(actual).toHaveProperty('global.read', [ actions.login, - actions.api.get('decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), actions.ui.get('globalSettings', 'show'), actions.savedObject.get('all-sub-feature-type', 'bulk_get'), actions.savedObject.get('all-sub-feature-type', 'get'), @@ -3244,10 +3273,10 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, - actions.api.get('decryptedTelemetry'), - actions.api.get('features'), - actions.api.get('taskManager'), - actions.api.get('manageSpaces'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'features'), + actions.api.get(ApiOperation.Manage, 'taskManager'), + actions.api.get(ApiOperation.Manage, 'spaces'), actions.space.manage, actions.ui.get('spaces', 'manage'), actions.ui.get('management', 'kibana', 'spaces'), @@ -3277,7 +3306,7 @@ describe('subFeatures', () => { ]); expect(actual).toHaveProperty('global.read', [ actions.login, - actions.api.get('decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), actions.ui.get('globalSettings', 'show'), actions.savedObject.get('all-sub-feature-type', 'bulk_get'), actions.savedObject.get('all-sub-feature-type', 'get'), @@ -3514,10 +3543,10 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, - actions.api.get('decryptedTelemetry'), - actions.api.get('features'), - actions.api.get('taskManager'), - actions.api.get('manageSpaces'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'features'), + actions.api.get(ApiOperation.Manage, 'taskManager'), + actions.api.get(ApiOperation.Manage, 'spaces'), actions.space.manage, actions.ui.get('spaces', 'manage'), actions.ui.get('management', 'kibana', 'spaces'), @@ -3565,7 +3594,7 @@ describe('subFeatures', () => { ]); expect(actual).toHaveProperty('global.read', [ actions.login, - actions.api.get('decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), actions.ui.get('globalSettings', 'show'), actions.savedObject.get('all-sub-feature-type', 'bulk_get'), actions.savedObject.get('all-sub-feature-type', 'get'), diff --git a/x-pack/packages/security/authorization_core/src/privileges/privileges.ts b/x-pack/packages/security/authorization_core/src/privileges/privileges.ts index 7f388e80defd2..b81eaba5fa54d 100644 --- a/x-pack/packages/security/authorization_core/src/privileges/privileges.ts +++ b/x-pack/packages/security/authorization_core/src/privileges/privileges.ts @@ -17,6 +17,7 @@ import { isMinimalPrivilegeId, } from '@kbn/security-authorization-core-common'; import type { RawKibanaPrivileges, SecurityLicense } from '@kbn/security-plugin-types-common'; +import { ApiOperation } from '@kbn/security-plugin-types-server'; import { featurePrivilegeBuilderFactory } from './feature_privilege_builder'; import type { Actions } from '../actions'; @@ -210,10 +211,10 @@ export function privilegesFactory( global: { all: [ actions.login, - actions.api.get('decryptedTelemetry'), - actions.api.get('features'), - actions.api.get('taskManager'), - actions.api.get('manageSpaces'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'features'), + actions.api.get(ApiOperation.Manage, 'taskManager'), + actions.api.get(ApiOperation.Manage, 'spaces'), actions.space.manage, actions.ui.get('spaces', 'manage'), actions.ui.get('management', 'kibana', 'spaces'), @@ -225,7 +226,7 @@ export function privilegesFactory( ], read: [ actions.login, - actions.api.get('decryptedTelemetry'), + actions.api.get(ApiOperation.Read, 'decryptedTelemetry'), actions.ui.get('globalSettings', 'show'), ...readActions, ], diff --git a/x-pack/packages/security/plugin_types_server/index.ts b/x-pack/packages/security/plugin_types_server/index.ts index 21ab0eb2b39af..2b46fa0146a2a 100644 --- a/x-pack/packages/security/plugin_types_server/index.ts +++ b/x-pack/packages/security/plugin_types_server/index.ts @@ -88,3 +88,4 @@ export { getRestApiKeyWithKibanaPrivilegesSchema, } from './src/authentication'; export { getKibanaRoleSchema, elasticsearchRoleSchema, GLOBAL_RESOURCE } from './src/authorization'; +export { ApiOperation } from './src/authorization'; diff --git a/x-pack/packages/security/plugin_types_server/src/authorization/actions/api.ts b/x-pack/packages/security/plugin_types_server/src/authorization/actions/api.ts index 30a1328ce5639..01fa535a1a0d5 100644 --- a/x-pack/packages/security/plugin_types_server/src/authorization/actions/api.ts +++ b/x-pack/packages/security/plugin_types_server/src/authorization/actions/api.ts @@ -6,5 +6,19 @@ */ export interface ApiActions { - get(operation: string): string; + get(operation: ApiOperation, subject: string): string; + + /** + * @deprecated use `get(operation: ApiOperation, subject: string)` instead + */ + get(subject: string): string; + actionFromRouteTag(routeTag: string): string; +} + +export enum ApiOperation { + Read = 'read', + Create = 'create', + Update = 'update', + Delete = 'delete', + Manage = 'manage', } diff --git a/x-pack/packages/security/plugin_types_server/src/authorization/actions/index.ts b/x-pack/packages/security/plugin_types_server/src/authorization/actions/index.ts index 6b3993423015f..baed1cde4457e 100644 --- a/x-pack/packages/security/plugin_types_server/src/authorization/actions/index.ts +++ b/x-pack/packages/security/plugin_types_server/src/authorization/actions/index.ts @@ -8,6 +8,7 @@ export type { Actions } from './actions'; export type { AlertingActions } from './alerting'; export type { ApiActions } from './api'; +export { ApiOperation } from './api'; export type { AppActions } from './app'; export type { CasesActions } from './cases'; export type { SavedObjectActions } from './saved_object'; diff --git a/x-pack/packages/security/plugin_types_server/src/authorization/index.ts b/x-pack/packages/security/plugin_types_server/src/authorization/index.ts index baeeeddc1fa74..c48e797dc1d1b 100644 --- a/x-pack/packages/security/plugin_types_server/src/authorization/index.ts +++ b/x-pack/packages/security/plugin_types_server/src/authorization/index.ts @@ -15,6 +15,7 @@ export type { SpaceActions, UIActions, } from './actions'; +export { ApiOperation } from './actions'; export type { AuthorizationServiceSetup } from './authorization_service'; export type { CheckPrivilegesOptions, diff --git a/x-pack/packages/security/ui_components/src/constants.ts b/x-pack/packages/security/ui_components/src/constants.ts index a47a9bff9842d..d30c61bf02d6d 100644 --- a/x-pack/packages/security/ui_components/src/constants.ts +++ b/x-pack/packages/security/ui_components/src/constants.ts @@ -5,5 +5,5 @@ * 2.0. */ -export const NO_PRIVILEGE_VALUE: string = 'none'; +export const NO_PRIVILEGE_VALUE = 'none' as const; export const CUSTOM_PRIVILEGE_VALUE: string = 'custom'; diff --git a/x-pack/packages/security/ui_components/src/kibana_privilege_table/change_all_privileges.tsx b/x-pack/packages/security/ui_components/src/kibana_privilege_table/change_all_privileges.tsx index 4793f86a7a2a5..e475a5da7a106 100644 --- a/x-pack/packages/security/ui_components/src/kibana_privilege_table/change_all_privileges.tsx +++ b/x-pack/packages/security/ui_components/src/kibana_privilege_table/change_all_privileges.tsx @@ -15,9 +15,9 @@ import { EuiPopover, EuiText, } from '@elastic/eui'; -import _ from 'lodash'; import React, { Component } from 'react'; +import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import type { KibanaPrivilege } from '@kbn/security-role-management-model'; @@ -38,6 +38,43 @@ export class ChangeAllPrivilegesControl extends Component { isPopoverOpen: false, }; + private getPrivilegeCopy = (privilege: string): { label?: string; icon?: string } => { + switch (privilege) { + case 'all': + return { + icon: 'documentEdit', + label: i18n.translate( + 'xpack.security.management.editRole.changeAllPrivileges.allSelectionLabel', + { + defaultMessage: 'Grant full access for all', + } + ), + }; + case 'read': + return { + icon: 'glasses', + label: i18n.translate( + 'xpack.security.management.editRole.changeAllPrivileges.readSelectionLabel', + { + defaultMessage: 'Grant read access for all', + } + ), + }; + case 'none': + return { + icon: 'trash', + label: i18n.translate( + 'xpack.security.management.editRole.changeAllPrivileges.noneSelectionLabel', + { + defaultMessage: 'Revoke access to all', + } + ), + }; + default: + return {}; + } + }; + public render() { const button = ( { ); const items = this.props.privileges.map((privilege) => { + const { icon, label } = this.getPrivilegeCopy(privilege.id); return ( { @@ -65,21 +104,24 @@ export class ChangeAllPrivilegesControl extends Component { }} disabled={this.props.disabled} > - {_.upperFirst(privilege.id)} + {label} ); }); items.push( { this.onSelectPrivilege(NO_PRIVILEGE_VALUE); }} disabled={this.props.disabled} + // @ts-expect-error leaving this here so that when https://github.com/elastic/eui/issues/8123 is fixed we remove this comment + css={({ euiTheme }) => ({ color: euiTheme.colors.danger })} > - {_.upperFirst(NO_PRIVILEGE_VALUE)} + {this.getPrivilegeCopy(NO_PRIVILEGE_VALUE).label} ); diff --git a/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table.test.tsx b/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table.test.tsx index 2c858e7bb6ff6..2ed172a49ad8b 100644 --- a/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table.test.tsx +++ b/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiAccordion, EuiIconTip } from '@elastic/eui'; +import { EuiAccordion, EuiIconTip, EuiThemeProvider } from '@elastic/eui'; import React from 'react'; import type { KibanaFeature, SubFeatureConfig } from '@kbn/features-plugin/public'; @@ -47,16 +47,18 @@ const setup = (config: TestConfig) => { const onChange = jest.fn(); const onChangeAll = jest.fn(); const wrapper = mountWithIntl( - + + + ); const displayedPrivileges = config.calculateDisplayedPrivileges diff --git a/x-pack/plugins/actions/kibana.jsonc b/x-pack/plugins/actions/kibana.jsonc index 56c583cd68dab..882c832245951 100644 --- a/x-pack/plugins/actions/kibana.jsonc +++ b/x-pack/plugins/actions/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/actions-plugin", - "owner": "@elastic/response-ops", + "owner": [ + "@elastic/response-ops" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "actions", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "actions" @@ -29,4 +33,4 @@ "common" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/aiops/kibana.jsonc b/x-pack/plugins/aiops/kibana.jsonc index 65ecf52c76258..d327a131aec4d 100644 --- a/x-pack/plugins/aiops/kibana.jsonc +++ b/x-pack/plugins/aiops/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/aiops-plugin", - "owner": "@elastic/ml-ui", + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "shared", "description": "AIOps plugin maintained by ML team.", "plugin": { "id": "aiops", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "charts", "data", @@ -31,7 +35,7 @@ "kibanaReact", "kibanaUtils", "embeddable", - "cases", + "cases" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/aiops/public/components/log_categorization/categorize_field_actions.ts b/x-pack/plugins/aiops/public/components/log_categorization/categorize_field_actions.ts index 10c6311d065db..e5e6ede863558 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/categorize_field_actions.ts +++ b/x-pack/plugins/aiops/public/components/log_categorization/categorize_field_actions.ts @@ -10,7 +10,6 @@ import { createAction } from '@kbn/ui-actions-plugin/public'; import type { CoreStart } from '@kbn/core/public'; import { ACTION_CATEGORIZE_FIELD, type CategorizeFieldContext } from '@kbn/ml-ui-actions'; import type { AiopsPluginStartDeps } from '../../types'; -import { showCategorizeFlyout } from './show_flyout'; export const createCategorizeFieldAction = (coreStart: CoreStart, plugins: AiopsPluginStartDeps) => createAction({ @@ -25,6 +24,7 @@ export const createCategorizeFieldAction = (coreStart: CoreStart, plugins: Aiops }, execute: async (context: CategorizeFieldContext) => { const { field, dataView, originatingApp, additionalFilter } = context; + const { showCategorizeFlyout } = await import('./show_flyout'); showCategorizeFlyout(field, dataView, coreStart, plugins, originatingApp, additionalFilter); }, }); diff --git a/x-pack/plugins/aiops/public/components/log_categorization/index.ts b/x-pack/plugins/aiops/public/components/log_categorization/index.ts index ace01d4f03389..748a0f8486420 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/index.ts +++ b/x-pack/plugins/aiops/public/components/log_categorization/index.ts @@ -7,7 +7,6 @@ export type { LogCategorizationAppStateProps } from './log_categorization_app_state'; import { LogCategorizationAppState } from './log_categorization_app_state'; -export { createCategorizeFieldAction } from './categorize_field_actions'; // required for dynamic import using React.lazy() // eslint-disable-next-line import/no-default-export diff --git a/x-pack/plugins/aiops/public/components/log_categorization/show_flyout.tsx b/x-pack/plugins/aiops/public/components/log_categorization/show_flyout.tsx index a97f4c7f7fe79..6dae21c36222d 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/show_flyout.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/show_flyout.tsx @@ -36,7 +36,7 @@ export async function showCategorizeFlyout( ): Promise { const { overlays, application, i18n } = coreStart; - return new Promise(async (resolve, reject) => { + return new Promise((resolve, reject) => { try { const onFlyoutClose = () => { flyoutSession.close(); diff --git a/x-pack/plugins/aiops/public/embeddables/change_point_chart/embeddable_change_point_chart_factory.tsx b/x-pack/plugins/aiops/public/embeddables/change_point_chart/embeddable_change_point_chart_factory.tsx index 2ce1a46780db1..5f7ff6ff67f76 100644 --- a/x-pack/plugins/aiops/public/embeddables/change_point_chart/embeddable_change_point_chart_factory.tsx +++ b/x-pack/plugins/aiops/public/embeddables/change_point_chart/embeddable_change_point_chart_factory.tsx @@ -15,13 +15,6 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common'; import type { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public'; import { i18n } from '@kbn/i18n'; -import { - apiHasExecutionContext, - fetch$, - initializeTimeRange, - initializeTitles, - useBatchedPublishingSubjects, -} from '@kbn/presentation-publishing'; import fastIsEqual from 'fast-deep-equal'; import { cloneDeep } from 'lodash'; @@ -61,6 +54,14 @@ export const getChangePointChartEmbeddableFactory = ( return serializedState; }, buildEmbeddable: async (state, buildApi, uuid, parentApi) => { + const { + apiHasExecutionContext, + fetch$, + initializeTimeRange, + initializeTitles, + useBatchedPublishingSubjects, + } = await import('@kbn/presentation-publishing'); + const [coreStart, pluginStart] = await getStartServices(); const { diff --git a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/embeddable_pattern_analysis_factory.tsx b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/embeddable_pattern_analysis_factory.tsx index d84043ea5f637..e0017668b338c 100644 --- a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/embeddable_pattern_analysis_factory.tsx +++ b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/embeddable_pattern_analysis_factory.tsx @@ -15,13 +15,6 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common'; import type { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public'; import { i18n } from '@kbn/i18n'; -import { - apiHasExecutionContext, - fetch$, - initializeTimeRange, - initializeTitles, - useBatchedPublishingSubjects, -} from '@kbn/presentation-publishing'; import fastIsEqual from 'fast-deep-equal'; import { cloneDeep } from 'lodash'; import React, { useMemo } from 'react'; @@ -58,6 +51,14 @@ export const getPatternAnalysisEmbeddableFactory = ( return serializedState; }, buildEmbeddable: async (state, buildApi, uuid, parentApi) => { + const { + apiHasExecutionContext, + fetch$, + initializeTimeRange, + initializeTitles, + useBatchedPublishingSubjects, + } = await import('@kbn/presentation-publishing'); + const [coreStart, pluginStart] = await getStartServices(); const { diff --git a/x-pack/plugins/aiops/public/plugin.tsx b/x-pack/plugins/aiops/public/plugin.tsx index ceb378b0f29bf..5863ea03b3072 100755 --- a/x-pack/plugins/aiops/public/plugin.tsx +++ b/x-pack/plugins/aiops/public/plugin.tsx @@ -8,15 +8,18 @@ import type { CoreStart, Plugin } from '@kbn/core/public'; import { type CoreSetup } from '@kbn/core/public'; import { firstValueFrom } from 'rxjs'; -import { dynamic } from '@kbn/shared-ux-utility'; import { getChangePointDetectionComponent } from './shared_components'; +import { LogCategorizationForDiscover as PatternAnalysisComponent } from './shared_lazy_components'; import type { AiopsPluginSetup, AiopsPluginSetupDeps, AiopsPluginStart, AiopsPluginStartDeps, } from './types'; +import { registerEmbeddables } from './embeddables'; +import { registerAiopsUiActions } from './ui_actions'; +import { registerChangePointChartsAttachment } from './cases/register_change_point_charts_attachment'; export type AiopsCoreSetup = CoreSetup; @@ -27,20 +30,8 @@ export class AiopsPlugin core: AiopsCoreSetup, { embeddable, cases, licensing, uiActions }: AiopsPluginSetupDeps ) { - Promise.all([ - firstValueFrom(licensing.license$), - import('./embeddables'), - import('./ui_actions'), - import('./cases/register_change_point_charts_attachment'), - core.getStartServices(), - ]).then( - ([ - license, - { registerEmbeddables }, - { registerAiopsUiActions }, - { registerChangePointChartsAttachment }, - [coreStart, pluginStart], - ]) => { + Promise.all([firstValueFrom(licensing.license$), core.getStartServices()]).then( + ([license, [coreStart, pluginStart]]) => { const { canUseAiops } = coreStart.application.capabilities.ml; if (license.hasAtLeast('platinum') && canUseAiops) { @@ -69,12 +60,7 @@ export class AiopsPlugin ); return getPatternAnalysisAvailable(plugins.licensing); }, - PatternAnalysisComponent: dynamic( - async () => - import( - './components/log_categorization/log_categorization_for_embeddable/log_categorization_for_discover_wrapper' - ) - ), + PatternAnalysisComponent, }; } diff --git a/x-pack/plugins/aiops/public/shared_lazy_components.tsx b/x-pack/plugins/aiops/public/shared_lazy_components.tsx index fe9e31f146590..b34efdd6bff04 100644 --- a/x-pack/plugins/aiops/public/shared_lazy_components.tsx +++ b/x-pack/plugins/aiops/public/shared_lazy_components.tsx @@ -12,6 +12,7 @@ import { EuiErrorBoundary, EuiSkeletonText } from '@elastic/eui'; import type { LogRateAnalysisAppStateProps } from './components/log_rate_analysis'; import type { LogRateAnalysisContentWrapperProps } from './components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content_wrapper'; import type { LogCategorizationAppStateProps } from './components/log_categorization'; +import type { LogCategorizationEmbeddableWrapperProps } from './components/log_categorization/log_categorization_for_embeddable/log_categorization_for_discover_wrapper'; import type { ChangePointDetectionAppStateProps } from './components/change_point_detection'; const LogRateAnalysisAppStateLazy = React.lazy(() => import('./components/log_rate_analysis')); @@ -58,6 +59,25 @@ export const LogCategorization: FC = (props) => ); +const LogCategorizationForDiscoverLazy = React.lazy( + () => + import( + './components/log_categorization/log_categorization_for_embeddable/log_categorization_for_discover_wrapper' + ) +); + +/** + * Lazy-wrapped LogCategorizationForDiscover React component + * @param {LogCategorizationEmbeddableWrapperProps} props - properties specifying the data on which to run the analysis. + */ +export const LogCategorizationForDiscover: FC = ( + props +) => ( + + + +); + const ChangePointDetectionLazy = React.lazy(() => import('./components/change_point_detection')); /** * Lazy-wrapped ChangePointDetectionAppStateProps React component diff --git a/x-pack/plugins/aiops/public/ui_actions/change_point_action_context.ts b/x-pack/plugins/aiops/public/ui_actions/change_point_action_context.ts index a4307b69d3fac..3bf2eee922560 100644 --- a/x-pack/plugins/aiops/public/ui_actions/change_point_action_context.ts +++ b/x-pack/plugins/aiops/public/ui_actions/change_point_action_context.ts @@ -6,7 +6,8 @@ */ import { isPopulatedObject } from '@kbn/ml-is-populated-object'; -import { apiIsOfType, type EmbeddableApiContext } from '@kbn/presentation-publishing'; +import type { EmbeddableApiContext } from '@kbn/presentation-publishing'; +import { apiIsOfType } from '@kbn/presentation-publishing/interfaces/has_type'; import { EMBEDDABLE_CHANGE_POINT_CHART_TYPE } from '@kbn/aiops-change-point-detection/constants'; import type { ChangePointEmbeddableApi } from '../embeddables/change_point_chart/types'; diff --git a/x-pack/plugins/aiops/public/ui_actions/index.ts b/x-pack/plugins/aiops/public/ui_actions/index.ts index 6081541c448e7..b0b39083aabd4 100644 --- a/x-pack/plugins/aiops/public/ui_actions/index.ts +++ b/x-pack/plugins/aiops/public/ui_actions/index.ts @@ -16,7 +16,7 @@ import type { CoreStart } from '@kbn/core/public'; import { createAddChangePointChartAction } from './create_change_point_chart'; import { createOpenChangePointInMlAppAction } from './open_change_point_ml'; import type { AiopsPluginStartDeps } from '../types'; -import { createCategorizeFieldAction } from '../components/log_categorization'; +import { createCategorizeFieldAction } from '../components/log_categorization/categorize_field_actions'; import { createAddPatternAnalysisEmbeddableAction } from './create_pattern_analysis_action'; import { createAddLogRateAnalysisEmbeddableAction } from './create_log_rate_analysis_actions'; diff --git a/x-pack/plugins/aiops/public/ui_actions/open_change_point_ml.tsx b/x-pack/plugins/aiops/public/ui_actions/open_change_point_ml.tsx index 8d2e4f1bbd089..3d52d34a72b85 100644 --- a/x-pack/plugins/aiops/public/ui_actions/open_change_point_ml.tsx +++ b/x-pack/plugins/aiops/public/ui_actions/open_change_point_ml.tsx @@ -10,17 +10,17 @@ import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; import { i18n } from '@kbn/i18n'; import type { CoreStart } from '@kbn/core/public'; import type { TimeRange } from '@kbn/es-query'; -import { apiHasParentApi, apiPublishesTimeRange } from '@kbn/presentation-publishing'; import type { ChangePointEmbeddableApi } from '../embeddables/change_point_chart/types'; import type { AiopsPluginStartDeps } from '../types'; import type { ChangePointChartActionContext } from './change_point_action_context'; -import { isChangePointChartEmbeddableContext } from './change_point_action_context'; export const OPEN_CHANGE_POINT_IN_ML_APP_ACTION = 'openChangePointInMlAppAction'; -export const getEmbeddableTimeRange = ( +const getEmbeddableTimeRange = async ( embeddable: ChangePointEmbeddableApi -): TimeRange | undefined => { +): Promise => { + const { apiHasParentApi, apiPublishesTimeRange } = await import('@kbn/presentation-publishing'); + let timeRange = embeddable.timeRange$?.getValue(); if (!timeRange && apiHasParentApi(embeddable) && apiPublishesTimeRange(embeddable.parentApi)) { @@ -45,6 +45,7 @@ export function createOpenChangePointInMlAppAction( defaultMessage: 'Open in AIOps Labs', }), async getHref(context): Promise { + const { isChangePointChartEmbeddableContext } = await import('./change_point_action_context'); if (!isChangePointChartEmbeddableContext(context)) { throw new IncompatibleActionError(); } @@ -57,7 +58,7 @@ export function createOpenChangePointInMlAppAction( page: 'aiops/change_point_detection', pageState: { index: dataViewId.getValue(), - timeRange: getEmbeddableTimeRange(context.embeddable), + timeRange: await getEmbeddableTimeRange(context.embeddable), fieldConfigs: [ { fn: fn.getValue(), @@ -69,6 +70,7 @@ export function createOpenChangePointInMlAppAction( }); }, async execute(context) { + const { isChangePointChartEmbeddableContext } = await import('./change_point_action_context'); if (!isChangePointChartEmbeddableContext(context)) { throw new IncompatibleActionError(); } @@ -78,6 +80,7 @@ export function createOpenChangePointInMlAppAction( } }, async isCompatible(context) { + const { isChangePointChartEmbeddableContext } = await import('./change_point_action_context'); return isChangePointChartEmbeddableContext(context); }, }; diff --git a/x-pack/plugins/alerting/kibana.jsonc b/x-pack/plugins/alerting/kibana.jsonc index 9b6e523e27ebf..0b5f930dbb34a 100644 --- a/x-pack/plugins/alerting/kibana.jsonc +++ b/x-pack/plugins/alerting/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/alerting-plugin", - "owner": "@elastic/response-ops", + "owner": [ + "@elastic/response-ops" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "alerting", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "alerting" @@ -32,11 +36,11 @@ "security", "monitoringCollection", "spaces", - "serverless", + "serverless" ], "extraPublicDirs": [ "common", "common/parse_duration" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/banners/kibana.jsonc b/x-pack/plugins/banners/kibana.jsonc index 75d275a6bde4a..67a5f3b1b79d0 100644 --- a/x-pack/plugins/banners/kibana.jsonc +++ b/x-pack/plugins/banners/kibana.jsonc @@ -1,22 +1,26 @@ { "type": "plugin", "id": "@kbn/banners-plugin", - "owner": "@elastic/appex-sharedux", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private", "plugin": { "id": "banners", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "banners" ], - "enabledOnAnonymousPages": true, "requiredPlugins": [ "licensing" ], "optionalPlugins": [ "screenshotMode" ], - "requiredBundles": [] + "requiredBundles": [], + "enabledOnAnonymousPages": true } -} +} \ No newline at end of file diff --git a/x-pack/plugins/canvas/kibana.jsonc b/x-pack/plugins/canvas/kibana.jsonc index f752f37443fef..8beb33892de1a 100644 --- a/x-pack/plugins/canvas/kibana.jsonc +++ b/x-pack/plugins/canvas/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/canvas-plugin", - "owner": "@elastic/kibana-presentation", + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "private", "description": "Adds Canvas application to Kibana", "plugin": { "id": "canvas", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "canvas" @@ -36,7 +40,7 @@ "home", "reporting", "spaces", - "usageCollection", + "usageCollection" ], "requiredBundles": [ "kibanaReact", @@ -45,6 +49,6 @@ "maps", "visualizations", "fieldFormats" - ], + ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/cases/kibana.jsonc b/x-pack/plugins/cases/kibana.jsonc index 300b1ee4c2c12..e5dafa52a3a11 100644 --- a/x-pack/plugins/cases/kibana.jsonc +++ b/x-pack/plugins/cases/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/cases-plugin", - "owner": "@elastic/response-ops", + "owner": [ + "@elastic/response-ops" + ], + "group": "platform", + "visibility": "shared", "description": "The Case management system in Kibana", "plugin": { "id": "cases", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "cases" @@ -27,7 +31,7 @@ "ruleRegistry", "files", "contentManagement", - "uiActions", + "uiActions" ], "optionalPlugins": [ "cloud", @@ -35,16 +39,16 @@ "taskManager", "usageCollection", "spaces", - "serverless", + "serverless" ], "requiredBundles": [ "esUiShared", "kibanaReact", "kibanaUtils", - "savedObjectsFinder", + "savedObjectsFinder" ], "extraPublicDirs": [ "common" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/cases/public/components/add_comment/index.test.tsx b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx index 68cf0c8a1e2b5..5664151aa6df0 100644 --- a/x-pack/plugins/cases/public/components/add_comment/index.test.tsx +++ b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx @@ -21,12 +21,13 @@ import { CasesTimelineIntegrationProvider } from '../timeline_context'; import { timelineIntegrationMock } from '../__mock__/timeline'; import type { CaseAttachmentWithoutOwner } from '../../types'; import type { AppMockRenderer } from '../../common/mock'; +import { useCreateAttachments } from '../../containers/use_create_attachments'; -jest.mock('../../containers/api', () => ({ - createAttachments: jest.fn(), -})); +jest.mock('../../containers/use_create_attachments'); -const createAttachmentsMock = createAttachments as jest.Mock; +const useCreateAttachmentsMock = useCreateAttachments as jest.Mock; + +const createAttachmentsMock = jest.fn().mockImplementation(() => defaultResponse); const onCommentSaving = jest.fn(); const onCommentPosted = jest.fn(); @@ -58,7 +59,10 @@ describe('AddComment ', () => { beforeEach(() => { jest.clearAllMocks(); appMockRender = createAppMockRenderer(); - createAttachmentsMock.mockImplementation(() => defaultResponse); + useCreateAttachmentsMock.mockReturnValue({ + isLoading: false, + mutate: createAttachmentsMock, + }); }); afterEach(() => { @@ -72,6 +76,11 @@ describe('AddComment ', () => { }); it('should render spinner and disable submit when loading', async () => { + useCreateAttachmentsMock.mockReturnValue({ + isLoading: true, + mutateAsync: createAttachmentsMock, + }); + appMockRender.render(); fireEvent.change(screen.getByLabelText('caseComment'), { @@ -109,16 +118,19 @@ describe('AddComment ', () => { await waitFor(() => expect(onCommentSaving).toBeCalled()); await waitFor(() => - expect(createAttachmentsMock).toBeCalledWith({ - caseId: addCommentProps.caseId, - attachments: [ - { - comment: sampleData.comment, - owner: SECURITY_SOLUTION_OWNER, - type: AttachmentType.user, - }, - ], - }) + expect(createAttachmentsMock).toBeCalledWith( + { + caseId: addCommentProps.caseId, + attachments: [ + { + comment: sampleData.comment, + type: AttachmentType.user, + }, + ], + caseOwner: SECURITY_SOLUTION_OWNER, + }, + { onSuccess: expect.any(Function) } + ) ); await waitFor(() => { expect(screen.getByTestId('euiMarkdownEditorTextArea')).toHaveTextContent(''); @@ -258,16 +270,19 @@ describe('draft comment ', () => { await waitFor(() => { expect(onCommentSaving).toBeCalled(); - expect(createAttachmentsMock).toBeCalledWith({ - caseId: addCommentProps.caseId, - attachments: [ - { - comment: sampleData.comment, - owner: SECURITY_SOLUTION_OWNER, - type: AttachmentType.user, - }, - ], - }); + expect(createAttachmentsMock).toBeCalledWith( + { + caseId: addCommentProps.caseId, + attachments: [ + { + comment: sampleData.comment, + type: AttachmentType.user, + }, + ], + caseOwner: SECURITY_SOLUTION_OWNER, + }, + { onSuccess: expect.any(Function) } + ); }); await waitFor(() => { diff --git a/x-pack/plugins/cases/public/components/create/index.test.tsx b/x-pack/plugins/cases/public/components/create/index.test.tsx index bb519b1f6f778..37e817d00f331 100644 --- a/x-pack/plugins/cases/public/components/create/index.test.tsx +++ b/x-pack/plugins/cases/public/components/create/index.test.tsx @@ -172,6 +172,8 @@ describe('CreateCase case', () => { await user.click(screen.getByTestId('create-case-submit')); - expect(defaultProps.onSuccess).toHaveBeenCalled(); + await waitFor(() => { + expect(defaultProps.onSuccess).toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx b/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx index fccca04bb278f..75c2694f89479 100644 --- a/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx +++ b/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx @@ -13,7 +13,6 @@ import { usePushToService } from '.'; import { noPushCasesPermissions, readCasesPermissions, TestProviders } from '../../common/mock'; import { usePostPushToService } from '../../containers/use_post_push_to_service'; import { actionLicenses } from '../../containers/mock'; -import { CLOSED_CASE_PUSH_ERROR_ID } from './callout/types'; import { useGetActionLicense } from '../../containers/use_get_action_license'; import { getCaseConnectorsMockResponse } from '../../common/mock/connectors'; import { useRefreshCaseViewPage } from '../case_view/use_on_refresh_case_view_page'; @@ -182,27 +181,6 @@ describe('usePushToService', () => { expect(result.current.hasErrorMessages).toBe(true); }); - it('Displays message when case is closed', async () => { - const { result } = renderHook< - React.PropsWithChildren, - ReturnUsePushToService - >( - () => - usePushToService({ - ...defaultArgs, - caseStatus: CaseStatuses.closed, - }), - { - wrapper: ({ children }) => {children}, - } - ); - - const errorsMsg = result.current.errorsMsg; - expect(errorsMsg).toHaveLength(1); - expect(errorsMsg[0].id).toEqual(CLOSED_CASE_PUSH_ERROR_ID); - expect(result.current.hasErrorMessages).toBe(true); - }); - it('should not call pushCaseToExternalService when the selected connector is none', async () => { const { result } = renderHook< React.PropsWithChildren, @@ -460,7 +438,7 @@ describe('usePushToService', () => { const { result } = renderHook< React.PropsWithChildren, ReturnUsePushToService - >(() => usePushToService({ ...defaultArgs, caseStatus: CaseStatuses.closed }), { + >(() => usePushToService({ ...defaultArgs, isValidConnector: false }), { wrapper: ({ children }) => {children}, }); diff --git a/x-pack/plugins/cases/public/components/use_push_to_service/index.tsx b/x-pack/plugins/cases/public/components/use_push_to_service/index.tsx index 63a016964651e..465e48bddade7 100644 --- a/x-pack/plugins/cases/public/components/use_push_to_service/index.tsx +++ b/x-pack/plugins/cases/public/components/use_push_to_service/index.tsx @@ -13,10 +13,8 @@ import { getKibanaConfigError, getConnectorMissingInfo, getDeletedConnectorError, - getCaseClosedInfo, } from './helpers'; import type { CaseConnector } from '../../../common/types/domain'; -import { CaseStatuses } from '../../../common/types/domain'; import type { ErrorMessage } from './callout/types'; import { useRefreshCaseViewPage } from '../case_view/use_on_refresh_case_view_page'; import { useGetActionLicense } from '../../containers/use_get_action_license'; @@ -44,7 +42,6 @@ export interface ReturnUsePushToService { export const usePushToService = ({ caseId, - caseStatus, caseConnectors, connector, isValidConnector, @@ -108,14 +105,9 @@ export const usePushToService = ({ return [getDeletedConnectorError()]; } - if (caseStatus === CaseStatuses.closed) { - return [getCaseClosedInfo()]; - } - return errors; }, [ actionLicense, - caseStatus, connector.id, hasLicenseError, isValidConnector, diff --git a/x-pack/plugins/cases/server/client/cases/push.ts b/x-pack/plugins/cases/server/client/cases/push.ts index 85d853f825907..6d4561c7b5119 100644 --- a/x-pack/plugins/cases/server/client/cases/push.ts +++ b/x-pack/plugins/cases/server/client/cases/push.ts @@ -140,12 +140,6 @@ export const push = async ( operation: Operations.pushCase, }); - if (theCase?.status === CaseStatuses.closed) { - throw Boom.conflict( - `The ${theCase.title} case is closed. Pushing a closed case is not allowed.` - ); - } - const alertsInfo = getAlertInfoFromComments(theCase?.comments); const alerts = await getAlerts(alertsInfo, clientArgs); const profiles = await getProfiles(theCase, securityStartPlugin); diff --git a/x-pack/plugins/cases/server/saved_object_types/cases/model_versions.test.ts b/x-pack/plugins/cases/server/saved_object_types/cases/model_versions.test.ts index 8520fd9673d31..2c301709ca5c9 100644 --- a/x-pack/plugins/cases/server/saved_object_types/cases/model_versions.test.ts +++ b/x-pack/plugins/cases/server/saved_object_types/cases/model_versions.test.ts @@ -10,51 +10,49 @@ import { modelVersion1 } from './model_versions'; describe('Model versions', () => { describe('1', () => { it('returns the model version correctly', () => { - expect(modelVersion1).toMatchInlineSnapshot(` - Object { - "changes": Array [ - Object { - "addedMappings": Object { - "customFields": Object { - "properties": Object { - "key": Object { - "type": "keyword", - }, - "type": Object { - "type": "keyword", - }, - "value": Object { - "fields": Object { - "boolean": Object { - "ignore_malformed": true, - "type": "boolean", - }, - "date": Object { - "ignore_malformed": true, - "type": "date", - }, - "ip": Object { - "ignore_malformed": true, - "type": "ip", - }, - "number": Object { - "ignore_malformed": true, - "type": "long", - }, - "string": Object { - "type": "text", - }, + expect(modelVersion1.changes).toMatchInlineSnapshot(` + Array [ + Object { + "addedMappings": Object { + "customFields": Object { + "properties": Object { + "key": Object { + "type": "keyword", + }, + "type": Object { + "type": "keyword", + }, + "value": Object { + "fields": Object { + "boolean": Object { + "ignore_malformed": true, + "type": "boolean", + }, + "date": Object { + "ignore_malformed": true, + "type": "date", + }, + "ip": Object { + "ignore_malformed": true, + "type": "ip", + }, + "number": Object { + "ignore_malformed": true, + "type": "long", + }, + "string": Object { + "type": "text", }, - "type": "keyword", }, + "type": "keyword", }, - "type": "nested", }, + "type": "nested", }, - "type": "mappings_addition", }, - ], - } + "type": "mappings_addition", + }, + ] `); }); }); diff --git a/x-pack/plugins/cases/server/saved_object_types/cases/model_versions.ts b/x-pack/plugins/cases/server/saved_object_types/cases/model_versions.ts index 56806e7dec607..7d46789a3b79f 100644 --- a/x-pack/plugins/cases/server/saved_object_types/cases/model_versions.ts +++ b/x-pack/plugins/cases/server/saved_object_types/cases/model_versions.ts @@ -6,6 +6,7 @@ */ import type { SavedObjectsModelVersion } from '@kbn/core-saved-objects-server'; +import { casesSchemaV1 } from './schemas'; /** * Adds custom fields to the cases SO. @@ -54,4 +55,7 @@ export const modelVersion1: SavedObjectsModelVersion = { }, }, ], + schemas: { + forwardCompatibility: casesSchemaV1.extends({}, { unknowns: 'ignore' }), + }, }; diff --git a/x-pack/plugins/cases/server/saved_object_types/cases/schemas/index.ts b/x-pack/plugins/cases/server/saved_object_types/cases/schemas/index.ts new file mode 100644 index 0000000000000..85d9239f72dba --- /dev/null +++ b/x-pack/plugins/cases/server/saved_object_types/cases/schemas/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './latest'; + +export { casesSchema as casesSchemaV1 } from './v1'; diff --git a/x-pack/plugins/cases/server/saved_object_types/cases/schemas/latest.ts b/x-pack/plugins/cases/server/saved_object_types/cases/schemas/latest.ts new file mode 100644 index 0000000000000..25300c97a6d2e --- /dev/null +++ b/x-pack/plugins/cases/server/saved_object_types/cases/schemas/latest.ts @@ -0,0 +1,8 @@ +/* + * 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 * from './v1'; diff --git a/x-pack/plugins/cases/server/saved_object_types/cases/schemas/v1.ts b/x-pack/plugins/cases/server/saved_object_types/cases/schemas/v1.ts new file mode 100644 index 0000000000000..1a6bb0a8cdd8c --- /dev/null +++ b/x-pack/plugins/cases/server/saved_object_types/cases/schemas/v1.ts @@ -0,0 +1,71 @@ +/* + * 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 '@kbn/config-schema'; + +const UserSchema = schema.object({ + email: schema.nullable(schema.string()), + full_name: schema.nullable(schema.string()), + username: schema.nullable(schema.string()), + profile_uid: schema.nullable(schema.string()), +}); + +const UserProfileSchema = schema.object({ uid: schema.string() }); + +const ConnectorSchema = schema.object({ + name: schema.string(), + type: schema.string(), + fields: schema.arrayOf(schema.object({ key: schema.string(), value: schema.string() })), +}); + +const ExternalServiceSchema = schema.object({ + connector_name: schema.string(), + external_id: schema.string(), + external_title: schema.string(), + external_url: schema.string(), + pushed_at: schema.string(), + pushed_by: UserSchema, +}); + +const SettingsSchema = schema.object({ syncAlerts: schema.boolean() }); + +const CustomFieldsSchema = schema.arrayOf( + schema.object({ + key: schema.string(), + type: schema.string(), + value: schema.nullable(schema.any()), + }) +); + +export const casesSchema = schema.object({ + assignees: schema.arrayOf(UserProfileSchema), + category: schema.maybe(schema.nullable(schema.string())), + closed_at: schema.nullable(schema.string()), + closed_by: schema.nullable(UserSchema), + created_at: schema.string(), + created_by: UserSchema, + connector: ConnectorSchema, + customFields: schema.maybe(schema.nullable(CustomFieldsSchema)), + description: schema.string(), + duration: schema.nullable(schema.number()), + external_service: schema.nullable(ExternalServiceSchema), + owner: schema.string(), + settings: SettingsSchema, + severity: schema.oneOf([ + schema.literal(10), + schema.literal(20), + schema.literal(30), + schema.literal(40), + ]), + status: schema.oneOf([schema.literal(0), schema.literal(10), schema.literal(20)]), + tags: schema.arrayOf(schema.string()), + title: schema.string(), + total_alerts: schema.number(), + total_comments: schema.number(), + updated_at: schema.nullable(schema.string()), + updated_by: schema.nullable(UserSchema), +}); diff --git a/x-pack/plugins/cloud/kibana.jsonc b/x-pack/plugins/cloud/kibana.jsonc index 17edf376bf5cb..18698c2a654b0 100644 --- a/x-pack/plugins/cloud/kibana.jsonc +++ b/x-pack/plugins/cloud/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/cloud-plugin", - "owner": "@elastic/kibana-core", + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "cloud", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "cloud" @@ -14,4 +18,4 @@ "usageCollection" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/cloud_defend/kibana.jsonc b/x-pack/plugins/cloud_defend/kibana.jsonc index d7854913945ff..f0155401048d2 100644 --- a/x-pack/plugins/cloud_defend/kibana.jsonc +++ b/x-pack/plugins/cloud_defend/kibana.jsonc @@ -1,13 +1,20 @@ { "type": "plugin", "id": "@kbn/cloud-defend-plugin", - "owner": "@elastic/kibana-cloud-security-posture", + "owner": [ + "@elastic/kibana-cloud-security-posture" + ], + "group": "security", + "visibility": "private", "description": "Defend for containers (D4C)", "plugin": { "id": "cloudDefend", - "server": true, "browser": true, - "configPath": ["xpack", "cloudDefend"], + "server": true, + "configPath": [ + "xpack", + "cloudDefend" + ], "requiredPlugins": [ "navigation", "data", @@ -19,7 +26,12 @@ "licensing", "kubernetesSecurity" ], - "optionalPlugins": ["usageCollection"], - "requiredBundles": ["kibanaReact", "usageCollection"] + "optionalPlugins": [ + "usageCollection" + ], + "requiredBundles": [ + "kibanaReact", + "usageCollection" + ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/kibana.jsonc b/x-pack/plugins/cloud_integrations/cloud_chat/kibana.jsonc index dad2a22752df1..1a1c833e59ca8 100644 --- a/x-pack/plugins/cloud_integrations/cloud_chat/kibana.jsonc +++ b/x-pack/plugins/cloud_integrations/cloud_chat/kibana.jsonc @@ -1,7 +1,11 @@ { "type": "plugin", "id": "@kbn/cloud-chat-plugin", - "owner": "@elastic/kibana-core", + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private", "description": "Chat available on Elastic Cloud deployments for quicker assistance.", "plugin": { "id": "cloudChat", @@ -15,9 +19,7 @@ "requiredPlugins": [ "cloud" ], - "requiredBundles": [ - ], - "optionalPlugins": [ - ] + "optionalPlugins": [], + "requiredBundles": [] } } diff --git a/x-pack/plugins/cloud_integrations/cloud_data_migration/kibana.jsonc b/x-pack/plugins/cloud_integrations/cloud_data_migration/kibana.jsonc index ea019ef61b15e..d2b9883a0c741 100644 --- a/x-pack/plugins/cloud_integrations/cloud_data_migration/kibana.jsonc +++ b/x-pack/plugins/cloud_integrations/cloud_data_migration/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/cloud-data-migration-plugin", - "owner": "@elastic/kibana-management", + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "private", "description": "Static migration page where self-managed users can see text/copy about migrating to Elastic Cloud", "plugin": { "id": "cloudDataMigration", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "cloud_integrations", @@ -22,4 +26,4 @@ "kibanaReact" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/kibana.jsonc b/x-pack/plugins/cloud_integrations/cloud_experiments/kibana.jsonc index 3c6b9f8279f01..8ea0fd75ea553 100644 --- a/x-pack/plugins/cloud_integrations/cloud_experiments/kibana.jsonc +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/cloud-experiments-plugin", - "owner": "@elastic/kibana-core", + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", "description": "Provides the necessary APIs to implement A/B testing scenarios, fetching the variations in configuration and reporting back metrics to track conversion rates of the experiments.", "plugin": { "id": "cloudExperiments", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "cloud_integrations", @@ -18,4 +22,4 @@ "usageCollection" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/cloud_integrations/cloud_full_story/kibana.jsonc b/x-pack/plugins/cloud_integrations/cloud_full_story/kibana.jsonc index 53a42a6e903f2..e9bb4a8df07dd 100644 --- a/x-pack/plugins/cloud_integrations/cloud_full_story/kibana.jsonc +++ b/x-pack/plugins/cloud_integrations/cloud_full_story/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/cloud-full-story-plugin", - "owner": "@elastic/kibana-core", + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private", "description": "When Kibana runs on Elastic Cloud, this plugin registers FullStory as a shipper for telemetry.", "plugin": { "id": "cloudFullStory", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "cloud_integrations", @@ -19,4 +23,4 @@ "security" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/cloud_integrations/cloud_links/kibana.jsonc b/x-pack/plugins/cloud_integrations/cloud_links/kibana.jsonc index a8dbc9b23af63..46259fa3072a5 100644 --- a/x-pack/plugins/cloud_integrations/cloud_links/kibana.jsonc +++ b/x-pack/plugins/cloud_integrations/cloud_links/kibana.jsonc @@ -1,20 +1,24 @@ { "type": "plugin", "id": "@kbn/cloud-links-plugin", - "owner": "@elastic/kibana-core", + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private", "description": "Adds the links to the Elastic Cloud console", "plugin": { "id": "cloudLinks", - "server": false, "browser": true, + "server": false, + "requiredPlugins": [ + "share" + ], "optionalPlugins": [ "cloud", "security", "guidedOnboarding" ], - "requiredBundles": [], - "requiredPlugins": [ - "share" - ] + "requiredBundles": [] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/cloud_security_posture/common/constants.ts b/x-pack/plugins/cloud_security_posture/common/constants.ts index efc56a0da7995..80a6532c4a094 100644 --- a/x-pack/plugins/cloud_security_posture/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/common/constants.ts @@ -174,4 +174,4 @@ export const SINGLE_ACCOUNT = 'single-account'; export const CLOUD_SECURITY_PLUGIN_VERSION = '1.9.0'; // Cloud Credentials Template url was implemented in 1.10.0-preview01. See PR - https://github.com/elastic/integrations/pull/9828 -export const CLOUD_CREDENTIALS_PACKAGE_VERSION = '1.11.0-preview10'; +export const CLOUD_CREDENTIALS_PACKAGE_VERSION = '1.11.0-preview13'; diff --git a/x-pack/plugins/cloud_security_posture/kibana.jsonc b/x-pack/plugins/cloud_security_posture/kibana.jsonc index d1aacf2f340fc..d43f37fd70484 100644 --- a/x-pack/plugins/cloud_security_posture/kibana.jsonc +++ b/x-pack/plugins/cloud_security_posture/kibana.jsonc @@ -1,13 +1,20 @@ { "type": "plugin", "id": "@kbn/cloud-security-posture-plugin", - "owner": "@elastic/kibana-cloud-security-posture", + "owner": [ + "@elastic/kibana-cloud-security-posture" + ], + "group": "security", + "visibility": "private", "description": "The cloud security posture plugin", "plugin": { "id": "cloudSecurityPosture", - "server": true, "browser": true, - "configPath": ["xpack", "cloudSecurityPosture"], + "server": true, + "configPath": [ + "xpack", + "cloudSecurityPosture" + ], "requiredPlugins": [ "navigation", "data", @@ -25,7 +32,12 @@ "alerting", "spaces" ], - "optionalPlugins": ["usageCollection"], - "requiredBundles": ["kibanaReact", "usageCollection"] + "optionalPlugins": [ + "usageCollection" + ], + "requiredBundles": [ + "kibanaReact", + "usageCollection" + ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credential_form.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credential_form.tsx index 638af9617e008..7d6d42c70e767 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credential_form.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credential_form.tsx @@ -500,7 +500,7 @@ export const GcpCredentialsForm = ({ diff --git a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/bulk_action/bulk_action.ts b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/bulk_action/bulk_action.ts index 14b55541a1baf..b72cb27088eda 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/bulk_action/bulk_action.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/bulk_action/bulk_action.ts @@ -44,8 +44,10 @@ export const defineBulkActionCspBenchmarkRulesRoute = (router: CspRouter) => .post({ access: 'internal', path: CSP_BENCHMARK_RULES_BULK_ACTION_ROUTE_PATH, - options: { - tags: ['access:cloud-security-posture-all'], + security: { + authz: { + requiredPrivileges: ['cloud-security-posture-all'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/find.ts b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/find.ts index 738a8774266d8..a205ad95419db 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/find.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/find.ts @@ -25,8 +25,10 @@ export const defineFindCspBenchmarkRuleRoute = (router: CspRouter) => .get({ access: 'internal', path: FIND_CSP_BENCHMARK_RULE_ROUTE_PATH, - options: { - tags: ['access:cloud-security-posture-read'], + security: { + authz: { + requiredPrivileges: ['cloud-security-posture-read'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/get_states/get_states.ts b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/get_states/get_states.ts index 31ef05abc7ccd..a737313ffc66a 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/get_states/get_states.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/get_states/get_states.ts @@ -16,8 +16,10 @@ export const defineGetCspBenchmarkRulesStatesRoute = (router: CspRouter) => .get({ access: 'internal', path: CSP_GET_BENCHMARK_RULES_STATE_ROUTE_PATH, - options: { - tags: ['access:cloud-security-posture-read'], + security: { + authz: { + requiredPrivileges: ['cloud-security-posture-read'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/cloud_security_posture/server/routes/benchmarks/benchmarks.ts b/x-pack/plugins/cloud_security_posture/server/routes/benchmarks/benchmarks.ts index c3854b1dafb4d..efbdedad3d3a5 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/benchmarks/benchmarks.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/benchmarks/benchmarks.ts @@ -20,8 +20,10 @@ export const defineGetBenchmarksRoute = (router: CspRouter) => .get({ access: 'internal', path: BENCHMARKS_ROUTE_PATH, - options: { - tags: ['access:cloud-security-posture-read'], + security: { + authz: { + requiredPrivileges: ['cloud-security-posture-read'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts index 851fa865566f7..481433e1efd56 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts @@ -65,8 +65,10 @@ export const defineGetComplianceDashboardRoute = (router: CspRouter) => .get({ access: 'internal', path: STATS_ROUTE_PATH, - options: { - tags: ['access:cloud-security-posture-read'], + security: { + authz: { + requiredPrivileges: ['cloud-security-posture-read'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/cloud_security_posture/server/routes/detection_engine/get_detection_engine_alerts_count_by_rule_tags.ts b/x-pack/plugins/cloud_security_posture/server/routes/detection_engine/get_detection_engine_alerts_count_by_rule_tags.ts index 6455b34707f70..38a9e356a1446 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/detection_engine/get_detection_engine_alerts_count_by_rule_tags.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/detection_engine/get_detection_engine_alerts_count_by_rule_tags.ts @@ -53,8 +53,10 @@ export const defineGetDetectionEngineAlertsStatus = (router: CspRouter) => .get({ access: 'internal', path: GET_DETECTION_RULE_ALERTS_STATUS_PATH, - options: { - tags: ['access:cloud-security-posture-read'], + security: { + authz: { + requiredPrivileges: ['cloud-security-posture-read'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/cloud_security_posture/server/routes/graph/route.ts b/x-pack/plugins/cloud_security_posture/server/routes/graph/route.ts index 9ff15c2be73e6..9e9744b33d940 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/graph/route.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/graph/route.ts @@ -20,8 +20,10 @@ export const defineGraphRoute = (router: CspRouter) => access: 'internal', enableQueryVersion: true, path: GRAPH_ROUTE_PATH, - options: { - tags: ['access:cloud-security-posture-read'], + security: { + authz: { + requiredPrivileges: ['cloud-security-posture-read'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/cloud_security_posture/server/routes/status/status.ts b/x-pack/plugins/cloud_security_posture/server/routes/status/status.ts index 4f5c84b936fb2..066d0c936e27c 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/status/status.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/status/status.ts @@ -437,8 +437,10 @@ export const defineGetCspStatusRoute = ( .get({ access: 'internal', path: STATUS_ROUTE_PATH, - options: { - tags: ['access:cloud-security-posture-read'], + security: { + authz: { + requiredPrivileges: ['cloud-security-posture-read'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/cloud_security_posture/server/routes/vulnerabilities_dashboard/vulnerabilities_dashboard.ts b/x-pack/plugins/cloud_security_posture/server/routes/vulnerabilities_dashboard/vulnerabilities_dashboard.ts index f7de7f1be4b65..e336e6dbc0c02 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/vulnerabilities_dashboard/vulnerabilities_dashboard.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/vulnerabilities_dashboard/vulnerabilities_dashboard.ts @@ -20,8 +20,10 @@ export const defineGetVulnerabilitiesDashboardRoute = (router: CspRouter): void { path: VULNERABILITIES_DASHBOARD_ROUTE_PATH, validate: false, - options: { - tags: ['access:cloud-security-posture-read'], + security: { + authz: { + requiredPrivileges: ['cloud-security-posture-read'], + }, }, }, async (context, request, response) => { diff --git a/x-pack/plugins/cross_cluster_replication/kibana.jsonc b/x-pack/plugins/cross_cluster_replication/kibana.jsonc index 0b85ba18781d8..5b29c4fb56618 100644 --- a/x-pack/plugins/cross_cluster_replication/kibana.jsonc +++ b/x-pack/plugins/cross_cluster_replication/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/cross-cluster-replication-plugin", - "owner": "@elastic/kibana-management", + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "private", "plugin": { "id": "crossClusterReplication", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "ccr" @@ -28,4 +32,4 @@ "data" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/custom_branding/kibana.jsonc b/x-pack/plugins/custom_branding/kibana.jsonc index 5bd0fccb1f020..01e982e86eb3f 100644 --- a/x-pack/plugins/custom_branding/kibana.jsonc +++ b/x-pack/plugins/custom_branding/kibana.jsonc @@ -1,15 +1,19 @@ { "type": "plugin", "id": "@kbn/custom-branding-plugin", - "owner": "@elastic/appex-sharedux", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private", "description": " Enables customization of Kibana", "plugin": { "id": "customBranding", - "server": true, "browser": false, + "server": true, "requiredPlugins": [ "licensing", "licenseApiGuard" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/dashboard_enhanced/kibana.jsonc b/x-pack/plugins/dashboard_enhanced/kibana.jsonc index e74f2000ce39f..bdf9ac804bac3 100644 --- a/x-pack/plugins/dashboard_enhanced/kibana.jsonc +++ b/x-pack/plugins/dashboard_enhanced/kibana.jsonc @@ -1,12 +1,19 @@ { "type": "plugin", "id": "@kbn/dashboard-enhanced-plugin", - "owner": "@elastic/kibana-presentation", + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "dashboardEnhanced", - "server": true, "browser": true, - "configPath": ["xpack", "dashboardEnhanced"], + "server": true, + "configPath": [ + "xpack", + "dashboardEnhanced" + ], "requiredPlugins": [ "dashboard", "data", @@ -24,4 +31,4 @@ "uiActions" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/data_quality/kibana.jsonc b/x-pack/plugins/data_quality/kibana.jsonc index ad1a64d4ed140..dc54e20f40bd7 100644 --- a/x-pack/plugins/data_quality/kibana.jsonc +++ b/x-pack/plugins/data_quality/kibana.jsonc @@ -2,6 +2,8 @@ "type": "plugin", "id": "@kbn/data-quality-plugin", "owner": "@elastic/obs-ux-logs-team", + "group": "observability", + "visibility": "private", "plugin": { "id": "dataQuality", "server": true, diff --git a/x-pack/plugins/data_usage/common/test_utils/test_query_client_options.ts b/x-pack/plugins/data_usage/common/test_utils/test_query_client_options.ts new file mode 100644 index 0000000000000..c674e9b342eea --- /dev/null +++ b/x-pack/plugins/data_usage/common/test_utils/test_query_client_options.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ +/* eslint-disable no-console */ +export const dataUsageTestQueryClientOptions = { + defaultOptions: { + queries: { + retry: false, + cacheTime: 0, + }, + }, + logger: { + log: console.log, + warn: console.warn, + error: () => {}, + }, +}; diff --git a/x-pack/plugins/data_usage/kibana.jsonc b/x-pack/plugins/data_usage/kibana.jsonc index ffd8833351267..3706875c1ad94 100644 --- a/x-pack/plugins/data_usage/kibana.jsonc +++ b/x-pack/plugins/data_usage/kibana.jsonc @@ -5,6 +5,8 @@ "@elastic/obs-ai-assistant", "@elastic/security-solution" ], + "group": "platform", + "visibility": "private", "plugin": { "id": "dataUsage", "server": true, diff --git a/x-pack/plugins/data_usage/public/app/components/charts.tsx b/x-pack/plugins/data_usage/public/app/components/charts.tsx index 8d04324fb2246..56857e7a63ff9 100644 --- a/x-pack/plugins/data_usage/public/app/components/charts.tsx +++ b/x-pack/plugins/data_usage/public/app/components/charts.tsx @@ -9,18 +9,21 @@ import { EuiFlexGroup } from '@elastic/eui'; import { MetricTypes } from '../../../common/rest_types'; import { ChartPanel } from './chart_panel'; import { UsageMetricsResponseSchemaBody } from '../../../common/rest_types'; +import { useTestIdGenerator } from '../../hooks/use_test_id_generator'; interface ChartsProps { data: UsageMetricsResponseSchemaBody; + 'data-test-subj'?: string; } -export const Charts: React.FC = ({ data }) => { +export const Charts: React.FC = ({ data, 'data-test-subj': dataTestSubj }) => { + const getTestId = useTestIdGenerator(dataTestSubj); const [popoverOpen, setPopoverOpen] = useState(null); const togglePopover = useCallback((streamName: string | null) => { setPopoverOpen((prev) => (prev === streamName ? null : streamName)); }, []); return ( - + {Object.entries(data.metrics).map(([metricType, series], idx) => ( { + return { + useBreadcrumbs: jest.fn(), + }; +}); + +jest.mock('../../utils/use_kibana', () => { + return { + useKibanaContextForPlugin: () => ({ + services: mockServices, + }), + }; +}); + +jest.mock('../../hooks/use_get_usage_metrics', () => { + const original = jest.requireActual('../../hooks/use_get_usage_metrics'); + return { + ...original, + useGetDataUsageMetrics: jest.fn(original.useGetDataUsageMetrics), + }; +}); + +const mockUseLocation = jest.fn(() => ({ pathname: '/' })); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: () => mockUseLocation(), + useHistory: jest.fn().mockReturnValue({ + push: jest.fn(), + listen: jest.fn(), + location: { + search: '', + }, + }), +})); + +jest.mock('../../hooks/use_get_data_streams', () => { + const original = jest.requireActual('../../hooks/use_get_data_streams'); + return { + ...original, + useGetDataUsageDataStreams: jest.fn(original.useGetDataUsageDataStreams), + }; +}); + +jest.mock('@kbn/kibana-react-plugin/public', () => { + const original = jest.requireActual('@kbn/kibana-react-plugin/public'); + return { + ...original, + useKibana: () => ({ + services: { + uiSettings: { + get: jest.fn().mockImplementation((key) => { + const get = (k: 'dateFormat' | 'timepicker:quickRanges') => { + const x = { + dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS', + 'timepicker:quickRanges': [ + { + from: 'now/d', + to: 'now/d', + display: 'Today', + }, + { + from: 'now/w', + to: 'now/w', + display: 'This week', + }, + { + from: 'now-15m', + to: 'now', + display: 'Last 15 minutes', + }, + { + from: 'now-30m', + to: 'now', + display: 'Last 30 minutes', + }, + { + from: 'now-1h', + to: 'now', + display: 'Last 1 hour', + }, + { + from: 'now-24h', + to: 'now', + display: 'Last 24 hours', + }, + { + from: 'now-7d', + to: 'now', + display: 'Last 7 days', + }, + { + from: 'now-30d', + to: 'now', + display: 'Last 30 days', + }, + { + from: 'now-90d', + to: 'now', + display: 'Last 90 days', + }, + { + from: 'now-1y', + to: 'now', + display: 'Last 1 year', + }, + ], + }; + return x[k]; + }; + return get(key); + }), + }, + }, + }), + }; +}); +const mockUseGetDataUsageMetrics = useGetDataUsageMetrics as jest.Mock; +const mockUseGetDataUsageDataStreams = useGetDataUsageDataStreams as jest.Mock; +const mockServices = mockCore.createStart(); + +const getBaseMockedDataStreams = () => ({ + error: undefined, + data: undefined, + isFetching: false, + refetch: jest.fn(), +}); +const getBaseMockedDataUsageMetrics = () => ({ + error: undefined, + data: undefined, + isFetching: false, + refetch: jest.fn(), +}); + +describe('DataUsageMetrics', () => { + let user: UserEvent; + const testId = 'test'; + const testIdFilter = `${testId}-filter`; + + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + beforeEach(() => { + jest.clearAllMocks(); + user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime, pointerEventsCheck: 0 }); + mockUseGetDataUsageMetrics.mockReturnValue(getBaseMockedDataUsageMetrics); + mockUseGetDataUsageDataStreams.mockReturnValue(getBaseMockedDataStreams); + }); + + it('renders', () => { + const { getByTestId } = render(); + expect(getByTestId(`${testId}`)).toBeTruthy(); + }); + + it('should show date filter', () => { + const { getByTestId } = render(); + expect(getByTestId(`${testIdFilter}-date-range`)).toBeTruthy(); + expect(getByTestId(`${testIdFilter}-date-range`).textContent).toContain('Last 24 hours'); + expect(getByTestId(`${testIdFilter}-super-refresh-button`)).toBeTruthy(); + }); + + it('should not show data streams filter while fetching API', () => { + mockUseGetDataUsageDataStreams.mockReturnValue({ + ...getBaseMockedDataStreams, + isFetching: true, + }); + const { queryByTestId } = render(); + expect(queryByTestId(`${testIdFilter}-dataStreams-popoverButton`)).not.toBeTruthy(); + }); + + it('should show data streams filter', () => { + const { getByTestId } = render(); + expect(getByTestId(`${testIdFilter}-dataStreams-popoverButton`)).toBeTruthy(); + }); + + it('should show selected data streams on the filter', () => { + mockUseGetDataUsageDataStreams.mockReturnValue({ + error: undefined, + data: [ + { + name: '.ds-1', + storageSizeBytes: 10000, + }, + { + name: '.ds-2', + storageSizeBytes: 20000, + }, + { + name: '.ds-3', + storageSizeBytes: 10300, + }, + { + name: '.ds-4', + storageSizeBytes: 23000, + }, + { + name: '.ds-5', + storageSizeBytes: 23200, + }, + ], + isFetching: false, + }); + const { getByTestId } = render(); + expect(getByTestId(`${testIdFilter}-dataStreams-popoverButton`)).toHaveTextContent( + 'Data streams5' + ); + }); + + it('should allow de-selecting all but one data stream option', async () => { + mockUseGetDataUsageDataStreams.mockReturnValue({ + error: undefined, + data: [ + { + name: '.ds-1', + storageSizeBytes: 10000, + }, + { + name: '.ds-2', + storageSizeBytes: 20000, + }, + { + name: '.ds-3', + storageSizeBytes: 10300, + }, + { + name: '.ds-4', + storageSizeBytes: 23000, + }, + { + name: '.ds-5', + storageSizeBytes: 23200, + }, + ], + isFetching: false, + }); + const { getByTestId, getAllByTestId } = render(); + expect(getByTestId(`${testIdFilter}-dataStreams-popoverButton`)).toHaveTextContent( + 'Data streams5' + ); + await user.click(getByTestId(`${testIdFilter}-dataStreams-popoverButton`)); + const allFilterOptions = getAllByTestId('dataStreams-filter-option'); + for (let i = 0; i < allFilterOptions.length - 1; i++) { + await user.click(allFilterOptions[i]); + } + + expect(getByTestId(`${testIdFilter}-dataStreams-popoverButton`)).toHaveTextContent( + 'Data streams1' + ); + }); + + it('should not call usage metrics API if no data streams', async () => { + mockUseGetDataUsageDataStreams.mockReturnValue({ + ...getBaseMockedDataStreams, + data: [], + }); + render(); + expect(mockUseGetDataUsageMetrics).toHaveBeenCalledWith( + expect.any(Object), + expect.objectContaining({ enabled: false }) + ); + }); + + it('should show charts loading if data usage metrics API is fetching', () => { + mockUseGetDataUsageMetrics.mockReturnValue({ + ...getBaseMockedDataUsageMetrics, + isFetching: true, + }); + const { getByTestId } = render(); + expect(getByTestId(`${testId}-charts-loading`)).toBeTruthy(); + }); + + it('should show charts', () => { + mockUseGetDataUsageMetrics.mockReturnValue({ + ...getBaseMockedDataUsageMetrics, + isFetched: true, + data: { + metrics: { + ingest_rate: [ + { + name: '.ds-1', + data: [{ x: new Date(), y: 1000 }], + }, + { + name: '.ds-10', + data: [{ x: new Date(), y: 1100 }], + }, + ], + storage_retained: [ + { + name: '.ds-2', + data: [{ x: new Date(), y: 2000 }], + }, + { + name: '.ds-20', + data: [{ x: new Date(), y: 2100 }], + }, + ], + }, + }, + }); + const { getByTestId } = render(); + expect(getByTestId(`${testId}-charts`)).toBeTruthy(); + }); + + it('should refetch usage metrics with `Refresh` button click', async () => { + const refetch = jest.fn(); + mockUseGetDataUsageMetrics.mockReturnValue({ + ...getBaseMockedDataUsageMetrics, + data: ['.ds-1', '.ds-2'], + isFetched: true, + }); + mockUseGetDataUsageMetrics.mockReturnValue({ + ...getBaseMockedDataUsageMetrics, + isFetched: true, + refetch, + }); + const { getByTestId } = render(); + const refreshButton = getByTestId(`${testIdFilter}-super-refresh-button`); + // click refresh 5 times + for (let i = 0; i < 5; i++) { + await user.click(refreshButton); + } + + expect(mockUseGetDataUsageMetrics).toHaveBeenLastCalledWith( + expect.any(Object), + expect.objectContaining({ enabled: false }) + ); + expect(refetch).toHaveBeenCalledTimes(5); + }); + + it('should show error toast if usage metrics API fails', async () => { + mockUseGetDataUsageMetrics.mockReturnValue({ + ...getBaseMockedDataUsageMetrics, + isFetched: true, + error: new Error('Uh oh!'), + }); + render(); + await waitFor(() => { + expect(mockServices.notifications.toasts.addDanger).toHaveBeenCalledWith({ + title: 'Error getting usage metrics', + text: 'Uh oh!', + }); + }); + }); + + it('should show error toast if data streams API fails', async () => { + mockUseGetDataUsageDataStreams.mockReturnValue({ + ...getBaseMockedDataStreams, + isFetched: true, + error: new Error('Uh oh!'), + }); + render(); + await waitFor(() => { + expect(mockServices.notifications.toasts.addDanger).toHaveBeenCalledWith({ + title: 'Error getting data streams', + text: 'Uh oh!', + }); + }); + }); +}); diff --git a/x-pack/plugins/data_usage/public/app/components/data_usage_metrics.tsx b/x-pack/plugins/data_usage/public/app/components/data_usage_metrics.tsx index 929ebf7a02490..59354a1746346 100644 --- a/x-pack/plugins/data_usage/public/app/components/data_usage_metrics.tsx +++ b/x-pack/plugins/data_usage/public/app/components/data_usage_metrics.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { css } from '@emotion/react'; import { EuiFlexGroup, EuiFlexItem, EuiLoadingElastic } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -14,11 +14,12 @@ import { useBreadcrumbs } from '../../utils/use_breadcrumbs'; import { useKibanaContextForPlugin } from '../../utils/use_kibana'; import { PLUGIN_NAME } from '../../../common'; import { useGetDataUsageMetrics } from '../../hooks/use_get_usage_metrics'; +import { useGetDataUsageDataStreams } from '../../hooks/use_get_data_streams'; import { useDataUsageMetricsUrlParams } from '../hooks/use_charts_url_params'; import { DEFAULT_DATE_RANGE_OPTIONS, useDateRangePicker } from '../hooks/use_date_picker'; import { DEFAULT_METRIC_TYPES, UsageMetricsRequestBody } from '../../../common/rest_types'; import { ChartFilters, ChartFiltersProps } from './filters/charts_filters'; -import { useGetDataUsageDataStreams } from '../../hooks/use_get_data_streams'; +import { useTestIdGenerator } from '../../hooks/use_test_id_generator'; const EuiItemCss = css` width: 100%; @@ -28,181 +29,188 @@ const FlexItemWithCss = ({ children }: { children: React.ReactNode }) => ( {children} ); -export const DataUsageMetrics = () => { - const { - services: { chrome, appParams, notifications }, - } = useKibanaContextForPlugin(); - useBreadcrumbs([{ text: PLUGIN_NAME }], appParams, chrome); - - const { - metricTypes: metricTypesFromUrl, - dataStreams: dataStreamsFromUrl, - startDate: startDateFromUrl, - endDate: endDateFromUrl, - setUrlMetricTypesFilter, - setUrlDataStreamsFilter, - setUrlDateRangeFilter, - } = useDataUsageMetricsUrlParams(); - - const { - error: errorFetchingDataStreams, - data: dataStreams, - isFetching: isFetchingDataStreams, - } = useGetDataUsageDataStreams({ - selectedDataStreams: dataStreamsFromUrl, - options: { - enabled: true, - retry: false, - }, - }); - - const [metricsFilters, setMetricsFilters] = useState({ - metricTypes: [...DEFAULT_METRIC_TYPES], - dataStreams: [], - from: DEFAULT_DATE_RANGE_OPTIONS.startDate, - to: DEFAULT_DATE_RANGE_OPTIONS.endDate, - }); - - useEffect(() => { - if (!metricTypesFromUrl) { - setUrlMetricTypesFilter(metricsFilters.metricTypes.join(',')); - } - if (!dataStreamsFromUrl && dataStreams) { - setUrlDataStreamsFilter(dataStreams.map((ds) => ds.name).join(',')); - } - if (!startDateFromUrl || !endDateFromUrl) { - setUrlDateRangeFilter({ startDate: metricsFilters.from, endDate: metricsFilters.to }); - } - }, [ - dataStreams, - dataStreamsFromUrl, - endDateFromUrl, - metricTypesFromUrl, - metricsFilters.dataStreams, - metricsFilters.from, - metricsFilters.metricTypes, - metricsFilters.to, - setUrlDataStreamsFilter, - setUrlDateRangeFilter, - setUrlMetricTypesFilter, - startDateFromUrl, - ]); - - useEffect(() => { - setMetricsFilters((prevState) => ({ - ...prevState, - metricTypes: metricTypesFromUrl?.length ? metricTypesFromUrl : prevState.metricTypes, - dataStreams: dataStreamsFromUrl?.length ? dataStreamsFromUrl : prevState.dataStreams, - })); - }, [metricTypesFromUrl, dataStreamsFromUrl]); - - const { dateRangePickerState, onRefreshChange, onTimeChange } = useDateRangePicker(); - - const { - error: errorFetchingDataUsageMetrics, - data, - isFetching, - isFetched, - refetch: refetchDataUsageMetrics, - } = useGetDataUsageMetrics( - { - ...metricsFilters, - from: dateRangePickerState.startDate, - to: dateRangePickerState.endDate, - }, - { - retry: false, - enabled: !!metricsFilters.dataStreams.length, - } - ); - - const onRefresh = useCallback(() => { - refetchDataUsageMetrics(); - }, [refetchDataUsageMetrics]); - - const onChangeDataStreamsFilter = useCallback( - (selectedDataStreams: string[]) => { - setMetricsFilters((prevState) => ({ ...prevState, dataStreams: selectedDataStreams })); - }, - [setMetricsFilters] - ); - - const onChangeMetricTypesFilter = useCallback( - (selectedMetricTypes: string[]) => { - setMetricsFilters((prevState) => ({ ...prevState, metricTypes: selectedMetricTypes })); - }, - [setMetricsFilters] - ); - - const filterOptions: ChartFiltersProps['filterOptions'] = useMemo(() => { - const dataStreamsOptions = dataStreams?.reduce>((acc, ds) => { - acc[ds.name] = ds.storageSizeBytes; - return acc; - }, {}); - - return { - dataStreams: { - filterName: 'dataStreams', - options: dataStreamsOptions ? Object.keys(dataStreamsOptions) : metricsFilters.dataStreams, - appendOptions: dataStreamsOptions, - selectedOptions: metricsFilters.dataStreams, - onChangeFilterOptions: onChangeDataStreamsFilter, - isFilterLoading: isFetchingDataStreams, - }, - metricTypes: { - filterName: 'metricTypes', - options: metricsFilters.metricTypes, - onChangeFilterOptions: onChangeMetricTypesFilter, +export const DataUsageMetrics = memo( + ({ 'data-test-subj': dataTestSubj = 'data-usage-metrics' }: { 'data-test-subj'?: string }) => { + const getTestId = useTestIdGenerator(dataTestSubj); + + const { + services: { chrome, appParams, notifications }, + } = useKibanaContextForPlugin(); + useBreadcrumbs([{ text: PLUGIN_NAME }], appParams, chrome); + + const { + metricTypes: metricTypesFromUrl, + dataStreams: dataStreamsFromUrl, + startDate: startDateFromUrl, + endDate: endDateFromUrl, + setUrlMetricTypesFilter, + setUrlDataStreamsFilter, + setUrlDateRangeFilter, + } = useDataUsageMetricsUrlParams(); + + const { + error: errorFetchingDataStreams, + data: dataStreams, + isFetching: isFetchingDataStreams, + } = useGetDataUsageDataStreams({ + selectedDataStreams: dataStreamsFromUrl, + options: { + enabled: true, + retry: false, }, - }; - }, [ - dataStreams, - isFetchingDataStreams, - metricsFilters.dataStreams, - metricsFilters.metricTypes, - onChangeDataStreamsFilter, - onChangeMetricTypesFilter, - ]); - - if (errorFetchingDataUsageMetrics) { - notifications.toasts.addDanger({ - title: i18n.translate('xpack.dataUsage.getMetrics.addFailure.toast.title', { - defaultMessage: 'Error getting usage metrics', - }), - text: errorFetchingDataUsageMetrics.message, }); - } - if (errorFetchingDataStreams) { - notifications.toasts.addDanger({ - title: i18n.translate('xpack.dataUsage.getDataStreams.addFailure.toast.title', { - defaultMessage: 'Error getting data streams', - }), - text: errorFetchingDataStreams.message, + + const [metricsFilters, setMetricsFilters] = useState({ + metricTypes: [...DEFAULT_METRIC_TYPES], + dataStreams: [], + from: DEFAULT_DATE_RANGE_OPTIONS.startDate, + to: DEFAULT_DATE_RANGE_OPTIONS.endDate, }); - } - return ( - - - - - - - {isFetched && data?.metrics ? ( - - ) : isFetching ? ( - - ) : null} - - - ); -}; + useEffect(() => { + if (!metricTypesFromUrl) { + setUrlMetricTypesFilter(metricsFilters.metricTypes.join(',')); + } + if (!dataStreamsFromUrl && dataStreams) { + setUrlDataStreamsFilter(dataStreams.map((ds) => ds.name).join(',')); + } + if (!startDateFromUrl || !endDateFromUrl) { + setUrlDateRangeFilter({ startDate: metricsFilters.from, endDate: metricsFilters.to }); + } + }, [ + dataStreams, + dataStreamsFromUrl, + endDateFromUrl, + metricTypesFromUrl, + metricsFilters.dataStreams, + metricsFilters.from, + metricsFilters.metricTypes, + metricsFilters.to, + setUrlDataStreamsFilter, + setUrlDateRangeFilter, + setUrlMetricTypesFilter, + startDateFromUrl, + ]); + + useEffect(() => { + setMetricsFilters((prevState) => ({ + ...prevState, + metricTypes: metricTypesFromUrl?.length ? metricTypesFromUrl : prevState.metricTypes, + dataStreams: dataStreamsFromUrl?.length ? dataStreamsFromUrl : prevState.dataStreams, + })); + }, [metricTypesFromUrl, dataStreamsFromUrl]); + + const { dateRangePickerState, onRefreshChange, onTimeChange } = useDateRangePicker(); + + const { + error: errorFetchingDataUsageMetrics, + data, + isFetching, + isFetched, + refetch: refetchDataUsageMetrics, + } = useGetDataUsageMetrics( + { + ...metricsFilters, + from: dateRangePickerState.startDate, + to: dateRangePickerState.endDate, + }, + { + retry: false, + enabled: !!metricsFilters.dataStreams.length, + } + ); + + const onRefresh = useCallback(() => { + refetchDataUsageMetrics(); + }, [refetchDataUsageMetrics]); + + const onChangeDataStreamsFilter = useCallback( + (selectedDataStreams: string[]) => { + setMetricsFilters((prevState) => ({ ...prevState, dataStreams: selectedDataStreams })); + }, + [setMetricsFilters] + ); + + const onChangeMetricTypesFilter = useCallback( + (selectedMetricTypes: string[]) => { + setMetricsFilters((prevState) => ({ ...prevState, metricTypes: selectedMetricTypes })); + }, + [setMetricsFilters] + ); + + const filterOptions: ChartFiltersProps['filterOptions'] = useMemo(() => { + const dataStreamsOptions = dataStreams?.reduce>((acc, ds) => { + acc[ds.name] = ds.storageSizeBytes; + return acc; + }, {}); + + return { + dataStreams: { + filterName: 'dataStreams', + options: dataStreamsOptions + ? Object.keys(dataStreamsOptions) + : metricsFilters.dataStreams, + appendOptions: dataStreamsOptions, + selectedOptions: metricsFilters.dataStreams, + onChangeFilterOptions: onChangeDataStreamsFilter, + isFilterLoading: isFetchingDataStreams, + }, + metricTypes: { + filterName: 'metricTypes', + options: metricsFilters.metricTypes, + onChangeFilterOptions: onChangeMetricTypesFilter, + }, + }; + }, [ + dataStreams, + isFetchingDataStreams, + metricsFilters.dataStreams, + metricsFilters.metricTypes, + onChangeDataStreamsFilter, + onChangeMetricTypesFilter, + ]); + + if (errorFetchingDataUsageMetrics) { + notifications.toasts.addDanger({ + title: i18n.translate('xpack.dataUsage.getMetrics.addFailure.toast.title', { + defaultMessage: 'Error getting usage metrics', + }), + text: errorFetchingDataUsageMetrics.message, + }); + } + if (errorFetchingDataStreams) { + notifications.toasts.addDanger({ + title: i18n.translate('xpack.dataUsage.getDataStreams.addFailure.toast.title', { + defaultMessage: 'Error getting data streams', + }), + text: errorFetchingDataStreams.message, + }); + } + + return ( + + + + + + + {isFetched && data?.metrics ? ( + + ) : isFetching ? ( + + ) : null} + + + ); + } +); diff --git a/x-pack/plugins/data_usage/public/app/components/filters/charts_filter.tsx b/x-pack/plugins/data_usage/public/app/components/filters/charts_filter.tsx index 83d417565f012..6b4806537e74b 100644 --- a/x-pack/plugins/data_usage/public/app/components/filters/charts_filter.tsx +++ b/x-pack/plugins/data_usage/public/app/components/filters/charts_filter.tsx @@ -193,13 +193,10 @@ export const ChartsFilter = memo( > {(list, search) => { return ( -
+
{isSearchable && ( {search} diff --git a/x-pack/plugins/data_usage/public/app/components/filters/charts_filter_popover.tsx b/x-pack/plugins/data_usage/public/app/components/filters/charts_filter_popover.tsx index 2ed96f012c497..3c0237c84a0c9 100644 --- a/x-pack/plugins/data_usage/public/app/components/filters/charts_filter_popover.tsx +++ b/x-pack/plugins/data_usage/public/app/components/filters/charts_filter_popover.tsx @@ -42,7 +42,7 @@ export const ChartsFilterPopover = memo( const button = useMemo( () => ( ( const filters = useMemo(() => { return ( <> - {showMetricsTypesFilter && } + {showMetricsTypesFilter && ( + + )} {!filterOptions.dataStreams.isFilterLoading && ( - + )} ); - }, [filterOptions, showMetricsTypesFilter]); + }, [dataTestSubj, filterOptions, showMetricsTypesFilter]); const onClickRefreshButton = useCallback(() => onClick(), [onClick]); @@ -68,6 +70,7 @@ export const ChartFilters = memo( onRefresh={onRefresh} onRefreshChange={onRefreshChange} onTimeChange={onTimeChange} + data-test-subj={dataTestSubj} /> diff --git a/x-pack/plugins/data_usage/public/app/components/filters/date_picker.tsx b/x-pack/plugins/data_usage/public/app/components/filters/date_picker.tsx index 4d9b280d763ce..044a036eea61f 100644 --- a/x-pack/plugins/data_usage/public/app/components/filters/date_picker.tsx +++ b/x-pack/plugins/data_usage/public/app/components/filters/date_picker.tsx @@ -15,6 +15,7 @@ import type { OnRefreshChangeProps, } from '@elastic/eui/src/components/date_picker/types'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; +import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; export interface DateRangePickerValues { autoRefreshOptions: { @@ -32,10 +33,19 @@ interface UsageMetricsDateRangePickerProps { onRefresh: () => void; onRefreshChange: (evt: OnRefreshChangeProps) => void; onTimeChange: ({ start, end }: DurationRange) => void; + 'data-test-subj'?: string; } export const UsageMetricsDateRangePicker = memo( - ({ dateRangePickerState, isDataLoading, onRefresh, onRefreshChange, onTimeChange }) => { + ({ + dateRangePickerState, + isDataLoading, + onRefresh, + onRefreshChange, + onTimeChange, + 'data-test-subj': dataTestSubj, + }) => { + const getTestId = useTestIdGenerator(dataTestSubj); const kibana = useKibana(); const { uiSettings } = kibana.services; const [commonlyUsedRanges] = useState(() => { @@ -54,6 +64,7 @@ export const UsageMetricsDateRangePicker = memo { + const getMetricTypesAsArray = (): MetricTypes[] => { + return [...METRIC_TYPE_VALUES]; + }; + + it('should not use invalid `metricTypes` values from URL params', () => { + expect(getDataUsageMetricsFiltersFromUrlParams({ metricTypes: 'bar,foo' })).toEqual({}); + }); + + it('should use valid `metricTypes` values from URL params', () => { + expect( + getDataUsageMetricsFiltersFromUrlParams({ + metricTypes: `${getMetricTypesAsArray().join()},foo,bar`, + }) + ).toEqual({ + metricTypes: getMetricTypesAsArray().sort(), + }); + }); + + it('should use given `dataStreams` values from URL params', () => { + expect( + getDataUsageMetricsFiltersFromUrlParams({ + dataStreams: 'ds-3,ds-1,ds-2', + }) + ).toEqual({ + dataStreams: ['ds-3', 'ds-1', 'ds-2'], + }); + }); + + it('should use valid `metricTypes` along with given `dataStreams` and date values from URL params', () => { + expect( + getDataUsageMetricsFiltersFromUrlParams({ + metricTypes: getMetricTypesAsArray().join(), + dataStreams: 'ds-5,ds-1,ds-2', + startDate: '2022-09-12T08:00:00.000Z', + endDate: '2022-09-12T08:30:33.140Z', + }) + ).toEqual({ + metricTypes: getMetricTypesAsArray().sort(), + endDate: '2022-09-12T08:30:33.140Z', + dataStreams: ['ds-5', 'ds-1', 'ds-2'], + startDate: '2022-09-12T08:00:00.000Z', + }); + }); + + it('should use given relative startDate and endDate values URL params', () => { + expect( + getDataUsageMetricsFiltersFromUrlParams({ + startDate: 'now-24h/h', + endDate: 'now', + }) + ).toEqual({ + endDate: 'now', + startDate: 'now-24h/h', + }); + }); + + it('should use given absolute startDate and endDate values URL params', () => { + expect( + getDataUsageMetricsFiltersFromUrlParams({ + startDate: '2022-09-12T08:00:00.000Z', + endDate: '2022-09-12T08:30:33.140Z', + }) + ).toEqual({ + endDate: '2022-09-12T08:30:33.140Z', + startDate: '2022-09-12T08:00:00.000Z', + }); + }); +}); diff --git a/x-pack/plugins/data_usage/public/hooks/use_get_data_streams.test.tsx b/x-pack/plugins/data_usage/public/hooks/use_get_data_streams.test.tsx new file mode 100644 index 0000000000000..04cee589a523d --- /dev/null +++ b/x-pack/plugins/data_usage/public/hooks/use_get_data_streams.test.tsx @@ -0,0 +1,120 @@ +/* + * 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, { ReactNode } from 'react'; +import { QueryClient, QueryClientProvider, useQuery as _useQuery } from '@tanstack/react-query'; +import { renderHook } from '@testing-library/react-hooks'; +import { useGetDataUsageDataStreams } from './use_get_data_streams'; +import { DATA_USAGE_DATA_STREAMS_API_ROUTE } from '../../common'; +import { coreMock as mockCore } from '@kbn/core/public/mocks'; +import { dataUsageTestQueryClientOptions } from '../../common/test_utils/test_query_client_options'; + +const useQueryMock = _useQuery as jest.Mock; + +jest.mock('@tanstack/react-query', () => { + const actualReactQueryModule = jest.requireActual('@tanstack/react-query'); + + return { + ...actualReactQueryModule, + useQuery: jest.fn((...args) => actualReactQueryModule.useQuery(...args)), + }; +}); + +const mockServices = mockCore.createStart(); +const createWrapper = () => { + const queryClient = new QueryClient(dataUsageTestQueryClientOptions); + return ({ children }: { children: ReactNode }) => ( + {children} + ); +}; + +jest.mock('../utils/use_kibana', () => { + return { + useKibanaContextForPlugin: () => ({ + services: mockServices, + }), + }; +}); + +const defaultDataStreamsRequestParams = { + options: { enabled: true }, +}; + +describe('useGetDataUsageDataStreams', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should call the correct API', async () => { + await renderHook(() => useGetDataUsageDataStreams(defaultDataStreamsRequestParams), { + wrapper: createWrapper(), + }); + + expect(mockServices.http.get).toHaveBeenCalledWith(DATA_USAGE_DATA_STREAMS_API_ROUTE, { + signal: expect.any(AbortSignal), + version: '1', + }); + }); + + it('should not send selected data stream names provided in the param when calling the API', async () => { + await renderHook( + () => + useGetDataUsageDataStreams({ + ...defaultDataStreamsRequestParams, + selectedDataStreams: ['ds-1'], + }), + { + wrapper: createWrapper(), + } + ); + + expect(mockServices.http.get).toHaveBeenCalledWith(DATA_USAGE_DATA_STREAMS_API_ROUTE, { + signal: expect.any(AbortSignal), + version: '1', + }); + }); + + it('should not call the API if disabled', async () => { + await renderHook( + () => + useGetDataUsageDataStreams({ + ...defaultDataStreamsRequestParams, + options: { enabled: false }, + }), + { + wrapper: createWrapper(), + } + ); + + expect(mockServices.http.get).not.toHaveBeenCalled(); + }); + + it('should allow custom options to be used', async () => { + await renderHook( + () => + useGetDataUsageDataStreams({ + selectedDataStreams: undefined, + options: { + queryKey: ['test-query-key'], + enabled: true, + retry: false, + }, + }), + { + wrapper: createWrapper(), + } + ); + + expect(useQueryMock).toHaveBeenCalledWith( + expect.objectContaining({ + queryKey: ['test-query-key'], + enabled: true, + retry: false, + }) + ); + }); +}); diff --git a/x-pack/plugins/data_usage/public/hooks/use_get_data_streams.ts b/x-pack/plugins/data_usage/public/hooks/use_get_data_streams.ts index 598acca3c1faf..acb41e45f4eb6 100644 --- a/x-pack/plugins/data_usage/public/hooks/use_get_data_streams.ts +++ b/x-pack/plugins/data_usage/public/hooks/use_get_data_streams.ts @@ -31,15 +31,16 @@ export const useGetDataUsageDataStreams = ({ selectedDataStreams?: string[]; options?: UseQueryOptions; }): UseQueryResult => { - const http = useKibanaContextForPlugin().services.http; + const { http } = useKibanaContextForPlugin().services; return useQuery({ queryKey: ['get-data-usage-data-streams'], ...options, keepPreviousData: true, - queryFn: async () => { + queryFn: async ({ signal }) => { const dataStreamsResponse = await http .get(DATA_USAGE_DATA_STREAMS_API_ROUTE, { + signal, version: '1', }) .catch((error) => { diff --git a/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.test.tsx b/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.test.tsx new file mode 100644 index 0000000000000..efc3d2a9f4640 --- /dev/null +++ b/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.test.tsx @@ -0,0 +1,102 @@ +/* + * 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, { ReactNode } from 'react'; +import { QueryClient, QueryClientProvider, useQuery as _useQuery } from '@tanstack/react-query'; +import { renderHook } from '@testing-library/react-hooks'; +import { useGetDataUsageMetrics } from './use_get_usage_metrics'; +import { DATA_USAGE_METRICS_API_ROUTE } from '../../common'; +import { coreMock as mockCore } from '@kbn/core/public/mocks'; +import { dataUsageTestQueryClientOptions } from '../../common/test_utils/test_query_client_options'; + +const useQueryMock = _useQuery as jest.Mock; + +jest.mock('@tanstack/react-query', () => { + const actualReactQueryModule = jest.requireActual('@tanstack/react-query'); + + return { + ...actualReactQueryModule, + useQuery: jest.fn((...args) => actualReactQueryModule.useQuery(...args)), + }; +}); + +const mockServices = mockCore.createStart(); +const createWrapper = () => { + const queryClient = new QueryClient(dataUsageTestQueryClientOptions); + return ({ children }: { children: ReactNode }) => ( + {children} + ); +}; + +jest.mock('../utils/use_kibana', () => { + return { + useKibanaContextForPlugin: () => ({ + services: mockServices, + }), + }; +}); + +const defaultUsageMetricsRequestBody = { + from: 'now-15m', + to: 'now', + metricTypes: ['ingest_rate'], + dataStreams: ['ds-1'], +}; + +describe('useGetDataUsageMetrics', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should call the correct API', async () => { + await renderHook( + () => useGetDataUsageMetrics(defaultUsageMetricsRequestBody, { enabled: true }), + { + wrapper: createWrapper(), + } + ); + + expect(mockServices.http.post).toHaveBeenCalledWith(DATA_USAGE_METRICS_API_ROUTE, { + signal: expect.any(AbortSignal), + version: '1', + body: JSON.stringify(defaultUsageMetricsRequestBody), + }); + }); + + it('should not call the API if disabled', async () => { + await renderHook( + () => useGetDataUsageMetrics(defaultUsageMetricsRequestBody, { enabled: false }), + { + wrapper: createWrapper(), + } + ); + + expect(mockServices.http.post).not.toHaveBeenCalled(); + }); + + it('should allow custom options to be used', async () => { + await renderHook( + () => + useGetDataUsageMetrics(defaultUsageMetricsRequestBody, { + queryKey: ['test-query-key'], + enabled: true, + retry: false, + }), + { + wrapper: createWrapper(), + } + ); + + expect(useQueryMock).toHaveBeenCalledWith( + expect.objectContaining({ + queryKey: ['test-query-key'], + enabled: true, + retry: false, + }) + ); + }); +}); diff --git a/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.ts b/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.ts index 7e7406d72b9c0..6b2ef5316b0f6 100644 --- a/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.ts +++ b/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.ts @@ -21,7 +21,7 @@ export const useGetDataUsageMetrics = ( body: UsageMetricsRequestBody, options: UseQueryOptions> = {} ): UseQueryResult> => { - const http = useKibanaContextForPlugin().services.http; + const { http } = useKibanaContextForPlugin().services; return useQuery>({ queryKey: ['get-data-usage-metrics', body], diff --git a/x-pack/plugins/data_usage/public/utils/format_bytes.test.ts b/x-pack/plugins/data_usage/public/utils/format_bytes.test.ts new file mode 100644 index 0000000000000..ccc7a4c2f0aa2 --- /dev/null +++ b/x-pack/plugins/data_usage/public/utils/format_bytes.test.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { formatBytes } from './format_bytes'; + +const exponentN = (number: number, exponent: number) => number ** exponent; + +describe('formatBytes', () => { + it('should format bytes to human readable format with decimal', () => { + expect(formatBytes(84 + 5)).toBe('89.0 B'); + expect(formatBytes(1024 + 256)).toBe('1.3 KB'); + expect(formatBytes(1024 + 582)).toBe('1.6 KB'); + expect(formatBytes(exponentN(1024, 2) + 582 * 1024)).toBe('1.6 MB'); + expect(formatBytes(exponentN(1024, 3) + 582 * exponentN(1024, 2))).toBe('1.6 GB'); + expect(formatBytes(exponentN(1024, 4) + 582 * exponentN(1024, 3))).toBe('1.6 TB'); + expect(formatBytes(exponentN(1024, 5) + 582 * exponentN(1024, 4))).toBe('1.6 PB'); + expect(formatBytes(exponentN(1024, 6) + 582 * exponentN(1024, 5))).toBe('1.6 EB'); + expect(formatBytes(exponentN(1024, 7) + 582 * exponentN(1024, 6))).toBe('1.6 ZB'); + expect(formatBytes(exponentN(1024, 8) + 582 * exponentN(1024, 7))).toBe('1.6 YB'); + }); +}); diff --git a/x-pack/plugins/data_usage/server/mocks/index.ts b/x-pack/plugins/data_usage/server/mocks/index.ts new file mode 100644 index 0000000000000..54260f7309fc6 --- /dev/null +++ b/x-pack/plugins/data_usage/server/mocks/index.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { loggingSystemMock } from '@kbn/core/server/mocks'; +import { DeepReadonly } from 'utility-types'; +import { PluginInitializerContext } from '@kbn/core/server'; +import { Observable } from 'rxjs'; +import { DataUsageContext } from '../types'; +import { DataUsageConfigType } from '../config'; + +export interface MockedDataUsageContext extends DataUsageContext { + logFactory: ReturnType['get']>; + config$?: Observable; + configInitialValue: DataUsageConfigType; + serverConfig: DeepReadonly; + kibanaInstanceId: PluginInitializerContext['env']['instanceUuid']; + kibanaVersion: PluginInitializerContext['env']['packageInfo']['version']; + kibanaBranch: PluginInitializerContext['env']['packageInfo']['branch']; +} + +export const createMockedDataUsageContext = ( + context: PluginInitializerContext +): MockedDataUsageContext => { + return { + logFactory: loggingSystemMock.create().get(), + config$: context.config.create(), + configInitialValue: context.config.get(), + serverConfig: context.config.get(), + kibanaInstanceId: context.env.instanceUuid, + kibanaVersion: context.env.packageInfo.version, + kibanaBranch: context.env.packageInfo.branch, + }; +}; diff --git a/x-pack/plugins/data_usage/server/routes/internal/data_streams.test.ts b/x-pack/plugins/data_usage/server/routes/internal/data_streams.test.ts new file mode 100644 index 0000000000000..7282dbc969fc7 --- /dev/null +++ b/x-pack/plugins/data_usage/server/routes/internal/data_streams.test.ts @@ -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 type { MockedKeys } from '@kbn/utility-types-jest'; +import type { CoreSetup } from '@kbn/core/server'; +import { registerDataStreamsRoute } from './data_streams'; +import { coreMock } from '@kbn/core/server/mocks'; +import { httpServerMock } from '@kbn/core/server/mocks'; +import { DataUsageService } from '../../services'; +import type { + DataUsageRequestHandlerContext, + DataUsageRouter, + DataUsageServerStart, +} from '../../types'; +import { DATA_USAGE_DATA_STREAMS_API_ROUTE } from '../../../common'; +import { createMockedDataUsageContext } from '../../mocks'; +import { getMeteringStats } from '../../utils/get_metering_stats'; +import { CustomHttpRequestError } from '../../utils'; + +jest.mock('../../utils/get_metering_stats'); +const mockGetMeteringStats = getMeteringStats as jest.Mock; + +describe('registerDataStreamsRoute', () => { + let mockCore: MockedKeys>; + let router: DataUsageRouter; + let dataUsageService: DataUsageService; + let context: DataUsageRequestHandlerContext; + + beforeEach(() => { + mockCore = coreMock.createSetup(); + router = mockCore.http.createRouter(); + context = coreMock.createCustomRequestHandlerContext( + coreMock.createRequestHandlerContext() + ) as unknown as DataUsageRequestHandlerContext; + + const mockedDataUsageContext = createMockedDataUsageContext( + coreMock.createPluginInitializerContext() + ); + dataUsageService = new DataUsageService(mockedDataUsageContext); + registerDataStreamsRoute(router, dataUsageService); + }); + + it('should request correct API', () => { + expect(router.versioned.get).toHaveBeenCalledTimes(1); + expect(router.versioned.get).toHaveBeenCalledWith({ + access: 'internal', + path: DATA_USAGE_DATA_STREAMS_API_ROUTE, + }); + }); + + it('should correctly sort response', async () => { + mockGetMeteringStats.mockResolvedValue({ + datastreams: [ + { + name: 'datastream1', + size_in_bytes: 100, + }, + { + name: 'datastream2', + size_in_bytes: 200, + }, + ], + }); + const mockRequest = httpServerMock.createKibanaRequest({ body: {} }); + const mockResponse = httpServerMock.createResponseFactory(); + const mockRouter = mockCore.http.createRouter.mock.results[0].value; + const [[, handler]] = mockRouter.versioned.get.mock.results[0].value.addVersion.mock.calls; + await handler(context, mockRequest, mockResponse); + + expect(mockResponse.ok).toHaveBeenCalledTimes(1); + expect(mockResponse.ok.mock.calls[0][0]).toEqual({ + body: [ + { + name: 'datastream2', + storageSizeBytes: 200, + }, + { + name: 'datastream1', + storageSizeBytes: 100, + }, + ], + }); + }); + + it('should return correct error if metering stats request fails', async () => { + // using custom error for test here to avoid having to import the actual error class + mockGetMeteringStats.mockRejectedValue( + new CustomHttpRequestError('Error getting metring stats!') + ); + const mockRequest = httpServerMock.createKibanaRequest({ body: {} }); + const mockResponse = httpServerMock.createResponseFactory(); + const mockRouter = mockCore.http.createRouter.mock.results[0].value; + const [[, handler]] = mockRouter.versioned.get.mock.results[0].value.addVersion.mock.calls; + await handler(context, mockRequest, mockResponse); + + expect(mockResponse.customError).toHaveBeenCalledTimes(1); + expect(mockResponse.customError).toHaveBeenCalledWith({ + body: new CustomHttpRequestError('Error getting metring stats!'), + statusCode: 500, + }); + }); + + it.each([ + ['no datastreams', {}, []], + ['empty array', { datastreams: [] }, []], + ['an empty element', { datastreams: [{}] }, [{ name: undefined, storageSizeBytes: 0 }]], + ])('should return empty array when no stats data with %s', async (_, stats, res) => { + mockGetMeteringStats.mockResolvedValue(stats); + const mockRequest = httpServerMock.createKibanaRequest({ body: {} }); + const mockResponse = httpServerMock.createResponseFactory(); + const mockRouter = mockCore.http.createRouter.mock.results[0].value; + const [[, handler]] = mockRouter.versioned.get.mock.results[0].value.addVersion.mock.calls; + await handler(context, mockRequest, mockResponse); + + expect(mockResponse.ok).toHaveBeenCalledTimes(1); + expect(mockResponse.ok.mock.calls[0][0]).toEqual({ + body: res, + }); + }); +}); diff --git a/x-pack/plugins/data_usage/server/routes/internal/data_streams_handler.ts b/x-pack/plugins/data_usage/server/routes/internal/data_streams_handler.ts index bc8c5e898c35e..66c2cc0df3513 100644 --- a/x-pack/plugins/data_usage/server/routes/internal/data_streams_handler.ts +++ b/x-pack/plugins/data_usage/server/routes/internal/data_streams_handler.ts @@ -5,27 +5,11 @@ * 2.0. */ -import { type ElasticsearchClient, RequestHandler } from '@kbn/core/server'; +import { RequestHandler } from '@kbn/core/server'; import { DataUsageRequestHandlerContext } from '../../types'; import { errorHandler } from '../error_handler'; import { DataUsageService } from '../../services'; - -export interface MeteringStats { - name: string; - num_docs: number; - size_in_bytes: number; -} - -interface MeteringStatsResponse { - datastreams: MeteringStats[]; -} - -const getMeteringStats = (client: ElasticsearchClient) => { - return client.transport.request({ - method: 'GET', - path: '/_metering/stats', - }); -}; +import { getMeteringStats } from '../../utils/get_metering_stats'; export const getDataStreamsHandler = ( dataUsageService: DataUsageService @@ -41,12 +25,15 @@ export const getDataStreamsHandler = ( core.elasticsearch.client.asSecondaryAuthUser ); - const body = meteringStats - .sort((a, b) => b.size_in_bytes - a.size_in_bytes) - .map((stat) => ({ - name: stat.name, - storageSizeBytes: stat.size_in_bytes ?? 0, - })); + const body = + meteringStats && !!meteringStats.length + ? meteringStats + .sort((a, b) => b.size_in_bytes - a.size_in_bytes) + .map((stat) => ({ + name: stat.name, + storageSizeBytes: stat.size_in_bytes ?? 0, + })) + : []; return response.ok({ body, diff --git a/x-pack/plugins/data_usage/server/routes/internal/usage_metrics.test.ts b/x-pack/plugins/data_usage/server/routes/internal/usage_metrics.test.ts new file mode 100644 index 0000000000000..e95ffd11807a9 --- /dev/null +++ b/x-pack/plugins/data_usage/server/routes/internal/usage_metrics.test.ts @@ -0,0 +1,208 @@ +/* + * 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 { MockedKeys } from '@kbn/utility-types-jest'; +import type { CoreSetup } from '@kbn/core/server'; +import { registerUsageMetricsRoute } from './usage_metrics'; +import { coreMock } from '@kbn/core/server/mocks'; +import { httpServerMock } from '@kbn/core/server/mocks'; +import { DataUsageService } from '../../services'; +import type { + DataUsageRequestHandlerContext, + DataUsageRouter, + DataUsageServerStart, +} from '../../types'; +import { DATA_USAGE_METRICS_API_ROUTE } from '../../../common'; +import { createMockedDataUsageContext } from '../../mocks'; +import { CustomHttpRequestError } from '../../utils'; +import { AutoOpsError } from '../../services/errors'; + +describe('registerUsageMetricsRoute', () => { + let mockCore: MockedKeys>; + let router: DataUsageRouter; + let dataUsageService: DataUsageService; + let context: DataUsageRequestHandlerContext; + + beforeEach(() => { + mockCore = coreMock.createSetup(); + router = mockCore.http.createRouter(); + context = coreMock.createCustomRequestHandlerContext( + coreMock.createRequestHandlerContext() + ) as unknown as DataUsageRequestHandlerContext; + + const mockedDataUsageContext = createMockedDataUsageContext( + coreMock.createPluginInitializerContext() + ); + dataUsageService = new DataUsageService(mockedDataUsageContext); + }); + + it('should request correct API', () => { + registerUsageMetricsRoute(router, dataUsageService); + + expect(router.versioned.post).toHaveBeenCalledTimes(1); + expect(router.versioned.post).toHaveBeenCalledWith({ + access: 'internal', + path: DATA_USAGE_METRICS_API_ROUTE, + }); + }); + + it('should throw error if no data streams in the request', async () => { + registerUsageMetricsRoute(router, dataUsageService); + + const mockRequest = httpServerMock.createKibanaRequest({ + body: { + from: 'now-15m', + to: 'now', + metricTypes: ['ingest_rate'], + dataStreams: [], + }, + }); + const mockResponse = httpServerMock.createResponseFactory(); + const mockRouter = mockCore.http.createRouter.mock.results[0].value; + const [[, handler]] = mockRouter.versioned.post.mock.results[0].value.addVersion.mock.calls; + await handler(context, mockRequest, mockResponse); + + expect(mockResponse.customError).toHaveBeenCalledTimes(1); + expect(mockResponse.customError).toHaveBeenCalledWith({ + body: new CustomHttpRequestError('[request body.dataStreams]: no data streams selected'), + statusCode: 400, + }); + }); + + it('should correctly transform response', async () => { + (await context.core).elasticsearch.client.asCurrentUser.indices.getDataStream = jest + .fn() + .mockResolvedValue({ + data_streams: [{ name: '.ds-1' }, { name: '.ds-2' }], + }); + + dataUsageService.getMetrics = jest.fn().mockResolvedValue({ + metrics: { + ingest_rate: [ + { + name: '.ds-1', + data: [ + [1726858530000, 13756849], + [1726862130000, 14657904], + ], + }, + { + name: '.ds-2', + data: [ + [1726858530000, 12894623], + [1726862130000, 14436905], + ], + }, + ], + storage_retained: [ + { + name: '.ds-1', + data: [ + [1726858530000, 12576413], + [1726862130000, 13956423], + ], + }, + { + name: '.ds-2', + data: [ + [1726858530000, 12894623], + [1726862130000, 14436905], + ], + }, + ], + }, + }); + + registerUsageMetricsRoute(router, dataUsageService); + + const mockRequest = httpServerMock.createKibanaRequest({ + body: { + from: 'now-15m', + to: 'now', + metricTypes: ['ingest_rate', 'storage_retained'], + dataStreams: ['.ds-1', '.ds-2'], + }, + }); + const mockResponse = httpServerMock.createResponseFactory(); + const mockRouter = mockCore.http.createRouter.mock.results[0].value; + const [[, handler]] = mockRouter.versioned.post.mock.results[0].value.addVersion.mock.calls; + await handler(context, mockRequest, mockResponse); + + expect(mockResponse.ok).toHaveBeenCalledTimes(1); + expect(mockResponse.ok.mock.calls[0][0]).toEqual({ + body: { + metrics: { + ingest_rate: [ + { + name: '.ds-1', + data: [ + { x: 1726858530000, y: 13756849 }, + { x: 1726862130000, y: 14657904 }, + ], + }, + { + name: '.ds-2', + data: [ + { x: 1726858530000, y: 12894623 }, + { x: 1726862130000, y: 14436905 }, + ], + }, + ], + storage_retained: [ + { + name: '.ds-1', + data: [ + { x: 1726858530000, y: 12576413 }, + { x: 1726862130000, y: 13956423 }, + ], + }, + { + name: '.ds-2', + data: [ + { x: 1726858530000, y: 12894623 }, + { x: 1726862130000, y: 14436905 }, + ], + }, + ], + }, + }, + }); + }); + + it('should throw error if error on requesting auto ops service', async () => { + (await context.core).elasticsearch.client.asCurrentUser.indices.getDataStream = jest + .fn() + .mockResolvedValue({ + data_streams: [{ name: '.ds-1' }, { name: '.ds-2' }], + }); + + dataUsageService.getMetrics = jest + .fn() + .mockRejectedValue(new AutoOpsError('Uh oh, something went wrong!')); + + registerUsageMetricsRoute(router, dataUsageService); + + const mockRequest = httpServerMock.createKibanaRequest({ + body: { + from: 'now-15m', + to: 'now', + metricTypes: ['ingest_rate'], + dataStreams: ['.ds-1', '.ds-2'], + }, + }); + const mockResponse = httpServerMock.createResponseFactory(); + const mockRouter = mockCore.http.createRouter.mock.results[0].value; + const [[, handler]] = mockRouter.versioned.post.mock.results[0].value.addVersion.mock.calls; + await handler(context, mockRequest, mockResponse); + + expect(mockResponse.customError).toHaveBeenCalledTimes(1); + expect(mockResponse.customError).toHaveBeenCalledWith({ + body: new AutoOpsError('Uh oh, something went wrong!'), + statusCode: 503, + }); + }); +}); diff --git a/x-pack/plugins/data_usage/server/utils/get_metering_stats.ts b/x-pack/plugins/data_usage/server/utils/get_metering_stats.ts new file mode 100644 index 0000000000000..4ba30f5bd3601 --- /dev/null +++ b/x-pack/plugins/data_usage/server/utils/get_metering_stats.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { type ElasticsearchClient } from '@kbn/core/server'; + +export interface MeteringStats { + name: string; + num_docs: number; + size_in_bytes: number; +} + +interface MeteringStatsResponse { + datastreams: MeteringStats[]; +} + +export const getMeteringStats = (client: ElasticsearchClient) => { + return client.transport.request({ + method: 'GET', + path: '/_metering/stats', + }); +}; diff --git a/x-pack/plugins/data_usage/tsconfig.json b/x-pack/plugins/data_usage/tsconfig.json index 78c501922f239..66c8a5247858b 100644 --- a/x-pack/plugins/data_usage/tsconfig.json +++ b/x-pack/plugins/data_usage/tsconfig.json @@ -31,6 +31,7 @@ "@kbn/repo-info", "@kbn/cloud-plugin", "@kbn/server-http-tools", + "@kbn/utility-types-jest", ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/data_visualizer/kibana.jsonc b/x-pack/plugins/data_visualizer/kibana.jsonc index 06d37106c2480..1e83c34113beb 100644 --- a/x-pack/plugins/data_visualizer/kibana.jsonc +++ b/x-pack/plugins/data_visualizer/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/data-visualizer-plugin", - "owner": "@elastic/ml-ui", + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private", "description": "The Data Visualizer tools help you understand your data, by analyzing the metrics and fields in a log file or an existing Elasticsearch index.", "plugin": { "id": "dataVisualizer", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "data", "usageCollection", @@ -40,4 +44,4 @@ "visualizations" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/discover_enhanced/kibana.jsonc b/x-pack/plugins/discover_enhanced/kibana.jsonc index 73243ee5d59d3..39f07bbbd23cc 100644 --- a/x-pack/plugins/discover_enhanced/kibana.jsonc +++ b/x-pack/plugins/discover_enhanced/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/discover-enhanced-plugin", - "owner": "@elastic/kibana-data-discovery", + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "private", "plugin": { "id": "discoverEnhanced", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "discoverEnhanced" @@ -25,4 +29,4 @@ "lens" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/drilldowns/url_drilldown/kibana.jsonc b/x-pack/plugins/drilldowns/url_drilldown/kibana.jsonc index a7d0217c4e2ee..05083a54a7080 100644 --- a/x-pack/plugins/drilldowns/url_drilldown/kibana.jsonc +++ b/x-pack/plugins/drilldowns/url_drilldown/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/url-drilldown-plugin", - "owner": "@elastic/appex-sharedux", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private", "description": "Adds drilldown implementations to Kibana", "plugin": { "id": "urlDrilldown", - "server": false, "browser": true, + "server": false, "requiredPlugins": [ "embeddable", "uiActionsEnhanced" @@ -18,4 +22,4 @@ "uiActions" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/ecs_data_quality_dashboard/kibana.jsonc b/x-pack/plugins/ecs_data_quality_dashboard/kibana.jsonc index 5adbe3eeee830..63d01bdc23e96 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/kibana.jsonc +++ b/x-pack/plugins/ecs_data_quality_dashboard/kibana.jsonc @@ -1,17 +1,21 @@ { "type": "plugin", "id": "@kbn/ecs-data-quality-dashboard-plugin", - "owner": "@elastic/security-threat-hunting-explore", + "owner": [ + "@elastic/security-threat-hunting-explore" + ], + "group": "security", + "visibility": "private", "description": "APIs used to assess the quality of data in Elasticsearch indexes", "plugin": { "id": "ecsDataQualityDashboard", - "server": true, "browser": false, + "server": true, "requiredPlugins": [ - "data", + "data" ], "optionalPlugins": [ - "spaces", + "spaces" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/helpers/get_available_indices.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/helpers/get_available_indices.ts index 8f7fdead51547..85da8b3d539ff 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/helpers/get_available_indices.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/helpers/get_available_indices.ts @@ -8,15 +8,15 @@ import type { SearchRequest } from '@elastic/elasticsearch/lib/api/types'; export const getRequestBody = ({ - indexPattern, + indexNameOrPattern, startDate = 'now-7d/d', endDate = 'now/d', }: { - indexPattern: string; + indexNameOrPattern: string; startDate: string; endDate: string; }): SearchRequest => ({ - index: indexPattern, + index: indexNameOrPattern, aggs: { index: { terms: { diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/helpers/get_range_filtered_indices.test.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/helpers/get_range_filtered_indices.test.ts new file mode 100644 index 0000000000000..87350abcf8a9c --- /dev/null +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/helpers/get_range_filtered_indices.test.ts @@ -0,0 +1,96 @@ +/* + * 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 { getRangeFilteredIndices } from './get_range_filtered_indices'; +import { fetchAvailableIndices } from '../lib/fetch_available_indices'; +import type { IScopedClusterClient, Logger } from '@kbn/core/server'; + +jest.mock('../lib/fetch_available_indices'); + +const fetchAvailableIndicesMock = fetchAvailableIndices as jest.Mock; + +describe('getRangeFilteredIndices', () => { + let client: jest.Mocked; + let logger: jest.Mocked; + + beforeEach(() => { + client = { + asCurrentUser: jest.fn(), + } as unknown as jest.Mocked; + + logger = { + warn: jest.fn(), + error: jest.fn(), + } as unknown as jest.Mocked; + + jest.clearAllMocks(); + }); + + describe('when fetching available indices is successful', () => { + describe('and there are available indices', () => { + it('should return the flattened available indices', async () => { + fetchAvailableIndicesMock.mockResolvedValueOnce(['index1', 'index2']); + fetchAvailableIndicesMock.mockResolvedValueOnce(['index3']); + + const result = await getRangeFilteredIndices({ + client, + authorizedIndexNames: ['auth1', 'auth2'], + startDate: '2023-01-01', + endDate: '2023-01-31', + logger, + pattern: 'pattern*', + }); + + expect(fetchAvailableIndices).toHaveBeenCalledTimes(2); + expect(result).toEqual(['index1', 'index2', 'index3']); + expect(logger.warn).not.toHaveBeenCalled(); + }); + }); + + describe('and there are no available indices', () => { + it('should log a warning and return an empty array', async () => { + fetchAvailableIndicesMock.mockResolvedValue([]); + + const result = await getRangeFilteredIndices({ + client, + authorizedIndexNames: ['auth1', 'auth2'], + startDate: '2023-01-01', + endDate: '2023-01-31', + logger, + pattern: 'pattern*', + }); + + expect(fetchAvailableIndices).toHaveBeenCalledTimes(2); + expect(result).toEqual([]); + expect(logger.warn).toHaveBeenCalledWith( + 'No available authorized indices found under pattern: pattern*, in the given date range: 2023-01-01 - 2023-01-31' + ); + }); + }); + }); + + describe('when fetching available indices fails', () => { + it('should log an error and return an empty array', async () => { + fetchAvailableIndicesMock.mockRejectedValue(new Error('Fetch error')); + + const result = await getRangeFilteredIndices({ + client, + authorizedIndexNames: ['auth1'], + startDate: '2023-01-01', + endDate: '2023-01-31', + logger, + pattern: 'pattern*', + }); + + expect(fetchAvailableIndices).toHaveBeenCalledTimes(1); + expect(result).toEqual([]); + expect(logger.error).toHaveBeenCalledWith( + 'Error fetching available indices in the given data range: 2023-01-01 - 2023-01-31' + ); + }); + }); +}); diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/helpers/get_range_filtered_indices.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/helpers/get_range_filtered_indices.ts new file mode 100644 index 0000000000000..45a87424169e8 --- /dev/null +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/helpers/get_range_filtered_indices.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IScopedClusterClient, Logger } from '@kbn/core/server'; + +import { fetchAvailableIndices } from '../lib/fetch_available_indices'; + +export const getRangeFilteredIndices = async ({ + client, + authorizedIndexNames, + startDate, + endDate, + logger, + pattern, +}: { + client: IScopedClusterClient; + authorizedIndexNames: string[]; + startDate: string; + endDate: string; + logger: Logger; + pattern: string; +}): Promise => { + const decodedStartDate = decodeURIComponent(startDate); + const decodedEndDate = decodeURIComponent(endDate); + try { + const currentUserEsClient = client.asCurrentUser; + + const availableIndicesPromises: Array> = []; + + for (const indexName of authorizedIndexNames) { + availableIndicesPromises.push( + fetchAvailableIndices(currentUserEsClient, { + indexNameOrPattern: indexName, + startDate: decodedStartDate, + endDate: decodedEndDate, + }) + ); + } + + const availableIndices = await Promise.all(availableIndicesPromises); + + const flattenedAvailableIndices = availableIndices.flat(); + + if (flattenedAvailableIndices.length === 0) { + logger.warn( + `No available authorized indices found under pattern: ${pattern}, in the given date range: ${decodedStartDate} - ${decodedEndDate}` + ); + } + + return flattenedAvailableIndices; + } catch (err) { + logger.error( + `Error fetching available indices in the given data range: ${decodedStartDate} - ${decodedEndDate}` + ); + return []; + } +}; diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/lib/fetch_available_indices.test.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/lib/fetch_available_indices.test.ts index fa26fb68289a6..9fe8213b4eb95 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/lib/fetch_available_indices.test.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/lib/fetch_available_indices.test.ts @@ -61,7 +61,7 @@ describe('fetchAvailableIndices', () => { const esClientMock = getEsClientMock(); await fetchAvailableIndices(esClientMock, { - indexPattern: 'logs-*', + indexNameOrPattern: 'logs-*', startDate: startDateString, endDate: endDateString, }); @@ -101,7 +101,7 @@ describe('fetchAvailableIndices', () => { const esClientMock = getEsClientMock(); await fetchAvailableIndices(esClientMock, { - indexPattern: 'logs-*', + indexNameOrPattern: 'logs-*', startDate: startDateString, endDate: endDateString, }); @@ -133,7 +133,7 @@ describe('fetchAvailableIndices', () => { ]); const result = await fetchAvailableIndices(esClientMock, { - indexPattern: 'logs-*', + indexNameOrPattern: 'logs-*', startDate: startDateString, endDate: endDateString, }); @@ -164,7 +164,7 @@ describe('fetchAvailableIndices', () => { ]); const result = await fetchAvailableIndices(esClientMock, { - indexPattern: 'logs-*', + indexNameOrPattern: 'logs-*', startDate: startDateString, endDate: endDateString, }); @@ -180,7 +180,7 @@ describe('fetchAvailableIndices', () => { esClientMock.cat.indices.mockResolvedValue([]); const result = await fetchAvailableIndices(esClientMock, { - indexPattern: 'nonexistent-*', + indexNameOrPattern: 'nonexistent-*', startDate: startDateString, endDate: endDateString, }); @@ -209,7 +209,7 @@ describe('fetchAvailableIndices', () => { }); const result = await fetchAvailableIndices(esClientMock, { - indexPattern: 'logs-*', + indexNameOrPattern: 'logs-*', startDate: startDateString, endDate: endDateString, }); @@ -243,7 +243,7 @@ describe('fetchAvailableIndices', () => { }); const result = await fetchAvailableIndices(esClientMock, { - indexPattern: 'logs-*', + indexNameOrPattern: 'logs-*', startDate: startDateString, endDate: endDateString, }); @@ -268,7 +268,7 @@ describe('fetchAvailableIndices', () => { ]); const result = await fetchAvailableIndices(esClientMock, { - indexPattern: 'logs-*', + indexNameOrPattern: 'logs-*', startDate: startDateString, endDate: endDateString, }); @@ -285,7 +285,7 @@ describe('fetchAvailableIndices', () => { await expect( fetchAvailableIndices(esClientMock, { - indexPattern: 'logs-*', + indexNameOrPattern: 'logs-*', startDate: startDateString, endDate: endDateString, }) @@ -307,7 +307,7 @@ describe('fetchAvailableIndices', () => { }); const result = await fetchAvailableIndices(esClientMock, { - indexPattern: 'logs-*', + indexNameOrPattern: 'logs-*', startDate: startDateString, endDate: endDateString, }); @@ -336,7 +336,7 @@ describe('fetchAvailableIndices', () => { }); const result = await fetchAvailableIndices(esClientMock, { - indexPattern: 'logs-*', + indexNameOrPattern: 'logs-*', startDate: startDateString, endDate: endDateString, }); @@ -371,7 +371,7 @@ describe('fetchAvailableIndices', () => { ]); const results = await fetchAvailableIndices(esClientMock, { - indexPattern: 'logs-*', + indexNameOrPattern: 'logs-*', startDate: 'now-7d/d', endDate: 'now/d', }); @@ -390,7 +390,7 @@ describe('fetchAvailableIndices', () => { ]); const results = await fetchAvailableIndices(esClientMock, { - indexPattern: 'logs-*', + indexNameOrPattern: 'logs-*', startDate: 'now-7d/d', endDate: 'now-1d/d', }); @@ -415,7 +415,7 @@ describe('fetchAvailableIndices', () => { await expect( fetchAvailableIndices(esClientMock, { - indexPattern: 'logs-*', + indexNameOrPattern: 'logs-*', startDate: startDateString, endDate: endDateString, }) @@ -429,7 +429,7 @@ describe('fetchAvailableIndices', () => { await expect( fetchAvailableIndices(esClientMock, { - indexPattern: 'logs-*', + indexNameOrPattern: 'logs-*', startDate: 'invalid-date', endDate: endDateString, }) @@ -443,7 +443,7 @@ describe('fetchAvailableIndices', () => { await expect( fetchAvailableIndices(esClientMock, { - indexPattern: 'logs-*', + indexNameOrPattern: 'logs-*', startDate: startDateString, endDate: 'invalid-date', }) diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/lib/fetch_available_indices.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/lib/fetch_available_indices.ts index 32311f28d636a..36009f315010b 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/lib/fetch_available_indices.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/lib/fetch_available_indices.ts @@ -32,15 +32,15 @@ const getParsedDateMs = (dateStr: string, roundUp = false) => { export const fetchAvailableIndices = async ( esClient: ElasticsearchClient, - params: { indexPattern: string; startDate: string; endDate: string } + params: { indexNameOrPattern: string; startDate: string; endDate: string } ): Promise => { - const { indexPattern, startDate, endDate } = params; + const { indexNameOrPattern, startDate, endDate } = params; const startDateMs = getParsedDateMs(startDate); const endDateMs = getParsedDateMs(endDate, true); const indicesCats = (await esClient.cat.indices({ - index: indexPattern, + index: indexNameOrPattern, format: 'json', h: 'index,creation.date', })) as FetchAvailableCatIndicesResponseRequired; diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_stats.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_stats.ts index d1bb25d34fc2a..fd1ec1694719d 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_stats.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_stats.ts @@ -85,7 +85,7 @@ export const getIndexStatsRoute = (router: IRouter, logger: Logger) => { const meteringStatsIndices = parseMeteringStats(meteringStats.indices); const availableIndices = await fetchAvailableIndices(esClient, { - indexPattern: decodedIndexName, + indexNameOrPattern: decodedIndexName, startDate: decodedStartDate, endDate: decodedEndDate, }); diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/get_index_results_latest.test.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/get_index_results_latest.test.ts index bfb38864916fe..94c892e401b5a 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/get_index_results_latest.test.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/get_index_results_latest.test.ts @@ -16,6 +16,24 @@ import { resultDocument } from './results.mock'; import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; import type { ResultDocument } from '../../schemas/result'; import type { CheckIndicesPrivilegesParam } from './privileges'; +import { getRangeFilteredIndices } from '../../helpers/get_range_filtered_indices'; + +const mockCheckIndicesPrivileges = jest.fn(({ indices }: CheckIndicesPrivilegesParam) => + Promise.resolve(Object.fromEntries(indices.map((index) => [index, true]))) +); +jest.mock('./privileges', () => ({ + checkIndicesPrivileges: (params: CheckIndicesPrivilegesParam) => + mockCheckIndicesPrivileges(params), +})); + +jest.mock('../../helpers/get_range_filtered_indices', () => ({ + getRangeFilteredIndices: jest.fn(), +})); + +const mockGetRangeFilteredIndices = getRangeFilteredIndices as jest.Mock; + +const startDate = 'now-7d'; +const endDate = 'now'; const searchResponse = { aggregations: { @@ -33,14 +51,6 @@ const searchResponse = { Record >; -const mockCheckIndicesPrivileges = jest.fn(({ indices }: CheckIndicesPrivilegesParam) => - Promise.resolve(Object.fromEntries(indices.map((index) => [index, true]))) -); -jest.mock('./privileges', () => ({ - checkIndicesPrivileges: (params: CheckIndicesPrivilegesParam) => - mockCheckIndicesPrivileges(params), -})); - describe('getIndexResultsLatestRoute route', () => { describe('querying', () => { let server: ReturnType; @@ -68,7 +78,7 @@ describe('getIndexResultsLatestRoute route', () => { getIndexResultsLatestRoute(server.router, logger); }); - it('gets result', async () => { + it('gets result without startDate and endDate', async () => { const mockSearch = context.core.elasticsearch.client.asInternalUser.search; mockSearch.mockResolvedValueOnce(searchResponse); @@ -80,6 +90,159 @@ describe('getIndexResultsLatestRoute route', () => { expect(response.status).toEqual(200); expect(response.body).toEqual([resultDocument]); + + expect(mockGetRangeFilteredIndices).not.toHaveBeenCalled(); + }); + + it('gets result with startDate and endDate', async () => { + const reqWithDate = requestMock.create({ + method: 'get', + path: GET_INDEX_RESULTS_LATEST, + params: { pattern: 'logs-*' }, + query: { startDate, endDate }, + }); + + const filteredIndices = ['filtered-index-1', 'filtered-index-2']; + mockGetRangeFilteredIndices.mockResolvedValueOnce(filteredIndices); + const mockSearch = context.core.elasticsearch.client.asInternalUser.search; + mockSearch.mockResolvedValueOnce(searchResponse); + + const response = await server.inject(reqWithDate, requestContextMock.convertContext(context)); + + expect(mockGetRangeFilteredIndices).toHaveBeenCalledWith({ + client: context.core.elasticsearch.client, + authorizedIndexNames: [resultDocument.indexName], + startDate, + endDate, + logger, + pattern: 'logs-*', + }); + + expect(mockSearch).toHaveBeenCalledWith({ + index: expect.any(String), + ...getQuery(filteredIndices), + }); + + expect(response.status).toEqual(200); + expect(response.body).toEqual([resultDocument]); + }); + + it('handles getRangeFilteredIndices error', async () => { + const errorMessage = 'Range Filter Error'; + + const reqWithDate = requestMock.create({ + method: 'get', + path: GET_INDEX_RESULTS_LATEST, + params: { pattern: 'logs-*' }, + query: { startDate, endDate }, + }); + + mockGetRangeFilteredIndices.mockRejectedValueOnce(new Error(errorMessage)); + + const response = await server.inject(reqWithDate, requestContextMock.convertContext(context)); + + expect(mockGetRangeFilteredIndices).toHaveBeenCalledWith({ + client: context.core.elasticsearch.client, + authorizedIndexNames: [resultDocument.indexName], + startDate, + endDate, + logger, + pattern: 'logs-*', + }); + + expect(response.status).toEqual(500); + expect(response.body).toEqual({ message: errorMessage, status_code: 500 }); + expect(logger.error).toHaveBeenCalledWith(errorMessage); + }); + + it('gets result with startDate and endDate and multiple filtered indices', async () => { + const filteredIndices = ['filtered-index-1', 'filtered-index-2', 'filtered-index-3']; + const filteredIndicesSearchResponse = { + aggregations: { + latest: { + buckets: filteredIndices.map((indexName) => ({ + key: indexName, + latest_doc: { hits: { hits: [{ _source: { indexName } }] } }, + })), + }, + }, + } as unknown as SearchResponse< + ResultDocument, + Record + >; + + const reqWithDate = requestMock.create({ + method: 'get', + path: GET_INDEX_RESULTS_LATEST, + params: { pattern: 'logs-*' }, + query: { startDate, endDate }, + }); + + mockGetRangeFilteredIndices.mockResolvedValueOnce(filteredIndices); + context.core.elasticsearch.client.asInternalUser.search.mockResolvedValueOnce( + filteredIndicesSearchResponse + ); + + const response = await server.inject(reqWithDate, requestContextMock.convertContext(context)); + + expect(mockGetRangeFilteredIndices).toHaveBeenCalledWith({ + client: context.core.elasticsearch.client, + authorizedIndexNames: [resultDocument.indexName], + startDate, + endDate, + logger, + pattern: 'logs-*', + }); + + expect(context.core.elasticsearch.client.asInternalUser.search).toHaveBeenCalledWith({ + index: expect.any(String), + ...getQuery(filteredIndices), + }); + + const expectedResults = filteredIndices.map((indexName) => ({ + indexName, + })) as ResultDocument[]; + expect(response.status).toEqual(200); + expect(response.body).toEqual(expectedResults); + }); + + it('handles partial authorization when using startDate and endDate', async () => { + const authorizationResult = { + 'filtered-index-1': true, + 'filtered-index-2': false, + }; + + mockGetRangeFilteredIndices.mockResolvedValueOnce(['filtered-index-1']); + mockCheckIndicesPrivileges.mockResolvedValueOnce(authorizationResult); + + const mockSearch = context.core.elasticsearch.client.asInternalUser.search; + mockSearch.mockResolvedValueOnce(searchResponse); + + const reqWithDate = requestMock.create({ + method: 'get', + path: GET_INDEX_RESULTS_LATEST, + params: { pattern: 'logs-*' }, + query: { startDate, endDate }, + }); + + const response = await server.inject(reqWithDate, requestContextMock.convertContext(context)); + + expect(mockGetRangeFilteredIndices).toHaveBeenCalledWith({ + client: context.core.elasticsearch.client, + authorizedIndexNames: ['filtered-index-1'], + startDate, + endDate, + logger, + pattern: 'logs-*', + }); + + expect(context.core.elasticsearch.client.asInternalUser.search).toHaveBeenCalledWith({ + index: expect.any(String), + ...getQuery(['filtered-index-1']), + }); + + expect(response.status).toEqual(200); + expect(response.body).toEqual([resultDocument]); }); it('handles results data stream error', async () => { diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/get_index_results_latest.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/get_index_results_latest.ts index 3a294409af869..f7d1d5eed74cc 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/get_index_results_latest.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/get_index_results_latest.ts @@ -4,18 +4,18 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import type { IRouter, Logger } from '@kbn/core/server'; import { INTERNAL_API_VERSION, GET_INDEX_RESULTS_LATEST } from '../../../common/constants'; import { buildResponse } from '../../lib/build_response'; import { buildRouteValidation } from '../../schemas/common'; -import { GetIndexResultsLatestParams } from '../../schemas/result'; +import { GetIndexResultsLatestParams, GetIndexResultsLatestQuery } from '../../schemas/result'; import type { ResultDocument } from '../../schemas/result'; import { API_DEFAULT_ERROR_MESSAGE } from '../../translations'; import type { DataQualityDashboardRequestHandlerContext } from '../../types'; import { API_RESULTS_INDEX_NOT_AVAILABLE } from './translations'; import { getAuthorizedIndexNames } from '../../helpers/get_authorized_index_names'; +import { getRangeFilteredIndices } from '../../helpers/get_range_filtered_indices'; export const getQuery = (indexName: string[]) => ({ size: 0, @@ -53,6 +53,7 @@ export const getIndexResultsLatestRoute = ( validate: { request: { params: buildRouteValidation(GetIndexResultsLatestParams), + query: buildRouteValidation(GetIndexResultsLatestQuery), }, }, }, @@ -81,8 +82,27 @@ export const getIndexResultsLatestRoute = ( return response.ok({ body: [] }); } + const { startDate, endDate } = request.query; + + let resultingIndices: string[] = []; + + if (startDate && endDate) { + resultingIndices = resultingIndices.concat( + await getRangeFilteredIndices({ + client, + authorizedIndexNames, + startDate, + endDate, + logger, + pattern, + }) + ); + } else { + resultingIndices = authorizedIndexNames; + } + // Get the latest result for each indexName - const query = { index, ...getQuery(authorizedIndexNames) }; + const query = { index, ...getQuery(resultingIndices) }; const { aggregations } = await client.asInternalUser.search< ResultDocument, Record diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/schemas/result.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/schemas/result.ts index 8ccb3fbc3f984..fb264fe10da8f 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/schemas/result.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/schemas/result.ts @@ -69,6 +69,11 @@ export const PostIndexResultBody = ResultDocument; export const GetIndexResultsLatestParams = t.type({ pattern: t.string }); export type GetIndexResultsLatestParams = t.TypeOf; +export const GetIndexResultsLatestQuery = t.partial({ + startDate: t.string, + endDate: t.string, +}); + export const GetIndexResultsParams = t.type({ pattern: t.string, }); diff --git a/x-pack/plugins/elastic_assistant/kibana.jsonc b/x-pack/plugins/elastic_assistant/kibana.jsonc index 8a3e0725c782a..435ec0b916d01 100644 --- a/x-pack/plugins/elastic_assistant/kibana.jsonc +++ b/x-pack/plugins/elastic_assistant/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/elastic-assistant-plugin", - "owner": "@elastic/security-generative-ai", + "owner": [ + "@elastic/security-generative-ai" + ], + "group": "security", + "visibility": "private", "description": "Server APIs for the Elastic AI Assistant", "plugin": { "id": "elasticAssistant", - "server": true, "browser": false, + "server": true, "requiredPlugins": [ "actions", "data", @@ -18,4 +22,4 @@ "security" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/embeddable_enhanced/kibana.jsonc b/x-pack/plugins/embeddable_enhanced/kibana.jsonc index 79c79ee89d649..d795afa4d7938 100644 --- a/x-pack/plugins/embeddable_enhanced/kibana.jsonc +++ b/x-pack/plugins/embeddable_enhanced/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/embeddable-enhanced-plugin", - "owner": "@elastic/kibana-presentation", + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "private", "description": "Extends embeddable plugin with more functionality", "plugin": { "id": "embeddableEnhanced", - "server": false, "browser": true, + "server": false, "requiredPlugins": [ "embeddable", "kibanaReact", @@ -14,4 +18,4 @@ "uiActionsEnhanced" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/encrypted_saved_objects/kibana.jsonc b/x-pack/plugins/encrypted_saved_objects/kibana.jsonc index 7e0cc158363ec..41097c7b70f0e 100644 --- a/x-pack/plugins/encrypted_saved_objects/kibana.jsonc +++ b/x-pack/plugins/encrypted_saved_objects/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/encrypted-saved-objects-plugin", - "owner": "@elastic/kibana-security", + "owner": [ + "@elastic/kibana-security" + ], + "group": "platform", + "visibility": "shared", "description": "This plugin provides encryption and decryption utilities for saved objects containing sensitive information.", "plugin": { "id": "encryptedSavedObjects", - "server": true, "browser": false, + "server": true, "configPath": [ "xpack", "encryptedSavedObjects" @@ -15,4 +19,4 @@ "security" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/kibana.jsonc b/x-pack/plugins/enterprise_search/kibana.jsonc index f631bd2dc53d1..14a36c85c6c87 100644 --- a/x-pack/plugins/enterprise_search/kibana.jsonc +++ b/x-pack/plugins/enterprise_search/kibana.jsonc @@ -2,6 +2,9 @@ "type": "plugin", "id": "@kbn/enterprise-search-plugin", "owner": "@elastic/search-kibana", + // Could be categorised as Search in the future, but it currently needs to run in Observability too + "group": "platform", + "visibility": "shared", "description": "Adds dashboards for discovering and managing Enterprise Search products.", "plugin": { "id": "enterpriseSearch", diff --git a/x-pack/plugins/enterprise_search/public/applications/applications/components/playground/playground.tsx b/x-pack/plugins/enterprise_search/public/applications/applications/components/playground/playground.tsx index e8e72e5dfb37a..c198062cb759b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/applications/components/playground/playground.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/applications/components/playground/playground.tsx @@ -15,7 +15,11 @@ import { KibanaLogic } from '../../../shared/kibana'; import { SearchPlaygroundPageTemplate } from './page_template'; -export const Playground: React.FC = () => { +interface PlaygroundProps { + pageMode?: 'chat' | 'search'; +} + +export const Playground: React.FC = ({ pageMode = 'chat' }) => { const { searchPlayground } = useValues(KibanaLogic); if (!searchPlayground) { @@ -35,7 +39,7 @@ export const Playground: React.FC = () => { customPageSections bottomBorder="extended" > - + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/applications/index.tsx index c9676137e70f2..a04ebf2e3edbb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/applications/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/applications/index.tsx @@ -13,7 +13,13 @@ import { Routes, Route } from '@kbn/shared-ux-router'; import { NotFound } from './components/not_found'; import { Playground } from './components/playground/playground'; import { SearchApplicationsRouter } from './components/search_applications/search_applications_router'; -import { PLAYGROUND_PATH, ROOT_PATH, SEARCH_APPLICATIONS_PATH } from './routes'; +import { + PLAYGROUND_CHAT_PATH, + PLAYGROUND_PATH, + PLAYGROUND_SEARCH_PATH, + ROOT_PATH, + SEARCH_APPLICATIONS_PATH, +} from './routes'; export const Applications = () => { return ( @@ -22,8 +28,12 @@ export const Applications = () => { - - + + + + + + diff --git a/x-pack/plugins/enterprise_search/public/applications/applications/routes.ts b/x-pack/plugins/enterprise_search/public/applications/applications/routes.ts index 5779f544c3f34..2df42a129938c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/applications/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/applications/routes.ts @@ -17,7 +17,9 @@ export enum SearchApplicationViewTabs { export const SEARCH_APPLICATION_CREATION_PATH = `${SEARCH_APPLICATIONS_PATH}/new`; export const SEARCH_APPLICATION_PATH = `${SEARCH_APPLICATIONS_PATH}/:searchApplicationName`; export const SEARCH_APPLICATION_TAB_PATH = `${SEARCH_APPLICATION_PATH}/:tabId`; -export const PLAYGROUND_PATH = `${ROOT_PATH}playground`; +export const PLAYGROUND_PATH = `${ROOT_PATH}playground/`; +export const PLAYGROUND_CHAT_PATH = `${PLAYGROUND_PATH}chat`; +export const PLAYGROUND_SEARCH_PATH = `${PLAYGROUND_PATH}search`; export const SEARCH_APPLICATION_CONNECT_PATH = `${SEARCH_APPLICATION_PATH}/${SearchApplicationViewTabs.CONNECT}/:connectTabId`; export enum SearchApplicationConnectTabs { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/new_connector_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/new_connector_logic.ts index 0d21db6e03baf..0c8a81d90149a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/new_connector_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/new_connector_logic.ts @@ -7,8 +7,7 @@ import { kea, MakeLogicType } from 'kea'; -import { Connector } from '@kbn/search-connectors'; -import { ConnectorDefinition } from '@kbn/search-connectors-plugin/public'; +import { Connector, ConnectorDefinition } from '@kbn/search-connectors'; import { Status } from '../../../../../../common/types/api'; import { Actions } from '../../../../shared/api_logic/create_api_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_search_index_page.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_search_index_page.tsx index 1b2889301d6a9..7aac8d87b89b4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_search_index_page.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_search_index_page.tsx @@ -15,7 +15,7 @@ import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { ConnectorDefinition } from '@kbn/search-connectors-plugin/public'; +import { ConnectorDefinition } from '@kbn/search-connectors'; import { CONNECTOR_CLIENTS_TYPE, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration_config.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration_config.tsx index d2681a5d3df97..d0edaacb3c712 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration_config.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration_config.tsx @@ -14,11 +14,12 @@ import { EuiSpacer, EuiLink, EuiFlexGroup, EuiFlexItem, EuiCallOut } from '@elas import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { Connector, ConnectorStatus } from '@kbn/search-connectors'; - -import { ConnectorConfigurationComponent } from '@kbn/search-connectors/components/configuration/connector_configuration'; - -import { ConnectorDefinition } from '@kbn/search-connectors-plugin/common/types'; +import { + Connector, + ConnectorConfigurationComponent, + ConnectorDefinition, + ConnectorStatus, +} from '@kbn/search-connectors'; import { Status } from '../../../../../../../common/types/api'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/research_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/research_configuration.tsx index 0625c60a354f7..764f952e0c02d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/research_configuration.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/research_configuration.tsx @@ -11,8 +11,7 @@ import { EuiText, EuiFlexGroup, EuiFlexItem, EuiLink, EuiCallOut } from '@elasti import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; - -import { ConnectorDefinition } from '@kbn/search-connectors-plugin/common/types'; +import { ConnectorDefinition } from '@kbn/search-connectors'; interface ResearchConfigurationProps { nativeConnector: ConnectorDefinition; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts index 6cd6e5410ef11..592e20f25f382 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts @@ -28,7 +28,7 @@ import { IndexMappingProps } from '@kbn/index-management-shared-types'; import { LensPublicStart } from '@kbn/lens-plugin/public'; import { MlPluginStart } from '@kbn/ml-plugin/public'; import { ELASTICSEARCH_URL_PLACEHOLDER } from '@kbn/search-api-panels/constants'; -import { ConnectorDefinition } from '@kbn/search-connectors-plugin/public'; +import { ConnectorDefinition } from '@kbn/search-connectors'; import { SearchInferenceEndpointsPluginStart } from '@kbn/search-inference-endpoints/public'; import { SearchPlaygroundPluginStart } from '@kbn/search-playground/public'; import { AuthenticatedUser, SecurityPluginStart } from '@kbn/security-plugin/public'; diff --git a/x-pack/plugins/enterprise_search/server/integrations.ts b/x-pack/plugins/enterprise_search/server/integrations.ts index 2918ef862dbdf..b3d0a6a3295ea 100644 --- a/x-pack/plugins/enterprise_search/server/integrations.ts +++ b/x-pack/plugins/enterprise_search/server/integrations.ts @@ -4,9 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + import type { CustomIntegrationsPluginSetup } from '@kbn/custom-integrations-plugin/server'; import { i18n } from '@kbn/i18n'; -import { ConnectorServerSideDefinition } from '@kbn/search-connectors-plugin/server'; +import { ConnectorServerSideDefinition } from '@kbn/search-connectors'; import { ConfigType } from '.'; diff --git a/x-pack/plugins/enterprise_search/server/utils/search_result_provider.ts b/x-pack/plugins/enterprise_search/server/utils/search_result_provider.ts index 15b1971c6aecd..a5ce3aeb367da 100644 --- a/x-pack/plugins/enterprise_search/server/utils/search_result_provider.ts +++ b/x-pack/plugins/enterprise_search/server/utils/search_result_provider.ts @@ -10,7 +10,7 @@ import { takeUntil, of, map } from 'rxjs'; import { GlobalSearchResultProvider } from '@kbn/global-search-plugin/server'; import { i18n } from '@kbn/i18n'; -import { ConnectorServerSideDefinition } from '@kbn/search-connectors-plugin/server'; +import { ConnectorServerSideDefinition } from '@kbn/search-connectors'; import { ConfigType } from '..'; import { diff --git a/x-pack/plugins/entity_manager/kibana.jsonc b/x-pack/plugins/entity_manager/kibana.jsonc index efd6d3a445b3f..d5dadcf8fd2b7 100644 --- a/x-pack/plugins/entity_manager/kibana.jsonc +++ b/x-pack/plugins/entity_manager/kibana.jsonc @@ -2,6 +2,8 @@ "type": "plugin", "id": "@kbn/entityManager-plugin", "owner": "@elastic/obs-entities", + "group": "platform", + "visibility": "shared", "description": "Entity manager plugin for entity assets (inventory, topology, etc)", "plugin": { "id": "entityManager", diff --git a/x-pack/plugins/event_log/kibana.jsonc b/x-pack/plugins/event_log/kibana.jsonc index ae1da1389b1eb..8a792f2a43914 100644 --- a/x-pack/plugins/event_log/kibana.jsonc +++ b/x-pack/plugins/event_log/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/event-log-plugin", - "owner": "@elastic/response-ops", + "owner": [ + "@elastic/response-ops" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "eventLog", - "server": true, "browser": false, + "server": true, "configPath": [ "xpack", "eventLog" @@ -15,4 +19,4 @@ "serverless" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/features/kibana.jsonc b/x-pack/plugins/features/kibana.jsonc index ac9f52175f458..f4b3e364a94fe 100644 --- a/x-pack/plugins/features/kibana.jsonc +++ b/x-pack/plugins/features/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/features-plugin", - "owner": "@elastic/kibana-core", + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "features", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "features" @@ -17,4 +21,4 @@ "common" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/features/server/routes/index.ts b/x-pack/plugins/features/server/routes/index.ts index b0da6cf4a0659..b06efbb170ad4 100644 --- a/x-pack/plugins/features/server/routes/index.ts +++ b/x-pack/plugins/features/server/routes/index.ts @@ -21,8 +21,12 @@ export function defineRoutes({ router, featureRegistry }: RouteDefinitionParams) router.get( { path: '/api/features', + security: { + authz: { + requiredPrivileges: ['read_features'], + }, + }, options: { - tags: ['access:features'], access: 'public', summary: `Get features`, }, diff --git a/x-pack/plugins/fields_metadata/kibana.jsonc b/x-pack/plugins/fields_metadata/kibana.jsonc index 2befc0c7be07b..37cdaaf92c2b3 100644 --- a/x-pack/plugins/fields_metadata/kibana.jsonc +++ b/x-pack/plugins/fields_metadata/kibana.jsonc @@ -2,6 +2,8 @@ "type": "plugin", "id": "@kbn/fields-metadata-plugin", "owner": "@elastic/obs-ux-logs-team", + "group": "platform", + "visibility": "shared", "description": "Exposes services for async usage and search of fields metadata.", "plugin": { "id": "fieldsMetadata", diff --git a/x-pack/plugins/file_upload/kibana.jsonc b/x-pack/plugins/file_upload/kibana.jsonc index 6c6e3fddd0e7c..5d49da5beb4a7 100644 --- a/x-pack/plugins/file_upload/kibana.jsonc +++ b/x-pack/plugins/file_upload/kibana.jsonc @@ -1,12 +1,17 @@ { "type": "plugin", "id": "@kbn/file-upload-plugin", - "owner": ["@elastic/kibana-gis", "@elastic/ml-ui"], + "owner": [ + "@elastic/kibana-presentation", + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private", "description": "The file upload plugin contains components and services for uploading a file, analyzing its data, and then importing the data into an Elasticsearch index. Supported file types include CSV, TSV, newline-delimited JSON and GeoJSON.", "plugin": { "id": "fileUpload", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "data", "usageCollection" diff --git a/x-pack/plugins/fleet/kibana.jsonc b/x-pack/plugins/fleet/kibana.jsonc index ce2416c265502..823328da8ada6 100644 --- a/x-pack/plugins/fleet/kibana.jsonc +++ b/x-pack/plugins/fleet/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/fleet-plugin", - "owner": "@elastic/fleet", + "owner": [ + "@elastic/fleet" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "fleet", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "fleet" @@ -38,7 +42,7 @@ "discover", "ingestPipelines", "guidedOnboarding", - "integrationAssistant", + "integrationAssistant" ], "requiredBundles": [ "kibanaReact", @@ -52,4 +56,4 @@ "common" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.test.tsx index 1ebfe5a897b07..62b39e2e8708a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.test.tsx @@ -6,7 +6,9 @@ */ import React from 'react'; -import { act, fireEvent, waitFor } from '@testing-library/react'; +import { waitFor, act } from '@testing-library/react'; + +import { userEvent } from '@testing-library/user-event'; import { getInheritedNamespace } from '../../../../../../../../common/services'; @@ -60,18 +62,6 @@ describe('StepDefinePackagePolicy', () => { package_policies: [], is_protected: false, }, - { - id: 'agent-policy-2', - namespace: 'default', - name: 'Agent policy 2', - is_managed: false, - status: 'active', - updated_at: '', - updated_by: '', - revision: 1, - package_policies: [], - is_protected: false, - }, ]; let packagePolicy: NewPackagePolicy; const mockUpdatePackagePolicy = jest.fn().mockImplementation((val: any) => { @@ -86,20 +76,23 @@ describe('StepDefinePackagePolicy', () => { description: null, namespace: null, inputs: {}, - vars: {}, + vars: { + 'Required var': ['Required var is required'], + }, }; let testRenderer: TestRenderer; let renderResult: ReturnType; - const render = () => + + const render = (namespacePlaceholder = getInheritedNamespace(agentPolicies)) => (renderResult = testRenderer.render( )); @@ -107,57 +100,100 @@ describe('StepDefinePackagePolicy', () => { packagePolicy = { name: '', description: 'desc', - namespace: 'default', + namespace: 'package-policy-ns', + enabled: true, policy_id: '', policy_ids: [''], - enabled: true, + package: { + name: 'apache', + title: 'Apache', + version: '1.0.0', + }, inputs: [], + vars: { + 'Show user var': { + type: 'string', + value: 'showUserVarVal', + }, + 'Required var': { + type: 'bool', + value: undefined, + }, + 'Advanced var': { + type: 'bool', + value: true, + }, + }, }; testRenderer = createFleetTestRendererMock(); }); describe('default API response', () => { - beforeEach(() => { - render(); - }); - it('should display vars coming from package policy', async () => { - waitFor(() => { - expect(renderResult.getByDisplayValue('showUserVarVal')).toBeInTheDocument(); - expect(renderResult.getByRole('switch')).toHaveAttribute('aria-label', 'Required var'); - expect(renderResult.getByText('Required var is required')).toHaveAttribute( - 'class', - 'euiFormErrorText' + act(() => { + render(); + }); + expect(renderResult.getByDisplayValue('showUserVarVal')).toBeInTheDocument(); + expect(renderResult.getByRole('switch', { name: 'Required var' })).toBeInTheDocument(); + expect(renderResult.queryByRole('switch', { name: 'Advanced var' })).not.toBeInTheDocument(); + + expect(renderResult.getByText('Required var is required')).toHaveClass('euiFormErrorText'); + + await userEvent.click(renderResult.getByText('Advanced options').closest('button')!); + + await waitFor(() => { + expect(renderResult.getByRole('switch', { name: 'Advanced var' })).toBeInTheDocument(); + expect(renderResult.getByTestId('packagePolicyNamespaceInput')).toHaveTextContent( + 'package-policy-ns' ); }); + }); - await act(async () => { - fireEvent.click(renderResult.getByText('Advanced options').closest('button')!); + it(`should display namespace from agent policy when there's no package policy namespace`, async () => { + packagePolicy.namespace = ''; + act(() => { + render(); }); - waitFor(() => { - expect(renderResult.getByRole('switch')).toHaveAttribute('aria-label', 'Advanced var'); - expect(renderResult.getByTestId('packagePolicyNamespaceInput')).toHaveAttribute( + await userEvent.click(renderResult.getByText('Advanced options').closest('button')!); + + await waitFor(() => { + expect(renderResult.getByTestId('comboBoxSearchInput')).toHaveAttribute( 'placeholder', 'ns' ); }); }); + + it(`should fallback to the default namespace when namespace is not set in package policy and there's no agent policy`, async () => { + packagePolicy.namespace = ''; + act(() => { + render(getInheritedNamespace([])); + }); + + await userEvent.click(renderResult.getByText('Advanced options').closest('button')!); + + await waitFor(() => { + expect(renderResult.getByTestId('comboBoxSearchInput')).toHaveAttribute( + 'placeholder', + 'default' + ); + }); + }); }); describe('update', () => { describe('when package vars are introduced in a new package version', () => { - it('should display new package vars', () => { - render(); - - waitFor(async () => { - expect(renderResult.getByDisplayValue('showUserVarVal')).toBeInTheDocument(); - expect(renderResult.getByText('Required var')).toBeInTheDocument(); + it('should display new package vars', async () => { + act(() => { + render(); + }); + expect(renderResult.getByDisplayValue('showUserVarVal')).toBeInTheDocument(); + expect(renderResult.getByText('Required var')).toBeInTheDocument(); - await act(async () => { - fireEvent.click(renderResult.getByText('Advanced options').closest('button')!); - }); + await userEvent.click(renderResult.getByText('Advanced options').closest('button')!); + await waitFor(async () => { expect(renderResult.getByText('Advanced var')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.test.tsx index 7d1962939d1fa..583957861bd79 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.test.tsx @@ -6,7 +6,9 @@ */ import React from 'react'; -import { act, fireEvent, waitFor } from '@testing-library/react'; +import { waitFor } from '@testing-library/react'; + +import { userEvent } from '@testing-library/user-event'; import type { TestRenderer } from '../../../../../../../mock'; import { createFleetTestRendererMock } from '../../../../../../../mock'; @@ -108,22 +110,23 @@ describe('StepSelectHosts', () => { testRenderer = createFleetTestRendererMock(); }); - it('should display create form when no agent policies', () => { + it('should display create form when no agent policies', async () => { (useGetAgentPolicies as jest.MockedFunction).mockReturnValue({ data: { items: [], }, }); + (useAllNonManagedAgentPolicies as jest.MockedFunction).mockReturnValue([]); render(); - waitFor(() => { - expect(renderResult.getByText('Agent policy 1')).toBeInTheDocument(); + await waitFor(() => { + expect(renderResult.getByText('New agent policy name')).toBeInTheDocument(); }); expect(renderResult.queryByRole('tablist')).not.toBeInTheDocument(); }); - it('should display tabs with New hosts selected when agent policies exist', () => { + it('should display tabs with New hosts selected when agent policies exist', async () => { (useGetAgentPolicies as jest.MockedFunction).mockReturnValue({ data: { items: [{ id: '1', name: 'Agent policy 1', namespace: 'default' }], @@ -135,10 +138,7 @@ describe('StepSelectHosts', () => { render(); - waitFor(() => { - expect(renderResult.getByRole('tablist')).toBeInTheDocument(); - expect(renderResult.getByText('Agent policy 3')).toBeInTheDocument(); - }); + expect(renderResult.getByRole('tablist')).toBeInTheDocument(); expect(renderResult.getByText('New hosts').closest('button')).toHaveAttribute( 'aria-selected', 'true' @@ -157,16 +157,15 @@ describe('StepSelectHosts', () => { render(); - waitFor(() => { - expect(renderResult.getByRole('tablist')).toBeInTheDocument(); - }); - act(() => { - fireEvent.click(renderResult.getByText('Existing hosts').closest('button')!); - }); + expect(renderResult.getByRole('tablist')).toBeInTheDocument(); + + await userEvent.click(renderResult.getByText('Existing hosts').closest('button')!); - expect( - renderResult.container.querySelector('[data-test-subj="agentPolicySelect"]')?.textContent - ).toContain('Agent policy 1'); + await waitFor(() => { + expect( + renderResult.container.querySelector('[data-test-subj="agentPolicySelect"]')?.textContent + ).toContain('Agent policy 1'); + }); }); it('should display dropdown without preselected value when Existing hosts selected with mulitple agent policies', async () => { @@ -185,14 +184,11 @@ describe('StepSelectHosts', () => { render(); - waitFor(() => { - expect(renderResult.getByRole('tablist')).toBeInTheDocument(); - }); - act(() => { - fireEvent.click(renderResult.getByText('Existing hosts').closest('button')!); - }); + expect(renderResult.getByRole('tablist')).toBeInTheDocument(); + + await userEvent.click(renderResult.getByText('Existing hosts').closest('button')!); - await act(async () => { + await waitFor(() => { const select = renderResult.container.querySelector('[data-test-subj="agentPolicySelect"]'); expect((select as any)?.value).toEqual(''); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.test.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.test.ts index ebdc578a04752..3fe1890e75f72 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.test.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react-hooks/dom'; import { waitFor } from '@testing-library/react'; @@ -228,7 +228,7 @@ describe('useSetupTechnology', () => { }); it('should fetch agentless policy if agentless feature is enabled and isServerless is true', async () => { - const { waitForNextUpdate } = renderHook(() => + renderHook(() => useSetupTechnology({ setNewAgentPolicy, newAgentPolicy: newAgentPolicyMock, @@ -238,9 +238,9 @@ describe('useSetupTechnology', () => { }) ); - await waitForNextUpdate(); - - expect(sendGetOneAgentPolicy).toHaveBeenCalled(); + await waitFor(() => { + expect(sendGetOneAgentPolicy).toHaveBeenCalled(); + }); }); it('should set agentless setup technology if agent policy supports agentless in edit page', async () => { @@ -286,7 +286,7 @@ describe('useSetupTechnology', () => { isCloudEnabled: true, }, }); - const { result, waitForNextUpdate } = renderHook(() => + const { result } = renderHook(() => useSetupTechnology({ setNewAgentPolicy, newAgentPolicy: newAgentPolicyMock, @@ -301,14 +301,13 @@ describe('useSetupTechnology', () => { act(() => { result.current.handleSetupTechnologyChange(SetupTechnology.AGENTLESS); }); - - waitForNextUpdate(); - - expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENTLESS); - expect(setNewAgentPolicy).toHaveBeenCalledWith({ - name: 'Agentless policy for endpoint-1', - supports_agentless: true, - inactivity_timeout: 3600, + await waitFor(() => { + expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENTLESS); + expect(setNewAgentPolicy).toHaveBeenCalledWith({ + name: 'Agentless policy for endpoint-1', + supports_agentless: true, + inactivity_timeout: 3600, + }); }); }); @@ -326,15 +325,18 @@ describe('useSetupTechnology', () => { isCloudEnabled: true, }, }); - const { result, rerender } = renderHook(() => - useSetupTechnology({ - setNewAgentPolicy, - newAgentPolicy: newAgentPolicyMock, - updateAgentPolicies: updateAgentPoliciesMock, - setSelectedPolicyTab: setSelectedPolicyTabMock, - packagePolicy: packagePolicyMock, - }) - ); + + const initialProps = { + setNewAgentPolicy, + newAgentPolicy: newAgentPolicyMock, + updateAgentPolicies: updateAgentPoliciesMock, + setSelectedPolicyTab: setSelectedPolicyTabMock, + packagePolicy: packagePolicyMock, + }; + + const { result, rerender } = renderHook((props = initialProps) => useSetupTechnology(props), { + initialProps, + }); expect(generateNewAgentPolicyWithDefaults).toHaveBeenCalled(); @@ -360,7 +362,7 @@ describe('useSetupTechnology', () => { }, }); - waitFor(() => { + await waitFor(() => { expect(setNewAgentPolicy).toHaveBeenCalledWith({ name: 'Agentless policy for endpoint-2', inactivity_timeout: 3600, @@ -377,7 +379,7 @@ describe('useSetupTechnology', () => { }, }); - const { result, waitForNextUpdate } = renderHook(() => + const { result } = renderHook(() => useSetupTechnology({ setNewAgentPolicy, newAgentPolicy: newAgentPolicyMock, @@ -393,8 +395,7 @@ describe('useSetupTechnology', () => { result.current.handleSetupTechnologyChange(SetupTechnology.AGENT_BASED); }); - waitForNextUpdate(); - expect(setNewAgentPolicy).toHaveBeenCalledTimes(0); + await waitFor(() => expect(setNewAgentPolicy).toHaveBeenCalledTimes(0)); }); it('should not fetch agentless policy if agentless is enabled but serverless is disabled', async () => { @@ -419,7 +420,7 @@ describe('useSetupTechnology', () => { }); it('should update agent policy and selected policy tab when setup technology is agentless', async () => { - const { result, waitForNextUpdate } = renderHook(() => + const { result } = renderHook(() => useSetupTechnology({ setNewAgentPolicy, newAgentPolicy: newAgentPolicyMock, @@ -429,18 +430,24 @@ describe('useSetupTechnology', () => { }) ); - await waitForNextUpdate(); - act(() => { result.current.handleSetupTechnologyChange(SetupTechnology.AGENTLESS); }); - expect(updateAgentPoliciesMock).toHaveBeenCalledWith([{ id: 'agentless-policy-id' }]); - expect(setSelectedPolicyTabMock).toHaveBeenCalledWith(SelectedPolicyTab.EXISTING); + await waitFor(() => { + expect(updateAgentPoliciesMock).toHaveBeenCalledWith([ + { + inactivity_timeout: 3600, + name: 'Agentless policy for endpoint-1', + supports_agentless: true, + }, + ]); + expect(setSelectedPolicyTabMock).toHaveBeenCalledWith(SelectedPolicyTab.EXISTING); + }); }); it('should update new agent policy and selected policy tab when setup technology is agent-based', async () => { - const { result, waitForNextUpdate } = renderHook(() => + const { result } = renderHook(() => useSetupTechnology({ setNewAgentPolicy, newAgentPolicy: newAgentPolicyMock, @@ -450,8 +457,6 @@ describe('useSetupTechnology', () => { }) ); - await waitForNextUpdate(); - expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENT_BASED); act(() => { @@ -466,8 +471,10 @@ describe('useSetupTechnology', () => { expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENT_BASED); - expect(setNewAgentPolicy).toHaveBeenCalledWith(newAgentPolicyMock); - expect(setSelectedPolicyTabMock).toHaveBeenCalledWith(SelectedPolicyTab.NEW); + await waitFor(() => { + expect(setNewAgentPolicy).toHaveBeenCalledWith(newAgentPolicyMock); + expect(setSelectedPolicyTabMock).toHaveBeenCalledWith(SelectedPolicyTab.NEW); + }); }); it('should not update agent policy and selected policy tab when agentless is disabled', async () => { @@ -495,7 +502,7 @@ describe('useSetupTechnology', () => { }); it('should not update agent policy and selected policy tab when setup technology matches the current one ', async () => { - const { result, waitForNextUpdate } = renderHook(() => + const { result } = renderHook(() => useSetupTechnology({ setNewAgentPolicy, newAgentPolicy: newAgentPolicyMock, @@ -505,7 +512,7 @@ describe('useSetupTechnology', () => { }) ); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENT_BASED); @@ -520,7 +527,7 @@ describe('useSetupTechnology', () => { }); it('should revert the agent policy name to the original value when switching from agentless back to agent-based', async () => { - const { result, rerender } = renderHook(() => + const { result } = renderHook(() => useSetupTechnology({ setNewAgentPolicy, newAgentPolicy: newAgentPolicyMock, @@ -529,7 +536,6 @@ describe('useSetupTechnology', () => { packagePolicy: packagePolicyMock, }) ); - await rerender(); expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENT_BASED); @@ -538,20 +544,68 @@ describe('useSetupTechnology', () => { }); expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENTLESS); - expect(setNewAgentPolicy).toHaveBeenCalledWith({ - id: 'agentless-policy-id', + + await waitFor(() => { + expect(setNewAgentPolicy).toHaveBeenCalledWith({ + name: 'Agentless policy for endpoint-1', + supports_agentless: true, + inactivity_timeout: 3600, + }); }); act(() => { result.current.handleSetupTechnologyChange(SetupTechnology.AGENT_BASED); }); + + expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENT_BASED); + expect(setNewAgentPolicy).toHaveBeenCalledWith(newAgentPolicyMock); + }); + + it('should have global_data_tags with the integration team when creating agentless policy with global_data_tags', async () => { + (useConfig as MockFn).mockReturnValue({ + agentless: { + enabled: true, + api: { + url: 'https://agentless.api.url', + }, + }, + } as any); + (useStartServices as MockFn).mockReturnValue({ + cloud: { + isCloudEnabled: true, + }, + }); + + const { result } = renderHook(() => + useSetupTechnology({ + setNewAgentPolicy, + newAgentPolicy: newAgentPolicyMock, + updateAgentPolicies: updateAgentPoliciesMock, + setSelectedPolicyTab: setSelectedPolicyTabMock, + packagePolicy: packagePolicyMock, + packageInfo: packageInfoMock, + }) + ); + + act(() => { + result.current.handleSetupTechnologyChange(SetupTechnology.AGENTLESS, 'cspm'); + }); + await waitFor(() => { - expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENT_BASED); - expect(setNewAgentPolicy).toHaveBeenCalledWith(newAgentPolicyMock); + expect(setNewAgentPolicy).toHaveBeenCalledWith( + expect.objectContaining({ + supports_agentless: true, + global_data_tags: [ + { name: 'organization', value: 'org' }, + { name: 'division', value: 'div' }, + { name: 'team', value: 'team' }, + ], + }) + ); }); }); - it('should have global_data_tags with the integration team when updating the agentless policy', async () => { + it('should not fail and not have global_data_tags when creating the agentless policy when it cannot find the policy template', async () => { (useConfig as MockFn).mockReturnValue({ agentless: { enabled: true, @@ -574,19 +628,23 @@ describe('useSetupTechnology', () => { setSelectedPolicyTab: setSelectedPolicyTabMock, packagePolicy: packagePolicyMock, packageInfo: packageInfoMock, - isEditPage: true, - agentPolicies: [{ id: 'agentless-policy-id', supports_agentless: true } as any], }) ); act(() => { - result.current.handleSetupTechnologyChange(SetupTechnology.AGENTLESS, 'cspm'); + result.current.handleSetupTechnologyChange( + SetupTechnology.AGENTLESS, + 'never-gonna-give-you-up' + ); }); - waitFor(() => { + await waitFor(() => { expect(setNewAgentPolicy).toHaveBeenCalledWith({ - ...newAgentPolicyMock, + name: 'Agentless policy for endpoint-1', supports_agentless: true, + inactivity_timeout: 3600, + }); + expect(setNewAgentPolicy).not.toHaveBeenCalledWith({ global_data_tags: [ { name: 'organization', value: 'org' }, { name: 'division', value: 'div' }, @@ -596,7 +654,7 @@ describe('useSetupTechnology', () => { }); }); - it('should not fail and not have global_data_tags when updating the agentless policy when it cannot find the policy template', async () => { + it('should not fail and not have global_data_tags when creating the agentless policy without the policy template name', async () => { (useConfig as MockFn).mockReturnValue({ agentless: { enabled: true, @@ -618,27 +676,31 @@ describe('useSetupTechnology', () => { updateAgentPolicies: updateAgentPoliciesMock, setSelectedPolicyTab: setSelectedPolicyTabMock, packagePolicy: packagePolicyMock, - isEditPage: true, - agentPolicies: [{ id: 'agentless-policy-id', supports_agentless: true } as any], + packageInfo: packageInfoMock, }) ); act(() => { - result.current.handleSetupTechnologyChange( - SetupTechnology.AGENTLESS, - 'never-gonna-give-you-up' - ); + result.current.handleSetupTechnologyChange(SetupTechnology.AGENTLESS); }); - waitFor(() => { + await waitFor(() => { expect(setNewAgentPolicy).toHaveBeenCalledWith({ - ...newAgentPolicyMock, + name: 'Agentless policy for endpoint-1', supports_agentless: true, + inactivity_timeout: 3600, + }); + expect(setNewAgentPolicy).not.toHaveBeenCalledWith({ + global_data_tags: [ + { name: 'organization', value: 'org' }, + { name: 'division', value: 'div' }, + { name: 'team', value: 'team' }, + ], }); }); }); - it('should not fail and not have global_data_tags when updating the agentless policy without the policy temaplte name', async () => { + it('should not fail and not have global_data_tags when creating the agentless policy without the packageInfo', async () => { (useConfig as MockFn).mockReturnValue({ agentless: { enabled: true, @@ -660,25 +722,30 @@ describe('useSetupTechnology', () => { updateAgentPolicies: updateAgentPoliciesMock, setSelectedPolicyTab: setSelectedPolicyTabMock, packagePolicy: packagePolicyMock, - packageInfo: packageInfoMock, - isEditPage: true, - agentPolicies: [{ id: 'agentless-policy-id', supports_agentless: true } as any], }) ); act(() => { - result.current.handleSetupTechnologyChange(SetupTechnology.AGENTLESS); + result.current.handleSetupTechnologyChange(SetupTechnology.AGENTLESS, 'cspm'); }); - waitFor(() => { + await waitFor(() => { expect(setNewAgentPolicy).toHaveBeenCalledWith({ - ...newAgentPolicyMock, + name: 'Agentless policy for endpoint-1', supports_agentless: true, + inactivity_timeout: 3600, + }); + expect(setNewAgentPolicy).not.toHaveBeenCalledWith({ + global_data_tags: [ + { name: 'organization', value: 'org' }, + { name: 'division', value: 'div' }, + { name: 'team', value: 'team' }, + ], }); }); }); - it('should not fail and not have global_data_tags when updating the agentless policy without the packageInfo', async () => { + it('should not have global_data_tags when switching from agentless to agent-based policy', async () => { (useConfig as MockFn).mockReturnValue({ agentless: { enabled: true, @@ -700,8 +767,7 @@ describe('useSetupTechnology', () => { updateAgentPolicies: updateAgentPoliciesMock, setSelectedPolicyTab: setSelectedPolicyTabMock, packagePolicy: packagePolicyMock, - isEditPage: true, - agentPolicies: [{ id: 'agentless-policy-id', supports_agentless: true } as any], + packageInfo: packageInfoMock, }) ); @@ -709,10 +775,31 @@ describe('useSetupTechnology', () => { result.current.handleSetupTechnologyChange(SetupTechnology.AGENTLESS, 'cspm'); }); - waitFor(() => { - expect(setNewAgentPolicy).toHaveBeenCalledWith({ - ...newAgentPolicyMock, - supports_agentless: true, + await waitFor(() => { + expect(setNewAgentPolicy).toHaveBeenCalledWith( + expect.objectContaining({ + supports_agentless: true, + global_data_tags: [ + { name: 'organization', value: 'org' }, + { name: 'division', value: 'div' }, + { name: 'team', value: 'team' }, + ], + }) + ); + }); + + act(() => { + result.current.handleSetupTechnologyChange(SetupTechnology.AGENT_BASED); + }); + + await waitFor(() => { + expect(setNewAgentPolicy).toHaveBeenCalledWith(newAgentPolicyMock); + expect(setNewAgentPolicy).not.toHaveBeenCalledWith({ + global_data_tags: [ + { name: 'organization', value: 'org' }, + { name: 'division', value: 'div' }, + { name: 'team', value: 'team' }, + ], }); }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts index 2a88fecc6b145..52841905b3c6d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts @@ -168,6 +168,7 @@ export function useSetupTechnology({ } as NewAgentPolicy; setNewAgentPolicy(agentlessPolicy); + setNewAgentlessPolicy(agentlessPolicy); setSelectedPolicyTab(SelectedPolicyTab.NEW); updateAgentPolicies([agentlessPolicy] as AgentPolicy[]); } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx index ff89db3e0c842..4826a6b329bf5 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx @@ -154,14 +154,7 @@ afterAll(() => { consoleDebugMock.mockRestore(); }); -// FLAKY: https://github.com/elastic/kibana/issues/196463 -// FLAKY: https://github.com/elastic/kibana/issues/196464 -// FLAKY: https://github.com/elastic/kibana/issues/196465 -// FLAKY: https://github.com/elastic/kibana/issues/196466 -// FLAKY: https://github.com/elastic/kibana/issues/196467 -// FLAKY: https://github.com/elastic/kibana/issues/196468 -// FLAKY: https://github.com/elastic/kibana/issues/196469 -describe.skip('When on the package policy create page', () => { +describe('When on the package policy create page', () => { afterEach(() => { jest.clearAllMocks(); }); @@ -868,7 +861,7 @@ describe.skip('When on the package policy create page', () => { test('should create agentless agent policy and package policy when in cloud and agentless API url is set', async () => { fireEvent.click(renderResult.getByTestId(SETUP_TECHNOLOGY_SELECTOR_TEST_SUBJ)); - fireEvent.click(renderResult.getByText('Agentless')); + fireEvent.click(renderResult.getAllByText('Agentless')[0]); await act(async () => { fireEvent.click(renderResult.getByText(/Save and continue/).closest('button')!); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/step_edit_hosts.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/step_edit_hosts.test.tsx index 064624d364a92..e30fa6c22c5ce 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/step_edit_hosts.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/step_edit_hosts.test.tsx @@ -6,7 +6,9 @@ */ import React from 'react'; -import { act, fireEvent, waitFor } from '@testing-library/react'; +import { waitFor } from '@testing-library/react'; + +import { userEvent } from '@testing-library/user-event'; import type { TestRenderer } from '../../../../../../mock'; import { createFleetTestRendererMock } from '../../../../../../mock'; @@ -111,18 +113,18 @@ describe('StepEditHosts', () => { testRenderer = createFleetTestRendererMock(); }); - it('should display create form when no agent policies', () => { + it('should display create form when no agent policies', async () => { (useGetAgentPolicies as jest.MockedFunction).mockReturnValue({ data: { items: [], }, }); + (useAllNonManagedAgentPolicies as jest.MockedFunction).mockReturnValue([]); + render(); - waitFor(() => { - expect(renderResult.getByText('Agent policy 1')).toBeInTheDocument(); - }); + expect(renderResult.getByText('New agent policy name')).toBeInTheDocument(); expect(renderResult.queryByRole('tablist')).not.toBeInTheDocument(); }); @@ -144,7 +146,7 @@ describe('StepEditHosts', () => { ).toContain('Agent policy 1'); }); - it('should display dropdown without preselected value when mulitple agent policies', () => { + it('should display dropdown without preselected value when multiple agent policies', async () => { (useGetAgentPolicies as jest.MockedFunction).mockReturnValue({ data: { items: [ @@ -156,12 +158,12 @@ describe('StepEditHosts', () => { render(); - waitFor(() => { - expect(renderResult.getByText('At least one agent policy is required.')).toBeInTheDocument(); - }); + expect( + renderResult.getByText('Select an agent policy to add this integration to') + ).toBeInTheDocument(); }); - it('should display delete button when add button clicked', () => { + it('should display delete button when add button clicked', async () => { (useGetAgentPolicies as jest.MockedFunction).mockReturnValue({ data: { items: [{ id: '1', name: 'Agent policy 1', namespace: 'default' }], @@ -173,10 +175,12 @@ describe('StepEditHosts', () => { render(); - act(() => { - fireEvent.click(renderResult.getByTestId('createNewAgentPolicyButton').closest('button')!); - }); + await userEvent.click( + renderResult.getByTestId('createNewAgentPolicyButton').closest('button')! + ); - expect(renderResult.getByTestId('deleteNewAgentPolicyButton')).toBeInTheDocument(); + await waitFor(() => { + expect(renderResult.getByTestId('deleteNewAgentPolicyButton')).toBeInTheDocument(); + }); }); }); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/documentation/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/documentation/index.tsx index a418c38cd8f33..c4fc2863868a3 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/documentation/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/documentation/index.tsx @@ -79,7 +79,7 @@ export const DocumentationPage: React.FunctionComponent = ({ packageInfo, defaultMessage="This documents all the inputs, streams, and variables available to use this integration programmatically via the Fleet Kibana API. {learnMore}" values={{ learnMore: ( - +
{children}
, h6: ({ children }) =>
{children}
, - link: ({ children, href }: { children: React.ReactNode[]; href?: string }) => ( + a: ({ children, href }: { children: React.ReactNode[]; href?: string }) => ( | undefined; } const MAX_ASSETS_TO_INSTALL_IN_PARALLEL = 100; @@ -37,11 +39,14 @@ export async function installKibanaAssetsWithStreaming({ packageInstallContext, savedObjectsClient, pkgName, - installedPkg, }: InstallKibanaAssetsWithStreamingArgs): Promise { const { archiveIterator } = packageInstallContext; - const { savedObjectClientWithSpace } = getSpaceAwareSaveobjectsClients(spaceId); + const { savedObjectClientWithSpace, savedObjectsImporter } = + getSpaceAwareSaveobjectsClients(spaceId); + + await createDefaultIndexPatterns(savedObjectsImporter); + await makeManagedIndexPatternsGlobal(savedObjectsClient); const assetRefs: KibanaAssetReference[] = []; let batch: ArchiveAsset[] = []; diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_kibana_assets.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_kibana_assets.ts index aabd23f2eb9cc..22c785f568402 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_kibana_assets.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_kibana_assets.ts @@ -40,7 +40,7 @@ export async function stepInstallKibanaAssets(context: InstallContext) { } export async function stepInstallKibanaAssetsWithStreaming(context: InstallContext) { - const { savedObjectsClient, installedPkg, packageInstallContext, spaceId } = context; + const { savedObjectsClient, packageInstallContext, spaceId } = context; const { packageInfo } = packageInstallContext; const { name: pkgName } = packageInfo; @@ -51,7 +51,6 @@ export async function stepInstallKibanaAssetsWithStreaming(context: InstallConte savedObjectsClient, pkgName, packageInstallContext, - installedPkg, spaceId, }) ); diff --git a/x-pack/plugins/global_search/kibana.jsonc b/x-pack/plugins/global_search/kibana.jsonc index 080d327dec4cb..9eb188986901d 100644 --- a/x-pack/plugins/global_search/kibana.jsonc +++ b/x-pack/plugins/global_search/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/global-search-plugin", - "owner": "@elastic/appex-sharedux", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "globalSearch", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "global_search" @@ -14,4 +18,4 @@ "licensing" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/global_search_bar/kibana.jsonc b/x-pack/plugins/global_search_bar/kibana.jsonc index 6412f7c8ed890..b61cce43fe485 100644 --- a/x-pack/plugins/global_search_bar/kibana.jsonc +++ b/x-pack/plugins/global_search_bar/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/global-search-bar-plugin", - "owner": "@elastic/appex-sharedux", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private", "plugin": { "id": "globalSearchBar", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "global_search_bar" @@ -18,4 +22,4 @@ "savedObjectsTagging" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/global_search_providers/kibana.jsonc b/x-pack/plugins/global_search_providers/kibana.jsonc index cdfed2ebbaf5d..aa8228118be09 100644 --- a/x-pack/plugins/global_search_providers/kibana.jsonc +++ b/x-pack/plugins/global_search_providers/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/global-search-providers-plugin", - "owner": "@elastic/appex-sharedux", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private", "plugin": { "id": "globalSearchProviders", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "global_search_providers" @@ -14,4 +18,4 @@ "globalSearch" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/graph/kibana.jsonc b/x-pack/plugins/graph/kibana.jsonc index 3c299bbeb4a2b..33862384394b4 100644 --- a/x-pack/plugins/graph/kibana.jsonc +++ b/x-pack/plugins/graph/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/graph-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "private", "plugin": { "id": "graph", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "graph" @@ -31,4 +35,4 @@ "savedObjects" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/grokdebugger/kibana.jsonc b/x-pack/plugins/grokdebugger/kibana.jsonc index c006355cc9265..f72fe281c1942 100644 --- a/x-pack/plugins/grokdebugger/kibana.jsonc +++ b/x-pack/plugins/grokdebugger/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/grokdebugger-plugin", - "owner": "@elastic/kibana-management", + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "private", "plugin": { "id": "grokdebugger", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "grokdebugger" @@ -19,4 +23,4 @@ "kibanaReact" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/index_management/kibana.jsonc b/x-pack/plugins/index_management/kibana.jsonc index b9bec8115e019..de2aa50a20eac 100644 --- a/x-pack/plugins/index_management/kibana.jsonc +++ b/x-pack/plugins/index_management/kibana.jsonc @@ -1,14 +1,38 @@ { "type": "plugin", "id": "@kbn/index-management-plugin", - "owner": "@elastic/kibana-management", + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "indexManagement", - "server": true, "browser": true, - "configPath": ["xpack", "index_management"], - "requiredPlugins": ["home", "management", "features", "share"], - "optionalPlugins": ["security", "usageCollection", "fleet", "cloud", "ml", "console","licensing"], - "requiredBundles": ["kibanaReact", "esUiShared", "runtimeFields"] + "server": true, + "configPath": [ + "xpack", + "index_management" + ], + "requiredPlugins": [ + "home", + "management", + "features", + "share" + ], + "optionalPlugins": [ + "security", + "usageCollection", + "fleet", + "cloud", + "ml", + "console", + "licensing" + ], + "requiredBundles": [ + "kibanaReact", + "esUiShared", + "runtimeFields" + ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/inference/kibana.jsonc b/x-pack/plugins/inference/kibana.jsonc index 6e4e389bdc5ff..f184a848cf982 100644 --- a/x-pack/plugins/inference/kibana.jsonc +++ b/x-pack/plugins/inference/kibana.jsonc @@ -2,6 +2,8 @@ "type": "plugin", "id": "@kbn/inference-plugin", "owner": "@elastic/appex-ai-infra", + "group": "platform", + "visibility": "shared", "plugin": { "id": "inference", "server": true, diff --git a/x-pack/plugins/ingest_pipelines/kibana.jsonc b/x-pack/plugins/ingest_pipelines/kibana.jsonc index 55fa46c61b377..85b3e43aedf4d 100644 --- a/x-pack/plugins/ingest_pipelines/kibana.jsonc +++ b/x-pack/plugins/ingest_pipelines/kibana.jsonc @@ -2,6 +2,8 @@ "type": "plugin", "id": "@kbn/ingest-pipelines-plugin", "owner": "@elastic/kibana-management", + "group": "platform", + "visibility": "shared", "plugin": { "id": "ingestPipelines", "server": true, diff --git a/x-pack/plugins/integration_assistant/kibana.jsonc b/x-pack/plugins/integration_assistant/kibana.jsonc index 94840008a3344..66ee0775fb481 100644 --- a/x-pack/plugins/integration_assistant/kibana.jsonc +++ b/x-pack/plugins/integration_assistant/kibana.jsonc @@ -2,6 +2,8 @@ "type": "plugin", "id": "@kbn/integration-assistant-plugin", "owner": "@elastic/security-scalability", + "group": "platform", + "visibility": "shared", "description": "Plugin implementing the Integration Assistant API and UI", "plugin": { "id": "integrationAssistant", diff --git a/x-pack/plugins/kubernetes_security/kibana.jsonc b/x-pack/plugins/kubernetes_security/kibana.jsonc index 838204a7bdbe5..793b5dcce3c6e 100644 --- a/x-pack/plugins/kubernetes_security/kibana.jsonc +++ b/x-pack/plugins/kubernetes_security/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/kubernetes-security-plugin", - "owner": "@elastic/kibana-cloud-security-posture", + "owner": [ + "@elastic/kibana-cloud-security-posture" + ], + "group": "security", + "visibility": "private", "plugin": { "id": "kubernetesSecurity", - "server": true, "browser": true, + "server": true, "requiredPlugins": [ "data", "timelines", @@ -16,4 +20,4 @@ "kibanaReact" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/kubernetes_security/public/test/index.tsx b/x-pack/plugins/kubernetes_security/public/test/index.tsx index a267169e6cf18..4aeaf93f746cc 100644 --- a/x-pack/plugins/kubernetes_security/public/test/index.tsx +++ b/x-pack/plugins/kubernetes_security/public/test/index.tsx @@ -17,7 +17,7 @@ import { coreMock } from '@kbn/core/public/mocks'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; -type UiRender = (ui: React.ReactElement, options?: RenderOptions) => RenderResult; +type UiRender = (ui: React.ReactNode, options?: RenderOptions) => RenderResult; /** * Mocked app root context renderer @@ -113,7 +113,7 @@ export const createAppRootMockRenderer = (): AppContextTestRender => { }, }); - const AppWrapper: React.FC<{ children: React.ReactElement }> = ({ children }) => ( + const AppWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( {children} diff --git a/x-pack/plugins/lens/common/embeddable_factory/index.ts b/x-pack/plugins/lens/common/embeddable_factory/index.ts index b794ec642f40a..68e6c77e9daeb 100644 --- a/x-pack/plugins/lens/common/embeddable_factory/index.ts +++ b/x-pack/plugins/lens/common/embeddable_factory/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { cloneDeep } from 'lodash'; import type { SerializableRecord, Serializable } from '@kbn/utility-types'; import type { SavedObjectReference } from '@kbn/core/types'; import type { @@ -17,7 +18,8 @@ export type LensEmbeddablePersistableState = EmbeddableStateWithType & { }; export const inject: EmbeddableRegistryDefinition['inject'] = (state, references) => { - const typedState = state as LensEmbeddablePersistableState; + // We need to clone the state because we can not modify the original state object. + const typedState = cloneDeep(state) as LensEmbeddablePersistableState; if ('attributes' in typedState && typedState.attributes !== undefined) { // match references based on name, so only references associated with this lens panel are injected. diff --git a/x-pack/plugins/lens/kibana.jsonc b/x-pack/plugins/lens/kibana.jsonc index 6a3f9875d1da9..4b0b14141474f 100644 --- a/x-pack/plugins/lens/kibana.jsonc +++ b/x-pack/plugins/lens/kibana.jsonc @@ -1,12 +1,16 @@ { "type": "plugin", "id": "@kbn/lens-plugin", - "owner": "@elastic/kibana-visualizations", + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "shared", "description": "Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana.", "plugin": { "id": "lens", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "lens" @@ -35,7 +39,7 @@ "expressionTagcloud", "eventAnnotation", "unifiedSearch", - "contentManagement", + "contentManagement" ], "optionalPlugins": [ "expressionLegacyMetricVis", @@ -57,10 +61,10 @@ "fieldFormats", "charts", "esqlDataGrid", - "esql", + "esql" ], "extraPublicDirs": [ "common/constants" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/license_api_guard/kibana.jsonc b/x-pack/plugins/license_api_guard/kibana.jsonc index a8d0ed45794d5..5f03763403d11 100644 --- a/x-pack/plugins/license_api_guard/kibana.jsonc +++ b/x-pack/plugins/license_api_guard/kibana.jsonc @@ -1,14 +1,18 @@ { "type": "plugin", "id": "@kbn/license-api-guard-plugin", - "owner": "@elastic/kibana-management", + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "private", "plugin": { "id": "licenseApiGuard", - "server": true, "browser": false, + "server": true, "configPath": [ "xpack", "licenseApiGuard" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/license_management/kibana.jsonc b/x-pack/plugins/license_management/kibana.jsonc index 72f1dc8b824ea..7fe037ed6b702 100644 --- a/x-pack/plugins/license_management/kibana.jsonc +++ b/x-pack/plugins/license_management/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/license-management-plugin", - "owner": "@elastic/kibana-management", + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "licenseManagement", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "license_management" @@ -29,4 +33,4 @@ "common/constants" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/licensing/kibana.jsonc b/x-pack/plugins/licensing/kibana.jsonc index 91eaa2eb4f38a..0fa69adb14b3a 100644 --- a/x-pack/plugins/licensing/kibana.jsonc +++ b/x-pack/plugins/licensing/kibana.jsonc @@ -1,15 +1,19 @@ { "type": "plugin", "id": "@kbn/licensing-plugin", - "owner": "@elastic/kibana-core", + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "licensing", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "licensing" ], "requiredBundles": [] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/lists/kibana.jsonc b/x-pack/plugins/lists/kibana.jsonc index 83be4431ce8e8..92b668dc7bb13 100644 --- a/x-pack/plugins/lists/kibana.jsonc +++ b/x-pack/plugins/lists/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/lists-plugin", - "owner": "@elastic/security-detection-engine", + "owner": [ + "@elastic/security-detection-engine" + ], + "group": "security", + "visibility": "private", "plugin": { "id": "lists", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "lists" @@ -18,4 +22,4 @@ "common" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/logstash/kibana.jsonc b/x-pack/plugins/logstash/kibana.jsonc index a59fe4ea61af0..83665c1ed344e 100644 --- a/x-pack/plugins/logstash/kibana.jsonc +++ b/x-pack/plugins/logstash/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/logstash-plugin", - "owner": "@elastic/logstash", + "owner": [ + "@elastic/logstash" + ], + "group": "observability", + "visibility": "private", "plugin": { "id": "logstash", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "logstash" @@ -22,4 +26,4 @@ ], "requiredBundles": [] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/maps/kibana.jsonc b/x-pack/plugins/maps/kibana.jsonc index b042d0250b0c2..8dacbf3c1b7f8 100644 --- a/x-pack/plugins/maps/kibana.jsonc +++ b/x-pack/plugins/maps/kibana.jsonc @@ -1,11 +1,15 @@ { "type": "plugin", "id": "@kbn/maps-plugin", - "owner": "@elastic/kibana-gis", + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "maps", - "server": true, "browser": true, + "server": true, "configPath": [ "xpack", "maps" @@ -50,7 +54,7 @@ "unifiedSearch", "fieldFormats", "esql", - "savedObjects", + "savedObjects" ], "extraPublicDirs": [ "common" diff --git a/x-pack/plugins/ml/kibana.jsonc b/x-pack/plugins/ml/kibana.jsonc index 4ec2cf57312a9..274a92c57a2c3 100644 --- a/x-pack/plugins/ml/kibana.jsonc +++ b/x-pack/plugins/ml/kibana.jsonc @@ -1,13 +1,20 @@ { "type": "plugin", "id": "@kbn/ml-plugin", - "owner": "@elastic/ml-ui", + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "shared", "description": "This plugin provides access to the machine learning features provided by Elastic.", "plugin": { "id": "ml", - "server": true, "browser": true, - "configPath": ["xpack", "ml"], + "server": true, + "configPath": [ + "xpack", + "ml" + ], "requiredPlugins": [ "aiops", "charts", @@ -58,6 +65,8 @@ "savedObjectsFinder", "usageCollection" ], - "extraPublicDirs": ["common"] + "extraPublicDirs": [ + "common" + ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/ml/public/application/components/collapsible_panel/collapsible_panel.tsx b/x-pack/plugins/ml/public/application/components/collapsible_panel/collapsible_panel.tsx index 903033481bd07..b33d056467d1a 100644 --- a/x-pack/plugins/ml/public/application/components/collapsible_panel/collapsible_panel.tsx +++ b/x-pack/plugins/ml/public/application/components/collapsible_panel/collapsible_panel.tsx @@ -16,6 +16,7 @@ import { } from '@elastic/eui'; import type { PropsWithChildren } from 'react'; import React, { type FC } from 'react'; +import { i18n } from '@kbn/i18n'; import { PanelHeaderItems } from './panel_header_items'; import { useCurrentThemeVars } from '../../contexts/kibana'; @@ -24,6 +25,7 @@ export interface CollapsiblePanelProps { onToggle: (isOpen: boolean) => void; header: React.ReactElement; headerItems?: React.ReactElement[]; + ariaLabel: string; } export const CollapsiblePanel: FC> = ({ @@ -32,6 +34,7 @@ export const CollapsiblePanel: FC> = ({ children, header, headerItems, + ariaLabel, }) => { const { euiTheme } = useCurrentThemeVars(); @@ -51,6 +54,17 @@ export const CollapsiblePanel: FC> = ({ { diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/utils.ts b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/utils.ts index c154abb6f5f69..d2dff81c32621 100644 --- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/utils.ts +++ b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/utils.ts @@ -285,13 +285,12 @@ async function buildDashboardUrlFromSettings( let query; // Override with filters and queries from saved dashboard if they are available. - const searchSourceJSON = dashboard.attributes.kibanaSavedObjectMeta.searchSourceJSON; - if (searchSourceJSON !== undefined) { - const searchSourceData = JSON.parse(searchSourceJSON); - if (Array.isArray(searchSourceData.filter) && searchSourceData.filter.length > 0) { - filters = searchSourceData.filter; + const { searchSource } = dashboard.attributes.kibanaSavedObjectMeta; + if (searchSource !== undefined) { + if (Array.isArray(searchSource.filter) && searchSource.filter.length > 0) { + filters = searchSource.filter; } - query = searchSourceData.query; + query = searchSource.query; } const queryFromEntityFieldNames = buildAppStateQueryParam(queryFieldNames ?? []); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/expandable_section_results.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/expandable_section_results.tsx index d8c20f7f3d6fc..366debdc3fea3 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/expandable_section_results.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/expandable_section_results.tsx @@ -337,7 +337,12 @@ export const ExpandableSectionResults: FC = ({ anchorPosition="upCenter" button={ setIsPopoverVisible(!isPopoverVisible)} diff --git a/x-pack/plugins/ml/public/application/explorer/alerts/alerts_panel.tsx b/x-pack/plugins/ml/public/application/explorer/alerts/alerts_panel.tsx index 4c76ebe628f4f..b9b680a9fbac5 100644 --- a/x-pack/plugins/ml/public/application/explorer/alerts/alerts_panel.tsx +++ b/x-pack/plugins/ml/public/application/explorer/alerts/alerts_panel.tsx @@ -101,6 +101,9 @@ export const AlertsPanel: FC = () => { ); })} + ariaLabel={i18n.translate('xpack.ml.explorer.alertsPanel.ariaLabel', { + defaultMessage: 'alerts panel', + })} > diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/function_help.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/function_help.tsx index a72380828a6db..e93b446d486ad 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/function_help.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/function_help.tsx @@ -28,7 +28,18 @@ export const FunctionHelpPopover = memo(() => { const onHelpClick = () => setIsHelpOpen((prevIsHelpOpen) => !prevIsHelpOpen); const closeHelp = () => setIsHelpOpen(false); - const helpButton = ; + const helpButton = ( + + ); const columns = [ { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/detector_title/detector_title.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/detector_title/detector_title.tsx index d153ee2bf2842..439bdc0c4e2f5 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/detector_title/detector_title.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/detector_title/detector_title.tsx @@ -51,7 +51,12 @@ export const DetectorTitle: FC> = ({ onClick={() => deleteDetector(index)} iconType="cross" size="s" - aria-label="Next" + aria-label={i18n.translate( + 'xpack.ml.newJob.wizard.pickFieldsStep.nextButtonAriaLabel', + { + defaultMessage: 'Next', + } + )} /> )} diff --git a/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx index c94f594f548c5..176633ecfd3a2 100644 --- a/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx +++ b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx @@ -119,6 +119,9 @@ export const AnalyticsPanel: FC = ({ setLazyJobCount }) => { })} , ]} + ariaLabel={i18n.translate('xpack.ml.overview.analyticsListPanel.ariaLabel', { + defaultMessage: 'data frame analytics panel', + })} > {noDFAJobs ? : null} diff --git a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx index 5e17304fff1c4..42aca6f330a91 100644 --- a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx +++ b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx @@ -209,6 +209,9 @@ export const AnomalyDetectionPanel: FC = ({ anomalyTimelineService, setLa })}
, ]} + ariaLabel={i18n.translate('xpack.ml.overview.adJobsPanel.ariaLabel', { + defaultMessage: 'anomaly detection panel', + })} > {noAdJobs ? : null} diff --git a/x-pack/plugins/ml/public/application/overview/overview_page.tsx b/x-pack/plugins/ml/public/application/overview/overview_page.tsx index c66bc6e1ea7e4..57e453b15af73 100644 --- a/x-pack/plugins/ml/public/application/overview/overview_page.tsx +++ b/x-pack/plugins/ml/public/application/overview/overview_page.tsx @@ -112,6 +112,9 @@ export const OverviewPage: FC = () => { })}
, ]} + ariaLabel={i18n.translate('xpack.ml.overview.nodesPanel.ariaLabel', { + defaultMessage: 'overview panel', + })} > diff --git a/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts b/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts index c7bf9ca8bc5d8..aa8bb89c47ea5 100644 --- a/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts +++ b/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts @@ -615,10 +615,6 @@ export function getMlClient( p ); }, - async postData(...p: Parameters) { - await jobIdsCheck('anomaly-detector', p); - return mlClient.postData(...p); - }, async previewDatafeed(...p: Parameters) { await datafeedIdsCheck(p); return mlClient.previewDatafeed(...p); diff --git a/x-pack/plugins/ml/server/lib/ml_client/types.ts b/x-pack/plugins/ml/server/lib/ml_client/types.ts index ca7b36df8f208..d610baa92bc53 100644 --- a/x-pack/plugins/ml/server/lib/ml_client/types.ts +++ b/x-pack/plugins/ml/server/lib/ml_client/types.ts @@ -101,7 +101,6 @@ export type MlClientParams = | Parameters | Parameters | Parameters - | Parameters | Parameters | Parameters | Parameters diff --git a/x-pack/plugins/monitoring/kibana.jsonc b/x-pack/plugins/monitoring/kibana.jsonc index 6ffcba1496163..51272f995b012 100644 --- a/x-pack/plugins/monitoring/kibana.jsonc +++ b/x-pack/plugins/monitoring/kibana.jsonc @@ -1,12 +1,18 @@ { "type": "plugin", "id": "@kbn/monitoring-plugin", - "owner": "@elastic/stack-monitoring", + "owner": [ + "@elastic/stack-monitoring" + ], + "group": "observability", + "visibility": "private", "plugin": { "id": "monitoring", - "server": true, "browser": true, - "configPath": ["monitoring"], + "server": true, + "configPath": [ + "monitoring" + ], "requiredPlugins": [ "licensing", "features", @@ -30,6 +36,10 @@ "dashboard", "fleet" ], - "requiredBundles": ["kibanaUtils", "alerting", "kibanaReact"] + "requiredBundles": [ + "kibanaUtils", + "alerting", + "kibanaReact" + ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts index 4ea0077c21ba8..98f3932984546 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts @@ -7,7 +7,7 @@ import { ElasticsearchClient } from '@kbn/core/server'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { ESLicense } from '@kbn/telemetry-collection-xpack-plugin/server'; +import type { LicenseGetLicenseInformation } from '@elastic/elasticsearch/lib/api/types'; import { INDEX_PATTERN_ELASTICSEARCH, USAGE_FETCH_INTERVAL } from '../../common/constants'; /** @@ -18,7 +18,7 @@ export async function getLicenses( callCluster: ElasticsearchClient, timestamp: number, maxBucketSize: number -): Promise<{ [clusterUuid: string]: ESLicense | undefined }> { +): Promise<{ [clusterUuid: string]: LicenseGetLicenseInformation | undefined }> { const response = await fetchLicenses(callCluster, clusterUuids, timestamp, maxBucketSize); return handleLicenses(response); } @@ -76,7 +76,7 @@ export async function fetchLicenses( export interface ESClusterStatsWithLicense { cluster_uuid: string; type: 'cluster_stats'; - license?: ESLicense; + license?: LicenseGetLicenseInformation; } /** diff --git a/x-pack/plugins/monitoring/tsconfig.json b/x-pack/plugins/monitoring/tsconfig.json index 957b256bd726b..3b78104e65b8c 100644 --- a/x-pack/plugins/monitoring/tsconfig.json +++ b/x-pack/plugins/monitoring/tsconfig.json @@ -19,7 +19,6 @@ "@kbn/features-plugin", "@kbn/infra-plugin", "@kbn/licensing-plugin", - "@kbn/telemetry-collection-xpack-plugin", "@kbn/triggers-actions-ui-plugin", "@kbn/expect", "@kbn/i18n", diff --git a/x-pack/plugins/monitoring_collection/kibana.jsonc b/x-pack/plugins/monitoring_collection/kibana.jsonc index c2df8e9015326..0e779a6f532a5 100644 --- a/x-pack/plugins/monitoring_collection/kibana.jsonc +++ b/x-pack/plugins/monitoring_collection/kibana.jsonc @@ -2,6 +2,8 @@ "type": "plugin", "id": "@kbn/monitoring-collection-plugin", "owner": "@elastic/stack-monitoring", + "group": "platform", + "visibility": "private", "plugin": { "id": "monitoringCollection", "server": true, diff --git a/x-pack/plugins/notifications/kibana.jsonc b/x-pack/plugins/notifications/kibana.jsonc index e223a12dbc793..fad93b4261b55 100644 --- a/x-pack/plugins/notifications/kibana.jsonc +++ b/x-pack/plugins/notifications/kibana.jsonc @@ -1,14 +1,18 @@ { "type": "plugin", "id": "@kbn/notifications-plugin", - "owner": "@elastic/appex-sharedux", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared", "plugin": { "id": "notifications", - "server": true, "browser": false, + "server": true, "optionalPlugins": [ "actions", "licensing" ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/integration_settings/integration_policy.cy.ts b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/integration_settings/integration_policy.cy.ts index da9a08339a45c..753e6476be1ed 100644 --- a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/integration_settings/integration_policy.cy.ts +++ b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/integration_settings/integration_policy.cy.ts @@ -28,7 +28,7 @@ const policyFormFields = [ }, ]; -describe('when navigating to integration page', () => { +describe.skip('when navigating to integration page', () => { beforeEach(() => { const integrationsPath = '/app/integrations/browse'; diff --git a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/transaction_details/transaction_details.cy.ts b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/transaction_details/transaction_details.cy.ts index 38ced9a6587ee..3ae431f5d3299 100644 --- a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/transaction_details/transaction_details.cy.ts +++ b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/transaction_details/transaction_details.cy.ts @@ -15,8 +15,8 @@ const timeRange = { rangeFrom: start, rangeTo: end, }; - -describe('Transaction details', () => { +// flaky +describe.skip('Transaction details', () => { before(() => { synthtrace.index( opbeans({ diff --git a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/tutorial/tutorial.cy.ts b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/tutorial/tutorial.cy.ts deleted file mode 100644 index 9aa71604e6a80..0000000000000 --- a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/tutorial/tutorial.cy.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -describe('APM tutorial', () => { - beforeEach(() => { - cy.loginAsViewerUser(); - cy.visitKibana('/app/home#/tutorial/apm'); - }); - - it('includes section for APM Server', () => { - cy.contains('APM Server'); - cy.contains('Linux DEB'); - cy.contains('Linux RPM'); - cy.contains('Other Linux'); - cy.contains('macOS'); - cy.contains('Windows'); - cy.contains('Fleet'); - }); - - it('includes section for APM Agents', () => { - cy.contains('APM agents'); - cy.contains('Java'); - cy.contains('RUM'); - cy.contains('Node.js'); - cy.contains('Django'); - cy.contains('Flask'); - cy.contains('Ruby on Rails'); - cy.contains('Rack'); - cy.contains('Go'); - cy.contains('.NET'); - cy.contains('PHP'); - }); -}); diff --git a/x-pack/plugins/observability_solution/apm/ftr_e2e/kibana.jsonc b/x-pack/plugins/observability_solution/apm/ftr_e2e/kibana.jsonc index e0bf29cc4757c..47319dbadc61c 100644 --- a/x-pack/plugins/observability_solution/apm/ftr_e2e/kibana.jsonc +++ b/x-pack/plugins/observability_solution/apm/ftr_e2e/kibana.jsonc @@ -2,5 +2,7 @@ "type": "test-helper", "id": "@kbn/apm-ftr-e2e", "owner": "@elastic/obs-ux-infra_services-team", + "group": "observability", + "visibility": "private", "devOnly": true } diff --git a/x-pack/plugins/observability_solution/apm/kibana.jsonc b/x-pack/plugins/observability_solution/apm/kibana.jsonc index e12b22a43d60a..656f898f24064 100644 --- a/x-pack/plugins/observability_solution/apm/kibana.jsonc +++ b/x-pack/plugins/observability_solution/apm/kibana.jsonc @@ -1,13 +1,20 @@ { "type": "plugin", "id": "@kbn/apm-plugin", - "owner": "@elastic/obs-ux-infra_services-team", + "owner": [ + "@elastic/obs-ux-infra_services-team" + ], + "group": "observability", + "visibility": "private", "description": "The user interface for Elastic APM", "plugin": { "id": "apm", - "server": true, "browser": true, - "configPath": ["xpack", "apm"], + "server": true, + "configPath": [ + "xpack", + "apm" + ], "requiredPlugins": [ "apmDataAccess", "data", @@ -47,12 +54,19 @@ "serverless", "taskManager", "usageCollection", - "customIntegrations", // Move this to requiredPlugins after completely migrating from the Tutorials Home App + "customIntegrations", "licenseManagement", "profilingDataAccess", "cases", "observabilityAIAssistant" ], - "requiredBundles": ["fleet", "kibanaReact", "kibanaUtils", "ml", "observability", "maps"] + "requiredBundles": [ + "fleet", + "kibanaReact", + "kibanaUtils", + "ml", + "observability", + "maps" + ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_dashboards/actions/save_dashboard_modal.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_dashboards/actions/save_dashboard_modal.tsx index 739fe26342893..5f2f91df44231 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/service_dashboards/actions/save_dashboard_modal.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_dashboards/actions/save_dashboard_modal.tsx @@ -23,7 +23,6 @@ import { EuiButtonEmpty, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { DashboardItem } from '@kbn/dashboard-plugin/common/content_management'; import { callApmApi } from '../../../../services/rest/create_call_apm_api'; import { useDashboardFetcher } from '../../../../hooks/use_dashboards_fetcher'; import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; @@ -76,7 +75,7 @@ export function SaveDashboardModal({ const isEditMode = !!currentDashboard?.id; - const options = allAvailableDashboards?.map((dashboardItem: DashboardItem) => ({ + const options = allAvailableDashboards?.map((dashboardItem) => ({ label: dashboardItem.attributes.title, value: dashboardItem.id, disabled: diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/transaction_action_menu/__snapshots__/transaction_action_menu.test.tsx.snap b/x-pack/plugins/observability_solution/apm/public/components/shared/transaction_action_menu/__snapshots__/transaction_action_menu.test.tsx.snap index 023caad499485..63f0a9ff2f3ce 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/shared/transaction_action_menu/__snapshots__/transaction_action_menu.test.tsx.snap +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/transaction_action_menu/__snapshots__/transaction_action_menu.test.tsx.snap @@ -3,7 +3,7 @@ exports[`TransactionActionMenu matches the snapshot 1`] = `