diff --git a/.i18nrc.json b/.i18nrc.json index 42b7f6119eda1..c293b3103a39c 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -52,10 +52,7 @@ "visTypeVega": "src/legacy/core_plugins/vis_type_vega", "visTypeVislib": "src/legacy/core_plugins/vis_type_vislib", "visTypeXy": "src/legacy/core_plugins/vis_type_xy", - "visualizations": [ - "src/plugins/visualizations", - "src/legacy/core_plugins/visualizations" - ] + "visualizations": "src/plugins/visualizations" }, "exclude": [ "src/legacy/ui/ui_render/ui_render_mixin.js" diff --git a/docs/apm/custom-links.asciidoc b/docs/apm/custom-links.asciidoc new file mode 100644 index 0000000000000..75c1c9d0009a2 --- /dev/null +++ b/docs/apm/custom-links.asciidoc @@ -0,0 +1,218 @@ +[[custom-links]] +=== Custom links + +Elastic's custom link feature allows you to easily create up to 500 dynamic links +based on your specific APM data. +Custom links can be filtered to only appear in the APM app for relevant services, +environments, transaction types, or transaction names. + +Ready to dive in? Jump straight to the <>. + +[float] +[[custom-links-create]] +=== Create a link + +Each custom link consists of a label, url, and optional filter. +The easiest way to create a custom link is from within the actions dropdown in the transaction detail page. +This method will automatically apply filters, scoping the link to that specific service, +environment, transaction type, and transaction name. + +Alternatively, you can create a custom link in the APM app by navigating to **Settings** > **Customize UI**, +and selecting **Create custom link**. + +[float] +[[custom-links-label]] +==== Label + +The name of your custom link. +This text will be shown in the actions context menu, +so keep it as short as possible. + +TIP: Custom links are displayed alphabetically in the actions menu. + +[float] +[[custom-links-url]] +==== URL + +The URL your link points to. +URLs support dynamic field name variables, encapsulated in double curly brackets: `{{field.name}}`. +These variables will be replaced with transaction metadata when the link is clicked. + +Because everyone's data is different, +you'll need to examine your own traces to see what metadata is available for use. +The easiest way to do this is to select a trace in the APM app, and click **Metadata** in the **Trace Sample** table. + +[role="screenshot"] +image::apm/images/example-metadata.png[Example metadata] + +[float] +[[custom-links-filters]] +==== Filters + +Filter each link to only appear so it only appears for specific services or transactions. +You can filter on the following fields: + +* `service.name` +* `service.env` +* `transaction.type` +* `transaction.name` + +Multiple values are allowed when comma separated. + +[float] +[[custom-links-examples]] +=== Custom link examples + +// Relevant documentation links +:jira-query-params: https://confluence.atlassian.com/jirakb/how-to-create-issues-using-direct-html-links-in-jira-server-159474.html +:github-query-params: https://help.github.com/en/github/managing-your-work-on-github/about-automation-for-issues-and-pull-requests-with-query-parameters + +Not sure where to start with custom links? +Take a look at the examples below, and customize them to your liking! + +[float] +[[custom-links-examples-email]] +==== Email + +Email the owner of a service. + +|==== +|Label |`Email engineer` +|Link |`mailto:@.com` +|Filters |`service.name:` +|==== + +**Example** + +This link opens an email addressed to the team or owner of `python-backend`. +It will only appear on services with the name `python-backend`. + +|==== +|Label |`Email python-backend engineers` +|Link |`mailto:python_team@elastic.co` +|Filters |`service.name:python-backend` +|==== + +[float] +[[custom-links-examples-gh]] +==== GitHub issue + +Open a GitHub issue with pre-populated metadata from the selected trace sample. + +|==== +|Label |`Open an issue in ` +|Link |`https://github.com///issues/new?title=&body=<BODY>` +|Filters |`service.name:client` +|==== + +**Example** + +This link opens a new GitHub issue in the apm-agent-rum repository. +It populates the issue body with relevant metadata from the currently active trace. +Clicking this link results in the following issue being created: + +[role="screenshot"] +image::apm/images/create-github-issue.png[Example github issue] + +|==== +|Label |`Open an issue in apm-rum-js` +|Link |`https://github.com/elastic/apm-agent-rum-js/issues/new?title=Investigate+APM+trace&body=Investigate+the+following+APM+trace%3A%0D%0A%0D%0Aservice.name%3A+{{service.name}}%0D%0Atransaction.id%3A+{{transaction.id}}%0D%0Acontainer.id%3A+{{container.id}}%0D%0Aurl.full%3A+{{url.full}}` +|Filters |`service.name:client` +|==== + +See the {github-query-params}[GitHub automation documentation] for a full list of supported query parameters. + +[float] +[[custom-links-examples-jira]] +==== Jira task + +Create a Jira task with pre-populated metadata from the selected trace sample. + +|==== +|Label |`Open an issue in Jira` +|Link |`https://<JIRA_BASE_URL>/secure/CreateIssueDetails!init.jspa?<ARGUMENTS>` +|==== + +**Example** + +This link creates a new task on the Engineering board in Jira. +It populates the issue body with relevant metadata from the currently active trace. +Clicking this link results in the following task being created in Jira: + +[role="screenshot"] +image::apm/images/create-jira-issue.png[Example jira issue] + +|==== +|Label |`Open a task in Jira` +|Link |`https://test-site-33.atlassian.net/secure/CreateIssueDetails!init.jspa?pid=10000&issuetype=10001&summary=Created+via+APM&description=Investigate+the+following+APM+trace%3A%0D%0A%0D%0Aservice.name%3A+{{service.name}}%0D%0Atransaction.id%3A+{{transaction.id}}%0D%0Acontainer.id%3A+{{container.id}}%0D%0Aurl.full%3A+{{url.full}}` +|==== + +See the {jira-query-params}[Jira application administration knowledge base] +for a full list of supported query parameters. + +[float] +[[custom-links-examples-kib]] +==== Kibana dashboards + +Link to a custom dashboard in Kibana. + +|==== +|Label |`Open transaction in custom visualization` +|Link |`https://kibana-instance/app/kibana#/dashboard?_g=query:(language:kuery,query:'transaction.id:{{transaction.id}}'...` +|==== + +**Example** + +This link opens the current `transaction.id` in a custom kibana dashboard. +There are no filters set. + +|==== +|Label |`Open transaction in Python drilldown viz` +|URL |`https://kibana-instance/app/kibana#/dashboard?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-24h,to:now))&_a=(description:'',filters:!(),fullScreenMode:!f,options:(hidePanelTitles:!f,useMargins:!t),panels:!((embeddableConfig:(),gridData:(h:15,i:cb79c1c0-1af8-472c-aaf7-d158a76946fb,w:24,x:0,y:0),id:c8c74b20-6a30-11ea-92ab-b5d3feff11df,panelIndex:cb79c1c0-1af8-472c-aaf7-d158a76946fb,type:visualization,version:'7.7')),query:(language:kuery,query:'transaction.id:{{transaction.id}}'),timeRestore:!f,title:'',viewMode:edit)` +|==== + +[float] +[[custom-links-examples-slack]] +==== Slack channel + +Open a specified slack channel. + +|==== +|Label |`Open SLACK_CHANNEL` +|Link |`https://COMPANY_SLACK.slack.com/archives/SLACK_CHANNEL` +|Filters |`service.name` : `SERVICE_NAME` +|==== + +**Example** + +This link opens a company slack channel, #apm-support. +It only appears when `transaction.name` is `GET user/login`. + +|==== +|Label |`Open #apm-user-support` +|Link |`https://microsoft.slack.com/archives/efk52kt23k` +|Filters |`transaction.name:GET user/login` +|==== + +[float] +[[custom-links-examples-web]] +==== Website + +Open an internal or external website. + +|==== +|Label |`Open <WEBSITE>` +|Link |`https://<COMPANY_SLACK>.slack.com/archives/<SLACK_CHANNEL>` +|Filters |`service.name:<SERVICE_NAME>` +|==== + +**Example** + +This link opens more data on a specific `user.email`. +It only appears on front-end transactions. + +|==== +|Label |`View user internally` +|Link |`https://internal-site.company.com/user/{{user.email}}` +|Filters |`service.name:client` +|==== diff --git a/docs/apm/images/create-github-issue.png b/docs/apm/images/create-github-issue.png new file mode 100644 index 0000000000000..81ea4e5e78c27 Binary files /dev/null and b/docs/apm/images/create-github-issue.png differ diff --git a/docs/apm/images/create-jira-issue.png b/docs/apm/images/create-jira-issue.png new file mode 100644 index 0000000000000..962c98df3f6c6 Binary files /dev/null and b/docs/apm/images/create-jira-issue.png differ diff --git a/docs/apm/images/example-metadata.png b/docs/apm/images/example-metadata.png new file mode 100644 index 0000000000000..0e35f90691723 Binary files /dev/null and b/docs/apm/images/example-metadata.png differ diff --git a/docs/apm/using-the-apm-ui.asciidoc b/docs/apm/using-the-apm-ui.asciidoc index 95ec41cf8a403..1361dc046e3b1 100644 --- a/docs/apm/using-the-apm-ui.asciidoc +++ b/docs/apm/using-the-apm-ui.asciidoc @@ -37,6 +37,8 @@ include::metrics.asciidoc[] include::agent-configuration.asciidoc[] +include::custom-links.asciidoc[] + include::advanced-queries.asciidoc[] include::settings.asciidoc[] diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md index 7e65ef85c8bec..afb6ea88f9fad 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md @@ -10,7 +10,7 @@ search: { aggs: { AggConfigs: typeof AggConfigs; - aggGroupNamesMap: () => Record<"buckets" | "metrics", string>; + aggGroupNamesMap: () => Record<"metrics" | "buckets", string>; aggTypeFilters: import("./search/aggs/filter/agg_type_filters").AggTypeFilters; CidrMask: typeof CidrMask; convertDateRangeToString: typeof convertDateRangeToString; diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md index e756eb9b72905..259d725b3bf0d 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md @@ -23,6 +23,7 @@ | Function | Description | | --- | --- | | [getDefaultSearchParams(config)](./kibana-plugin-plugins-data-server.getdefaultsearchparams.md) | | +| [getTotalLoaded({ total, failed, successful })](./kibana-plugin-plugins-data-server.gettotalloaded.md) | | | [parseInterval(interval)](./kibana-plugin-plugins-data-server.parseinterval.md) | | | [plugin(initializerContext)](./kibana-plugin-plugins-data-server.plugin.md) | Static code to be shared externally | | [shouldReadFieldFromDocValues(aggregatable, esType)](./kibana-plugin-plugins-data-server.shouldreadfieldfromdocvalues.md) | | diff --git a/package.json b/package.json index 91013bc12f347..c2763f098b984 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "uiFramework:documentComponent": "cd packages/kbn-ui-framework && yarn documentComponent", "kbn:watch": "node scripts/kibana --dev --logging.json=false", "build:types": "tsc --p tsconfig.types.json", - "docs:acceptApiChanges": "node scripts/check_published_api_changes.js --accept", + "docs:acceptApiChanges": "node --max-old-space-size=6144 scripts/check_published_api_changes.js --accept", "kbn:bootstrap": "yarn build:types && node scripts/register_git_hook", "spec_to_console": "node scripts/spec_to_console", "backport-skip-ci": "backport --prDescription \"[skip-ci]\"", @@ -118,7 +118,7 @@ "@babel/core": "^7.9.0", "@babel/register": "^7.9.0", "@elastic/apm-rum": "^4.6.0", - "@elastic/charts": "^18.1.0", + "@elastic/charts": "^18.1.1", "@elastic/datemath": "5.0.2", "@elastic/ems-client": "7.7.1", "@elastic/eui": "21.0.1", @@ -170,8 +170,8 @@ "deepmerge": "^4.2.2", "del": "^5.1.0", "elastic-apm-node": "^3.2.0", - "elasticsearch": "^16.5.0", - "elasticsearch-browser": "^16.5.0", + "elasticsearch": "^16.7.0", + "elasticsearch-browser": "^16.7.0", "execa": "^4.0.0", "expiry-js": "0.1.7", "fast-deep-equal": "^3.1.1", @@ -330,11 +330,13 @@ "@types/glob": "^7.1.1", "@types/globby": "^8.0.0", "@types/graphql": "^0.13.2", + "@types/h2o2": "^8.1.1", "@types/hapi": "^17.0.18", "@types/hapi-auth-cookie": "^9.1.0", "@types/has-ansi": "^3.0.0", "@types/history": "^4.7.3", "@types/hoek": "^4.1.3", + "@types/inert": "^5.1.2", "@types/jest": "24.0.19", "@types/joi": "^13.4.2", "@types/jquery": "^3.3.31", diff --git a/packages/kbn-optimizer/src/worker/webpack.config.ts b/packages/kbn-optimizer/src/worker/webpack.config.ts index 5d8ef7626f630..dabfed7f9725c 100644 --- a/packages/kbn-optimizer/src/worker/webpack.config.ts +++ b/packages/kbn-optimizer/src/worker/webpack.config.ts @@ -131,12 +131,21 @@ export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) { loader: 'resolve-url-loader', options: { join: (_: string, __: any) => (uri: string, base?: string) => { - if (!base) { + // apply only to legacy platform styles + if (!base || !parseDirPath(base).dirs.includes('legacy')) { return null; } + if (uri.startsWith('ui/assets')) { + return Path.resolve( + worker.repoRoot, + 'src/core/server/core_app/', + uri.replace('ui/', '') + ); + } + // manually force ui/* urls in legacy styles to resolve to ui/legacy/public - if (uri.startsWith('ui/') && parseDirPath(base).dirs.includes('legacy')) { + if (uri.startsWith('ui/')) { return Path.resolve( worker.repoRoot, 'src/legacy/ui/public', diff --git a/packages/kbn-storybook/storybook_config/middleware.js b/packages/kbn-storybook/storybook_config/middleware.js index f517477b405bd..046758948b2cf 100644 --- a/packages/kbn-storybook/storybook_config/middleware.js +++ b/packages/kbn-storybook/storybook_config/middleware.js @@ -22,5 +22,5 @@ const path = require('path'); // Extend the Storybook Middleware to include a route to access Legacy UI assets module.exports = function(router) { - router.get('/ui', serve(path.resolve(__dirname, '../../../../src/legacy/ui/public/assets'))); + router.get('/ui', serve(path.resolve(__dirname, '../../../src/core/server/core_app/assets'))); }; diff --git a/packages/kbn-test/src/index.ts b/packages/kbn-test/src/index.ts index cfbd1ee0fe64c..57cdc8ffd494f 100644 --- a/packages/kbn-test/src/index.ts +++ b/packages/kbn-test/src/index.ts @@ -58,3 +58,5 @@ export { export { runFailedTestsReporterCli } from './failed_tests_reporter'; export { makeJunitReportPath } from './junit_report_path'; + +export { CI_PARALLEL_PROCESS_PREFIX } from './ci_parallel_process_prefix'; diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index 1e9ceb42433f0..c76e909d2adbc 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -9,7 +9,7 @@ "kbn:watch": "node scripts/build --watch" }, "devDependencies": { - "@elastic/charts": "^18.1.0", + "@elastic/charts": "^18.1.1", "abortcontroller-polyfill": "^1.4.0", "@elastic/eui": "21.0.1", "@kbn/babel-preset": "1.0.0", diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index 50fd1f716ba37..5d7b467052029 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -43,6 +43,7 @@ - [Core services](#core-services-1) - [Plugin services](#plugin-services) - [UI Exports](#ui-exports) + - [Plugin Spec](#plugin-spec) - [How to](#how-to) - [Configure plugin](#configure-plugin) - [Handle plugin configuration deprecations](#handle-plugin-configuration-deprecations) @@ -1152,12 +1153,12 @@ _See also: [Public's CoreStart API Docs](/docs/development/core/public/kibana-pl ##### Plugins for shared application services -In client code, we have a series of plugins which house shared application services that are being built in the shape of the new platform, but for the time being, are only available in legacy. So if your plugin depends on any of the APIs below, you'll need build your plugin as a legacy plugin that shims the new platform. Once these API's have been moved to the new platform you can migrate your plugin and declare a dependency on the plugin that owns the API's you require. +In client code, we have a series of plugins which house shared application services which are not technically part of `core`, but are often used in Kibana plugins. -The contracts for these plugins are exposed for you to consume in your own plugin; we have created dedicated exports for the `setup` and `start` contracts in a file called `legacy`. By passing these contracts to your plugin's `setup` and `start` methods, you can mimic the functionality that will eventually be provided in the new platform. +This table maps some of the most commonly used legacy items to their new platform locations. ```ts -import { setup, start } from '../core_plugins/visualizations/public/legacy'; +import { npStart: { plugins } } from 'ui/new_platform'; ``` | Legacy Platform | New Platform | Notes | @@ -1264,40 +1265,19 @@ This table shows where these uiExports have moved to in the New Platform. In mos | `visTypes` | `plugins.visualizations.types` | | | `visualize` | | | -Examples: - -- **uiSettingDefaults** - -Before: - -```js -uiExports: { - uiSettingDefaults: { - 'my-plugin:my-setting': { - name: 'just-work', - value: true, - description: 'make it work', - category: ['my-category'], - }, - } -} -``` - -After: - -```ts -// src/plugins/my-plugin/server/plugin.ts -setup(core: CoreSetup){ - core.uiSettings.register({ - 'my-plugin:my-setting': { - name: 'just-work', - value: true, - description: 'make it work', - category: ['my-category'], - }, - }) -} -``` +#### Plugin Spec +| Legacy Platform | New Platform | +| ----------------------------- | ----------------------------------------------------------------------------------------------------------- | +| `id` | [`manifest.id`](/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md) | +| `require` | [`manifest.requiredPlugins`](/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md) | +| `version` | [`manifest.version`](/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md) | +| `kibanaVersion` | [`manifest.kibanaVersion`](/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md) | +| `configPrefix` | [`manifest.configPath`](/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md) | +| `config` | [export config](#configure-plugin) | +| `deprecations` | [export config](#handle-plugin-configuration-deprecations) | +| `uiExports` | `N/A`. Use platform & plugin public contracts | +| `publicDir` | `N/A`. Platform serves static assets from `/public/assets` folder under `/plugins/{id}/assets/{path*}` URL. | +| `preInit`, `init`, `postInit` | `N/A`. Use NP [lifecycle events](#services) | ## How to diff --git a/src/core/MIGRATION_EXAMPLES.md b/src/core/MIGRATION_EXAMPLES.md index 29edef476d7c3..37d0b9297ed3c 100644 --- a/src/core/MIGRATION_EXAMPLES.md +++ b/src/core/MIGRATION_EXAMPLES.md @@ -20,6 +20,7 @@ APIs to their New Platform equivalents. - [Chromeless Applications](#chromeless-applications) - [Render HTML Content](#render-html-content) - [Saved Objects types](#saved-objects-types) + - [UiSettings](#uisettings) ## Configuration @@ -975,4 +976,37 @@ const migration: SavedObjectMigrationFn = (doc, { log }) => {...} The `registerType` API will throw if called after the service has started, and therefor cannot be used from legacy plugin code. Legacy plugins should use the legacy savedObjects service and the legacy way to register -saved object types until migrated. \ No newline at end of file +saved object types until migrated. + +## UiSettings +UiSettings defaults registration performed during `setup` phase via `core.uiSettings.register` API. + +```js +// Before: +uiExports: { + uiSettingDefaults: { + 'my-plugin:my-setting': { + name: 'just-work', + value: true, + description: 'make it work', + category: ['my-category'], + }, + } +} +``` + +```ts +// After: +// src/plugins/my-plugin/server/plugin.ts +setup(core: CoreSetup){ + core.uiSettings.register({ + 'my-plugin:my-setting': { + name: 'just-work', + value: true, + description: 'make it work', + category: ['my-category'], + schema: schema.boolean(), + }, + }) +} +``` \ No newline at end of file diff --git a/src/legacy/ui/public/assets/favicons/android-chrome-192x192.png b/src/core/server/core_app/assets/favicons/android-chrome-192x192.png similarity index 100% rename from src/legacy/ui/public/assets/favicons/android-chrome-192x192.png rename to src/core/server/core_app/assets/favicons/android-chrome-192x192.png diff --git a/src/legacy/ui/public/assets/favicons/android-chrome-256x256.png b/src/core/server/core_app/assets/favicons/android-chrome-256x256.png similarity index 100% rename from src/legacy/ui/public/assets/favicons/android-chrome-256x256.png rename to src/core/server/core_app/assets/favicons/android-chrome-256x256.png diff --git a/src/legacy/ui/public/assets/favicons/android-chrome-512x512.png b/src/core/server/core_app/assets/favicons/android-chrome-512x512.png similarity index 100% rename from src/legacy/ui/public/assets/favicons/android-chrome-512x512.png rename to src/core/server/core_app/assets/favicons/android-chrome-512x512.png diff --git a/src/legacy/ui/public/assets/favicons/apple-touch-icon.png b/src/core/server/core_app/assets/favicons/apple-touch-icon.png similarity index 100% rename from src/legacy/ui/public/assets/favicons/apple-touch-icon.png rename to src/core/server/core_app/assets/favicons/apple-touch-icon.png diff --git a/src/legacy/ui/public/assets/favicons/browserconfig.xml b/src/core/server/core_app/assets/favicons/browserconfig.xml similarity index 100% rename from src/legacy/ui/public/assets/favicons/browserconfig.xml rename to src/core/server/core_app/assets/favicons/browserconfig.xml diff --git a/src/legacy/ui/public/assets/favicons/favicon-16x16.png b/src/core/server/core_app/assets/favicons/favicon-16x16.png similarity index 100% rename from src/legacy/ui/public/assets/favicons/favicon-16x16.png rename to src/core/server/core_app/assets/favicons/favicon-16x16.png diff --git a/src/legacy/ui/public/assets/favicons/favicon-32x32.png b/src/core/server/core_app/assets/favicons/favicon-32x32.png similarity index 100% rename from src/legacy/ui/public/assets/favicons/favicon-32x32.png rename to src/core/server/core_app/assets/favicons/favicon-32x32.png diff --git a/src/legacy/ui/public/assets/favicons/favicon.ico b/src/core/server/core_app/assets/favicons/favicon.ico similarity index 100% rename from src/legacy/ui/public/assets/favicons/favicon.ico rename to src/core/server/core_app/assets/favicons/favicon.ico diff --git a/src/legacy/ui/public/assets/favicons/manifest.json b/src/core/server/core_app/assets/favicons/manifest.json similarity index 100% rename from src/legacy/ui/public/assets/favicons/manifest.json rename to src/core/server/core_app/assets/favicons/manifest.json diff --git a/src/legacy/ui/public/assets/favicons/mstile-144x144.png b/src/core/server/core_app/assets/favicons/mstile-144x144.png similarity index 100% rename from src/legacy/ui/public/assets/favicons/mstile-144x144.png rename to src/core/server/core_app/assets/favicons/mstile-144x144.png diff --git a/src/legacy/ui/public/assets/favicons/mstile-150x150.png b/src/core/server/core_app/assets/favicons/mstile-150x150.png similarity index 100% rename from src/legacy/ui/public/assets/favicons/mstile-150x150.png rename to src/core/server/core_app/assets/favicons/mstile-150x150.png diff --git a/src/legacy/ui/public/assets/favicons/mstile-310x150.png b/src/core/server/core_app/assets/favicons/mstile-310x150.png similarity index 100% rename from src/legacy/ui/public/assets/favicons/mstile-310x150.png rename to src/core/server/core_app/assets/favicons/mstile-310x150.png diff --git a/src/legacy/ui/public/assets/favicons/mstile-310x310.png b/src/core/server/core_app/assets/favicons/mstile-310x310.png similarity index 100% rename from src/legacy/ui/public/assets/favicons/mstile-310x310.png rename to src/core/server/core_app/assets/favicons/mstile-310x310.png diff --git a/src/legacy/ui/public/assets/favicons/mstile-70x70.png b/src/core/server/core_app/assets/favicons/mstile-70x70.png similarity index 100% rename from src/legacy/ui/public/assets/favicons/mstile-70x70.png rename to src/core/server/core_app/assets/favicons/mstile-70x70.png diff --git a/src/legacy/ui/public/assets/favicons/safari-pinned-tab.svg b/src/core/server/core_app/assets/favicons/safari-pinned-tab.svg similarity index 100% rename from src/legacy/ui/public/assets/favicons/safari-pinned-tab.svg rename to src/core/server/core_app/assets/favicons/safari-pinned-tab.svg diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Black.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Black.woff similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Black.woff rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Black.woff diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Black.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Black.woff2 similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Black.woff2 rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Black.woff2 diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-BlackItalic.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-BlackItalic.woff similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-BlackItalic.woff rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-BlackItalic.woff diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-BlackItalic.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-BlackItalic.woff2 similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-BlackItalic.woff2 rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-BlackItalic.woff2 diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Bold.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Bold.woff similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Bold.woff rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Bold.woff diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Bold.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Bold.woff2 similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Bold.woff2 rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Bold.woff2 diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-BoldItalic.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-BoldItalic.woff similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-BoldItalic.woff rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-BoldItalic.woff diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-BoldItalic.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-BoldItalic.woff2 similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-BoldItalic.woff2 rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-BoldItalic.woff2 diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-ExtraBold.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraBold.woff similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-ExtraBold.woff rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraBold.woff diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-ExtraBold.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraBold.woff2 similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-ExtraBold.woff2 rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraBold.woff2 diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-ExtraBoldItalic.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraBoldItalic.woff similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-ExtraBoldItalic.woff rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraBoldItalic.woff diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-ExtraBoldItalic.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraBoldItalic.woff2 similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-ExtraBoldItalic.woff2 rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraBoldItalic.woff2 diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-ExtraLight-BETA.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraLight-BETA.woff similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-ExtraLight-BETA.woff rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraLight-BETA.woff diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-ExtraLight-BETA.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraLight-BETA.woff2 similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-ExtraLight-BETA.woff2 rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraLight-BETA.woff2 diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-ExtraLightItalic-BETA.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraLightItalic-BETA.woff similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-ExtraLightItalic-BETA.woff rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraLightItalic-BETA.woff diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-ExtraLightItalic-BETA.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraLightItalic-BETA.woff2 similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-ExtraLightItalic-BETA.woff2 rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ExtraLightItalic-BETA.woff2 diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Italic.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Italic.woff similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Italic.woff rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Italic.woff diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Italic.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Italic.woff2 similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Italic.woff2 rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Italic.woff2 diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Light-BETA.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Light-BETA.woff similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Light-BETA.woff rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Light-BETA.woff diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Light-BETA.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Light-BETA.woff2 similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Light-BETA.woff2 rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Light-BETA.woff2 diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-LightItalic-BETA.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-LightItalic-BETA.woff similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-LightItalic-BETA.woff rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-LightItalic-BETA.woff diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-LightItalic-BETA.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-LightItalic-BETA.woff2 similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-LightItalic-BETA.woff2 rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-LightItalic-BETA.woff2 diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Medium.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Medium.woff similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Medium.woff rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Medium.woff diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Medium.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Medium.woff2 similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Medium.woff2 rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Medium.woff2 diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-MediumItalic.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-MediumItalic.woff similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-MediumItalic.woff rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-MediumItalic.woff diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-MediumItalic.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-MediumItalic.woff2 similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-MediumItalic.woff2 rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-MediumItalic.woff2 diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Regular.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Regular.woff similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Regular.woff rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Regular.woff diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Regular.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Regular.woff2 similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Regular.woff2 rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Regular.woff2 diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-SemiBold.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-SemiBold.woff similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-SemiBold.woff rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-SemiBold.woff diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-SemiBold.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-SemiBold.woff2 similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-SemiBold.woff2 rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-SemiBold.woff2 diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-SemiBoldItalic.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-SemiBoldItalic.woff similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-SemiBoldItalic.woff rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-SemiBoldItalic.woff diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-SemiBoldItalic.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-SemiBoldItalic.woff2 similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-SemiBoldItalic.woff2 rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-SemiBoldItalic.woff2 diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Thin-BETA.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Thin-BETA.woff similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Thin-BETA.woff rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Thin-BETA.woff diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Thin-BETA.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Thin-BETA.woff2 similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-Thin-BETA.woff2 rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-Thin-BETA.woff2 diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-ThinItalic-BETA.woff b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ThinItalic-BETA.woff similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-ThinItalic-BETA.woff rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ThinItalic-BETA.woff diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-ThinItalic-BETA.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ThinItalic-BETA.woff2 similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-ThinItalic-BETA.woff2 rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-ThinItalic-BETA.woff2 diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-italic.var.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-italic.var.woff2 similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-italic.var.woff2 rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-italic.var.woff2 diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-upright.var.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-upright.var.woff2 similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI-upright.var.woff2 rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI-upright.var.woff2 diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI.var.woff2 b/src/core/server/core_app/assets/fonts/inter_ui/Inter-UI.var.woff2 similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/Inter-UI.var.woff2 rename to src/core/server/core_app/assets/fonts/inter_ui/Inter-UI.var.woff2 diff --git a/src/legacy/ui/public/assets/fonts/inter_ui/LICENSE.txt b/src/core/server/core_app/assets/fonts/inter_ui/LICENSE.txt similarity index 100% rename from src/legacy/ui/public/assets/fonts/inter_ui/LICENSE.txt rename to src/core/server/core_app/assets/fonts/inter_ui/LICENSE.txt diff --git a/src/legacy/ui/public/assets/fonts/readme.md b/src/core/server/core_app/assets/fonts/readme.md similarity index 100% rename from src/legacy/ui/public/assets/fonts/readme.md rename to src/core/server/core_app/assets/fonts/readme.md diff --git a/src/legacy/ui/public/assets/fonts/roboto_mono/LICENSE.txt b/src/core/server/core_app/assets/fonts/roboto_mono/LICENSE.txt similarity index 100% rename from src/legacy/ui/public/assets/fonts/roboto_mono/LICENSE.txt rename to src/core/server/core_app/assets/fonts/roboto_mono/LICENSE.txt diff --git a/src/legacy/ui/public/assets/fonts/roboto_mono/RobotoMono-Bold.ttf b/src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Bold.ttf similarity index 100% rename from src/legacy/ui/public/assets/fonts/roboto_mono/RobotoMono-Bold.ttf rename to src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Bold.ttf diff --git a/src/legacy/ui/public/assets/fonts/roboto_mono/RobotoMono-BoldItalic.ttf b/src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-BoldItalic.ttf similarity index 100% rename from src/legacy/ui/public/assets/fonts/roboto_mono/RobotoMono-BoldItalic.ttf rename to src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-BoldItalic.ttf diff --git a/src/legacy/ui/public/assets/fonts/roboto_mono/RobotoMono-Italic.ttf b/src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Italic.ttf similarity index 100% rename from src/legacy/ui/public/assets/fonts/roboto_mono/RobotoMono-Italic.ttf rename to src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Italic.ttf diff --git a/src/legacy/ui/public/assets/fonts/roboto_mono/RobotoMono-Light.ttf b/src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Light.ttf similarity index 100% rename from src/legacy/ui/public/assets/fonts/roboto_mono/RobotoMono-Light.ttf rename to src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Light.ttf diff --git a/src/legacy/ui/public/assets/fonts/roboto_mono/RobotoMono-LightItalic.ttf b/src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-LightItalic.ttf similarity index 100% rename from src/legacy/ui/public/assets/fonts/roboto_mono/RobotoMono-LightItalic.ttf rename to src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-LightItalic.ttf diff --git a/src/legacy/ui/public/assets/fonts/roboto_mono/RobotoMono-Medium.ttf b/src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Medium.ttf similarity index 100% rename from src/legacy/ui/public/assets/fonts/roboto_mono/RobotoMono-Medium.ttf rename to src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Medium.ttf diff --git a/src/legacy/ui/public/assets/fonts/roboto_mono/RobotoMono-MediumItalic.ttf b/src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-MediumItalic.ttf similarity index 100% rename from src/legacy/ui/public/assets/fonts/roboto_mono/RobotoMono-MediumItalic.ttf rename to src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-MediumItalic.ttf diff --git a/src/legacy/ui/public/assets/fonts/roboto_mono/RobotoMono-Regular.ttf b/src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Regular.ttf similarity index 100% rename from src/legacy/ui/public/assets/fonts/roboto_mono/RobotoMono-Regular.ttf rename to src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Regular.ttf diff --git a/src/legacy/ui/public/assets/fonts/roboto_mono/RobotoMono-Thin.ttf b/src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Thin.ttf similarity index 100% rename from src/legacy/ui/public/assets/fonts/roboto_mono/RobotoMono-Thin.ttf rename to src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Thin.ttf diff --git a/src/legacy/ui/public/assets/fonts/roboto_mono/RobotoMono-ThinItalic.ttf b/src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-ThinItalic.ttf similarity index 100% rename from src/legacy/ui/public/assets/fonts/roboto_mono/RobotoMono-ThinItalic.ttf rename to src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-ThinItalic.ttf diff --git a/src/legacy/ui/public/assets/images/bg_bottom_branded.svg b/src/core/server/core_app/assets/images/bg_bottom_branded.svg similarity index 100% rename from src/legacy/ui/public/assets/images/bg_bottom_branded.svg rename to src/core/server/core_app/assets/images/bg_bottom_branded.svg diff --git a/src/legacy/ui/public/assets/images/bg_bottom_branded_dark.svg b/src/core/server/core_app/assets/images/bg_bottom_branded_dark.svg similarity index 100% rename from src/legacy/ui/public/assets/images/bg_bottom_branded_dark.svg rename to src/core/server/core_app/assets/images/bg_bottom_branded_dark.svg diff --git a/src/legacy/ui/public/assets/images/bg_top_branded.svg b/src/core/server/core_app/assets/images/bg_top_branded.svg similarity index 100% rename from src/legacy/ui/public/assets/images/bg_top_branded.svg rename to src/core/server/core_app/assets/images/bg_top_branded.svg diff --git a/src/legacy/ui/public/assets/images/bg_top_branded_dark.svg b/src/core/server/core_app/assets/images/bg_top_branded_dark.svg similarity index 100% rename from src/legacy/ui/public/assets/images/bg_top_branded_dark.svg rename to src/core/server/core_app/assets/images/bg_top_branded_dark.svg diff --git a/src/legacy/ui/public/assets/images/kibana.svg b/src/core/server/core_app/assets/images/kibana.svg similarity index 100% rename from src/legacy/ui/public/assets/images/kibana.svg rename to src/core/server/core_app/assets/images/kibana.svg diff --git a/src/core/server/core_app/core_app.ts b/src/core/server/core_app/core_app.ts index 2f8c85f47a76e..5e1a3794632ee 100644 --- a/src/core/server/core_app/core_app.ts +++ b/src/core/server/core_app/core_app.ts @@ -16,6 +16,9 @@ * specific language governing permissions and limitations * under the License. */ +import Path from 'path'; +import { fromRoot } from '../../../core/server/utils'; + import { InternalCoreSetup } from '../internal_types'; import { CoreContext } from '../core_context'; import { Logger } from '../logging'; @@ -29,6 +32,7 @@ export class CoreApp { setup(coreSetup: InternalCoreSetup) { this.logger.debug('Setting up core app.'); this.registerDefaultRoutes(coreSetup); + this.registerStaticDirs(coreSetup); } private registerDefaultRoutes(coreSetup: InternalCoreSetup) { @@ -49,4 +53,12 @@ export class CoreApp { res.ok({ body: { version: '0.0.1' } }) ); } + private registerStaticDirs(coreSetup: InternalCoreSetup) { + coreSetup.http.registerStaticDir('/ui/{path*}', Path.resolve(__dirname, './assets')); + + coreSetup.http.registerStaticDir( + '/node_modules/@kbn/ui-framework/dist/{path*}', + fromRoot('node_modules/@kbn/ui-framework/dist') + ); + } } diff --git a/src/core/server/core_app/integration_tests/static_assets.test.ts b/src/core/server/core_app/integration_tests/static_assets.test.ts new file mode 100644 index 0000000000000..aad2510ef8c0e --- /dev/null +++ b/src/core/server/core_app/integration_tests/static_assets.test.ts @@ -0,0 +1,51 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import * as kbnTestServer from '../../../../test_utils/kbn_server'; +import { Root } from '../../root'; + +describe('Platform assets', function() { + let root: Root; + + beforeAll(async function() { + root = kbnTestServer.createRoot(); + + await root.setup(); + await root.start(); + }); + + afterAll(async function() { + await root.shutdown(); + }); + + it('exposes static assets', async () => { + await kbnTestServer.request.get(root, '/ui/favicons/favicon.ico').expect(200); + }); + + it('returns 404 if not found', async function() { + await kbnTestServer.request.get(root, '/ui/favicons/not-a-favicon.ico').expect(404); + }); + + it('does not expose folder content', async function() { + await kbnTestServer.request.get(root, '/ui/favicons/').expect(403); + }); + + it('does not allow file tree traversing', async function() { + await kbnTestServer.request.get(root, '/ui/../../../../../README.md').expect(404); + }); +}); diff --git a/src/core/server/http/base_path_proxy_server.ts b/src/core/server/http/base_path_proxy_server.ts index e418726465efa..acefbd00ae2be 100644 --- a/src/core/server/http/base_path_proxy_server.ts +++ b/src/core/server/http/base_path_proxy_server.ts @@ -23,6 +23,7 @@ import { Agent as HttpsAgent, ServerOptions as TlsOptions } from 'https'; import apm from 'elastic-apm-node'; import { ByteSizeValue } from '@kbn/config-schema'; import { Server, Request, ResponseToolkit } from 'hapi'; +import HapiProxy from 'h2o2'; import { sample } from 'lodash'; import BrowserslistUserAgent from 'browserslist-useragent'; import * as Rx from 'rxjs'; @@ -102,7 +103,7 @@ export class BasePathProxyServer { // Register hapi plugin that adds proxying functionality. It can be configured // through the route configuration object (see { handler: { proxy: ... } }). - await this.server.register({ plugin: require('h2o2') }); + await this.server.register([HapiProxy]); if (this.httpConfig.ssl.enabled) { const tlsOptions = serverOptions.tls as TlsOptions; @@ -166,7 +167,8 @@ export class BasePathProxyServer { host: this.server.info.host, passThrough: true, port: this.devConfig.basePathProxyTargetPort, - protocol: this.server.info.protocol, + // typings mismatch. h2o2 doesn't support "socket" + protocol: this.server.info.protocol as HapiProxy.ProxyHandlerOptions['protocol'], xforward: true, }, }, @@ -195,7 +197,7 @@ export class BasePathProxyServer { agent: this.httpsAgent, passThrough: true, xforward: true, - mapUri: (request: Request) => ({ + mapUri: async (request: Request) => ({ uri: Url.format({ hostname: request.server.info.host, port: this.devConfig.basePathProxyTargetPort, diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index f898ed0ea1a99..77d3d99fb48cb 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -17,6 +17,7 @@ * under the License. */ import { Server } from 'hapi'; +import HapiStaticFiles from 'inert'; import url from 'url'; import { Logger, LoggerFactory } from '../logging'; @@ -44,6 +45,7 @@ export interface HttpServerSetup { * @param router {@link IRouter} - a router with registered route handlers. */ registerRouter: (router: IRouter) => void; + registerStaticDir: (path: string, dirPath: string) => void; basePath: HttpServiceSetup['basePath']; csp: HttpServiceSetup['csp']; createCookieSessionStorageFactory: HttpServiceSetup['createCookieSessionStorageFactory']; @@ -97,10 +99,11 @@ export class HttpServer { this.registeredRouters.add(router); } - public setup(config: HttpConfig): HttpServerSetup { + public async setup(config: HttpConfig): Promise<HttpServerSetup> { const serverOptions = getServerOptions(config); const listenerOptions = getListenerOptions(config); this.server = createServer(serverOptions, listenerOptions); + await this.server.register([HapiStaticFiles]); this.config = config; const basePathService = new BasePath(config.basePath); @@ -109,6 +112,7 @@ export class HttpServer { return { registerRouter: this.registerRouter.bind(this), + registerStaticDir: this.registerStaticDir.bind(this), registerOnPreAuth: this.registerOnPreAuth.bind(this), registerOnPostAuth: this.registerOnPostAuth.bind(this), registerOnPreResponse: this.registerOnPreResponse.bind(this), @@ -339,4 +343,23 @@ export class HttpServer { return t.next({ headers: authResponseHeaders }); }); } + + private registerStaticDir(path: string, dirPath: string) { + if (this.server === undefined) { + throw new Error('Http server is not setup up yet'); + } + + this.server.route({ + path, + method: 'GET', + handler: { + directory: { + path: dirPath, + listing: false, + lookupCompressed: true, + }, + }, + options: { auth: false }, + }); + } } diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index 442bc93190d86..0788a8f2af7a1 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -72,6 +72,7 @@ const createSetupContractMock = () => { registerRouteHandlerContext: jest.fn(), registerOnPreResponse: jest.fn(), createRouter: jest.fn().mockImplementation(() => mockRouter.create({})), + registerStaticDir: jest.fn(), basePath: createBasePathMock(), csp: CspConfig.DEFAULT, auth: createAuthMock(), diff --git a/src/core/server/http/http_tools.test.ts b/src/core/server/http/http_tools.test.ts index c1322a5aa94db..bdaab4f2999ed 100644 --- a/src/core/server/http/http_tools.test.ts +++ b/src/core/server/http/http_tools.test.ts @@ -18,6 +18,8 @@ */ jest.mock('fs', () => ({ + // Hapi Inert patches native methods + ...jest.requireActual('fs'), readFileSync: jest.fn(), })); diff --git a/src/core/server/http/types.ts b/src/core/server/http/types.ts index 6327844108055..4be7e59acb7b9 100644 --- a/src/core/server/http/types.ts +++ b/src/core/server/http/types.ts @@ -265,6 +265,7 @@ export interface InternalHttpServiceSetup auth: HttpServerSetup['auth']; server: HttpServerSetup['server']; createRouter: (path: string, plugin?: PluginOpaqueId) => IRouter; + registerStaticDir: (path: string, dirPath: string) => void; getAuthHeaders: GetAuthHeaders; registerRouteHandlerContext: <T extends keyof RequestHandlerContext>( pluginOpaqueId: PluginOpaqueId, diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index ef31be559b30b..c6860086e7784 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -91,7 +91,15 @@ beforeEach(() => { contracts: new Map([['plugin-id', 'plugin-value']]), uiPlugins: { public: new Map([['plugin-id', {} as DiscoveredPlugin]]), - internal: new Map([['plugin-id', { publicTargetDir: 'path/to/target/public' }]]), + internal: new Map([ + [ + 'plugin-id', + { + publicTargetDir: 'path/to/target/public', + publicAssetsDir: '/plugins/name/assets/', + }, + ], + ]), browserConfigs: new Map(), }, }, diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 361fade6a4d0c..bb5f6d5617aae 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -334,6 +334,9 @@ export class LegacyService implements CoreService { plugins: startDeps.plugins, }, __internals: { + http: { + registerStaticDir: setupDeps.core.http.registerStaticDir, + }, hapiServer: setupDeps.core.http.server, kibanaMigrator: startDeps.core.savedObjects.migrator, uiPlugins: setupDeps.core.plugins.uiPlugins, diff --git a/src/core/server/plugins/plugins_service.test.ts b/src/core/server/plugins/plugins_service.test.ts index 4f69a2b4156be..14147ab9f2a8d 100644 --- a/src/core/server/plugins/plugins_service.test.ts +++ b/src/core/server/plugins/plugins_service.test.ts @@ -540,13 +540,15 @@ describe('PluginsService', () => { it('includes disabled plugins', async () => { config$.next({ plugins: { initialize: true }, plugin1: { enabled: false } }); await pluginsService.discover(); - const { uiPlugins } = await pluginsService.setup({} as any); + const { uiPlugins } = await pluginsService.setup(setupDeps); expect(uiPlugins.internal).toMatchInlineSnapshot(` Map { "plugin-1" => Object { + "publicAssetsDir": <absolute path>/path-1/public/assets, "publicTargetDir": <absolute path>/path-1/target/public, }, "plugin-2" => Object { + "publicAssetsDir": <absolute path>/path-2/public/assets, "publicTargetDir": <absolute path>/path-2/target/public, }, } @@ -558,7 +560,7 @@ describe('PluginsService', () => { it('does initialize if plugins.initialize is true', async () => { config$.next({ plugins: { initialize: true } }); await pluginsService.discover(); - const { initialized } = await pluginsService.setup({} as any); + const { initialized } = await pluginsService.setup(setupDeps); expect(mockPluginSystem.setupPlugins).toHaveBeenCalled(); expect(initialized).toBe(true); }); @@ -566,7 +568,7 @@ describe('PluginsService', () => { it('does not initialize if plugins.initialize is false', async () => { config$.next({ plugins: { initialize: false } }); await pluginsService.discover(); - const { initialized } = await pluginsService.setup({} as any); + const { initialized } = await pluginsService.setup(setupDeps); expect(mockPluginSystem.setupPlugins).not.toHaveBeenCalled(); expect(initialized).toBe(false); }); diff --git a/src/core/server/plugins/plugins_service.ts b/src/core/server/plugins/plugins_service.ts index 9987d1633c502..a0ecee47c675f 100644 --- a/src/core/server/plugins/plugins_service.ts +++ b/src/core/server/plugins/plugins_service.ts @@ -110,6 +110,7 @@ export class PluginsService implements CoreService<PluginsServiceSetup, PluginsS const initialize = config.initialize && !this.coreContext.env.isDevClusterMaster; if (initialize) { contracts = await this.pluginsSystem.setupPlugins(deps); + this.registerPluginStaticDirs(deps); } else { this.log.info('Plugin initialization disabled.'); } @@ -223,6 +224,7 @@ export class PluginsService implements CoreService<PluginsServiceSetup, PluginsS if (plugin.includesUiPlugin) { this.uiPluginInternalInfo.set(plugin.name, { publicTargetDir: Path.resolve(plugin.path, 'target/public'), + publicAssetsDir: Path.resolve(plugin.path, 'public/assets'), }); } @@ -262,4 +264,13 @@ export class PluginsService implements CoreService<PluginsServiceSetup, PluginsS ) ); } + + private registerPluginStaticDirs(deps: PluginsServiceSetupDeps) { + for (const [pluginName, pluginInfo] of this.uiPluginInternalInfo) { + deps.http.registerStaticDir( + `/plugins/${pluginName}/assets/{path*}`, + pluginInfo.publicAssetsDir + ); + } + } } diff --git a/src/core/server/plugins/types.ts b/src/core/server/plugins/types.ts index 100e3c2288dbf..4fa4e1780e596 100644 --- a/src/core/server/plugins/types.ts +++ b/src/core/server/plugins/types.ts @@ -194,6 +194,10 @@ export interface InternalPluginInfo { * served */ readonly publicTargetDir: string; + /** + * Path to the plugin assets directory. + */ + readonly publicAssetsDir: string; } /** diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index a0a4c9e07b2a5..e4e2b8d7adbb7 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -2350,8 +2350,8 @@ export const validBodyOutput: readonly ["data", "stream"]; // src/core/server/legacy/types.ts:165:3 - (ae-forgotten-export) The symbol "LegacyAppSpec" needs to be exported by the entry point index.d.ts // src/core/server/legacy/types.ts:166:16 - (ae-forgotten-export) The symbol "LegacyPluginSpec" needs to be exported by the entry point index.d.ts // src/core/server/plugins/plugins_service.ts:47:5 - (ae-forgotten-export) The symbol "InternalPluginInfo" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:226:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:226:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:228:3 - (ae-forgotten-export) The symbol "PathConfigType" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:230:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:230:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:232:3 - (ae-forgotten-export) The symbol "PathConfigType" needs to be exported by the entry point index.d.ts ``` diff --git a/src/dev/build/tasks/nodejs/__tests__/download.js b/src/dev/build/tasks/nodejs/__tests__/download.js index c76ff15b89289..81ed7a6195ae7 100644 --- a/src/dev/build/tasks/nodejs/__tests__/download.js +++ b/src/dev/build/tasks/nodejs/__tests__/download.js @@ -18,27 +18,39 @@ */ import { createServer } from 'http'; -import { resolve } from 'path'; -import { readFileSync } from 'fs'; +import { join } from 'path'; +import { tmpdir } from 'os'; +import { mkdirp, readFileSync } from 'fs-extra'; import del from 'del'; import sinon from 'sinon'; +import { CI_PARALLEL_PROCESS_PREFIX } from '@kbn/test'; import expect from '@kbn/expect'; import Wreck from '@hapi/wreck'; import { ToolingLog } from '@kbn/dev-utils'; import { download } from '../download'; -const TMP_DESTINATION = resolve(__dirname, '__tmp__'); -beforeEach(async () => { - await del(TMP_DESTINATION); -}); -after(async () => { - await del(TMP_DESTINATION); -}); +const getTempFolder = async () => { + const dir = join(tmpdir(), CI_PARALLEL_PROCESS_PREFIX, 'download-js-test-tmp-dir'); + console.log(dir); + await mkdirp(dir); + return dir; +}; describe('src/dev/build/tasks/nodejs/download', () => { const sandbox = sinon.createSandbox(); + let TMP_DESTINATION; + let TMP_DIR; + + beforeEach(async () => { + TMP_DIR = await getTempFolder(); + TMP_DESTINATION = join(TMP_DIR, '__tmp_download_js_test_file__'); + }); + + afterEach(async () => { + await del(TMP_DIR, { force: true }); + }); afterEach(() => sandbox.reset()); const onLogLine = sandbox.stub(); diff --git a/src/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js index ef114f51f3100..1b5110a61cbc4 100644 --- a/src/dev/precommit_hook/casing_check_config.js +++ b/src/dev/precommit_hook/casing_check_config.js @@ -41,7 +41,7 @@ export const IGNORE_FILE_GLOBS = [ '**/.*', '**/{webpackShims,__mocks__}/**/*', 'x-pack/docs/**/*', - 'src/legacy/ui/public/assets/fonts/**/*', + 'src/core/server/core_app/assets/fonts/**/*', 'packages/kbn-utility-types/test-d/**/*', '**/Jenkinsfile*', 'Dockerfile*', @@ -123,18 +123,18 @@ export const TEMPORARILY_IGNORED_PATHS = [ 'src/legacy/core_plugins/timelion/server/series_functions/__tests__/fixtures/tlConfig.js', 'src/fixtures/config_upgrade_from_4.0.0_to_4.0.1-snapshot.json', 'src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/terms/_seriesMultiple.js', - 'src/legacy/ui/public/assets/favicons/android-chrome-192x192.png', - 'src/legacy/ui/public/assets/favicons/android-chrome-256x256.png', - 'src/legacy/ui/public/assets/favicons/android-chrome-512x512.png', - 'src/legacy/ui/public/assets/favicons/apple-touch-icon.png', - 'src/legacy/ui/public/assets/favicons/favicon-16x16.png', - 'src/legacy/ui/public/assets/favicons/favicon-32x32.png', - 'src/legacy/ui/public/assets/favicons/mstile-70x70.png', - 'src/legacy/ui/public/assets/favicons/mstile-144x144.png', - 'src/legacy/ui/public/assets/favicons/mstile-150x150.png', - 'src/legacy/ui/public/assets/favicons/mstile-310x150.png', - 'src/legacy/ui/public/assets/favicons/mstile-310x310.png', - 'src/legacy/ui/public/assets/favicons/safari-pinned-tab.svg', + 'src/core/server/core_app/assets/favicons/android-chrome-192x192.png', + 'src/core/server/core_app/assets/favicons/android-chrome-256x256.png', + 'src/core/server/core_app/assets/favicons/android-chrome-512x512.png', + 'src/core/server/core_app/assets/favicons/apple-touch-icon.png', + 'src/core/server/core_app/assets/favicons/favicon-16x16.png', + 'src/core/server/core_app/assets/favicons/favicon-32x32.png', + 'src/core/server/core_app/assets/favicons/mstile-70x70.png', + 'src/core/server/core_app/assets/favicons/mstile-144x144.png', + 'src/core/server/core_app/assets/favicons/mstile-150x150.png', + 'src/core/server/core_app/assets/favicons/mstile-310x150.png', + 'src/core/server/core_app/assets/favicons/mstile-310x310.png', + 'src/core/server/core_app/assets/favicons/safari-pinned-tab.svg', 'src/legacy/ui/public/styles/bootstrap/component-animations.less', 'src/legacy/ui/public/styles/bootstrap/input-groups.less', 'src/legacy/ui/public/styles/bootstrap/list-group.less', diff --git a/src/legacy/core_plugins/input_control_vis/index.ts b/src/legacy/core_plugins/input_control_vis/index.ts index d67472ac4b95f..0529aa24dffd7 100644 --- a/src/legacy/core_plugins/input_control_vis/index.ts +++ b/src/legacy/core_plugins/input_control_vis/index.ts @@ -25,7 +25,7 @@ import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy const inputControlVisPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => new Plugin({ id: 'input_control_vis', - require: ['kibana', 'elasticsearch', 'visualizations', 'interpreter'], + require: ['kibana', 'elasticsearch', 'interpreter'], publicDir: resolve(__dirname, 'public'), uiExports: { styleSheetPaths: resolve(__dirname, 'public/index.scss'), diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.tsx index d7a62e07b26f3..db2af742c70bc 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.tsx @@ -23,7 +23,7 @@ import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { getDepsMock, getIndexPatternMock } from '../../test_utils'; import { ControlsTab, ControlsTabUiProps } from './controls_tab'; -import { Vis } from 'src/legacy/core_plugins/visualizations/public'; +import { Vis } from '../../../../../../plugins/visualizations/public'; const indexPatternsMock = { get: getIndexPatternMock, diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.test.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.test.tsx index 8c77f1b7c4b4f..639e3d2f68c75 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.test.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.test.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { Vis } from 'src/legacy/core_plugins/visualizations/public'; +import { Vis } from '../../../../../../plugins/visualizations/public'; import { OptionsTab, OptionsTabProps } from './options_tab'; describe('OptionsTab', () => { diff --git a/src/legacy/core_plugins/input_control_vis/public/legacy.ts b/src/legacy/core_plugins/input_control_vis/public/legacy.ts index 438cdffdb323a..67299068819e8 100644 --- a/src/legacy/core_plugins/input_control_vis/public/legacy.ts +++ b/src/legacy/core_plugins/input_control_vis/public/legacy.ts @@ -26,21 +26,17 @@ import { InputControlVisPluginSetupDependencies, InputControlVisPluginStartDependencies, } from './plugin'; -import { - setup as visualizationsSetup, - start as visualizationsStart, -} from '../../visualizations/public/np_ready/public/legacy'; const setupPlugins: Readonly<InputControlVisPluginSetupDependencies> = { expressions: npSetup.plugins.expressions, data: npSetup.plugins.data, - visualizations: visualizationsSetup, + visualizations: npSetup.plugins.visualizations, }; const startPlugins: Readonly<InputControlVisPluginStartDependencies> = { expressions: npStart.plugins.expressions, data: npStart.plugins.data, - visualizations: visualizationsStart, + visualizations: npStart.plugins.visualizations, }; const pluginInstance = plugin({} as PluginInitializerContext); diff --git a/src/legacy/core_plugins/input_control_vis/public/plugin.ts b/src/legacy/core_plugins/input_control_vis/public/plugin.ts index c45e0d17872ec..b743468065430 100644 --- a/src/legacy/core_plugins/input_control_vis/public/plugin.ts +++ b/src/legacy/core_plugins/input_control_vis/public/plugin.ts @@ -20,7 +20,10 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'kibana/p import { DataPublicPluginSetup, DataPublicPluginStart } from 'src/plugins/data/public'; import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; -import { VisualizationsSetup, VisualizationsStart } from '../../visualizations/public'; +import { + VisualizationsSetup, + VisualizationsStart, +} from '../../../../plugins/visualizations/public'; import { createInputControlVisFn } from './input_control_fn'; import { createInputControlVisTypeDefinition } from './input_control_vis_type'; diff --git a/src/legacy/core_plugins/input_control_vis/public/vis_controller.tsx b/src/legacy/core_plugins/input_control_vis/public/vis_controller.tsx index c0ab235c1b9d1..c4a7d286850e3 100644 --- a/src/legacy/core_plugins/input_control_vis/public/vis_controller.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/vis_controller.tsx @@ -31,7 +31,7 @@ import { RangeControl } from './control/range_control_factory'; import { ListControl } from './control/list_control_factory'; import { InputControlVisDependencies } from './plugin'; import { FilterManager, Filter } from '../../../../plugins/data/public'; -import { VisParams, Vis } from '../../visualizations/public'; +import { VisParams, Vis } from '../../../../plugins/visualizations/public'; export const createInputControlVisController = (deps: InputControlVisDependencies) => { return class InputControlVisController { diff --git a/src/legacy/core_plugins/kibana/public/discover/build_services.ts b/src/legacy/core_plugins/kibana/public/discover/build_services.ts index f881eb96e4e81..180ff13cdddc0 100644 --- a/src/legacy/core_plugins/kibana/public/discover/build_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/build_services.ts @@ -36,7 +36,7 @@ import { import { DiscoverStartPlugins } from './plugin'; import { SharePluginStart } from '../../../../../plugins/share/public'; import { ChartsPluginStart } from '../../../../../plugins/charts/public'; -import { VisualizationsStart } from '../../../visualizations/public'; +import { VisualizationsStart } from '../../../../../plugins/visualizations/public'; import { createSavedSearchesLoader, DocViewerComponent, diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index e1ff274911284..2ceb06f325a9e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -47,10 +47,15 @@ export function setServices(newServices: any) { services = newServices; } +export const [getUrlTracker, setUrlTracker] = createGetterSetter<{ + setTrackedUrl: (url: string) => void; +}>('urlTracker'); + // EXPORT legacy static dependencies, should be migrated when available in a new version; export { angular }; export { wrapInI18nContext } from 'ui/i18n'; import { search } from '../../../../../plugins/data/public'; +import { createGetterSetter } from '../../../../../plugins/kibana_utils/common'; export const { getRequestInspectorStats, getResponseInspectorStats, tabifyAggResponse } = search; export { unhashUrl, diff --git a/src/legacy/core_plugins/kibana/public/discover/legacy.ts b/src/legacy/core_plugins/kibana/public/discover/legacy.ts index a1ef646f4fe85..f08fd22c71850 100644 --- a/src/legacy/core_plugins/kibana/public/discover/legacy.ts +++ b/src/legacy/core_plugins/kibana/public/discover/legacy.ts @@ -20,18 +20,8 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; import { plugin } from './index'; -import { - setup as visualizationsSetup, - start as visualizationsStart, -} from '../../../../core_plugins/visualizations/public/np_ready/public/legacy'; // Legacy compatibility part - to be removed at cutover, replaced by a kibana.json file export const pluginInstance = plugin({} as PluginInitializerContext); -export const setup = pluginInstance.setup(npSetup.core, { - ...npSetup.plugins, - visualizations: visualizationsSetup, -}); -export const start = pluginInstance.start(npStart.core, { - ...npStart.plugins, - visualizations: visualizationsStart, -}); +export const setup = pluginInstance.setup(npSetup.core, npSetup.plugins); +export const start = pluginInstance.start(npStart.core, npStart.plugins); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html index 2334e33deadba..e9338b8bd57cc 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html @@ -1,4 +1,4 @@ -<discover-app class="app-container"> +<discover-app class="app-container" data-fetch-counter="{{fetchCounter}}"> <h1 class="euiScreenReaderOnly">{{screenTitle}}</h1> <!-- Local nav. --> diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js index 8e4e77b2d18a6..630ba22d4f3a6 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js @@ -44,6 +44,7 @@ import { getRequestInspectorStats, getResponseInspectorStats, getServices, + getUrlTracker, unhashUrl, subscribeWithScope, tabifyAggResponse, @@ -160,6 +161,9 @@ app.config($routeProvider => { '/management/kibana/objects/savedSearches/' + $route.current.params.id, }, toastNotifications, + onBeforeRedirect() { + getUrlTracker().setTrackedUrl('/discover'); + }, }) ), }); @@ -194,6 +198,8 @@ function discoverController( const savedSearch = $route.current.locals.savedObjects.savedSearch; $scope.searchSource = savedSearch.searchSource; $scope.indexPattern = resolveIndexPatternLoading(); + //used for functional testing + $scope.fetchCounter = 0; const getTimeField = () => { return isDefaultType($scope.indexPattern) ? $scope.indexPattern.timeFieldName : undefined; @@ -275,6 +281,7 @@ function discoverController( filterManager.getUpdates$(), { next: () => { + $scope.state.filters = filterManager.getAppFilters(); $scope.updateDataSource(); }, }, @@ -784,7 +791,7 @@ function discoverController( $scope.opts.fetch = $scope.fetch = function() { // ignore requests to fetch before the app inits if (!init.complete) return; - + $scope.fetchCounter++; $scope.fetchError = undefined; // Abort any in-progress requests before fetching again @@ -821,9 +828,11 @@ function discoverController( }); }; - $scope.updateQuery = function({ query }) { - setAppState({ query }); - $fetchObservable.next(); + $scope.updateQuery = function({ query }, isUpdate = true) { + if (!_.isEqual(query, appStateContainer.getState().query) || isUpdate === false) { + setAppState({ query }); + $fetchObservable.next(); + } }; $scope.updateSavedQueryId = newSavedQueryId => { diff --git a/src/legacy/core_plugins/kibana/public/discover/plugin.ts b/src/legacy/core_plugins/kibana/public/discover/plugin.ts index 42f3ec6726097..fcac7aa74f54a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/discover/plugin.ts @@ -31,7 +31,7 @@ import { registerFeature } from './np_ready/register_feature'; import './kibana_services'; import { EmbeddableStart, EmbeddableSetup } from '../../../../../plugins/embeddable/public'; import { getInnerAngularModule, getInnerAngularModuleEmbeddable } from './get_inner_angular'; -import { setAngularModule, setServices } from './kibana_services'; +import { setAngularModule, setServices, setUrlTracker } from './kibana_services'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../../plugins/navigation/public'; import { ChartsPluginStart } from '../../../../../plugins/charts/public'; import { buildServices } from './build_services'; @@ -45,7 +45,7 @@ import { HomePublicPluginSetup } from '../../../../../plugins/home/public'; import { VisualizationsStart, VisualizationsSetup, -} from '../../../visualizations/public/np_ready/public'; +} from '../../../../../plugins/visualizations/public'; import { createKbnUrlTracker } from '../../../../../plugins/kibana_utils/public'; export interface DiscoverSetupPlugins { @@ -92,7 +92,12 @@ export class DiscoverPlugin implements Plugin<void, void> { public initializeServices?: () => Promise<{ core: CoreStart; plugins: DiscoverStartPlugins }>; setup(core: CoreSetup<DiscoverStartPlugins, void>, plugins: DiscoverSetupPlugins) { - const { appMounted, appUnMounted, stop: stopUrlTracker } = createKbnUrlTracker({ + const { + appMounted, + appUnMounted, + stop: stopUrlTracker, + setActiveUrl: setTrackedUrl, + } = createKbnUrlTracker({ baseUrl: core.http.basePath.prepend('/app/kibana'), defaultSubUrl: '#/discover', storageKey: 'lastUrl:discover', @@ -113,6 +118,7 @@ export class DiscoverPlugin implements Plugin<void, void> { }, ], }); + setUrlTracker({ setTrackedUrl }); this.stopUrlTracking = () => { stopUrlTracker(); }; diff --git a/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts b/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts index f3a37e2b7348f..7261b2ba03372 100644 --- a/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts +++ b/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts @@ -21,7 +21,6 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import { npStart } from 'ui/new_platform'; import { SavedObjectLoader } from '../../../../../plugins/saved_objects/public'; -import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; import { createSavedSearchesLoader } from '../../../../../plugins/discover/public'; /** @@ -63,7 +62,7 @@ const services = { savedObjectManagementRegistry.register({ id: 'savedVisualizations', - service: visualizations.savedVisualizationsLoader, + service: npStart.plugins.visualizations.savedVisualizationsLoader, title: 'visualizations', }); diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index 7e96d7bde6e13..f29f07ba4b20b 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -32,7 +32,7 @@ import { Storage } from '../../../../../plugins/kibana_utils/public'; import { EmbeddableStart } from '../../../../../plugins/embeddable/public'; import { SharePluginStart } from '../../../../../plugins/share/public'; import { DataPublicPluginStart, IndexPatternsContract } from '../../../../../plugins/data/public'; -import { VisualizationsStart } from '../../../visualizations/public'; +import { VisualizationsStart } from '../../../../../plugins/visualizations/public'; import { SavedVisualizations } from './np_ready/types'; import { UsageCollectionSetup } from '../../../../../plugins/usage_collection/public'; import { KibanaLegacyStart } from '../../../../../plugins/kibana_legacy/public'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy.ts index fbbc7ab944daf..4ef2c93689714 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/legacy.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy.ts @@ -19,14 +19,10 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; -import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; import { plugin } from './index'; const instance = plugin({ env: npSetup.plugins.kibanaLegacy.env, } as PluginInitializerContext); instance.setup(npSetup.core, npSetup.plugins); -instance.start(npStart.core, { - ...npStart.plugins, - visualizations, -}); +instance.start(npStart.core, npStart.plugins); diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts index a2e2ba3543104..a6774e2dd47e8 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts @@ -28,7 +28,10 @@ export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url'; export { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; export { wrapInI18nContext } from 'ui/i18n'; export { DashboardConstants } from '../dashboard/np_ready/dashboard_constants'; -export { VisSavedObject, VISUALIZE_EMBEDDABLE_TYPE } from '../../../visualizations/public/'; +export { + VisSavedObject, + VISUALIZE_EMBEDDABLE_TYPE, +} from '../../../../../plugins/visualizations/public/'; export { configureAppAngularModule, migrateLegacyQuery, diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js index b0b1ae31a02a5..d1bf4411cac2a 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js @@ -204,6 +204,9 @@ export function initVisualizeApp(app, deps) { '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, }, toastNotifications, + onBeforeRedirect() { + deps.setActiveUrl(VisualizeConstants.LANDING_PAGE_PATH); + }, }) ); }, diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts index 246a031f05769..e376b4f2bbacf 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts @@ -27,9 +27,8 @@ import { import { EmbeddableStart } from 'src/plugins/embeddable/public'; import { PersistedState } from 'src/plugins/visualizations/public'; import { LegacyCoreStart } from 'kibana/public'; -import { Vis } from 'src/legacy/core_plugins/visualizations/public'; import { VisSavedObject } from '../legacy_imports'; -import { SavedVisState } from '../../../../visualizations/public/np_ready/public/types'; +import { SavedVisState } from '../../../../../../plugins/visualizations/public'; import { SavedSearch } from '../../../../../../plugins/discover/public'; export type PureVisState = SavedVisState; diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index 9d88152c59aa7..59b814c98dd08 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -43,7 +43,7 @@ import { KibanaLegacySetup, AngularRenderedAppUpdater, } from '../../../../../plugins/kibana_legacy/public'; -import { VisualizationsStart } from '../../../visualizations/public'; +import { VisualizationsStart } from '../../../../../plugins/visualizations/public'; import { VisualizeConstants } from './np_ready/visualize_constants'; import { setServices, VisualizeKibanaServices } from './kibana_services'; import { diff --git a/src/legacy/core_plugins/management/public/legacy.ts b/src/legacy/core_plugins/management/public/legacy.ts index 4481bad79c47d..96d2c74398a0e 100644 --- a/src/legacy/core_plugins/management/public/legacy.ts +++ b/src/legacy/core_plugins/management/public/legacy.ts @@ -29,7 +29,7 @@ * simply delete this shim file. * * We are also calling `setup/start` here and exporting our public contract so that - * other legacy plugins are able to import from '../core_plugins/visualizations/legacy' + * other legacy plugins are able to import from '../core_plugins/management/legacy' * and receive the response value of the `setup/start` contract, mimicking the * data that will eventually be injected by the new platform. */ diff --git a/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js b/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js index 23ca99791e92e..3880f42d52561 100644 --- a/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js +++ b/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js @@ -37,11 +37,13 @@ import afterdatachangePng from './afterdatachange.png'; import afterdatachangeandresizePng from './afterdatachangeandresize.png'; import aftercolorchangePng from './aftercolorchange.png'; import changestartupPng from './changestartup.png'; -import { setup as visualizationsSetup } from '../../../visualizations/public/np_ready/public/legacy'; import { createRegionMapVisualization } from '../region_map_visualization'; import { createRegionMapTypeDefinition } from '../region_map_type'; -import { ExprVis } from '../../../visualizations/public/np_ready/public/expressions/vis'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ExprVis } from '../../../../../plugins/visualizations/public/expressions/vis'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { BaseVisType } from '../../../../../plugins/visualizations/public/vis_types/base_vis_type'; const THRESHOLD = 0.45; const PIXEL_DIFF = 96; @@ -50,6 +52,7 @@ describe('RegionMapsVisualizationTests', function() { let domNode; let RegionMapsVisualization; let vis; + let regionMapVisType; let dependencies; let imageComparator; @@ -84,8 +87,6 @@ describe('RegionMapsVisualizationTests', function() { ], }; - let visRegComplete = false; - beforeEach(ngMock.module('kibana')); let getManifestStub; @@ -105,11 +106,7 @@ describe('RegionMapsVisualizationTests', function() { uiSettings, }; - if (!visRegComplete) { - visRegComplete = true; - visualizationsSetup.createBaseVisualization(createRegionMapTypeDefinition(dependencies)); - } - + regionMapVisType = new BaseVisType(createRegionMapTypeDefinition(dependencies)); RegionMapsVisualization = createRegionMapVisualization(dependencies); ChoroplethLayer.prototype._makeJsonAjaxCall = async function() { @@ -154,7 +151,7 @@ describe('RegionMapsVisualizationTests', function() { imageComparator = new ImageComparator(); vis = new ExprVis({ - type: 'region_map', + type: regionMapVisType, }); vis.params.bucket = { diff --git a/src/legacy/core_plugins/region_map/public/legacy.ts b/src/legacy/core_plugins/region_map/public/legacy.ts index 495e558e29dd7..08615946affa2 100644 --- a/src/legacy/core_plugins/region_map/public/legacy.ts +++ b/src/legacy/core_plugins/region_map/public/legacy.ts @@ -20,7 +20,6 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; -import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; import { RegionMapPluginSetupDependencies, RegionMapsConfig } from './plugin'; import { LegacyDependenciesPlugin } from './shim'; import { plugin } from '.'; @@ -31,7 +30,7 @@ const regionmapsConfig = npSetup.core.injectedMetadata.getInjectedVar( const plugins: Readonly<RegionMapPluginSetupDependencies> = { expressions: npSetup.plugins.expressions, - visualizations: visualizationsSetup, + visualizations: npSetup.plugins.visualizations, // Temporary solution // It will be removed when all dependent services are migrated to the new platform. diff --git a/src/legacy/core_plugins/region_map/public/plugin.ts b/src/legacy/core_plugins/region_map/public/plugin.ts index 98fb5604c3d65..cae569f8fd26d 100644 --- a/src/legacy/core_plugins/region_map/public/plugin.ts +++ b/src/legacy/core_plugins/region_map/public/plugin.ts @@ -24,7 +24,7 @@ import { IUiSettingsClient, } from '../../../../core/public'; import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; -import { VisualizationsSetup } from '../../visualizations/public'; +import { VisualizationsSetup } from '../../../../plugins/visualizations/public'; import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; diff --git a/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js b/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js index 3b8a7dfbed313..2c142b19d9096 100644 --- a/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js +++ b/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js @@ -31,11 +31,13 @@ import EMS_TILES from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_ import EMS_STYLE_ROAD_MAP_BRIGHT from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_style_bright'; import EMS_STYLE_ROAD_MAP_DESATURATED from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_style_desaturated'; import EMS_STYLE_DARK_MAP from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_style_dark'; -import { setup as visualizationsSetup } from '../../../visualizations/public/np_ready/public/legacy'; import { createTileMapVisualization } from '../tile_map_visualization'; import { createTileMapTypeDefinition } from '../tile_map_type'; -import { ExprVis } from '../../../visualizations/public/np_ready/public/expressions/vis'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ExprVis } from '../../../../../plugins/visualizations/public/expressions/vis'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { BaseVisType } from '../../../../../plugins/visualizations/public/vis_types/base_vis_type'; function mockRawData() { const stack = [dummyESResponse]; @@ -59,13 +61,13 @@ mockRawData(); const THRESHOLD = 0.45; const PIXEL_DIFF = 64; -let visRegComplete = false; describe('CoordinateMapsVisualizationTest', function() { let domNode; let CoordinateMapsVisualization; let vis; let dependencies; + let visType; let imageComparator; @@ -82,10 +84,7 @@ describe('CoordinateMapsVisualizationTest', function() { $injector, }; - if (!visRegComplete) { - visRegComplete = true; - visualizationsSetup.createBaseVisualization(createTileMapTypeDefinition(dependencies)); - } + visType = new BaseVisType(createTileMapTypeDefinition(dependencies)); CoordinateMapsVisualization = createTileMapVisualization(dependencies); @@ -120,7 +119,7 @@ describe('CoordinateMapsVisualizationTest', function() { imageComparator = new ImageComparator(); vis = new ExprVis({ - type: 'tile_map', + type: visType, }); vis.params = { mapType: 'Scaled Circle Markers', diff --git a/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx b/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx index 204ad5efa9b40..27127b781cd4d 100644 --- a/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx +++ b/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx @@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { TmsLayer } from 'ui/vis/map/service_settings'; -import { Vis } from '../../../visualizations/public'; +import { Vis } from '../../../../../plugins/visualizations/public'; import { RegionMapVisParams } from '../../../region_map/public/types'; import { SelectOption, SwitchOption } from '../../../vis_type_vislib/public'; import { WmsInternalOptions } from './wms_internal_options'; diff --git a/src/legacy/core_plugins/tile_map/public/legacy.ts b/src/legacy/core_plugins/tile_map/public/legacy.ts index 74be8482bfd30..7b1f916076f61 100644 --- a/src/legacy/core_plugins/tile_map/public/legacy.ts +++ b/src/legacy/core_plugins/tile_map/public/legacy.ts @@ -20,14 +20,13 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; -import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; import { TileMapPluginSetupDependencies } from './plugin'; import { LegacyDependenciesPlugin } from './shim'; import { plugin } from '.'; const plugins: Readonly<TileMapPluginSetupDependencies> = { expressions: npSetup.plugins.expressions, - visualizations: visualizationsSetup, + visualizations: npSetup.plugins.visualizations, // Temporary solution // It will be removed when all dependent services are migrated to the new platform. diff --git a/src/legacy/core_plugins/tile_map/public/plugin.ts b/src/legacy/core_plugins/tile_map/public/plugin.ts index a12c2753cc525..f2addbe3ab872 100644 --- a/src/legacy/core_plugins/tile_map/public/plugin.ts +++ b/src/legacy/core_plugins/tile_map/public/plugin.ts @@ -24,7 +24,7 @@ import { IUiSettingsClient, } from '../../../../core/public'; import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; -import { VisualizationsSetup } from '../../visualizations/public'; +import { VisualizationsSetup } from '../../../../plugins/visualizations/public'; import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; diff --git a/src/legacy/core_plugins/timelion/public/app.js b/src/legacy/core_plugins/timelion/public/app.js index a50f8a2cd3e8d..c15318d29e761 100644 --- a/src/legacy/core_plugins/timelion/public/app.js +++ b/src/legacy/core_plugins/timelion/public/app.js @@ -41,7 +41,6 @@ import './directives/saved_object_save_as_checkbox'; import './services/saved_sheet_register'; import rootTemplate from 'plugins/timelion/index.html'; -import { start as visualizations } from '../../visualizations/public/np_ready/public/legacy'; import { loadKbnTopNavDirectives } from '../../../../plugins/kibana_legacy/public'; loadKbnTopNavDirectives(npStart.plugins.navigation.ui); @@ -125,7 +124,7 @@ app.controller('timelion', function( timefilter.enableAutoRefreshSelector(); timefilter.enableTimeRangeSelector(); - const savedVisualizations = visualizations.savedVisualizationsLoader; + const savedVisualizations = npStart.plugins.visualizations.savedVisualizationsLoader; const timezone = getTimezone(config); const defaultExpression = '.es(*)'; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_common_props.ts b/src/legacy/core_plugins/vis_default_editor/public/components/agg_common_props.ts index 3aae10879138a..0c130a96230b4 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_common_props.ts +++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg_common_props.ts @@ -17,7 +17,7 @@ * under the License. */ -import { VisParams } from 'src/legacy/core_plugins/visualizations/public'; +import { VisParams } from 'src/plugins/visualizations/public'; import { IAggType, IAggConfig, IAggGroupNames } from 'src/plugins/data/public'; import { Schema } from '../schemas'; import { EditorVisState } from './sidebar/state/reducers'; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/agg_control_props.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/controls/agg_control_props.tsx index 98540d3414f2d..329704ca106db 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/agg_control_props.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/controls/agg_control_props.tsx @@ -17,7 +17,7 @@ * under the License. */ -import { VisParams } from 'src/legacy/core_plugins/visualizations/public'; +import { VisParams } from 'src/plugins/visualizations/public'; import { IAggConfig } from 'src/plugins/data/public'; import { DefaultEditorAggCommonProps } from '../agg_common_props'; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/controls.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/controls.tsx index 55d5bc2426e92..18b445b4a26db 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/controls.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/controls.tsx @@ -30,7 +30,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { useDebounce } from 'react-use'; -import { Vis } from 'src/legacy/core_plugins/visualizations/public'; +import { Vis } from '../../../../../../plugins/visualizations/public'; import { discardChanges, EditorAction } from './state'; interface DefaultEditorControlsProps { diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar.tsx index 04c931f593e5a..29039715066be 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar.tsx @@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n'; import { keyCodes, EuiButtonIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { EventEmitter } from 'events'; -import { Vis } from 'src/legacy/core_plugins/visualizations/public'; +import { Vis } from 'src/plugins/visualizations/public'; import { DefaultEditorNavBar, OptionTab } from './navbar'; import { DefaultEditorControls } from './controls'; import { setStateParamValue, useEditorReducer, useEditorFormState, discardChanges } from './state'; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar_title.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar_title.tsx index 575ad5ae2a95c..fb63a598a4fae 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar_title.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar_title.tsx @@ -35,7 +35,7 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { Vis } from 'src/legacy/core_plugins/visualizations/public'; +import { Vis } from '../../../../../../plugins/visualizations/public'; import { SavedSearch } from '../../../../../../plugins/discover/public'; interface LinkedSearchProps { diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/actions.ts b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/actions.ts index e3577218b7e25..16c3632bed697 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/actions.ts +++ b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/actions.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Vis, VisParams } from 'src/legacy/core_plugins/visualizations/public'; +import { Vis, VisParams } from 'src/plugins/visualizations/public'; import { IAggConfig } from 'src/plugins/data/public'; import { EditorStateActionTypes } from './constants'; import { Schema } from '../../../schemas'; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/index.ts b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/index.ts index 11cbc3f93e9d3..d39d6d07b32d2 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/index.ts +++ b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/index.ts @@ -20,7 +20,7 @@ import { useReducer, useCallback } from 'react'; import { EventEmitter } from 'events'; -import { Vis } from 'src/legacy/core_plugins/visualizations/public'; +import { Vis } from 'src/plugins/visualizations/public'; import { createEditorStateReducer, initEditorState, EditorVisState } from './reducers'; import { EditorStateActionTypes } from './constants'; import { EditorAction } from './actions'; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts index 6e5bec7c69c90..b9f89cebd8bf3 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts +++ b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts @@ -19,7 +19,7 @@ import { cloneDeep } from 'lodash'; -import { Vis } from 'src/legacy/core_plugins/visualizations/public'; +import { Vis } from 'src/plugins/visualizations/public'; import { AggGroupNames, DataPublicPluginStart } from '../../../../../../../plugins/data/public'; import { EditorStateActionTypes } from './constants'; import { getEnabledMetricAggsCount } from '../../agg_group_helper'; diff --git a/src/legacy/core_plugins/vis_default_editor/public/default_editor_controller.tsx b/src/legacy/core_plugins/vis_default_editor/public/default_editor_controller.tsx index 13fcabd799959..0b6d4e5982a00 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/default_editor_controller.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/default_editor_controller.tsx @@ -24,19 +24,18 @@ import { I18nProvider } from '@kbn/i18n/react'; import { EventEmitter } from 'events'; import { EditorRenderProps } from 'src/legacy/core_plugins/kibana/public/visualize/np_ready/types'; -import { Vis } from 'src/legacy/core_plugins/visualizations/public/'; +import { Vis, VisualizeEmbeddableContract } from '../../../../plugins/visualizations/public'; import { Storage } from '../../../../plugins/kibana_utils/public'; import { KibanaContextProvider } from '../../../../plugins/kibana_react/public'; import { DefaultEditor } from './default_editor'; import { DefaultEditorDataTab, OptionTab } from './components/sidebar'; -import { VisualizeEmbeddable } from '../../visualizations/public/np_ready/public/embeddable'; const localStorage = new Storage(window.localStorage); export interface DefaultEditorControllerState { vis: Vis; eventEmitter: EventEmitter; - embeddableHandler: VisualizeEmbeddable; + embeddableHandler: VisualizeEmbeddableContract; optionTabs: OptionTab[]; } diff --git a/src/legacy/core_plugins/vis_default_editor/public/vis_options_props.tsx b/src/legacy/core_plugins/vis_default_editor/public/vis_options_props.tsx index 3239e871a2465..a9b20ec0f00da 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/vis_options_props.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/vis_options_props.tsx @@ -17,9 +17,8 @@ * under the License. */ -import { PersistedState } from 'src/plugins/visualizations/public'; +import { Vis, PersistedState } from 'src/plugins/visualizations/public'; import { IAggConfigs } from 'src/plugins/data/public'; -import { Vis } from '../../visualizations/public'; export interface VisOptionsProps<VisParamType = unknown> { aggs: IAggConfigs; diff --git a/src/legacy/core_plugins/vis_type_markdown/public/legacy.ts b/src/legacy/core_plugins/vis_type_markdown/public/legacy.ts index d4a5290df865c..1cfc583f6e005 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_markdown/public/legacy.ts @@ -19,14 +19,12 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; - -import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; import { MarkdownPluginSetupDependencies } from './plugin'; import { plugin } from '.'; const plugins: Readonly<MarkdownPluginSetupDependencies> = { expressions: npSetup.plugins.expressions, - visualizations: visualizationsSetup, + visualizations: npSetup.plugins.visualizations, }; const pluginInstance = plugin({} as PluginInitializerContext); diff --git a/src/legacy/core_plugins/vis_type_markdown/public/plugin.ts b/src/legacy/core_plugins/vis_type_markdown/public/plugin.ts index 71d6c1c69ef2d..0445d270c9330 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_markdown/public/plugin.ts @@ -19,7 +19,7 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../../core/public'; import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; -import { VisualizationsSetup } from '../../visualizations/public'; +import { VisualizationsSetup } from '../../../../plugins/visualizations/public'; import { markdownVisDefinition } from './markdown_vis'; import { createMarkdownVisFn } from './markdown_fn'; diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.test.tsx b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.test.tsx index 00e8df2f0f936..2bd423656b0f0 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.test.tsx +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.test.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { MetricVisComponent, MetricVisComponentProps } from './metric_vis_component'; -import { ExprVis } from '../../../visualizations/public/np_ready/public/expressions/vis'; +import { ExprVis } from '../../../../../plugins/visualizations/public'; jest.mock('../services', () => ({ getFormatService: () => ({ diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx index 3fca1df92eacb..de2cc66a99c79 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx @@ -27,8 +27,7 @@ import { KibanaDatatable } from '../../../../../plugins/expressions/public'; import { getHeatmapColors } from '../../../../../plugins/charts/public'; import { VisParams, MetricVisMetric } from '../types'; import { getFormatService } from '../services'; -import { SchemaConfig } from '../../../visualizations/public'; -import { ExprVis } from '../../../visualizations/public/np_ready/public/expressions/vis'; +import { SchemaConfig, ExprVis } from '../../../../../plugins/visualizations/public'; export interface MetricVisComponentProps { visParams: VisParams; diff --git a/src/legacy/core_plugins/vis_type_metric/public/legacy.ts b/src/legacy/core_plugins/vis_type_metric/public/legacy.ts index 5fc2e48609d4b..ba883601e5d65 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/legacy.ts @@ -19,14 +19,12 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; - -import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; import { MetricVisPluginSetupDependencies } from './plugin'; import { plugin } from '.'; const plugins: Readonly<MetricVisPluginSetupDependencies> = { expressions: npSetup.plugins.expressions, - visualizations: visualizationsSetup, + visualizations: npSetup.plugins.visualizations, charts: npSetup.plugins.charts, }; diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts index 2a5478f23e850..459da47556307 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts @@ -17,112 +17,19 @@ * under the License. */ -import $ from 'jquery'; - -// TODO This is an integration test and thus requires a running platform. When moving to the new platform, -// this test has to be migrated to a real unit test. -// @ts-ignore -import getStubIndexPattern from 'fixtures/stubbed_logstash_index_pattern'; - -import { Vis } from '../../visualizations/public'; -import { - setup as visualizationsSetup, - start as visualizationsStart, -} from '../../visualizations/public/np_ready/public/legacy'; import { createMetricVisTypeDefinition } from './metric_vis_type'; +import { MetricVisComponent } from './components/metric_vis_component'; jest.mock('ui/new_platform'); -jest.mock('./services', () => ({ - getFormatService: () => ({ - deserialize: () => { - return { - convert: (x: unknown) => `<a href="http://ip.info?address={{${x}}">ip[${x}]</a>`, - }; - }, - }), -})); - jest.mock('../../vis_default_editor/public', () => ({ Schemas: class {}, })); describe('metric_vis - createMetricVisTypeDefinition', () => { - let vis: Vis; - - beforeAll(() => { - visualizationsSetup.createReactVisualization(createMetricVisTypeDefinition()); - }); - - const setup = () => { - const stubIndexPattern = getStubIndexPattern(); - - stubIndexPattern.stubSetFieldFormat('ip', 'url', { - urlTemplate: 'http://ip.info?address={{value}}', - labelTemplate: 'ip[{{value}}]', - }); - - const searchSource = { - getField: (name: string) => { - if (name === 'index') { - return stubIndexPattern; - } - }, - }; - - // TODO: remove when Vis is converted to typescript. Only importing Vis as type - // @ts-ignore - vis = visualizationsStart.createVis('metric', { - type: 'metric', - data: { - searchSource, - aggs: [{ id: '1', type: 'top_hits', schema: 'metric', params: { field: 'ip' } }], - }, - }); - - vis.params.dimensions = { - metrics: [ - { - accessor: 0, - format: { - id: 'url', - params: { - urlTemplate: 'http://ip.info?address={{value}}', - labelTemplate: 'ip[{{value}}]', - }, - }, - }, - ], - }; - - const el = document.createElement('div'); - const metricVisType = visualizationsStart.get('metric'); - const Controller = metricVisType.visualization; - const controller = new Controller(el, vis); - const render = (esResponse: any) => { - controller.render(esResponse, vis.params); - }; - - return { el, render }; - }; - - it('renders html value from field formatter', () => { - const { el, render } = setup(); - - const ip = '235.195.237.208'; - render({ - columns: [{ id: 'col-0', name: 'ip' }], - rows: [{ 'col-0': ip }], - }); - - const links = $(el) - .find('a[href]') - .filter(function() { - // @ts-ignore - return this.href.includes('ip.info'); - }); + it('has metric vis component set', () => { + const def = createMetricVisTypeDefinition(); - expect(links.length).toBe(1); - expect(links.text()).toBe(`ip[${ip}]`); + expect(def.visConfig.component).toBe(MetricVisComponent); }); }); diff --git a/src/legacy/core_plugins/vis_type_metric/public/plugin.ts b/src/legacy/core_plugins/vis_type_metric/public/plugin.ts index 1c3e1568f4de2..cb65d5cafbdd2 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/plugin.ts @@ -19,7 +19,7 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../../core/public'; import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; -import { VisualizationsSetup } from '../../visualizations/public'; +import { VisualizationsSetup } from '../../../../plugins/visualizations/public'; import { createMetricVisFn } from './metric_vis_fn'; import { createMetricVisTypeDefinition } from './metric_vis_type'; diff --git a/src/legacy/core_plugins/vis_type_metric/public/types.ts b/src/legacy/core_plugins/vis_type_metric/public/types.ts index 298eebf23027d..cae18dd8a2ab1 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/types.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/types.ts @@ -18,7 +18,7 @@ */ import { Range } from '../../../../plugins/expressions/public'; -import { SchemaConfig } from '../../visualizations/public'; +import { SchemaConfig } from '../../../../plugins/visualizations/public'; import { ColorModes, Labels, Style } from '../../vis_type_vislib/public'; import { ColorSchemas } from '../../../../plugins/charts/public'; diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js index 211b79e915038..a23407a599ae2 100644 --- a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js +++ b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js @@ -21,105 +21,17 @@ import $ from 'jquery'; import moment from 'moment'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; -import { - metricOnly, - threeTermBuckets, - oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative, -} from 'fixtures/fake_hierarchical_data'; import sinon from 'sinon'; import { npStart } from '../../legacy_imports'; -import { search } from '../../../../../../plugins/data/public'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; import { round } from 'lodash'; -import { tableVisTypeDefinition } from '../../table_vis_type'; -import { - setup as visualizationsSetup, - start as visualizationsStart, -} from '../../../../visualizations/public/np_ready/public/legacy'; import { getAngularModule } from '../../get_inner_angular'; import { initTableVisLegacyModule } from '../../table_vis_legacy_module'; -import { tableVisResponseHandler } from '../../table_vis_response_handler'; - -const { tabifyAggResponse } = search; +import { tabifiedData } from './tabified_data'; describe('Table Vis - AggTable Directive', function() { let $rootScope; let $compile; - let indexPattern; let settings; - const tabifiedData = {}; - - const init = () => { - const searchSource = { - getField: name => { - if (name === 'index') { - return indexPattern; - } - }, - }; - const vis1 = visualizationsStart.createVis('table', { - type: 'table', - data: { searchSource, aggs: [] }, - }); - tabifiedData.metricOnly = tabifyAggResponse(vis1.data.aggs, metricOnly); - - const vis2 = visualizationsStart.createVis('table', { - type: 'table', - params: { - showMetricsAtAllLevels: true, - }, - data: { - aggs: [ - { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, - { type: 'terms', schema: 'bucket', params: { field: 'extension' } }, - { type: 'terms', schema: 'bucket', params: { field: 'geo.src' } }, - { type: 'terms', schema: 'bucket', params: { field: 'machine.os' } }, - ], - searchSource, - }, - }); - vis2.data.aggs.aggs.forEach(function(agg, i) { - agg.id = 'agg_' + (i + 1); - }); - tabifiedData.threeTermBuckets = tabifyAggResponse(vis2.data.aggs, threeTermBuckets, { - metricsAtAllLevels: true, - }); - - const vis3 = visualizationsStart.createVis('table', { - type: 'table', - data: { - aggs: [ - { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, - { type: 'min', schema: 'metric', params: { field: '@timestamp' } }, - { type: 'terms', schema: 'bucket', params: { field: 'extension' } }, - { - type: 'date_histogram', - schema: 'bucket', - params: { field: '@timestamp', interval: 'd' }, - }, - { - type: 'derivative', - schema: 'metric', - params: { metricAgg: 'custom', customMetric: { id: '5-orderAgg', type: 'count' } }, - }, - { - type: 'top_hits', - schema: 'metric', - params: { field: 'bytes', aggregate: { val: 'min' }, size: 1 }, - }, - ], - searchSource, - }, - }); - vis3.data.aggs.aggs.forEach(function(agg, i) { - agg.id = 'agg_' + (i + 1); - }); - - tabifiedData.oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative = tabifyAggResponse( - vis3.data.aggs, - oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative - ); - }; const initLocalAngular = () => { const tableVisModule = getAngularModule('kibana/table_vis', npStart.core); @@ -128,20 +40,13 @@ describe('Table Vis - AggTable Directive', function() { beforeEach(initLocalAngular); - ngMock.inject(function() { - visualizationsSetup.createBaseVisualization(tableVisTypeDefinition); - }); - beforeEach(ngMock.module('kibana/table_vis')); beforeEach( - ngMock.inject(function($injector, Private, config) { - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); + ngMock.inject(function($injector, config) { settings = config; $rootScope = $injector.get('$rootScope'); $compile = $injector.get('$compile'); - - init(); }) ); @@ -158,7 +63,7 @@ describe('Table Vis - AggTable Directive', function() { metrics: [{ accessor: 0, format: { id: 'number' }, params: {} }], buckets: [], }; - $scope.table = tableVisResponseHandler(tabifiedData.metricOnly, $scope.dimensions).tables[0]; + $scope.table = tabifiedData.metricOnly.tables[0]; const $el = $compile('<kbn-agg-table table="table" dimensions="dimensions"></kbn-agg-table>')( $scope @@ -194,10 +99,7 @@ describe('Table Vis - AggTable Directive', function() { { accessor: 5, params: {} }, ], }; - $scope.table = tableVisResponseHandler( - tabifiedData.threeTermBuckets, - $scope.dimensions - ).tables[0]; + $scope.table = tabifiedData.threeTermBuckets.tables[0]; const $el = $('<kbn-agg-table table="table" dimensions="dimensions"></kbn-agg-table>'); $compile($el)($scope); $scope.$digest(); @@ -261,11 +163,8 @@ describe('Table Vis - AggTable Directive', function() { { accessor: 5, format: { id: 'number' } }, ], }; - const response = tableVisResponseHandler( - tabifiedData.oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative, - $scope.dimensions - ); - $scope.table = response.tables[0]; + $scope.table = + tabifiedData.oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative.tables[0]; $scope.showTotal = true; $scope.totalFunc = totalFunc; const $el = $(`<kbn-agg-table @@ -361,10 +260,7 @@ describe('Table Vis - AggTable Directive', function() { { accessor: 5, params: {} }, ], }; - $scope.table = tableVisResponseHandler( - tabifiedData.threeTermBuckets, - $scope.dimensions - ).tables[0]; + $scope.table = tabifiedData.threeTermBuckets.tables[0]; const $el = $compile('<kbn-agg-table table="table" dimensions="dimensions"></kbn-agg-table>')( $scope @@ -419,10 +315,7 @@ describe('Table Vis - AggTable Directive', function() { { accessor: 5, params: {} }, ], }; - $scope.table = tableVisResponseHandler( - tabifiedData.threeTermBuckets, - $scope.dimensions - ).tables[0]; + $scope.table = tabifiedData.threeTermBuckets.tables[0]; const $el = $compile('<kbn-agg-table table="table" dimensions="dimensions"></kbn-agg-table>')( $scope @@ -481,11 +374,8 @@ describe('Table Vis - AggTable Directive', function() { { accessor: 5, format: { id: 'number' } }, ], }; - const response = tableVisResponseHandler( - tabifiedData.oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative, - $scope.dimensions - ); - $scope.table = response.tables[0]; + $scope.table = + tabifiedData.oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative.tables[0]; $scope.percentageCol = 'Average bytes'; const $el = $(`<kbn-agg-table diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js index 77f817e44ba79..40a0993ccb017 100644 --- a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js +++ b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js @@ -20,54 +20,14 @@ import $ from 'jquery'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; -import { metricOnly, threeTermBuckets } from 'fixtures/fake_hierarchical_data'; import { npStart } from '../../legacy_imports'; -import { search } from '../../../../../../plugins/data/public'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; import { getAngularModule } from '../../get_inner_angular'; import { initTableVisLegacyModule } from '../../table_vis_legacy_module'; -import { tableVisResponseHandler } from '../../table_vis_response_handler'; -import { start as visualizationsStart } from '../../../../visualizations/public/np_ready/public/legacy'; - -const { tabifyAggResponse } = search; +import { tabifiedData } from './tabified_data'; describe('Table Vis - AggTableGroup Directive', function() { let $rootScope; let $compile; - let indexPattern; - const tabifiedData = {}; - - const init = () => { - const searchSource = { - getField: name => { - if (name === 'index') { - return indexPattern; - } - }, - }; - const vis1 = visualizationsStart.createVis('table', { - type: 'table', - data: { searchSource, aggs: [] }, - }); - tabifiedData.metricOnly = tabifyAggResponse(vis1.data.aggs, metricOnly); - - const vis2 = visualizationsStart.createVis('pie', { - type: 'pie', - data: { - aggs: [ - { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, - { type: 'terms', schema: 'split', params: { field: 'extension' } }, - { type: 'terms', schema: 'segment', params: { field: 'geo.src' } }, - { type: 'terms', schema: 'segment', params: { field: 'machine.os' } }, - ], - searchSource, - }, - }); - vis2.data.aggs.aggs.forEach(function(agg, i) { - agg.id = 'agg_' + (i + 1); - }); - tabifiedData.threeTermBuckets = tabifyAggResponse(vis2.data.aggs, threeTermBuckets); - }; const initLocalAngular = () => { const tableVisModule = getAngularModule('kibana/table_vis', npStart.core); @@ -78,23 +38,9 @@ describe('Table Vis - AggTableGroup Directive', function() { beforeEach(ngMock.module('kibana/table_vis')); beforeEach( - ngMock.inject(function($injector, Private) { - // this is provided in table_vis_controller.js - // tech debt that will be resolved through further deangularization and moving tests to jest - /* - legacyDependencies = { - // eslint-disable-next-line new-cap - createAngularVisualization: VisFactoryProvider(Private).createAngularVisualization, - }; - - visualizationsSetup.types.registerVisualization(() => createTableVisTypeDefinition(legacyDependencies)); - */ - - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); + ngMock.inject(function($injector) { $rootScope = $injector.get('$rootScope'); $compile = $injector.get('$compile'); - - init(); }) ); @@ -111,7 +57,7 @@ describe('Table Vis - AggTableGroup Directive', function() { metrics: [{ accessor: 0, format: { id: 'number' }, params: {} }], buckets: [], }; - $scope.group = tableVisResponseHandler(tabifiedData.metricOnly, $scope.dimensions); + $scope.group = tabifiedData.metricOnly; $scope.sort = { columnIndex: null, direction: null, @@ -156,10 +102,7 @@ describe('Table Vis - AggTableGroup Directive', function() { { accessor: 5, params: {} }, ], }; - const group = ($scope.group = tableVisResponseHandler( - tabifiedData.threeTermBuckets, - $scope.dimensions - )); + const group = ($scope.group = tabifiedData.threeTermBucketsWithSplit); const $el = $( '<kbn-agg-table-group dimensions="dimensions" group="group"></kbn-agg-table-group>' ); diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/tabified_data.js b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/tabified_data.js new file mode 100644 index 0000000000000..857b0ea8662cd --- /dev/null +++ b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/tabified_data.js @@ -0,0 +1,795 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const tabifiedData = { + metricOnly: { + tables: [ + { + columns: [ + { + id: 'col-0-1', + name: 'Count', + }, + ], + rows: [ + { + 'col-0-1': 1000, + }, + ], + }, + ], + }, + threeTermBuckets: { + tables: [ + { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'geo.src: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'machine.os: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 0, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + ], + }, + threeTermBucketsWithSplit: { + tables: [ + { + title: 'png: extension: Descending', + name: 'extension: Descending', + key: 'png', + column: 0, + row: 0, + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_3', + name: 'geo.src: Descending', + }, + { + id: 'col-2-agg_4', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-1-agg_3': 'IT', + 'col-2-agg_4': 'win', + 'col-3-agg_1': 0, + }, + { + 'col-0-agg_2': 'png', + 'col-1-agg_3': 'IT', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-1-agg_3': 'US', + 'col-2-agg_4': 'linux', + 'col-3-agg_1': 3992, + }, + { + 'col-0-agg_2': 'png', + 'col-1-agg_3': 'US', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 3029, + }, + { + 'col-0-agg_2': 'css', + 'col-1-agg_3': 'MX', + 'col-2-agg_4': 'win', + 'col-3-agg_1': 4992, + }, + { + 'col-0-agg_2': 'css', + 'col-1-agg_3': 'MX', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 5892, + }, + { + 'col-0-agg_2': 'css', + 'col-1-agg_3': 'US', + 'col-2-agg_4': 'linux', + 'col-3-agg_1': 3992, + }, + { + 'col-0-agg_2': 'css', + 'col-1-agg_3': 'US', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 3029, + }, + { + 'col-0-agg_2': 'html', + 'col-1-agg_3': 'CN', + 'col-2-agg_4': 'win', + 'col-3-agg_1': 4992, + }, + { + 'col-0-agg_2': 'html', + 'col-1-agg_3': 'CN', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 5892, + }, + { + 'col-0-agg_2': 'html', + 'col-1-agg_3': 'FR', + 'col-2-agg_4': 'win', + 'col-3-agg_1': 3992, + }, + { + 'col-0-agg_2': 'html', + 'col-1-agg_3': 'FR', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 3029, + }, + ], + }, + tables: [ + { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_3', + name: 'geo.src: Descending', + }, + { + id: 'col-2-agg_4', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-1-agg_3': 'IT', + 'col-2-agg_4': 'win', + 'col-3-agg_1': 0, + }, + { + 'col-0-agg_2': 'png', + 'col-1-agg_3': 'IT', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-1-agg_3': 'US', + 'col-2-agg_4': 'linux', + 'col-3-agg_1': 3992, + }, + { + 'col-0-agg_2': 'png', + 'col-1-agg_3': 'US', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 3029, + }, + ], + }, + ], + }, + { + title: 'css: extension: Descending', + name: 'extension: Descending', + key: 'css', + column: 0, + row: 4, + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_3', + name: 'geo.src: Descending', + }, + { + id: 'col-2-agg_4', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-1-agg_3': 'IT', + 'col-2-agg_4': 'win', + 'col-3-agg_1': 0, + }, + { + 'col-0-agg_2': 'png', + 'col-1-agg_3': 'IT', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-1-agg_3': 'US', + 'col-2-agg_4': 'linux', + 'col-3-agg_1': 3992, + }, + { + 'col-0-agg_2': 'png', + 'col-1-agg_3': 'US', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 3029, + }, + { + 'col-0-agg_2': 'css', + 'col-1-agg_3': 'MX', + 'col-2-agg_4': 'win', + 'col-3-agg_1': 4992, + }, + { + 'col-0-agg_2': 'css', + 'col-1-agg_3': 'MX', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 5892, + }, + { + 'col-0-agg_2': 'css', + 'col-1-agg_3': 'US', + 'col-2-agg_4': 'linux', + 'col-3-agg_1': 3992, + }, + { + 'col-0-agg_2': 'css', + 'col-1-agg_3': 'US', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 3029, + }, + { + 'col-0-agg_2': 'html', + 'col-1-agg_3': 'CN', + 'col-2-agg_4': 'win', + 'col-3-agg_1': 4992, + }, + { + 'col-0-agg_2': 'html', + 'col-1-agg_3': 'CN', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 5892, + }, + { + 'col-0-agg_2': 'html', + 'col-1-agg_3': 'FR', + 'col-2-agg_4': 'win', + 'col-3-agg_1': 3992, + }, + { + 'col-0-agg_2': 'html', + 'col-1-agg_3': 'FR', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 3029, + }, + ], + }, + tables: [ + { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_3', + name: 'geo.src: Descending', + }, + { + id: 'col-2-agg_4', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'css', + 'col-1-agg_3': 'MX', + 'col-2-agg_4': 'win', + 'col-3-agg_1': 4992, + }, + { + 'col-0-agg_2': 'css', + 'col-1-agg_3': 'MX', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 5892, + }, + { + 'col-0-agg_2': 'css', + 'col-1-agg_3': 'US', + 'col-2-agg_4': 'linux', + 'col-3-agg_1': 3992, + }, + { + 'col-0-agg_2': 'css', + 'col-1-agg_3': 'US', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 3029, + }, + ], + }, + ], + }, + { + title: 'html: extension: Descending', + name: 'extension: Descending', + key: 'html', + column: 0, + row: 8, + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_3', + name: 'geo.src: Descending', + }, + { + id: 'col-2-agg_4', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-1-agg_3': 'IT', + 'col-2-agg_4': 'win', + 'col-3-agg_1': 0, + }, + { + 'col-0-agg_2': 'png', + 'col-1-agg_3': 'IT', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-1-agg_3': 'US', + 'col-2-agg_4': 'linux', + 'col-3-agg_1': 3992, + }, + { + 'col-0-agg_2': 'png', + 'col-1-agg_3': 'US', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 3029, + }, + { + 'col-0-agg_2': 'css', + 'col-1-agg_3': 'MX', + 'col-2-agg_4': 'win', + 'col-3-agg_1': 4992, + }, + { + 'col-0-agg_2': 'css', + 'col-1-agg_3': 'MX', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 5892, + }, + { + 'col-0-agg_2': 'css', + 'col-1-agg_3': 'US', + 'col-2-agg_4': 'linux', + 'col-3-agg_1': 3992, + }, + { + 'col-0-agg_2': 'css', + 'col-1-agg_3': 'US', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 3029, + }, + { + 'col-0-agg_2': 'html', + 'col-1-agg_3': 'CN', + 'col-2-agg_4': 'win', + 'col-3-agg_1': 4992, + }, + { + 'col-0-agg_2': 'html', + 'col-1-agg_3': 'CN', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 5892, + }, + { + 'col-0-agg_2': 'html', + 'col-1-agg_3': 'FR', + 'col-2-agg_4': 'win', + 'col-3-agg_1': 3992, + }, + { + 'col-0-agg_2': 'html', + 'col-1-agg_3': 'FR', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 3029, + }, + ], + }, + tables: [ + { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_3', + name: 'geo.src: Descending', + }, + { + id: 'col-2-agg_4', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'html', + 'col-1-agg_3': 'CN', + 'col-2-agg_4': 'win', + 'col-3-agg_1': 4992, + }, + { + 'col-0-agg_2': 'html', + 'col-1-agg_3': 'CN', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 5892, + }, + { + 'col-0-agg_2': 'html', + 'col-1-agg_3': 'FR', + 'col-2-agg_4': 'win', + 'col-3-agg_1': 3992, + }, + { + 'col-0-agg_2': 'html', + 'col-1-agg_3': 'FR', + 'col-2-agg_4': 'mac', + 'col-3-agg_1': 3029, + }, + ], + }, + ], + }, + ], + direction: 'row', + }, + oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative: { + tables: [ + { + columns: [ + { + id: 'col-0-agg_3', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_4', + name: '@timestamp per day', + }, + { + id: 'col-2-agg_1', + name: 'Average bytes', + }, + { + id: 'col-3-agg_2', + name: 'Min @timestamp', + }, + { + id: 'col-4-agg_5', + name: 'Derivative of Count', + }, + { + id: 'col-5-agg_6', + name: 'Last bytes', + }, + ], + rows: [ + { + 'col-0-agg_3': 'png', + 'col-1-agg_4': 1411862400000, + 'col-2-agg_1': 9283, + 'col-3-agg_2': 1411862400000, + 'col-5-agg_6': 23, + }, + { + 'col-0-agg_3': 'png', + 'col-1-agg_4': 1411948800000, + 'col-2-agg_1': 28349, + 'col-3-agg_2': 1411948800000, + 'col-4-agg_5': 203, + 'col-5-agg_6': 39, + }, + { + 'col-0-agg_3': 'png', + 'col-1-agg_4': 1412035200000, + 'col-2-agg_1': 84330, + 'col-3-agg_2': 1412035200000, + 'col-4-agg_5': 200, + 'col-5-agg_6': 329, + }, + { + 'col-0-agg_3': 'png', + 'col-1-agg_4': 1412121600000, + 'col-2-agg_1': 34992, + 'col-3-agg_2': 1412121600000, + 'col-4-agg_5': 103, + 'col-5-agg_6': 22, + }, + { + 'col-0-agg_3': 'png', + 'col-1-agg_4': 1412208000000, + 'col-2-agg_1': 145432, + 'col-3-agg_2': 1412208000000, + 'col-4-agg_5': 153, + 'col-5-agg_6': 93, + }, + { + 'col-0-agg_3': 'png', + 'col-1-agg_4': 1412294400000, + 'col-2-agg_1': 220943, + 'col-3-agg_2': 1412294400000, + 'col-4-agg_5': 239, + 'col-5-agg_6': 72, + }, + { + 'col-0-agg_3': 'css', + 'col-1-agg_4': 1411862400000, + 'col-2-agg_1': 9283, + 'col-3-agg_2': 1411862400000, + 'col-5-agg_6': 75, + }, + { + 'col-0-agg_3': 'css', + 'col-1-agg_4': 1411948800000, + 'col-2-agg_1': 28349, + 'col-3-agg_2': 1411948800000, + 'col-4-agg_5': 10, + 'col-5-agg_6': 11, + }, + { + 'col-0-agg_3': 'css', + 'col-1-agg_4': 1412035200000, + 'col-2-agg_1': 84330, + 'col-3-agg_2': 1412035200000, + 'col-4-agg_5': 24, + 'col-5-agg_6': 238, + }, + { + 'col-0-agg_3': 'css', + 'col-1-agg_4': 1412121600000, + 'col-2-agg_1': 34992, + 'col-3-agg_2': 1412121600000, + 'col-4-agg_5': 49, + 'col-5-agg_6': 343, + }, + { + 'col-0-agg_3': 'css', + 'col-1-agg_4': 1412208000000, + 'col-2-agg_1': 145432, + 'col-3-agg_2': 1412208000000, + 'col-4-agg_5': 100, + 'col-5-agg_6': 837, + }, + { + 'col-0-agg_3': 'css', + 'col-1-agg_4': 1412294400000, + 'col-2-agg_1': 220943, + 'col-3-agg_2': 1412294400000, + 'col-4-agg_5': 23, + 'col-5-agg_6': 302, + }, + { + 'col-0-agg_3': 'html', + 'col-1-agg_4': 1411862400000, + 'col-2-agg_1': 9283, + 'col-3-agg_2': 1411862400000, + 'col-5-agg_6': 30, + }, + { + 'col-0-agg_3': 'html', + 'col-1-agg_4': 1411948800000, + 'col-2-agg_1': 28349, + 'col-3-agg_2': 1411948800000, + 'col-4-agg_5': 1, + 'col-5-agg_6': 43, + }, + { + 'col-0-agg_3': 'html', + 'col-1-agg_4': 1412035200000, + 'col-2-agg_1': 84330, + 'col-3-agg_2': 1412035200000, + 'col-4-agg_5': 5, + 'col-5-agg_6': 88, + }, + { + 'col-0-agg_3': 'html', + 'col-1-agg_4': 1412121600000, + 'col-2-agg_1': 34992, + 'col-3-agg_2': 1412121600000, + 'col-4-agg_5': 10, + 'col-5-agg_6': 91, + }, + { + 'col-0-agg_3': 'html', + 'col-1-agg_4': 1412208000000, + 'col-2-agg_1': 145432, + 'col-3-agg_2': 1412208000000, + 'col-4-agg_5': 43, + 'col-5-agg_6': 534, + }, + { + 'col-0-agg_3': 'html', + 'col-1-agg_4': 1412294400000, + 'col-2-agg_1': 220943, + 'col-3-agg_2': 1412294400000, + 'col-4-agg_5': 1, + 'col-5-agg_6': 553, + }, + ], + }, + ], + }, +}; diff --git a/src/legacy/core_plugins/vis_type_table/public/legacy.ts b/src/legacy/core_plugins/vis_type_table/public/legacy.ts index 30403139d212d..3d5f8c1b3efe9 100644 --- a/src/legacy/core_plugins/vis_type_table/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_table/public/legacy.ts @@ -22,11 +22,10 @@ import { npSetup, npStart } from './legacy_imports'; import { plugin } from '.'; import { TablePluginSetupDependencies } from './plugin'; -import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; const plugins: Readonly<TablePluginSetupDependencies> = { expressions: npSetup.plugins.expressions, - visualizations: visualizationsSetup, + visualizations: npSetup.plugins.visualizations, }; const pluginInstance = plugin({} as PluginInitializerContext); diff --git a/src/legacy/core_plugins/vis_type_table/public/plugin.ts b/src/legacy/core_plugins/vis_type_table/public/plugin.ts index 42bd36c83e28c..ea12a5320a14d 100644 --- a/src/legacy/core_plugins/vis_type_table/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_table/public/plugin.ts @@ -17,7 +17,7 @@ * under the License. */ import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; -import { VisualizationsSetup } from '../../visualizations/public'; +import { VisualizationsSetup } from '../../../../plugins/visualizations/public'; import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../../core/public'; diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts index ad56607e9296c..8d6f88bf8dd4a 100644 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts @@ -27,7 +27,7 @@ import StubIndexPattern from 'test_utils/stub_index_pattern'; import { getAngularModule } from './get_inner_angular'; import { initTableVisLegacyModule } from './table_vis_legacy_module'; import { tableVisTypeDefinition } from './table_vis_type'; -import { Vis } from '../../visualizations/public'; +import { Vis } from '../../../../plugins/visualizations/public'; // eslint-disable-next-line import { stubFields } from '../../../../plugins/data/public/stubs'; // eslint-disable-next-line diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_type.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_type.ts index e70b09904253f..d26e860e51272 100644 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_type.ts +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_type.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { AggGroupNames } from '../../../../plugins/data/public'; import { Schemas } from '../../vis_default_editor/public'; -import { Vis } from '../../visualizations/public'; +import { Vis } from '../../../../plugins/visualizations/public'; import { tableVisResponseHandler } from './table_vis_response_handler'; // @ts-ignore import tableVisTemplate from './table_vis.html'; diff --git a/src/legacy/core_plugins/vis_type_table/public/types.ts b/src/legacy/core_plugins/vis_type_table/public/types.ts index 39023d1305cb6..c6de14b9f050c 100644 --- a/src/legacy/core_plugins/vis_type_table/public/types.ts +++ b/src/legacy/core_plugins/vis_type_table/public/types.ts @@ -17,7 +17,7 @@ * under the License. */ -import { SchemaConfig } from '../../visualizations/public'; +import { SchemaConfig } from '../../../../plugins/visualizations/public'; export enum AggTypes { SUM = 'sum', diff --git a/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts b/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts index 2feaad9f4e6b6..5bb730d2f9b10 100644 --- a/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts +++ b/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts @@ -20,11 +20,10 @@ import angular, { IModule, auto, IRootScopeService, IScope, ICompileService } from 'angular'; import $ from 'jquery'; -import { VisParams } from '../../visualizations/public'; +import { VisParams, ExprVis } from '../../../../plugins/visualizations/public'; import { npStart } from './legacy_imports'; import { getAngularModule } from './get_inner_angular'; import { initTableVisLegacyModule } from './table_vis_legacy_module'; -import { ExprVis } from '../../visualizations/public/np_ready/public/expressions/vis'; const innerAngularName = 'kibana/table_vis'; diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/components/__tests__/tag_cloud_visualization.js b/src/legacy/core_plugins/vis_type_tagcloud/public/components/__tests__/tag_cloud_visualization.js index 6f54744a2f508..9e611861417cd 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/components/__tests__/tag_cloud_visualization.js +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/components/__tests__/tag_cloud_visualization.js @@ -19,20 +19,23 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import { start as visualizationsStart } from '../../../../../core_plugins/visualizations/public/np_ready/public/legacy'; import { ImageComparator } from 'test_utils/image_comparator'; import { createTagCloudVisualization } from '../tag_cloud_visualization'; import basicdrawPng from './basicdraw.png'; import afterresizePng from './afterresize.png'; import afterparamChange from './afterparamchange.png'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ExprVis } from '../../../../../../plugins/visualizations/public/expressions/vis'; // Replace with mock when converting to jest tests // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { seedColors } from '../../../../../../plugins/charts/public/services/colors/seed_colors'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { BaseVisType } from '../../../../../../plugins/visualizations/public/vis_types/base_vis_type'; +import { createTagCloudVisTypeDefinition } from '../../tag_cloud_type'; const THRESHOLD = 0.65; const PIXEL_DIFF = 64; - describe('TagCloudVisualizationTest', function() { let domNode; let vis; @@ -67,10 +70,11 @@ describe('TagCloudVisualizationTest', function() { describe('TagCloudVisualization - basics', function() { beforeEach(async function() { + const visType = new BaseVisType(createTagCloudVisTypeDefinition({ colors: seedColors })); setupDOM('512px', '512px'); imageComparator = new ImageComparator(); - vis = visualizationsStart.createVis('tagcloud', { - type: 'tagcloud', + vis = new ExprVis({ + type: visType, params: { bucket: { accessor: 0, format: {} }, metric: { accessor: 0, format: {} }, diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/legacy.ts b/src/legacy/core_plugins/vis_type_tagcloud/public/legacy.ts index 96073caf20515..f70789edc66ba 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/legacy.ts @@ -19,14 +19,12 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; - -import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; import { TagCloudPluginSetupDependencies } from './plugin'; import { plugin } from '.'; const plugins: Readonly<TagCloudPluginSetupDependencies> = { expressions: npSetup.plugins.expressions, - visualizations: visualizationsSetup, + visualizations: npSetup.plugins.visualizations, charts: npSetup.plugins.charts, }; diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/plugin.ts b/src/legacy/core_plugins/vis_type_tagcloud/public/plugin.ts index c9dd4943519be..1061271aa315b 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/plugin.ts @@ -19,7 +19,7 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../../core/public'; import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; -import { VisualizationsSetup } from '../../visualizations/public'; +import { VisualizationsSetup } from '../../../../plugins/visualizations/public'; import { ChartsPluginSetup } from '../../../../plugins/charts/public'; import { createTagCloudFn } from './tag_cloud_fn'; diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/services.ts b/src/legacy/core_plugins/vis_type_tagcloud/public/services.ts index 30fd185be877d..fef46282eb8dd 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/services.ts +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/services.ts @@ -23,3 +23,5 @@ import { DataPublicPluginStart } from '../../../../plugins/data/public'; export const [getFormatService, setFormatService] = createGetterSetter< DataPublicPluginStart['fieldFormats'] >('data.fieldFormats'); + +export { npStart } from 'ui/new_platform'; diff --git a/src/legacy/core_plugins/vis_type_timelion/index.ts b/src/legacy/core_plugins/vis_type_timelion/index.ts index 6c1e3f452959e..7bca5154c84fd 100644 --- a/src/legacy/core_plugins/vis_type_timelion/index.ts +++ b/src/legacy/core_plugins/vis_type_timelion/index.ts @@ -25,7 +25,7 @@ import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy const timelionVisPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => new Plugin({ id: 'timelion_vis', - require: ['kibana', 'elasticsearch', 'visualizations'], + require: ['kibana', 'elasticsearch'], publicDir: resolve(__dirname, 'public'), uiExports: { styleSheetPaths: resolve(__dirname, 'public/index.scss'), diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx index f55d1602ea342..0fad0a164bf0b 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx @@ -23,7 +23,7 @@ import { IUiSettingsClient } from 'kibana/public'; import { ChartComponent } from './chart'; import { VisParams } from '../timelion_vis_fn'; import { TimelionSuccessResponse } from '../helpers/timelion_request_handler'; -import { ExprVis } from '../../../visualizations/public/np_ready/public/expressions/vis'; +import { ExprVis } from '../../../../../plugins/visualizations/public'; export interface TimelionVisComponentProp { config: IUiSettingsClient; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts index 47bfed6340e93..61e31420f73ba 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; import { KIBANA_CONTEXT_NAME } from 'src/plugins/expressions/public'; -import { VisParams } from 'src/legacy/core_plugins/visualizations/public'; +import { VisParams } from '../../../../../plugins/visualizations/public'; import { TimeRange, Filter, esQuery, Query } from '../../../../../plugins/data/public'; import { TimelionVisDependencies } from '../plugin'; import { getTimezone } from './get_timezone'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts b/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts index 9935f3d92f6bd..f8de9f94dcedf 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts @@ -20,15 +20,13 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from './legacy_imports'; - -import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; import { TimelionVisSetupDependencies } from './plugin'; import { plugin } from '.'; const setupPlugins: Readonly<TimelionVisSetupDependencies> = { expressions: npSetup.plugins.expressions, data: npSetup.plugins.data, - visualizations: visualizationsSetup, + visualizations: npSetup.plugins.visualizations, }; const pluginInstance = plugin({} as PluginInitializerContext); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts index 8d067369fef70..b5aa64db19aa4 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts @@ -29,7 +29,7 @@ import { Plugin as ExpressionsPlugin } from 'src/plugins/expressions/public'; import { DataPublicPluginSetup, TimefilterContract } from 'src/plugins/data/public'; import { PluginsStart } from './legacy_imports'; -import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; +import { VisualizationsSetup } from '../../../../plugins/visualizations/public'; import { getTimelionVisualizationConfig } from './timelion_vis_fn'; import { getTimelionVisDefinition } from './timelion_vis_type'; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/legacy.ts b/src/legacy/core_plugins/vis_type_timeseries/public/legacy.ts index fb22bbd4146e2..42f116701be51 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/public/legacy.ts @@ -19,14 +19,12 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; - -import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; import { MetricsPluginSetupDependencies } from './plugin'; import { plugin } from '.'; const plugins: Readonly<MetricsPluginSetupDependencies> = { expressions: npSetup.plugins.expressions, - visualizations: visualizationsSetup, + visualizations: npSetup.plugins.visualizations, }; const pluginInstance = plugin({} as PluginInitializerContext); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts b/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts index 441b1f05ea78c..0310ecf6cfd87 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts @@ -18,7 +18,7 @@ */ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'kibana/public'; import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; -import { VisualizationsSetup } from '../../visualizations/public'; +import { VisualizationsSetup } from '../../../../plugins/visualizations/public'; import { createMetricsFn } from './metrics_fn'; import { metricsVisDefinition } from './metrics_type'; diff --git a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js index 0db3e6cefa724..c7fbc0815b07c 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js +++ b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js @@ -39,15 +39,15 @@ import vegaMapImage256 from './vega_map_image_256.png'; import { VegaParser } from '../data_model/vega_parser'; import { SearchCache } from '../data_model/search_cache'; -import { - setup as visualizationsSetup, - start as visualizationsStart, -} from '../../../visualizations/public/np_ready/public/legacy'; import { createVegaTypeDefinition } from '../vega_type'; // TODO This is an integration test and thus requires a running platform. When moving to the new platform, // this test has to be migrated to the newly created integration test environment. // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { npStart } from 'ui/new_platform'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { BaseVisType } from '../../../../../plugins/visualizations/public/vis_types/base_vis_type'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ExprVis } from '../../../../../plugins/visualizations/public/expressions/vis'; import { setInjectedVars } from '../services'; const THRESHOLD = 0.1; @@ -59,7 +59,7 @@ describe('VegaVisualizations', () => { let vis; let imageComparator; let vegaVisualizationDependencies; - let visRegComplete = false; + let vegaVisType; setInjectedVars({ emsTileLayerId: {}, @@ -89,13 +89,7 @@ describe('VegaVisualizations', () => { }, }; - if (!visRegComplete) { - visRegComplete = true; - visualizationsSetup.createBaseVisualization( - createVegaTypeDefinition(vegaVisualizationDependencies) - ); - } - + vegaVisType = new BaseVisType(createVegaTypeDefinition(vegaVisualizationDependencies)); VegaVisualization = createVegaVisualization(vegaVisualizationDependencies); }) ); @@ -105,7 +99,9 @@ describe('VegaVisualizations', () => { setupDOM('512px', '512px'); imageComparator = new ImageComparator(); - vis = visualizationsStart.createVis('vega', { type: 'vega' }); + vis = new ExprVis({ + type: vegaVisType, + }); }); afterEach(function() { diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/vega_parser.js b/src/legacy/core_plugins/vis_type_vega/public/data_model/vega_parser.js index 7c2638d1f5165..735ce60f76d47 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/data_model/vega_parser.js +++ b/src/legacy/core_plugins/vis_type_vega/public/data_model/vega_parser.js @@ -116,7 +116,8 @@ export class VegaParser { */ _compileVegaLite() { this.vlspec = this.spec; - const logger = vega.logger(vega.Warn); + // eslint-disable-next-line import/namespace + const logger = vega.logger(vega.Warn); // note: eslint has a false positive here logger.warn = this._onWarning.bind(this); this.spec = vegaLite.compile(this.vlspec, logger).spec; diff --git a/src/legacy/core_plugins/vis_type_vega/public/legacy.ts b/src/legacy/core_plugins/vis_type_vega/public/legacy.ts index 38ce706ed13ef..b2c73894d978d 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/legacy.ts @@ -19,15 +19,13 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; - -import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; import { VegaPluginSetupDependencies, VegaPluginStartDependencies } from './plugin'; import { LegacyDependenciesPlugin } from './shim'; import { plugin } from '.'; const setupPlugins: Readonly<VegaPluginSetupDependencies> = { ...npSetup.plugins, - visualizations: visualizationsSetup, + visualizations: npSetup.plugins.visualizations, // Temporary solution // It will be removed when all dependent services are migrated to the new platform. diff --git a/src/legacy/core_plugins/vis_type_vega/public/plugin.ts b/src/legacy/core_plugins/vis_type_vega/public/plugin.ts index 3b01d9ceca5a6..38b92a40cd99a 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/plugin.ts @@ -20,7 +20,7 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../.. import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; import { Plugin as DataPublicPlugin } from '../../../../plugins/data/public'; -import { VisualizationsSetup } from '../../visualizations/public'; +import { VisualizationsSetup } from '../../../../plugins/visualizations/public'; import { setNotifications, setData, diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_base_view.js b/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_base_view.js index 76a2e672e0bd0..c90f059ff7c94 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_base_view.js +++ b/src/legacy/core_plugins/vis_type_vega/public/vega_view/vega_base_view.js @@ -132,7 +132,8 @@ export class VegaBaseView { createViewConfig() { const config = { - logLevel: vega.Warn, + // eslint-disable-next-line import/namespace + logLevel: vega.Warn, // note: eslint has a false positive here renderer: this._parser.renderer, }; diff --git a/src/legacy/core_plugins/vis_type_vislib/index.ts b/src/legacy/core_plugins/vis_type_vislib/index.ts index 1f75aea31ba0b..da9476285a9b2 100644 --- a/src/legacy/core_plugins/vis_type_vislib/index.ts +++ b/src/legacy/core_plugins/vis_type_vislib/index.ts @@ -25,7 +25,7 @@ import { LegacyPluginApi, LegacyPluginInitializer } from '../../types'; const visTypeVislibPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => new Plugin({ id: 'vis_type_vislib', - require: ['kibana', 'elasticsearch', 'visualizations', 'interpreter'], + require: ['kibana', 'elasticsearch', 'interpreter'], publicDir: resolve(__dirname, 'public'), styleSheetPaths: resolve(__dirname, 'public/index.scss'), uiExports: { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/chart_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/chart_options.tsx index bc12e04e29468..ec7a325ba43d1 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/chart_options.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/chart_options.tsx @@ -22,7 +22,7 @@ import React, { useMemo, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { Vis } from 'src/legacy/core_plugins/visualizations/public'; +import { Vis } from '../../../../../../../plugins/visualizations/public'; import { SeriesParam, ValueAxis } from '../../../types'; import { ChartTypes } from '../../../utils/collections'; import { SelectOption } from '../../common'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.tsx index a53d21b121f7d..01a69a6fac70b 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.tsx @@ -22,7 +22,7 @@ import React, { useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { Vis } from 'src/legacy/core_plugins/visualizations/public'; +import { Vis } from '../../../../../../../plugins/visualizations/public'; import { SeriesParam } from '../../../types'; import { NumberInputOption, SelectOption, SwitchOption } from '../../common'; import { SetChart } from './chart_options'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/mocks.ts b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/mocks.ts index 58c75629f1fa1..0d9fa8c25a4f7 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/mocks.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/mocks.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Vis } from 'src/legacy/core_plugins/visualizations/public'; +import { Vis } from '../../../../../../../plugins/visualizations/public'; import { Axis, ValueAxis, SeriesParam, Style } from '../../../types'; import { ChartTypes, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/series_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/series_panel.tsx index 44e7a4cfb0088..22a726b53363b 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/series_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/series_panel.tsx @@ -23,7 +23,7 @@ import { EuiPanel, EuiTitle, EuiSpacer, EuiAccordion } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Vis } from 'src/legacy/core_plugins/visualizations/public'; +import { Vis } from '../../../../../../../plugins/visualizations/public'; import { ValueAxis, SeriesParam } from '../../../types'; import { ChartOptions } from './chart_options'; import { SetParamByIndex, ChangeValueAxis } from './'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.tsx index 30d80ed595fe7..912c3b904b110 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.tsx @@ -31,7 +31,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Vis } from 'src/legacy/core_plugins/visualizations/public'; +import { Vis } from '../../../../../../../plugins/visualizations/public'; import { SeriesParam, ValueAxis } from '../../../types'; import { ValueAxisOptions } from './value_axis_options'; import { SetParamByIndex } from './'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx index 0e78bf2f31ef6..8f0327e78c7ab 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx @@ -21,7 +21,7 @@ import React, { useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiSpacer, EuiAccordion, EuiHorizontalRule } from '@elastic/eui'; -import { Vis } from 'src/legacy/core_plugins/visualizations/public'; +import { Vis } from '../../../../../../../plugins/visualizations/public'; import { ValueAxis } from '../../../types'; import { Positions } from '../../../utils/collections'; import { SelectOption, SwitchOption, TextInputOption } from '../../common'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts b/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts index ef7277222e5fd..aa11e0ef41fba 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts @@ -25,11 +25,10 @@ import { VisTypeVislibPluginSetupDependencies, VisTypeVislibPluginStartDependencies, } from './plugin'; -import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; const setupPlugins: Readonly<VisTypeVislibPluginSetupDependencies> = { expressions: npSetup.plugins.expressions, - visualizations: visualizationsSetup, + visualizations: npSetup.plugins.visualizations, charts: npSetup.plugins.charts, }; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts index 9f7b8cbeea11e..da16a38deba9f 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts @@ -19,6 +19,7 @@ import { search } from '../../../../plugins/data/public'; export const { tabifyAggResponse, tabifyGetColumns } = search; + // @ts-ignore export { buildHierarchicalData } from 'ui/agg_response/hierarchical/build_hierarchical_data'; // @ts-ignore diff --git a/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts b/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts index 8b4510050802e..2731fb6f5fbe6 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts @@ -25,7 +25,7 @@ import { } from 'kibana/public'; import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; -import { VisualizationsSetup } from '../../visualizations/public'; +import { VisualizationsSetup } from '../../../../plugins/visualizations/public'; import { createVisTypeVislibVisFn } from './vis_type_vislib_vis_fn'; import { createPieVisFn } from './pie_fn'; import { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vis_controller.tsx b/src/legacy/core_plugins/vis_type_vislib/public/vis_controller.tsx index 010b61a0900b0..ec091e5d29cfd 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vis_controller.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/vis_controller.tsx @@ -26,8 +26,7 @@ import { Positions } from './utils/collections'; import { VisTypeVislibDependencies } from './plugin'; import { mountReactNode } from '../../../../core/public/utils'; import { VisLegend, CUSTOM_LEGEND_VIS_TYPES } from './vislib/components/legend'; -import { VisParams } from '../../visualizations/public'; -import { ExprVis } from '../../visualizations/public/np_ready/public/expressions/vis'; +import { VisParams, ExprVis } from '../../../../plugins/visualizations/public'; const legendClassName = { top: 'visLib--legend-top', diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js index 21f4e60e4bc6e..caafb2c636271 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js @@ -22,91 +22,8 @@ import _ from 'lodash'; import $ from 'jquery'; import expect from '@kbn/expect'; -import { threeTermBuckets } from 'fixtures/fake_hierarchical_data'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; - -import { start as visualizationsStart } from '../../../../../visualizations/public/np_ready/public/legacy'; import { getVis, getMockUiState } from '../lib/fixtures/_vis_fixture'; -import { tabifyAggResponse } from '../../../legacy_imports'; -import { vislibSlicesResponseHandler } from '../../response_handler'; - -const rowAgg = [ - { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, - { type: 'terms', schema: 'split', params: { field: 'extension', rows: true } }, - { type: 'terms', schema: 'segment', params: { field: 'machine.os' } }, - { type: 'terms', schema: 'segment', params: { field: 'geo.src' } }, -]; - -const rowAggDimensions = { - splitRow: [ - { - accessor: 0, - }, - ], - buckets: [ - { - accessor: 2, - }, - { - accessor: 4, - }, - ], - metric: { - accessor: 5, - }, -}; - -const colAgg = [ - { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, - { type: 'terms', schema: 'split', params: { field: 'extension', row: false } }, - { type: 'terms', schema: 'segment', params: { field: 'machine.os' } }, - { type: 'terms', schema: 'segment', params: { field: 'geo.src' } }, -]; - -const colAggDimensions = { - splitColumn: [ - { - accessor: 0, - }, - ], - buckets: [ - { - accessor: 2, - }, - { - accessor: 4, - }, - ], - metric: { - accessor: 5, - }, -}; - -const sliceAgg = [ - { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, - { type: 'terms', schema: 'segment', params: { field: 'machine.os' } }, - { type: 'terms', schema: 'segment', params: { field: 'geo.src' } }, -]; - -const sliceAggDimensions = { - buckets: [ - { - accessor: 0, - }, - { - accessor: 2, - }, - ], - metric: { - accessor: 3, - }, -}; - -const aggArray = [ - [rowAgg, rowAggDimensions], - [colAgg, colAggDimensions], - [sliceAgg, sliceAggDimensions], -]; +import { pieChartMockData } from './pie_chart_mock_data'; const names = ['rows', 'columns', 'slices']; @@ -121,47 +38,14 @@ describe('No global chart settings', function() { }; let chart1; let mockUiState; - let indexPattern; - let responseHandler; - let data1; - let stubVis1; beforeEach(() => { chart1 = getVis(visLibParams1); mockUiState = getMockUiState(); - indexPattern = new FixturesStubbedLogstashIndexPatternProvider(); - responseHandler = vislibSlicesResponseHandler; - - let id1 = 1; - stubVis1 = visualizationsStart.createVis('pie', { - type: 'pie', - data: { - aggs: rowAgg, - searchSource: { - getField: name => { - if (name === 'index') { - return indexPattern; - } - }, - }, - }, - }); - - stubVis1.isHierarchical = () => true; - - // We need to set the aggs to a known value. - _.each(stubVis1.data.aggs.aggs, function(agg) { - agg.id = 'agg_' + id1++; - }); }); beforeEach(async () => { - const table1 = tabifyAggResponse(stubVis1.data.aggs, threeTermBuckets, { - metricsAtAllLevels: true, - }); - data1 = await responseHandler(table1, rowAggDimensions); - - chart1.render(data1, mockUiState); + chart1.render(pieChartMockData.rowData, mockUiState); }); afterEach(function() { @@ -209,55 +93,21 @@ describe('No global chart settings', function() { }); describe('Vislib PieChart Class Test Suite', function() { - aggArray.forEach(function(aggItem, i) { - const [dataAgg, dataDimensions] = aggItem; + ['rowData', 'columnData', 'sliceData'].forEach(function(aggItem, i) { describe('Vislib PieChart Class Test Suite for ' + names[i] + ' data', function() { + const mockPieData = pieChartMockData[aggItem]; + const visLibParams = { type: 'pie', addLegend: true, addTooltip: true, }; let vis; - let mockUiState; - let indexPattern; - let data; - let stubVis; - let responseHandler; - - beforeEach(() => { - vis = getVis(visLibParams); - mockUiState = getMockUiState(); - indexPattern = new FixturesStubbedLogstashIndexPatternProvider(); - responseHandler = vislibSlicesResponseHandler; - - let id = 1; - stubVis = visualizationsStart.createVis('pie', { - type: 'pie', - data: { - aggs: dataAgg, - searchSource: { - getField: name => { - if (name === 'index') { - return indexPattern; - } - }, - }, - }, - }); - - // We need to set the aggs to a known value. - _.each(stubVis.data.aggs.aggs, function(agg) { - agg.id = 'agg_' + id++; - }); - }); beforeEach(async () => { - const table = tabifyAggResponse(stubVis.data.aggs, threeTermBuckets, { - metricsAtAllLevels: true, - }); - data = await responseHandler(table, dataDimensions); - - vis.render(data, mockUiState); + vis = getVis(visLibParams); + const mockUiState = getMockUiState(); + vis.render(mockPieData, mockUiState); }); afterEach(function() { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart_mock_data.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart_mock_data.js new file mode 100644 index 0000000000000..188cd51759e51 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart_mock_data.js @@ -0,0 +1,3742 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const pieChartMockData = { + rowData: { + rows: [ + { + hits: 4, + raw: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 0, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + names: ['IT', 'win', 'mac', 'US', 'linux'], + slices: { + children: [ + { + name: 'IT', + size: 9299, + children: [ + { + name: 'win', + size: 0, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 0, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 0, + column: 4, + value: 'win', + }, + }, + { + name: 'mac', + size: 9299, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 0, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 1, + column: 4, + value: 'mac', + }, + }, + ], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 0, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 0, + column: 2, + value: 'IT', + }, + }, + { + name: 'US', + size: 8293, + children: [ + { + name: 'linux', + size: 3992, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 0, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 2, + column: 4, + value: 'linux', + }, + }, + { + name: 'mac', + size: 3029, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 0, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 3, + column: 4, + value: 'mac', + }, + }, + ], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 0, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 2, + column: 2, + value: 'US', + }, + }, + ], + }, + label: 'png: extension: Descending', + }, + { + hits: 4, + raw: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + names: ['MX', 'win', 'mac', 'US', 'linux'], + slices: { + children: [ + { + name: 'MX', + size: 9299, + children: [ + { + name: 'win', + size: 4992, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 0, + column: 4, + value: 'win', + }, + }, + { + name: 'mac', + size: 5892, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 1, + column: 4, + value: 'mac', + }, + }, + ], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 0, + column: 2, + value: 'MX', + }, + }, + { + name: 'US', + size: 8293, + children: [ + { + name: 'linux', + size: 3992, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 2, + column: 4, + value: 'linux', + }, + }, + { + name: 'mac', + size: 3029, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 3, + column: 4, + value: 'mac', + }, + }, + ], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 2, + column: 2, + value: 'US', + }, + }, + ], + }, + label: 'css: extension: Descending', + }, + { + hits: 4, + raw: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + names: ['CN', 'win', 'mac', 'FR'], + slices: { + children: [ + { + name: 'CN', + size: 9299, + children: [ + { + name: 'win', + size: 4992, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 0, + column: 4, + value: 'win', + }, + }, + { + name: 'mac', + size: 5892, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 1, + column: 4, + value: 'mac', + }, + }, + ], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 0, + column: 2, + value: 'CN', + }, + }, + { + name: 'FR', + size: 8293, + children: [ + { + name: 'win', + size: 3992, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 2, + column: 4, + value: 'win', + }, + }, + { + name: 'mac', + size: 3029, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 3, + column: 4, + value: 'mac', + }, + }, + ], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 2, + column: 2, + value: 'FR', + }, + }, + ], + }, + label: 'html: extension: Descending', + }, + ], + hits: 12, + }, + columnData: { + columns: [ + { + hits: 4, + raw: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 0, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + names: ['IT', 'win', 'mac', 'US', 'linux'], + slices: { + children: [ + { + name: 'IT', + size: 9299, + children: [ + { + name: 'win', + size: 0, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 0, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 0, + column: 4, + value: 'win', + }, + }, + { + name: 'mac', + size: 9299, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 0, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 1, + column: 4, + value: 'mac', + }, + }, + ], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 0, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 0, + column: 2, + value: 'IT', + }, + }, + { + name: 'US', + size: 8293, + children: [ + { + name: 'linux', + size: 3992, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 0, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 2, + column: 4, + value: 'linux', + }, + }, + { + name: 'mac', + size: 3029, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 0, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 3, + column: 4, + value: 'mac', + }, + }, + ], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 0, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 2, + column: 2, + value: 'US', + }, + }, + ], + }, + label: 'png: extension: Descending', + }, + { + hits: 4, + raw: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + names: ['MX', 'win', 'mac', 'US', 'linux'], + slices: { + children: [ + { + name: 'MX', + size: 9299, + children: [ + { + name: 'win', + size: 4992, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 0, + column: 4, + value: 'win', + }, + }, + { + name: 'mac', + size: 5892, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 1, + column: 4, + value: 'mac', + }, + }, + ], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 0, + column: 2, + value: 'MX', + }, + }, + { + name: 'US', + size: 8293, + children: [ + { + name: 'linux', + size: 3992, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 2, + column: 4, + value: 'linux', + }, + }, + { + name: 'mac', + size: 3029, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 3, + column: 4, + value: 'mac', + }, + }, + ], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'linux', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 2, + column: 2, + value: 'US', + }, + }, + ], + }, + label: 'css: extension: Descending', + }, + { + hits: 4, + raw: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + names: ['CN', 'win', 'mac', 'FR'], + slices: { + children: [ + { + name: 'CN', + size: 9299, + children: [ + { + name: 'win', + size: 4992, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 0, + column: 4, + value: 'win', + }, + }, + { + name: 'mac', + size: 5892, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 1, + column: 4, + value: 'mac', + }, + }, + ], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 0, + column: 2, + value: 'CN', + }, + }, + { + name: 'FR', + size: 8293, + children: [ + { + name: 'win', + size: 3992, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 2, + column: 4, + value: 'win', + }, + }, + { + name: 'mac', + size: 3029, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 3, + column: 4, + value: 'mac', + }, + }, + ], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'extension: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'machine.os: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + { + id: 'col-4-agg_4', + name: 'geo.src: Descending', + }, + { + id: 'col-5-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 4992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + 'col-5-agg_1': 5892, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'win', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3992, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-4-agg_4': 'mac', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + 'col-5-agg_1': 3029, + }, + ], + }, + row: 2, + column: 2, + value: 'FR', + }, + }, + ], + }, + label: 'html: extension: Descending', + }, + ], + hits: 12, + }, + sliceData: { + hits: 6, + raw: { + columns: [ + { + id: 'col-0-agg_2', + name: 'machine.os: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'geo.src: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + ], + }, + names: ['png', 'IT', 'US', 'css', 'MX', 'html', 'CN', 'FR'], + slices: { + children: [ + { + name: 'png', + size: 412032, + children: [ + { + name: 'IT', + size: 9299, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'machine.os: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'geo.src: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + ], + }, + row: 0, + column: 2, + value: 'IT', + }, + }, + { + name: 'US', + size: 8293, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'machine.os: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'geo.src: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + ], + }, + row: 1, + column: 2, + value: 'US', + }, + }, + ], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'machine.os: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'geo.src: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + ], + }, + row: 0, + column: 0, + value: 'png', + }, + }, + { + name: 'css', + size: 412032, + children: [ + { + name: 'MX', + size: 9299, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'machine.os: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'geo.src: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + ], + }, + row: 2, + column: 2, + value: 'MX', + }, + }, + { + name: 'US', + size: 8293, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'machine.os: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'geo.src: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + ], + }, + row: 3, + column: 2, + value: 'US', + }, + }, + ], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'machine.os: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'geo.src: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + ], + }, + row: 2, + column: 0, + value: 'css', + }, + }, + { + name: 'html', + size: 412032, + children: [ + { + name: 'CN', + size: 9299, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'machine.os: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'geo.src: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + ], + }, + row: 4, + column: 2, + value: 'CN', + }, + }, + { + name: 'FR', + size: 8293, + children: [], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'machine.os: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'geo.src: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + ], + }, + row: 5, + column: 2, + value: 'FR', + }, + }, + ], + rawData: { + table: { + columns: [ + { + id: 'col-0-agg_2', + name: 'machine.os: Descending', + }, + { + id: 'col-1-agg_1', + name: 'Average bytes', + }, + { + id: 'col-2-agg_3', + name: 'geo.src: Descending', + }, + { + id: 'col-3-agg_1', + name: 'Average bytes', + }, + ], + rows: [ + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'IT', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'png', + 'col-2-agg_3': 'US', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'MX', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'css', + 'col-2-agg_3': 'US', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'CN', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 9299, + }, + { + 'col-0-agg_2': 'html', + 'col-2-agg_3': 'FR', + 'col-1-agg_1': 412032, + 'col-3-agg_1': 8293, + }, + ], + }, + row: 4, + column: 0, + value: 'html', + }, + }, + ], + }, + }, +}; diff --git a/src/legacy/core_plugins/vis_type_xy/public/legacy.ts b/src/legacy/core_plugins/vis_type_xy/public/legacy.ts index e1cee9c30804a..740ceeaac6a7d 100644 --- a/src/legacy/core_plugins/vis_type_xy/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_xy/public/legacy.ts @@ -22,20 +22,16 @@ import { PluginInitializerContext } from 'kibana/public'; import { plugin } from '.'; import { VisTypeXyPluginSetupDependencies, VisTypeXyPluginStartDependencies } from './plugin'; -import { - setup as visualizationsSetup, - start as visualizationsStart, -} from '../../visualizations/public/np_ready/public/legacy'; const setupPlugins: Readonly<VisTypeXyPluginSetupDependencies> = { expressions: npSetup.plugins.expressions, - visualizations: visualizationsSetup, + visualizations: npSetup.plugins.visualizations, charts: npSetup.plugins.charts, }; const startPlugins: Readonly<VisTypeXyPluginStartDependencies> = { expressions: npStart.plugins.expressions, - visualizations: visualizationsStart, + visualizations: npStart.plugins.visualizations, }; const pluginInstance = plugin({} as PluginInitializerContext); diff --git a/src/legacy/core_plugins/vis_type_xy/public/plugin.ts b/src/legacy/core_plugins/vis_type_xy/public/plugin.ts index 0884cdf6a5e18..ab01b6b3153fb 100644 --- a/src/legacy/core_plugins/vis_type_xy/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_xy/public/plugin.ts @@ -26,7 +26,10 @@ import { } from 'kibana/public'; import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; -import { VisualizationsSetup, VisualizationsStart } from '../../visualizations/public'; +import { + VisualizationsSetup, + VisualizationsStart, +} from '../../../../plugins/visualizations/public'; import { ChartsPluginSetup } from '../../../../plugins/charts/public'; export interface VisTypeXyDependencies { diff --git a/src/legacy/core_plugins/visualizations/package.json b/src/legacy/core_plugins/visualizations/package.json deleted file mode 100644 index 5b436f0c2fef2..0000000000000 --- a/src/legacy/core_plugins/visualizations/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "visualizations", - "version": "kibana" -} diff --git a/src/legacy/core_plugins/visualizations/public/index.scss b/src/legacy/core_plugins/visualizations/public/index.scss deleted file mode 100644 index 238f58fbfa295..0000000000000 --- a/src/legacy/core_plugins/visualizations/public/index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import 'src/legacy/ui/public/styles/styling_constants'; -@import './np_ready/public/index'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/kibana.json b/src/legacy/core_plugins/visualizations/public/np_ready/kibana.json deleted file mode 100644 index f8637a71b2d35..0000000000000 --- a/src/legacy/core_plugins/visualizations/public/np_ready/kibana.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "id": "visualizations", - "version": "kibana", - "server": false, - "ui": true, - "requiredPlugins": ["data", "expressions", "uiActions", "embeddable", "usageCollection"] -} diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts deleted file mode 100644 index 078cc4a3f4035..0000000000000 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Visualizations Plugin - public - * - * This is the entry point for the entire client-side public contract of the plugin. - * If something is not explicitly exported here, you can safely assume it is private - * to the plugin and not considered stable. - * - * All stateful contracts will be injected by the platform at runtime, and are defined - * in the setup/start interfaces in `plugin.ts`. The remaining items exported here are - * either types, or static code. - */ - -import { PublicContract } from '@kbn/utility-types'; -import { PluginInitializerContext } from '../../../../../../core/public'; -import { VisualizationsPlugin, VisualizationsSetup, VisualizationsStart } from './plugin'; - -/** @public */ -export { VisualizationsSetup, VisualizationsStart }; - -/** @public types */ -export { VisTypeAlias, VisType } from './vis_types'; -export { VisSavedObject } from './types'; -export { Vis, VisParams, SerializedVis, SerializedVisData, VisData } from './vis'; -import { VisualizeEmbeddableFactory, VisualizeEmbeddable } from './embeddable'; -export type VisualizeEmbeddableFactoryContract = PublicContract<VisualizeEmbeddableFactory>; -export type VisualizeEmbeddableContract = PublicContract<VisualizeEmbeddable>; -export { TypesService } from './vis_types/types_service'; -export { VISUALIZE_EMBEDDABLE_TYPE, VisualizeInput } from './embeddable'; -export { SchemaConfig } from './legacy/build_pipeline'; - -export function plugin(initializerContext: PluginInitializerContext) { - return new VisualizationsPlugin(initializerContext); -} diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/_vis.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/_vis.js deleted file mode 100644 index deb345a77cdb6..0000000000000 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/_vis.js +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import ngMock from 'ng_mock'; -import expect from '@kbn/expect'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { start as visualizations } from '../../legacy'; - -describe('Vis Class', function() { - let indexPattern; - let visTypes; - - let vis; - const stateFixture = { - type: 'pie', - aggs: [ - { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, - { type: 'terms', schema: 'segment', params: { field: 'machine.os' } }, - { type: 'terms', schema: 'segment', params: { field: 'geo.src' } }, - ], - params: { isDonut: true }, - listeners: { click: _.noop }, - }; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private) { - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - visTypes = visualizations; - }) - ); - - beforeEach(function() { - vis = visualizations.createVis(indexPattern, stateFixture); - }); - - const verifyVis = function(vis) { - expect(vis).to.have.property('aggs'); - expect(vis.aggs.aggs).to.have.length(3); - - expect(vis).to.have.property('type'); - expect(vis.type).to.eql(visTypes.get('pie')); - - expect(vis).to.have.property('params'); - expect(vis.params).to.have.property('isDonut', true); - expect(vis).to.have.property('indexPattern', indexPattern); - }; - - describe('initialization', function() { - it('should set the state', function() { - verifyVis(vis); - }); - }); - - describe('getState()', function() { - it('should get a state that represents the... er... state', function() { - const state = vis.getEnabledState(); - expect(state).to.have.property('type', 'pie'); - - expect(state).to.have.property('params'); - expect(state.params).to.have.property('isDonut', true); - - expect(state).to.have.property('aggs'); - expect(state.aggs).to.have.length(3); - }); - }); - - describe('setState()', function() { - it('should set the state to defaults', function() { - const vis = visualizations.createVis(indexPattern); - expect(vis).to.have.property('type'); - expect(vis.type).to.eql(visTypes.get('histogram')); - expect(vis).to.have.property('aggs'); - expect(vis.aggs.aggs).to.have.length(1); - expect(vis).to.have.property('params'); - expect(vis.params).to.have.property('addLegend', true); - expect(vis.params).to.have.property('addTooltip', true); - }); - }); - - describe('isHierarchical()', function() { - it('should return true for hierarchical vis (like pie)', function() { - expect(vis.isHierarchical()).to.be(true); - }); - it('should return false for non-hierarchical vis (like histogram)', function() { - const vis = visualizations.createVis(indexPattern); - expect(vis.isHierarchical()).to.be(false); - }); - }); -}); diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_types/base_vis_type.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_types/base_vis_type.js deleted file mode 100644 index 9c1dfd9780255..0000000000000 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_types/base_vis_type.js +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { BaseVisType } from '../../../vis_types/base_vis_type'; - -describe('Base Vis Type', function() { - beforeEach(ngMock.module('kibana')); - - describe('initialization', () => { - it('should throw if mandatory properties are missing', () => { - expect(() => { - new BaseVisType({}); - }).to.throwError('vis_type must define its name'); - - expect(() => { - new BaseVisType({ name: 'test' }); - }).to.throwError('vis_type must define its title'); - - expect(() => { - new BaseVisType({ name: 'test', title: 'test' }); - }).to.throwError('vis_type must define its description'); - - expect(() => { - new BaseVisType({ name: 'test', title: 'test', description: 'test' }); - }).to.throwError('vis_type must define its icon or image'); - - expect(() => { - new BaseVisType({ name: 'test', title: 'test', description: 'test', icon: 'test' }); - }).to.throwError('vis_type must define visualization controller'); - - expect(() => { - new BaseVisType({ - name: 'test', - title: 'test', - description: 'test', - icon: 'test', - visualization: {}, - }); - }).to.not.throwError(); - }); - }); -}); diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts deleted file mode 100644 index 17f777e4e80e1..0000000000000 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { PluginInitializerContext } from '../../../../../../core/public'; -import { VisualizationsSetup, VisualizationsStart } from './'; -import { VisualizationsPlugin } from './plugin'; -import { coreMock } from '../../../../../../core/public/mocks'; -import { embeddablePluginMock } from '../../../../../../plugins/embeddable/public/mocks'; -import { expressionsPluginMock } from '../../../../../../plugins/expressions/public/mocks'; -import { dataPluginMock } from '../../../../../../plugins/data/public/mocks'; -import { usageCollectionPluginMock } from '../../../../../../plugins/usage_collection/public/mocks'; -import { uiActionsPluginMock } from '../../../../../../plugins/ui_actions/public/mocks'; - -const createSetupContract = (): VisualizationsSetup => ({ - createBaseVisualization: jest.fn(), - createReactVisualization: jest.fn(), - registerAlias: jest.fn(), - hideTypes: jest.fn(), -}); - -const createStartContract = (): VisualizationsStart => ({ - get: jest.fn(), - all: jest.fn(), - getAliases: jest.fn(), - savedVisualizationsLoader: {} as any, - showNewVisModal: jest.fn(), - createVis: jest.fn(), - convertFromSerializedVis: jest.fn(), - convertToSerializedVis: jest.fn(), -}); - -const createInstance = async () => { - const plugin = new VisualizationsPlugin({} as PluginInitializerContext); - - const setup = plugin.setup(coreMock.createSetup(), { - data: dataPluginMock.createSetupContract(), - expressions: expressionsPluginMock.createSetupContract(), - embeddable: embeddablePluginMock.createSetupContract(), - usageCollection: usageCollectionPluginMock.createSetupContract(), - }); - const doStart = () => - plugin.start(coreMock.createStart(), { - data: dataPluginMock.createStartContract(), - expressions: expressionsPluginMock.createStartContract(), - uiActions: uiActionsPluginMock.createStartContract(), - }); - - return { - plugin, - setup, - doStart, - }; -}; - -export const visualizationsPluginMock = { - createSetupContract, - createStartContract, - createInstance, -}; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts deleted file mode 100644 index 3ade6cee0d4d2..0000000000000 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { - PluginInitializerContext, - CoreSetup, - CoreStart, - Plugin, -} from '../../../../../../core/public'; -import { TypesService, TypesSetup, TypesStart } from './vis_types'; -import { - setUISettings, - setTypes, - setI18n, - setCapabilities, - setHttp, - setIndexPatterns, - setSavedObjects, - setUsageCollector, - setFilterManager, - setExpressions, - setUiActions, - setSavedVisualizationsLoader, - setTimeFilter, - setAggs, - setChrome, - setOverlays, -} from './services'; -import { VISUALIZE_EMBEDDABLE_TYPE, VisualizeEmbeddableFactory } from './embeddable'; -import { ExpressionsSetup, ExpressionsStart } from '../../../../../../plugins/expressions/public'; -import { EmbeddableSetup } from '../../../../../../plugins/embeddable/public'; -import { visualization as visualizationFunction } from './expressions/visualization_function'; -import { visualization as visualizationRenderer } from './expressions/visualization_renderer'; -import { - DataPublicPluginSetup, - DataPublicPluginStart, -} from '../../../../../../plugins/data/public'; -import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/public'; -import { createSavedVisLoader, SavedVisualizationsLoader } from './saved_visualizations'; -import { SerializedVis, Vis } from './vis'; -import { showNewVisModal } from './wizard'; -import { UiActionsStart } from '../../../../../../plugins/ui_actions/public'; -import { - convertFromSerializedVis, - convertToSerializedVis, -} from './saved_visualizations/_saved_vis'; - -/** - * Interface for this plugin's returned setup/start contracts. - * - * @public - */ - -export type VisualizationsSetup = TypesSetup; - -export interface VisualizationsStart extends TypesStart { - savedVisualizationsLoader: SavedVisualizationsLoader; - createVis: (visType: string, visState?: SerializedVis) => Vis; - convertToSerializedVis: typeof convertToSerializedVis; - convertFromSerializedVis: typeof convertFromSerializedVis; - showNewVisModal: typeof showNewVisModal; -} - -export interface VisualizationsSetupDeps { - expressions: ExpressionsSetup; - embeddable: EmbeddableSetup; - usageCollection: UsageCollectionSetup; - data: DataPublicPluginSetup; -} - -export interface VisualizationsStartDeps { - data: DataPublicPluginStart; - expressions: ExpressionsStart; - uiActions: UiActionsStart; -} - -/** - * Visualizations Plugin - public - * - * This plugin's stateful contracts are returned from the `setup` and `start` methods - * below. The interfaces for these contracts are provided above. - * - * @internal - */ -export class VisualizationsPlugin - implements - Plugin< - VisualizationsSetup, - VisualizationsStart, - VisualizationsSetupDeps, - VisualizationsStartDeps - > { - private readonly types: TypesService = new TypesService(); - - constructor(initializerContext: PluginInitializerContext) {} - - public setup( - core: CoreSetup, - { expressions, embeddable, usageCollection, data }: VisualizationsSetupDeps - ): VisualizationsSetup { - setUISettings(core.uiSettings); - setUsageCollector(usageCollection); - - expressions.registerFunction(visualizationFunction); - expressions.registerRenderer(visualizationRenderer); - - const embeddableFactory = new VisualizeEmbeddableFactory(); - embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); - - return { - ...this.types.setup(), - }; - } - - public start( - core: CoreStart, - { data, expressions, uiActions }: VisualizationsStartDeps - ): VisualizationsStart { - const types = this.types.start(); - setI18n(core.i18n); - setTypes(types); - setCapabilities(core.application.capabilities); - setHttp(core.http); - setSavedObjects(core.savedObjects); - setIndexPatterns(data.indexPatterns); - setFilterManager(data.query.filterManager); - setExpressions(expressions); - setUiActions(uiActions); - setTimeFilter(data.query.timefilter.timefilter); - setAggs(data.search.aggs); - setOverlays(core.overlays); - setChrome(core.chrome); - const savedVisualizationsLoader = createSavedVisLoader({ - savedObjectsClient: core.savedObjects.client, - indexPatterns: data.indexPatterns, - chrome: core.chrome, - overlays: core.overlays, - visualizationTypes: types, - }); - setSavedVisualizationsLoader(savedVisualizationsLoader); - - return { - ...types, - showNewVisModal, - /** - * creates new instance of Vis - * @param {IIndexPattern} indexPattern - index pattern to use - * @param {VisState} visState - visualization configuration - */ - createVis: (visType: string, visState?: SerializedVis) => new Vis(visType, visState), - convertToSerializedVis, - convertFromSerializedVis, - savedVisualizationsLoader, - }; - } - - public stop() { - this.types.stop(); - } -} diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/types.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/types.ts deleted file mode 100644 index 8f93a179af3bc..0000000000000 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/types.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { SavedObject } from '../../../../../../plugins/saved_objects/public'; -import { ISearchSource, AggConfigOptions } from '../../../../../../plugins/data/public'; -import { SerializedVis, Vis, VisParams } from './vis'; - -export { Vis, SerializedVis, VisParams }; - -export interface VisualizationController { - render(visData: any, visParams: any): Promise<void>; - destroy(): void; - isLoaded?(): Promise<void> | void; -} - -export interface SavedVisState { - type: string; - params: VisParams; - aggs: AggConfigOptions[]; -} - -export interface ISavedVis { - id: string; - title: string; - description?: string; - visState: SavedVisState; - searchSource?: ISearchSource; - uiStateJSON?: string; - savedSearchRefName?: string; - savedSearchId?: string; -} - -// @ts-ignore-next-line -export interface VisSavedObject extends SavedObject, ISavedVis {} diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/base_vis_type.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/base_vis_type.js deleted file mode 100644 index 50ff74cfe9dd3..0000000000000 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/base_vis_type.js +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -export class BaseVisType { - constructor(opts = {}) { - if (!opts.name) { - throw 'vis_type must define its name'; - } - if (!opts.title) { - throw 'vis_type must define its title'; - } - if (!opts.description) { - throw 'vis_type must define its description'; - } - if (!opts.icon && !opts.image) { - throw 'vis_type must define its icon or image'; - } - if (!opts.visualization) { - throw 'vis_type must define visualization controller'; - } - - const _defaults = { - // name, title, description, icon, image - visualization: null, // must be a class with render/resize/destroy methods - visConfig: { - defaults: {}, // default configuration - }, - requestHandler: 'courier', // select one from registry or pass a function - responseHandler: 'none', - editor: null, // no default is provided - editorConfig: { - collections: {}, // collections used for configuration (list of positions, ...) - }, - options: { - // controls the visualize editor - showTimePicker: true, - showQueryBar: true, - showFilterBar: true, - showIndexSelection: true, - hierarchicalData: false, // we should get rid of this i guess ? - }, - stage: 'production', - feedbackMessage: '', - hidden: false, - }; - - _.defaultsDeep(this, opts, _defaults); - - this.requiresSearch = this.requestHandler !== 'none'; - } - - shouldMarkAsExperimentalInUI() { - return this.stage === 'experimental'; - } - - get schemas() { - if (this.editorConfig && this.editorConfig.schemas) { - return this.editorConfig.schemas; - } - return []; - } -} diff --git a/src/legacy/server/http/index.js b/src/legacy/server/http/index.js index d616afb533d0a..3649987d89b9a 100644 --- a/src/legacy/server/http/index.js +++ b/src/legacy/server/http/index.js @@ -18,7 +18,6 @@ */ import { format } from 'url'; -import { resolve } from 'path'; import _ from 'lodash'; import Boom from 'boom'; @@ -32,22 +31,6 @@ export default async function(kbnServer, server, config) { await registerHapiPlugins(server); - // provide a simple way to expose static directories - server.decorate('server', 'exposeStaticDir', function(routePath, dirPath) { - this.route({ - path: routePath, - method: 'GET', - handler: { - directory: { - path: dirPath, - listing: false, - lookupCompressed: true, - }, - }, - config: { auth: false }, - }); - }); - // helper for creating view managers for servers server.decorate('server', 'setupViews', function(path, engines) { this.views({ @@ -77,7 +60,4 @@ export default async function(kbnServer, server, config) { .permanent(true); }, }); - - // Expose static assets - server.exposeStaticDir('/ui/{path*}', resolve(__dirname, '../../ui/public/assets')); } diff --git a/src/legacy/server/plugins/lib/plugin.js b/src/legacy/server/plugins/lib/plugin.js index e73f1bf2c4d38..2b392d13d595a 100644 --- a/src/legacy/server/plugins/lib/plugin.js +++ b/src/legacy/server/plugins/lib/plugin.js @@ -73,7 +73,10 @@ export class Plugin { }); if (this.publicDir) { - server.exposeStaticDir(`/plugins/${id}/{path*}`, this.publicDir); + server.newPlatform.__internals.http.registerStaticDir( + `/plugins/${id}/{path*}`, + this.publicDir + ); } // Many of the plugins are simply adding static assets to the server and we don't need diff --git a/src/legacy/server/sass/build.js b/src/legacy/server/sass/build.js index 3d892ce321c2e..1ec656786ccc5 100644 --- a/src/legacy/server/sass/build.js +++ b/src/legacy/server/sass/build.js @@ -34,7 +34,7 @@ const access = promisify(fs.access); const copyFile = promisify(fs.copyFile); const mkdirAsync = promisify(fs.mkdir); -const UI_ASSETS_DIR = resolve(__dirname, '../../ui/public/assets'); +const UI_ASSETS_DIR = resolve(__dirname, '../../../core/server/core_app/assets'); const DARK_THEME_IMPORTER = url => { if (url.includes('eui_colors_light')) { return { file: url.replace('eui_colors_light', 'eui_colors_dark') }; diff --git a/src/legacy/ui/public/new_platform/__mocks__/helpers.ts b/src/legacy/ui/public/new_platform/__mocks__/helpers.ts index 35d6e070ae8f7..f9f4494929014 100644 --- a/src/legacy/ui/public/new_platform/__mocks__/helpers.ts +++ b/src/legacy/ui/public/new_platform/__mocks__/helpers.ts @@ -31,6 +31,7 @@ import { kibanaLegacyPluginMock } from '../../../../../plugins/kibana_legacy/pub import { chartPluginMock } from '../../../../../plugins/charts/public/mocks'; import { advancedSettingsMock } from '../../../../../plugins/advanced_settings/public/mocks'; import { savedObjectsManagementPluginMock } from '../../../../../plugins/saved_objects_management/public/mocks'; +import { visualizationsPluginMock } from '../../../../../plugins/visualizations/public/mocks'; /* eslint-enable @kbn/eslint/no-restricted-paths */ export const pluginsMock = { @@ -44,6 +45,7 @@ export const pluginsMock = { uiActions: uiActionsPluginMock.createSetupContract(), usageCollection: usageCollectionPluginMock.createSetupContract(), advancedSettings: advancedSettingsMock.createSetupContract(), + visualizations: visualizationsPluginMock.createSetupContract(), kibanaLegacy: kibanaLegacyPluginMock.createSetupContract(), savedObjectsManagement: savedObjectsManagementPluginMock.createSetupContract(), }), @@ -57,6 +59,7 @@ export const pluginsMock = { uiActions: uiActionsPluginMock.createStartContract(), management: managementPluginMock.createStartContract(), advancedSettings: advancedSettingsMock.createStartContract(), + visualizations: visualizationsPluginMock.createStartContract(), kibanaLegacy: kibanaLegacyPluginMock.createStartContract(), savedObjectsManagement: savedObjectsManagementPluginMock.createStartContract(), }), diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index 67877c5382633..25647e4a08897 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -20,20 +20,7 @@ import sinon from 'sinon'; import { getFieldFormatsRegistry } from '../../../../test_utils/public/stub_field_formats'; import { METRIC_TYPE } from '@kbn/analytics'; -import { - setFieldFormats, - setIndexPatterns, - setInjectedMetadata, - setHttp, - setNotifications, - setOverlays, - setQueryService, - setSearchService, - setUiSettings, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../plugins/data/public/services'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { setAggs } from '../../../../../src/legacy/core_plugins/visualizations/public/np_ready/public/services'; +import { setSetupServices, setStartServices } from './set_services'; import { AggTypesRegistry, getAggTypes, @@ -57,6 +44,10 @@ const mockComponent = () => { return null; }; +let refreshInterval = undefined; +let isTimeRangeSelectorEnabled = true; +let isAutoRefreshSelectorEnabled = true; + export const mockUiSettings = { get: item => { return mockUiSettings[item]; @@ -64,6 +55,7 @@ export const mockUiSettings = { getUpdate$: () => ({ subscribe: sinon.fake(), }), + isDefault: sinon.fake(), 'query:allowLeadingWildcards': true, 'query:queryString:options': {}, 'courier:ignoreFilterIfFieldNotInIndex': true, @@ -71,20 +63,110 @@ export const mockUiSettings = { 'format:defaultTypeMap': {}, }; -const mockCore = { +const mockCoreSetup = { chrome: {}, + http: { + basePath: { + get: sinon.fake.returns(''), + }, + }, + injectedMetadata: {}, uiSettings: mockUiSettings, +}; + +const mockCoreStart = { + application: { + capabilities: {}, + }, + chrome: { + overlays: { + openModal: sinon.fake(), + }, + }, http: { basePath: { get: sinon.fake.returns(''), }, }, + i18n: {}, + overlays: {}, + savedObjects: { + client: {}, + }, + uiSettings: mockUiSettings, +}; + +const querySetup = { + state$: mockObservable(), + filterManager: { + getFetches$: sinon.fake(), + getFilters: sinon.fake(), + getAppFilters: sinon.fake(), + getGlobalFilters: sinon.fake(), + removeFilter: sinon.fake(), + addFilters: sinon.fake(), + setFilters: sinon.fake(), + removeAll: sinon.fake(), + getUpdates$: mockObservable, + }, + timefilter: { + timefilter: { + getFetch$: mockObservable, + getAutoRefreshFetch$: mockObservable, + getEnabledUpdated$: mockObservable, + getTimeUpdate$: mockObservable, + getRefreshIntervalUpdate$: mockObservable, + isTimeRangeSelectorEnabled: () => { + return isTimeRangeSelectorEnabled; + }, + isAutoRefreshSelectorEnabled: () => { + return isAutoRefreshSelectorEnabled; + }, + disableAutoRefreshSelector: () => { + isAutoRefreshSelectorEnabled = false; + }, + enableAutoRefreshSelector: () => { + isAutoRefreshSelectorEnabled = true; + }, + getRefreshInterval: () => { + return refreshInterval; + }, + setRefreshInterval: interval => { + refreshInterval = interval; + }, + enableTimeRangeSelector: () => { + isTimeRangeSelectorEnabled = true; + }, + disableTimeRangeSelector: () => { + isTimeRangeSelectorEnabled = false; + }, + getTime: sinon.fake(), + setTime: sinon.fake(), + getActiveBounds: sinon.fake(), + getBounds: sinon.fake(), + calculateBounds: sinon.fake(), + createFilter: sinon.fake(), + }, + history: sinon.fake(), + }, + savedQueries: { + saveQuery: sinon.fake(), + getAllSavedQueries: sinon.fake(), + findSavedQueries: sinon.fake(), + getSavedQuery: sinon.fake(), + deleteSavedQuery: sinon.fake(), + getSavedQueryCount: sinon.fake(), + }, }; const mockAggTypesRegistry = () => { const registry = new AggTypesRegistry(); const registrySetup = registry.setup(); - const aggTypes = getAggTypes({ uiSettings: mockCore.uiSettings }); + const aggTypes = getAggTypes({ + uiSettings: mockCoreSetup.uiSettings, + notifications: mockCoreStart.notifications, + query: querySetup, + }); aggTypes.buckets.forEach(type => registrySetup.registerBucket(type)); aggTypes.metrics.forEach(type => registrySetup.registerMetric(type)); @@ -93,12 +175,8 @@ const mockAggTypesRegistry = () => { const aggTypesRegistry = mockAggTypesRegistry(); -let refreshInterval = undefined; -let isTimeRangeSelectorEnabled = true; -let isAutoRefreshSelectorEnabled = true; - export const npSetup = { - core: mockCore, + core: mockCoreSetup, plugins: { advancedSettings: { component: { @@ -135,72 +213,7 @@ export const npSetup = { addProvider: sinon.fake(), getProvider: sinon.fake(), }, - query: { - state$: mockObservable(), - filterManager: { - getFetches$: sinon.fake(), - getFilters: sinon.fake(), - getAppFilters: sinon.fake(), - getGlobalFilters: sinon.fake(), - removeFilter: sinon.fake(), - addFilters: sinon.fake(), - setFilters: sinon.fake(), - removeAll: sinon.fake(), - getUpdates$: mockObservable, - }, - timefilter: { - timefilter: { - getTime: sinon.fake(), - getRefreshInterval: sinon.fake(), - getTimeUpdate$: mockObservable, - getRefreshIntervalUpdate$: mockObservable, - getFetch$: mockObservable, - getAutoRefreshFetch$: mockObservable, - getEnabledUpdated$: mockObservable, - getTimeUpdate$: mockObservable, - getRefreshIntervalUpdate$: mockObservable, - isTimeRangeSelectorEnabled: () => { - return isTimeRangeSelectorEnabled; - }, - isAutoRefreshSelectorEnabled: () => { - return isAutoRefreshSelectorEnabled; - }, - disableAutoRefreshSelector: () => { - isAutoRefreshSelectorEnabled = false; - }, - enableAutoRefreshSelector: () => { - isAutoRefreshSelectorEnabled = true; - }, - getRefreshInterval: () => { - return refreshInterval; - }, - setRefreshInterval: interval => { - refreshInterval = interval; - }, - enableTimeRangeSelector: () => { - isTimeRangeSelectorEnabled = true; - }, - disableTimeRangeSelector: () => { - isTimeRangeSelectorEnabled = false; - }, - getTime: sinon.fake(), - setTime: sinon.fake(), - getActiveBounds: sinon.fake(), - getBounds: sinon.fake(), - calculateBounds: sinon.fake(), - createFilter: sinon.fake(), - }, - history: sinon.fake(), - }, - savedQueries: { - saveQuery: sinon.fake(), - getAllSavedQueries: sinon.fake(), - findSavedQueries: sinon.fake(), - getSavedQuery: sinon.fake(), - deleteSavedQuery: sinon.fake(), - getSavedQueryCount: sinon.fake(), - }, - }, + query: querySetup, search: { aggs: { calculateAutoTimeExpression: sinon.fake(), @@ -213,7 +226,7 @@ export const npSetup = { }, }, }, - fieldFormats: getFieldFormatsRegistry(mockCore), + fieldFormats: getFieldFormatsRegistry(mockCoreSetup), }, share: { register: () => {}, @@ -280,17 +293,17 @@ export const npSetup = { visTypeVega: { config: sinon.fake(), }, + visualizations: { + createBaseVisualization: sinon.fake(), + createReactVisualization: sinon.fake(), + registerAlias: sinon.fake(), + hideTypes: sinon.fake(), + }, }, }; export const npStart = { - core: { - chrome: { - overlays: { - openModal: sinon.fake(), - }, - }, - }, + core: mockCoreStart, plugins: { management: { legacy: { @@ -410,7 +423,6 @@ export const npStart = { search: { aggs: { calculateAutoTimeExpression: sinon.fake(), - createAggConfigs: sinon.fake(), createAggConfigs: (indexPattern, configStates = []) => { return new AggConfigs(indexPattern, configStates, { typesRegistry: aggTypesRegistry.start(), @@ -435,7 +447,7 @@ export const npStart = { }, }, }, - fieldFormats: getFieldFormatsRegistry(mockCore), + fieldFormats: getFieldFormatsRegistry(mockCoreStart), }, share: { toggleShareContextMenu: () => {}, @@ -457,6 +469,16 @@ export const npStart = { getTriggerActions: sinon.fake(), getTriggerCompatibleActions: sinon.fake(), }, + visualizations: { + get: sinon.fake(), + all: sinon.fake(), + getAliases: sinon.fake(), + savedVisualizationsLoader: {}, + showNewVisModal: sinon.fake(), + createVis: sinon.fake(), + convertFromSerializedVis: sinon.fake(), + convertToSerializedVis: sinon.fake(), + }, navigation: { ui: { TopNavMenu: mockComponent, @@ -483,23 +505,15 @@ export function __setup__(coreSetup) { // bootstrap an LP plugin outside of tests) npSetup.core.application.register = () => {}; - // Services that need to be set in the legacy platform since the legacy data plugin - // which previously provided them has been removed. - setInjectedMetadata(npSetup.core.injectedMetadata); + // Services that need to be set in the legacy platform since the legacy data + // & vis plugins which previously provided them have been removed. + setSetupServices(npSetup); } export function __start__(coreStart) { npStart.core = coreStart; - // Services that need to be set in the legacy platform since the legacy data plugin - // which previously provided them has been removed. - setHttp(npStart.core.http); - setNotifications(npStart.core.notifications); - setOverlays(npStart.core.overlays); - setUiSettings(npStart.core.uiSettings); - setFieldFormats(npStart.plugins.data.fieldFormats); - setIndexPatterns(npStart.plugins.data.indexPatterns); - setQueryService(npStart.plugins.data.query); - setSearchService(npStart.plugins.data.search); - setAggs(npStart.plugins.data.search.aggs); + // Services that need to be set in the legacy platform since the legacy data + // & vis plugins which previously provided them have been removed. + setStartServices(npStart); } diff --git a/src/legacy/ui/public/new_platform/new_platform.test.ts b/src/legacy/ui/public/new_platform/new_platform.test.ts index dd41093f3a1f0..1629aac588a61 100644 --- a/src/legacy/ui/public/new_platform/new_platform.test.ts +++ b/src/legacy/ui/public/new_platform/new_platform.test.ts @@ -20,19 +20,8 @@ jest.mock('history'); import { setRootControllerMock, historyMock } from './new_platform.test.mocks'; -import { - legacyAppRegister, - __reset__, - __setup__, - __start__, - PluginsSetup, - PluginsStart, -} from './new_platform'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import * as dataServices from '../../../../plugins/data/public/services'; -import { LegacyCoreSetup, LegacyCoreStart } from '../../../../core/public'; +import { legacyAppRegister, __reset__, __setup__, __start__ } from './new_platform'; import { coreMock } from '../../../../core/public/mocks'; -import { npSetup, npStart } from './__mocks__'; describe('ui/new_platform', () => { describe('legacyAppRegister', () => { @@ -119,25 +108,4 @@ describe('ui/new_platform', () => { expect(unmountMock).toHaveBeenCalled(); }); }); - - describe('service getters', () => { - const services: Record<string, Function> = dataServices; - const getters = Object.keys(services).filter(k => k.substring(0, 3) === 'get'); - - getters.forEach(g => { - it(`sets a value for ${g}`, () => { - __reset__(); - __setup__( - (coreMock.createSetup() as unknown) as LegacyCoreSetup, - (npSetup.plugins as unknown) as PluginsSetup - ); - __start__( - (coreMock.createStart() as unknown) as LegacyCoreStart, - (npStart.plugins as unknown) as PluginsStart - ); - - expect(services[g]()).toBeDefined(); - }); - }); - }); }); diff --git a/src/legacy/ui/public/new_platform/new_platform.ts b/src/legacy/ui/public/new_platform/new_platform.ts index 4fa368af313b4..b4b5099081759 100644 --- a/src/legacy/ui/public/new_platform/new_platform.ts +++ b/src/legacy/ui/public/new_platform/new_platform.ts @@ -23,6 +23,7 @@ import { UiActionsStart, UiActionsSetup } from 'src/plugins/ui_actions/public'; import { EmbeddableStart, EmbeddableSetup } from 'src/plugins/embeddable/public'; import { createBrowserHistory } from 'history'; import { DashboardStart } from '../../../../plugins/dashboard/public'; +import { setSetupServices, setStartServices } from './set_services'; import { LegacyCoreSetup, LegacyCoreStart, @@ -31,18 +32,6 @@ import { ScopedHistory, } from '../../../../core/public'; import { Plugin as DataPlugin } from '../../../../plugins/data/public'; -import { - setFieldFormats, - setIndexPatterns, - setInjectedMetadata, - setHttp, - setNotifications, - setOverlays, - setQueryService, - setSearchService, - setUiSettings, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../plugins/data/public/services'; import { Plugin as ExpressionsPlugin } from '../../../../plugins/expressions/public'; import { Setup as InspectorSetup, @@ -71,6 +60,10 @@ import { SavedObjectsManagementPluginSetup, SavedObjectsManagementPluginStart, } from '../../../../plugins/saved_objects_management/public'; +import { + VisualizationsSetup, + VisualizationsStart, +} from '../../../../plugins/visualizations/public'; export interface PluginsSetup { bfetch: BfetchPublicSetup; @@ -90,6 +83,7 @@ export interface PluginsSetup { management: ManagementSetup; visTypeVega: VisTypeVegaSetup; discover: DiscoverSetup; + visualizations: VisualizationsSetup; telemetry?: TelemetryPluginSetup; savedObjectsManagement: SavedObjectsManagementPluginSetup; } @@ -109,6 +103,7 @@ export interface PluginsStart { management: ManagementStart; advancedSettings: AdvancedSettingsStart; discover: DiscoverStart; + visualizations: VisualizationsStart; telemetry?: TelemetryPluginStart; dashboard: DashboardStart; savedObjectsManagement: SavedObjectsManagementPluginStart; @@ -143,25 +138,18 @@ export function __setup__(coreSetup: LegacyCoreSetup, plugins: PluginsSetup) { // Setup compatibility layer for AppService in legacy platform npSetup.core.application.register = legacyAppRegister; - // Services that need to be set in the legacy platform since the legacy data plugin - // which previously provided them has been removed. - setInjectedMetadata(npSetup.core.injectedMetadata); + // Services that need to be set in the legacy platform since the legacy data + // & vis plugins which previously provided them have been removed. + setSetupServices(npSetup); } export function __start__(coreStart: LegacyCoreStart, plugins: PluginsStart) { npStart.core = coreStart; npStart.plugins = plugins; - // Services that need to be set in the legacy platform since the legacy data plugin - // which previously provided them has been removed. - setHttp(npStart.core.http); - setNotifications(npStart.core.notifications); - setOverlays(npStart.core.overlays); - setUiSettings(npStart.core.uiSettings); - setFieldFormats(npStart.plugins.data.fieldFormats); - setIndexPatterns(npStart.plugins.data.indexPatterns); - setQueryService(npStart.plugins.data.query); - setSearchService(npStart.plugins.data.search); + // Services that need to be set in the legacy platform since the legacy data + // & vis plugins which previously provided them have been removed. + setStartServices(npStart); } /** Flag used to ensure `legacyAppRegister` is only called once. */ diff --git a/src/legacy/ui/public/new_platform/set_services.test.ts b/src/legacy/ui/public/new_platform/set_services.test.ts new file mode 100644 index 0000000000000..25a4524925169 --- /dev/null +++ b/src/legacy/ui/public/new_platform/set_services.test.ts @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { __reset__, __setup__, __start__, PluginsSetup, PluginsStart } from './new_platform'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import * as dataServices from '../../../../plugins/data/public/services'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import * as visualizationsServices from '../../../../plugins/visualizations/public/services'; +import { LegacyCoreSetup, LegacyCoreStart } from '../../../../core/public'; +import { coreMock } from '../../../../core/public/mocks'; +import { npSetup, npStart } from './__mocks__'; + +describe('ui/new_platform', () => { + describe('set service getters', () => { + const testServiceGetters = (name: string, services: Record<string, Function>) => { + const getters = Object.keys(services).filter(k => k.substring(0, 3) === 'get'); + getters.forEach(g => { + it(`ui/new_platform sets a value for ${name} getter ${g}`, () => { + __reset__(); + __setup__( + (coreMock.createSetup() as unknown) as LegacyCoreSetup, + (npSetup.plugins as unknown) as PluginsSetup + ); + __start__( + (coreMock.createStart() as unknown) as LegacyCoreStart, + (npStart.plugins as unknown) as PluginsStart + ); + + expect(services[g]()).toBeDefined(); + }); + }); + }; + + testServiceGetters('data', dataServices); + testServiceGetters('visualizations', visualizationsServices); + }); +}); diff --git a/src/legacy/ui/public/new_platform/set_services.ts b/src/legacy/ui/public/new_platform/set_services.ts new file mode 100644 index 0000000000000..8cf015d5dff5c --- /dev/null +++ b/src/legacy/ui/public/new_platform/set_services.ts @@ -0,0 +1,83 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { pick } from 'lodash'; + +import { PluginsSetup, PluginsStart } from './new_platform'; +import { LegacyCoreSetup, LegacyCoreStart } from '../../../../core/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import * as dataServices from '../../../../plugins/data/public/services'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import * as visualizationsServices from '../../../../plugins/visualizations/public/services'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { createSavedVisLoader } from '../../../../plugins/visualizations/public/saved_visualizations/saved_visualizations'; + +interface NpSetup { + core: LegacyCoreSetup; + plugins: PluginsSetup; +} + +interface NpStart { + core: LegacyCoreStart; + plugins: PluginsStart; +} + +export function setSetupServices(npSetup: NpSetup) { + // Services that need to be set in the legacy platform since the legacy data plugin + // which previously provided them has been removed. + dataServices.setInjectedMetadata(npSetup.core.injectedMetadata); + visualizationsServices.setUISettings(npSetup.core.uiSettings); + visualizationsServices.setUsageCollector(npSetup.plugins.usageCollection); +} + +export function setStartServices(npStart: NpStart) { + // Services that need to be set in the legacy platform since the legacy data plugin + // which previously provided them has been removed. + dataServices.setHttp(npStart.core.http); + dataServices.setNotifications(npStart.core.notifications); + dataServices.setOverlays(npStart.core.overlays); + dataServices.setUiSettings(npStart.core.uiSettings); + dataServices.setFieldFormats(npStart.plugins.data.fieldFormats); + dataServices.setIndexPatterns(npStart.plugins.data.indexPatterns); + dataServices.setQueryService(npStart.plugins.data.query); + dataServices.setSearchService(npStart.plugins.data.search); + visualizationsServices.setI18n(npStart.core.i18n); + visualizationsServices.setTypes( + pick(npStart.plugins.visualizations, ['get', 'all', 'getAliases']) + ); + visualizationsServices.setCapabilities(npStart.core.application.capabilities); + visualizationsServices.setHttp(npStart.core.http); + visualizationsServices.setSavedObjects(npStart.core.savedObjects); + visualizationsServices.setIndexPatterns(npStart.plugins.data.indexPatterns); + visualizationsServices.setFilterManager(npStart.plugins.data.query.filterManager); + visualizationsServices.setExpressions(npStart.plugins.expressions); + visualizationsServices.setUiActions(npStart.plugins.uiActions); + visualizationsServices.setTimeFilter(npStart.plugins.data.query.timefilter.timefilter); + visualizationsServices.setAggs(npStart.plugins.data.search.aggs); + visualizationsServices.setOverlays(npStart.core.overlays); + visualizationsServices.setChrome(npStart.core.chrome); + const savedVisualizationsLoader = createSavedVisLoader({ + savedObjectsClient: npStart.core.savedObjects.client, + indexPatterns: npStart.plugins.data.indexPatterns, + chrome: npStart.core.chrome, + overlays: npStart.core.overlays, + visualizationTypes: visualizationsServices.getTypes(), + }); + visualizationsServices.setSavedVisualizationsLoader(savedVisualizationsLoader); +} diff --git a/src/legacy/ui/public/visualize/_index.scss b/src/legacy/ui/public/visualize/_index.scss index c528c1e37b412..d9761f741353b 100644 --- a/src/legacy/ui/public/visualize/_index.scss +++ b/src/legacy/ui/public/visualize/_index.scss @@ -1 +1 @@ -@import '../../../core_plugins/visualizations/public/np_ready/public/components/index'; +@import '../../../../plugins/visualizations/public/components/index'; diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/index.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/index.ts index f19940726ef2d..fe7f239fbea3b 100644 --- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/index.ts +++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { buildPipeline } from '../../../../../core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline'; +export { buildPipeline } from '../../../../../../plugins/visualizations/public/legacy/build_pipeline'; diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index 0a1b95c23450b..99560b0bf653f 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -23,8 +23,6 @@ import { resolve } from 'path'; import { i18n } from '@kbn/i18n'; import * as UiSharedDeps from '@kbn/ui-shared-deps'; import { AppBootstrap } from './bootstrap'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { fromRoot } from '../../../core/server/utils'; import { getApmConfig } from '../apm'; import { DllCompiler } from '../../../optimize/dynamic_dll_plugin'; @@ -43,11 +41,6 @@ export function uiRenderMixin(kbnServer, server, config) { // render all views from ./views server.setupViews(resolve(__dirname, 'views')); - server.exposeStaticDir( - '/node_modules/@kbn/ui-framework/dist/{path*}', - fromRoot('node_modules/@kbn/ui-framework/dist') - ); - const translationsCache = { translations: null, hash: null }; server.route({ path: '/translations/{locale}.json', diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index fc5dde94fa851..26587470adfd9 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -121,7 +121,10 @@ export class DataPublicPlugin implements Plugin<DataPublicPluginSetup, DataPubli return { autocomplete: this.autocomplete.setup(core), - search: this.searchService.setup(core, this.packageInfo), + search: this.searchService.setup(core, { + packageInfo: this.packageInfo, + query: queryService, + }), fieldFormats: this.fieldFormatsService.setup(core), query: queryService, }; diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index fcdbccfb42592..e6e866e7284ef 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1524,7 +1524,7 @@ export type SavedQueryTimeFilter = TimeRange & { export const search: { aggs: { AggConfigs: typeof AggConfigs; - aggGroupNamesMap: () => Record<"buckets" | "metrics", string>; + aggGroupNamesMap: () => Record<"metrics" | "buckets", string>; aggTypeFilters: import("./search/aggs/filter/agg_type_filters").AggTypeFilters; CidrMask: typeof CidrMask; convertDateRangeToString: typeof convertDateRangeToString; diff --git a/src/plugins/data/public/search/aggs/agg_types.ts b/src/plugins/data/public/search/aggs/agg_types.ts index 73c6a5046fd23..556f6b0c93c41 100644 --- a/src/plugins/data/public/search/aggs/agg_types.ts +++ b/src/plugins/data/public/search/aggs/agg_types.ts @@ -17,7 +17,8 @@ * under the License. */ -import { IUiSettingsClient } from 'src/core/public'; +import { IUiSettingsClient, NotificationsSetup } from 'src/core/public'; +import { QuerySetup } from '../../query/query_service'; import { countMetricAgg } from './metrics/count'; import { avgMetricAgg } from './metrics/avg'; @@ -36,10 +37,10 @@ import { derivativeMetricAgg } from './metrics/derivative'; import { cumulativeSumMetricAgg } from './metrics/cumulative_sum'; import { movingAvgMetricAgg } from './metrics/moving_avg'; import { serialDiffMetricAgg } from './metrics/serial_diff'; -import { dateHistogramBucketAgg } from './buckets/date_histogram'; -import { histogramBucketAgg } from './buckets/histogram'; +import { getDateHistogramBucketAgg } from './buckets/date_histogram'; +import { getHistogramBucketAgg } from './buckets/histogram'; import { rangeBucketAgg } from './buckets/range'; -import { dateRangeBucketAgg } from './buckets/date_range'; +import { getDateRangeBucketAgg } from './buckets/date_range'; import { ipRangeBucketAgg } from './buckets/ip_range'; import { termsBucketAgg } from './buckets/terms'; import { filterBucketAgg } from './buckets/filter'; @@ -52,44 +53,47 @@ import { bucketAvgMetricAgg } from './metrics/bucket_avg'; import { bucketMinMetricAgg } from './metrics/bucket_min'; import { bucketMaxMetricAgg } from './metrics/bucket_max'; -export function getAggTypes(deps: { uiSettings: IUiSettingsClient }) { - const { uiSettings } = deps; - return { - metrics: [ - countMetricAgg, - avgMetricAgg, - sumMetricAgg, - medianMetricAgg, - minMetricAgg, - maxMetricAgg, - stdDeviationMetricAgg, - cardinalityMetricAgg, - percentilesMetricAgg, - percentileRanksMetricAgg, - topHitMetricAgg, - derivativeMetricAgg, - cumulativeSumMetricAgg, - movingAvgMetricAgg, - serialDiffMetricAgg, - bucketAvgMetricAgg, - bucketSumMetricAgg, - bucketMinMetricAgg, - bucketMaxMetricAgg, - geoBoundsMetricAgg, - geoCentroidMetricAgg, - ], - buckets: [ - dateHistogramBucketAgg, - histogramBucketAgg, - rangeBucketAgg, - dateRangeBucketAgg, - ipRangeBucketAgg, - termsBucketAgg, - filterBucketAgg, - getFiltersBucketAgg({ uiSettings }), - significantTermsBucketAgg, - geoHashBucketAgg, - geoTileBucketAgg, - ], - }; +export interface AggTypesDependencies { + notifications: NotificationsSetup; + uiSettings: IUiSettingsClient; + query: QuerySetup; } + +export const getAggTypes = ({ notifications, uiSettings, query }: AggTypesDependencies) => ({ + metrics: [ + countMetricAgg, + avgMetricAgg, + sumMetricAgg, + medianMetricAgg, + minMetricAgg, + maxMetricAgg, + stdDeviationMetricAgg, + cardinalityMetricAgg, + percentilesMetricAgg, + percentileRanksMetricAgg, + topHitMetricAgg, + derivativeMetricAgg, + cumulativeSumMetricAgg, + movingAvgMetricAgg, + serialDiffMetricAgg, + bucketAvgMetricAgg, + bucketSumMetricAgg, + bucketMinMetricAgg, + bucketMaxMetricAgg, + geoBoundsMetricAgg, + geoCentroidMetricAgg, + ], + buckets: [ + getDateHistogramBucketAgg({ uiSettings, query }), + getHistogramBucketAgg({ uiSettings, notifications }), + rangeBucketAgg, + getDateRangeBucketAgg({ uiSettings }), + ipRangeBucketAgg, + termsBucketAgg, + filterBucketAgg, + getFiltersBucketAgg({ uiSettings }), + significantTermsBucketAgg, + geoHashBucketAgg, + geoTileBucketAgg, + ], +}); diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts index 12817a9ba1159..def354c4557cb 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts @@ -22,23 +22,35 @@ import { createFilterDateHistogram } from './date_histogram'; import { intervalOptions } from '../_interval_options'; import { AggConfigs } from '../../agg_configs'; import { mockDataServices, mockAggTypesRegistry } from '../../test_helpers'; -import { dateHistogramBucketAgg, IBucketDateHistogramAggConfig } from '../date_histogram'; +import { + getDateHistogramBucketAgg, + DateHistogramBucketAggDependencies, + IBucketDateHistogramAggConfig, +} from '../date_histogram'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { RangeFilter } from '../../../../../common'; +import { coreMock } from '../../../../../../../core/public/mocks'; +import { queryServiceMock } from '../../../../query/mocks'; describe('AggConfig Filters', () => { describe('date_histogram', () => { - beforeEach(() => { - mockDataServices(); - }); - - const typesRegistry = mockAggTypesRegistry([dateHistogramBucketAgg]); - + let aggTypesDependencies: DateHistogramBucketAggDependencies; let agg: IBucketDateHistogramAggConfig; let filter: RangeFilter; let bucketStart: any; let field: any; + beforeEach(() => { + const { uiSettings } = coreMock.createSetup(); + + aggTypesDependencies = { + uiSettings, + query: queryServiceMock.createSetupContract(), + }; + + mockDataServices(); + }); + const init = (interval: string = 'auto', duration: any = moment.duration(15, 'minutes')) => { field = { name: 'date', @@ -61,7 +73,7 @@ describe('AggConfig Filters', () => { params: { field: field.name, interval, customInterval: '5d' }, }, ], - { typesRegistry } + { typesRegistry: mockAggTypesRegistry([getDateHistogramBucketAgg(aggTypesDependencies)]) } ); const bucketKey = 1422579600000; diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts index d18a30fb6c6f8..6a03176959a83 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts @@ -18,7 +18,7 @@ */ import moment from 'moment'; -import { dateRangeBucketAgg } from '../date_range'; +import { getDateRangeBucketAgg, DateRangeBucketAggDependencies } from '../date_range'; import { createFilterDateRange } from './date_range'; import { FieldFormatsGetConfigFn } from '../../../../../common'; import { DateFormat } from '../../../../field_formats'; @@ -26,10 +26,20 @@ import { AggConfigs } from '../../agg_configs'; import { mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../_bucket_agg_type'; +import { coreMock } from '../../../../../../../core/public/mocks'; describe('AggConfig Filters', () => { describe('Date range', () => { - const typesRegistry = mockAggTypesRegistry([dateRangeBucketAgg]); + let aggTypesDependencies: DateRangeBucketAggDependencies; + + beforeEach(() => { + const { uiSettings } = coreMock.createSetup(); + + aggTypesDependencies = { + uiSettings, + }; + }); + const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { @@ -57,7 +67,7 @@ describe('AggConfig Filters', () => { }, }, ], - { typesRegistry } + { typesRegistry: mockAggTypesRegistry([getDateRangeBucketAgg(aggTypesDependencies)]) } ); }; diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts index 33ab1ce8186a1..32ada8d57c768 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts @@ -17,25 +17,24 @@ * under the License. */ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { coreMock } from '../../../../../../../../src/core/public/mocks'; -import { getFiltersBucketAgg } from '../filters'; +import { getFiltersBucketAgg, FiltersBucketAggDependencies } from '../filters'; import { createFilterFilters } from './filters'; import { AggConfigs } from '../../agg_configs'; -import { mockDataServices, mockAggTypesRegistry } from '../../test_helpers'; +import { mockAggTypesRegistry } from '../../test_helpers'; import { IBucketAggConfig } from '../_bucket_agg_type'; +import { coreMock } from '../../../../../../../core/public/mocks'; describe('AggConfig Filters', () => { describe('filters', () => { + let aggTypesDependencies: FiltersBucketAggDependencies; + beforeEach(() => { - mockDataServices(); - }); + const { uiSettings } = coreMock.createSetup(); - const typesRegistry = mockAggTypesRegistry([ - getFiltersBucketAgg({ - uiSettings: coreMock.createSetup().uiSettings, - }), - ]); + aggTypesDependencies = { + uiSettings, + }; + }); const getAggConfigs = () => { const field = { @@ -65,7 +64,7 @@ describe('AggConfig Filters', () => { }, }, ], - { typesRegistry } + { typesRegistry: mockAggTypesRegistry([getFiltersBucketAgg(aggTypesDependencies)]) } ); }; it('should return a filters filter', () => { diff --git a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts b/src/plugins/data/public/search/aggs/buckets/date_histogram.ts index d600b16f56764..7701f1bbcb4d0 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_histogram.ts @@ -17,9 +17,10 @@ * under the License. */ -import _ from 'lodash'; +import { get, noop, find, every } from 'lodash'; import moment from 'moment-timezone'; import { i18n } from '@kbn/i18n'; +import { IUiSettingsClient } from 'src/core/public'; import { TimeBuckets } from './lib/time_buckets'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; @@ -32,7 +33,8 @@ import { isMetricAggType } from '../metrics/metric_agg_type'; import { FIELD_FORMAT_IDS, KBN_FIELD_TYPES } from '../../../../common'; import { TimefilterContract } from '../../../query'; -import { getFieldFormats, getQueryService, getUiSettings } from '../../../../public/services'; +import { getFieldFormats } from '../../../../public/services'; +import { QuerySetup } from '../../../query/query_service'; const detectedTimezone = moment.tz.guess(); const tzOffset = moment().format('Z'); @@ -56,6 +58,11 @@ interface ITimeBuckets { getInterval: Function; } +export interface DateHistogramBucketAggDependencies { + uiSettings: IUiSettingsClient; + query: QuerySetup; +} + export interface IBucketDateHistogramAggConfig extends IBucketAggConfig { buckets: ITimeBuckets; } @@ -64,212 +71,214 @@ export function isDateHistogramBucketAggConfig(agg: any): agg is IBucketDateHist return Boolean(agg.buckets); } -export const dateHistogramBucketAgg = new BucketAggType<IBucketDateHistogramAggConfig>({ - name: BUCKET_TYPES.DATE_HISTOGRAM, - title: i18n.translate('data.search.aggs.buckets.dateHistogramTitle', { - defaultMessage: 'Date Histogram', - }), - ordered: { - date: true, - }, - makeLabel(agg) { - let output: Record<string, any> = {}; +export const getDateHistogramBucketAgg = ({ + uiSettings, + query, +}: DateHistogramBucketAggDependencies) => + new BucketAggType<IBucketDateHistogramAggConfig>({ + name: BUCKET_TYPES.DATE_HISTOGRAM, + title: i18n.translate('data.search.aggs.buckets.dateHistogramTitle', { + defaultMessage: 'Date Histogram', + }), + ordered: { + date: true, + }, + makeLabel(agg) { + let output: Record<string, any> = {}; - if (this.params) { - output = writeParams(this.params, agg); - } + if (this.params) { + output = writeParams(this.params, agg); + } - const field = agg.getFieldDisplayName(); - return i18n.translate('data.search.aggs.buckets.dateHistogramLabel', { - defaultMessage: '{fieldName} per {intervalDescription}', - values: { - fieldName: field, - intervalDescription: output.metricScaleText || output.bucketInterval.description, - }, - }); - }, - createFilter: createFilterDateHistogram, - decorateAggConfig() { - const uiSettings = getUiSettings(); - let buckets: any; + const field = agg.getFieldDisplayName(); + return i18n.translate('data.search.aggs.buckets.dateHistogramLabel', { + defaultMessage: '{fieldName} per {intervalDescription}', + values: { + fieldName: field, + intervalDescription: output.metricScaleText || output.bucketInterval.description, + }, + }); + }, + createFilter: createFilterDateHistogram, + decorateAggConfig() { + let buckets: any; - return { - buckets: { - configurable: true, - get() { - if (buckets) return buckets; + return { + buckets: { + configurable: true, + get() { + if (buckets) return buckets; - const { timefilter } = getQueryService().timefilter; - buckets = new TimeBuckets({ uiSettings }); - updateTimeBuckets(this, timefilter, buckets); + const { timefilter } = query.timefilter; + buckets = new TimeBuckets({ uiSettings }); + updateTimeBuckets(this, timefilter, buckets); - return buckets; - }, - } as any, - }; - }, - getFormat(agg) { - const DateFieldFormat = getFieldFormats().getType(FIELD_FORMAT_IDS.DATE); + return buckets; + }, + } as any, + }; + }, + getFormat(agg) { + const DateFieldFormat = getFieldFormats().getType(FIELD_FORMAT_IDS.DATE); - if (!DateFieldFormat) { - throw new Error('Unable to retrieve Date Field Format'); - } + if (!DateFieldFormat) { + throw new Error('Unable to retrieve Date Field Format'); + } - return new DateFieldFormat( + return new DateFieldFormat( + { + pattern: agg.buckets.getScaledDateFormat(), + }, + (key: string) => uiSettings.get(key) + ); + }, + params: [ { - pattern: agg.buckets.getScaledDateFormat(), + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.DATE, + default(agg: IBucketDateHistogramAggConfig) { + return agg.getIndexPattern().timeFieldName; + }, + onChange(agg: IBucketDateHistogramAggConfig) { + if (get(agg, 'params.interval') === 'auto' && !agg.fieldIsTimeField()) { + delete agg.params.interval; + } + }, }, - (key: string) => getUiSettings().get(key) - ); - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.DATE, - default(agg: IBucketDateHistogramAggConfig) { - return agg.getIndexPattern().timeFieldName; + { + name: 'timeRange', + default: null, + write: noop, }, - onChange(agg: IBucketDateHistogramAggConfig) { - if (_.get(agg, 'params.interval') === 'auto' && !agg.fieldIsTimeField()) { - delete agg.params.interval; - } + { + name: 'useNormalizedEsInterval', + default: true, + write: noop, }, - }, - { - name: 'timeRange', - default: null, - write: _.noop, - }, - { - name: 'useNormalizedEsInterval', - default: true, - write: _.noop, - }, - { - name: 'scaleMetricValues', - default: false, - write: _.noop, - advanced: true, - }, - { - name: 'interval', - deserialize(state: any, agg) { - // For upgrading from 7.0.x to 7.1.x - intervals are now stored as key of options or custom value - if (state === 'custom') { - return _.get(agg, 'params.customInterval'); - } + { + name: 'scaleMetricValues', + default: false, + write: noop, + advanced: true, + }, + { + name: 'interval', + deserialize(state: any, agg) { + // For upgrading from 7.0.x to 7.1.x - intervals are now stored as key of options or custom value + if (state === 'custom') { + return get(agg, 'params.customInterval'); + } - const interval = _.find(intervalOptions, { val: state }); + const interval = find(intervalOptions, { val: state }); - // For upgrading from 4.0.x to 4.1.x - intervals are now stored as 'y' instead of 'year', - // but this maps the old values to the new values - if (!interval && state === 'year') { - return 'y'; - } - return state; - }, - default: 'auto', - options: intervalOptions, - write(agg, output, aggs) { - const { timefilter } = getQueryService().timefilter; - updateTimeBuckets(agg, timefilter); + // For upgrading from 4.0.x to 4.1.x - intervals are now stored as 'y' instead of 'year', + // but this maps the old values to the new values + if (!interval && state === 'year') { + return 'y'; + } + return state; + }, + default: 'auto', + options: intervalOptions, + write(agg, output, aggs) { + const { timefilter } = query.timefilter; + updateTimeBuckets(agg, timefilter); - const { useNormalizedEsInterval, scaleMetricValues } = agg.params; - const interval = agg.buckets.getInterval(useNormalizedEsInterval); - output.bucketInterval = interval; - if (interval.expression === '0ms') { - // We are hitting this code a couple of times while configuring in editor - // with an interval of 0ms because the overall time range has not yet been - // set. Since 0ms is not a valid ES interval, we cannot pass it through dateHistogramInterval - // below, since it would throw an exception. So in the cases we still have an interval of 0ms - // here we simply skip the rest of the method and never write an interval into the DSL, since - // this DSL will anyway not be used before we're passing this code with an actual interval. - return; - } - output.params = { - ...output.params, - ...dateHistogramInterval(interval.expression), - }; + const { useNormalizedEsInterval, scaleMetricValues } = agg.params; + const interval = agg.buckets.getInterval(useNormalizedEsInterval); + output.bucketInterval = interval; + if (interval.expression === '0ms') { + // We are hitting this code a couple of times while configuring in editor + // with an interval of 0ms because the overall time range has not yet been + // set. Since 0ms is not a valid ES interval, we cannot pass it through dateHistogramInterval + // below, since it would throw an exception. So in the cases we still have an interval of 0ms + // here we simply skip the rest of the method and never write an interval into the DSL, since + // this DSL will anyway not be used before we're passing this code with an actual interval. + return; + } + output.params = { + ...output.params, + ...dateHistogramInterval(interval.expression), + }; - const scaleMetrics = scaleMetricValues && interval.scaled && interval.scale < 1; - if (scaleMetrics && aggs) { - const metrics = aggs.aggs.filter(a => isMetricAggType(a.type)); - const all = _.every(metrics, (a: IBucketAggConfig) => { - const { type } = a; + const scaleMetrics = scaleMetricValues && interval.scaled && interval.scale < 1; + if (scaleMetrics && aggs) { + const metrics = aggs.aggs.filter(a => isMetricAggType(a.type)); + const all = every(metrics, (a: IBucketAggConfig) => { + const { type } = a; - if (isMetricAggType(type)) { - return type.isScalable(); + if (isMetricAggType(type)) { + return type.isScalable(); + } + }); + if (all) { + output.metricScale = interval.scale; + output.metricScaleText = interval.preScaled.description; } - }); - if (all) { - output.metricScale = interval.scale; - output.metricScaleText = interval.preScaled.description; } - } + }, }, - }, - { - name: 'time_zone', - default: undefined, - // We don't ever want this parameter to be serialized out (when saving or to URLs) - // since we do all the logic handling it "on the fly" in the `write` method, to prevent - // time_zones being persisted into saved_objects - serialize: _.noop, - write(agg, output) { - // If a time_zone has been set explicitly always prefer this. - let tz = agg.params.time_zone; - if (!tz && agg.params.field) { - // If a field has been configured check the index pattern's typeMeta if a date_histogram on that - // field requires a specific time_zone - tz = _.get(agg.getIndexPattern(), [ - 'typeMeta', - 'aggs', - 'date_histogram', - agg.params.field.name, - 'time_zone', - ]); - } - if (!tz) { - const config = getUiSettings(); - // If the index pattern typeMeta data, didn't had a time zone assigned for the selected field use the configured tz - const isDefaultTimezone = config.isDefault('dateFormat:tz'); - tz = isDefaultTimezone ? detectedTimezone || tzOffset : config.get('dateFormat:tz'); - } - output.params.time_zone = tz; + { + name: 'time_zone', + default: undefined, + // We don't ever want this parameter to be serialized out (when saving or to URLs) + // since we do all the logic handling it "on the fly" in the `write` method, to prevent + // time_zones being persisted into saved_objects + serialize: noop, + write(agg, output) { + // If a time_zone has been set explicitly always prefer this. + let tz = agg.params.time_zone; + if (!tz && agg.params.field) { + // If a field has been configured check the index pattern's typeMeta if a date_histogram on that + // field requires a specific time_zone + tz = get(agg.getIndexPattern(), [ + 'typeMeta', + 'aggs', + 'date_histogram', + agg.params.field.name, + 'time_zone', + ]); + } + if (!tz) { + // If the index pattern typeMeta data, didn't had a time zone assigned for the selected field use the configured tz + const isDefaultTimezone = uiSettings.isDefault('dateFormat:tz'); + tz = isDefaultTimezone ? detectedTimezone || tzOffset : uiSettings.get('dateFormat:tz'); + } + output.params.time_zone = tz; + }, }, - }, - { - name: 'drop_partials', - default: false, - write: _.noop, - shouldShow: agg => { - const field = agg.params.field; - return field && field.name && field.name === agg.getIndexPattern().timeFieldName; + { + name: 'drop_partials', + default: false, + write: noop, + shouldShow: agg => { + const field = agg.params.field; + return field && field.name && field.name === agg.getIndexPattern().timeFieldName; + }, }, - }, - { - name: 'format', - }, - { - name: 'min_doc_count', - default: 1, - }, - { - name: 'extended_bounds', - default: {}, - write(agg, output) { - const val = agg.params.extended_bounds; + { + name: 'format', + }, + { + name: 'min_doc_count', + default: 1, + }, + { + name: 'extended_bounds', + default: {}, + write(agg, output) { + const val = agg.params.extended_bounds; - if (val.min != null || val.max != null) { - output.params.extended_bounds = { - min: moment(val.min).valueOf(), - max: moment(val.max).valueOf(), - }; + if (val.min != null || val.max != null) { + output.params.extended_bounds = { + min: moment(val.min).valueOf(), + max: moment(val.max).valueOf(), + }; - return; - } + return; + } + }, }, - }, - ], -}); + ], + }); diff --git a/src/plugins/data/public/search/aggs/buckets/date_range.test.ts b/src/plugins/data/public/search/aggs/buckets/date_range.test.ts index 03a453836e113..4ea550492fa09 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_range.test.ts @@ -17,20 +17,22 @@ * under the License. */ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths import { coreMock } from '../../../../../../../src/core/public/mocks'; -import { setUiSettings } from '../../../../public/services'; -import { dateRangeBucketAgg } from './date_range'; +import { getDateRangeBucketAgg, DateRangeBucketAggDependencies } from './date_range'; import { AggConfigs } from '../agg_configs'; -import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; +import { mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; describe('date_range params', () => { + let aggTypesDependencies: DateRangeBucketAggDependencies; + beforeEach(() => { - mockDataServices(); - }); + const { uiSettings } = coreMock.createSetup(); - const typesRegistry = mockAggTypesRegistry([dateRangeBucketAgg]); + aggTypesDependencies = { + uiSettings, + }; + }); const getAggConfigs = (params: Record<string, any> = {}, hasIncludeTypeMeta: boolean = true) => { const field = { @@ -67,12 +69,12 @@ describe('date_range params', () => { params, }, ], - { typesRegistry } + { typesRegistry: mockAggTypesRegistry([getDateRangeBucketAgg(aggTypesDependencies)]) } ); }; describe('getKey', () => { - it('should return object', () => { + test('should return object', () => { const aggConfigs = getAggConfigs(); const dateRange = aggConfigs.aggs[0]; const bucket = { from: 'from-date', to: 'to-date', key: 'from-dateto-date' }; @@ -82,7 +84,7 @@ describe('date_range params', () => { }); describe('time_zone', () => { - it('should use the specified time_zone', () => { + test('should use the specified time_zone', () => { const aggConfigs = getAggConfigs({ time_zone: 'Europe/Minsk', field: 'bytes', @@ -93,7 +95,7 @@ describe('date_range params', () => { expect(params.time_zone).toBe('Europe/Minsk'); }); - it('should use the fixed time_zone from the index pattern typeMeta', () => { + test('should use the fixed time_zone from the index pattern typeMeta', () => { const aggConfigs = getAggConfigs({ field: 'bytes', }); @@ -103,12 +105,14 @@ describe('date_range params', () => { expect(params.time_zone).toBe('defaultTimeZone'); }); - it('should use the Kibana time_zone if no parameter specified', () => { - const core = coreMock.createStart(); - setUiSettings({ - ...core.uiSettings, - get: () => 'kibanaTimeZone' as any, - }); + test('should use the Kibana time_zone if no parameter specified', () => { + aggTypesDependencies = { + ...aggTypesDependencies, + uiSettings: { + ...aggTypesDependencies.uiSettings, + get: () => 'kibanaTimeZone' as any, + }, + }; const aggConfigs = getAggConfigs( { @@ -119,8 +123,6 @@ describe('date_range params', () => { const dateRange = aggConfigs.aggs[0]; const params = dateRange.toDsl()[BUCKET_TYPES.DATE_RANGE]; - setUiSettings(core.uiSettings); // clean up - expect(params.time_zone).toBe('kibanaTimeZone'); }); }); diff --git a/src/plugins/data/public/search/aggs/buckets/date_range.ts b/src/plugins/data/public/search/aggs/buckets/date_range.ts index 59e78af2d7b95..8133a47ec7248 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_range.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_range.ts @@ -20,86 +20,92 @@ import { get } from 'lodash'; import moment from 'moment-timezone'; import { i18n } from '@kbn/i18n'; +import { IUiSettingsClient } from 'src/core/public'; + import { BUCKET_TYPES } from './bucket_agg_types'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { createFilterDateRange } from './create_filter/date_range'; import { convertDateRangeToString, DateRangeKey } from './lib/date_range'; import { KBN_FIELD_TYPES, FieldFormat, TEXT_CONTEXT_TYPE } from '../../../../common'; -import { getFieldFormats, getUiSettings } from '../../../../public/services'; +import { getFieldFormats } from '../../../../public/services'; const dateRangeTitle = i18n.translate('data.search.aggs.buckets.dateRangeTitle', { defaultMessage: 'Date Range', }); -export const dateRangeBucketAgg = new BucketAggType({ - name: BUCKET_TYPES.DATE_RANGE, - title: dateRangeTitle, - createFilter: createFilterDateRange, - getKey({ from, to }): DateRangeKey { - return { from, to }; - }, - getFormat(agg) { - const fieldFormatsService = getFieldFormats(); +export interface DateRangeBucketAggDependencies { + uiSettings: IUiSettingsClient; +} - const formatter = agg.fieldOwnFormatter( - TEXT_CONTEXT_TYPE, - fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.DATE) - ); - const DateRangeFormat = FieldFormat.from(function(range: DateRangeKey) { - return convertDateRangeToString(range, formatter); - }); - return new DateRangeFormat(); - }, - makeLabel(aggConfig) { - return aggConfig.getFieldDisplayName() + ' date ranges'; - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.DATE, - default(agg: IBucketAggConfig) { - return agg.getIndexPattern().timeFieldName; - }, +export const getDateRangeBucketAgg = ({ uiSettings }: DateRangeBucketAggDependencies) => + new BucketAggType({ + name: BUCKET_TYPES.DATE_RANGE, + title: dateRangeTitle, + createFilter: createFilterDateRange, + getKey({ from, to }): DateRangeKey { + return { from, to }; }, - { - name: 'ranges', - default: [ - { - from: 'now-1w/w', - to: 'now', - }, - ], + getFormat(agg) { + const fieldFormatsService = getFieldFormats(); + + const formatter = agg.fieldOwnFormatter( + TEXT_CONTEXT_TYPE, + fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.DATE) + ); + const DateRangeFormat = FieldFormat.from(function(range: DateRangeKey) { + return convertDateRangeToString(range, formatter); + }); + return new DateRangeFormat(); + }, + makeLabel(aggConfig) { + return aggConfig.getFieldDisplayName() + ' date ranges'; }, - { - name: 'time_zone', - default: undefined, - // Implimentation method is the same as that of date_histogram - serialize: () => undefined, - write: (agg, output) => { - const field = agg.getParam('field'); - let tz = agg.getParam('time_zone'); + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.DATE, + default(agg: IBucketAggConfig) { + return agg.getIndexPattern().timeFieldName; + }, + }, + { + name: 'ranges', + default: [ + { + from: 'now-1w/w', + to: 'now', + }, + ], + }, + { + name: 'time_zone', + default: undefined, + // Implimentation method is the same as that of date_histogram + serialize: () => undefined, + write: (agg, output) => { + const field = agg.getParam('field'); + let tz = agg.getParam('time_zone'); - if (!tz && field) { - tz = get(agg.getIndexPattern(), [ - 'typeMeta', - 'aggs', - 'date_range', - field.name, - 'time_zone', - ]); - } - if (!tz) { - const config = getUiSettings(); - const detectedTimezone = moment.tz.guess(); - const tzOffset = moment().format('Z'); - const isDefaultTimezone = config.isDefault('dateFormat:tz'); + if (!tz && field) { + tz = get(agg.getIndexPattern(), [ + 'typeMeta', + 'aggs', + 'date_range', + field.name, + 'time_zone', + ]); + } + if (!tz) { + const detectedTimezone = moment.tz.guess(); + const tzOffset = moment().format('Z'); + const isDefaultTimezone = uiSettings.isDefault('dateFormat:tz'); - tz = isDefaultTimezone ? detectedTimezone || tzOffset : config.get('dateFormat:tz'); - } - output.params.time_zone = tz; + tz = isDefaultTimezone ? detectedTimezone || tzOffset : uiSettings.get('dateFormat:tz'); + } + output.params.time_zone = tz; + }, }, - }, - ], -}); + ], + }); diff --git a/src/plugins/data/public/search/aggs/buckets/filters.ts b/src/plugins/data/public/search/aggs/buckets/filters.ts index 0ad28b8be2132..8b9aca87f8735 100644 --- a/src/plugins/data/public/search/aggs/buckets/filters.ts +++ b/src/plugins/data/public/search/aggs/buckets/filters.ts @@ -17,9 +17,8 @@ * under the License. */ -import _ from 'lodash'; import { i18n } from '@kbn/i18n'; - +import { size, transform, cloneDeep } from 'lodash'; import { IUiSettingsClient } from 'src/core/public'; import { createFilterFilters } from './create_filter/filters'; @@ -27,7 +26,6 @@ import { toAngularJSON } from '../utils'; import { BucketAggType } from './_bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { Storage } from '../../../../../../plugins/kibana_utils/public'; - import { getEsQueryConfig, buildEsQuery, Query } from '../../../../common'; import { getQueryLog } from '../../../query'; @@ -43,9 +41,12 @@ interface FilterValue { id: string; } -export function getFiltersBucketAgg(deps: { uiSettings: IUiSettingsClient }) { - const { uiSettings } = deps; - return new BucketAggType({ +export interface FiltersBucketAggDependencies { + uiSettings: IUiSettingsClient; +} + +export const getFiltersBucketAgg = ({ uiSettings }: FiltersBucketAggDependencies) => + new BucketAggType({ name: BUCKET_TYPES.FILTERS, title: filtersTitle, createFilter: createFilterFilters, @@ -58,7 +59,7 @@ export function getFiltersBucketAgg(deps: { uiSettings: IUiSettingsClient }) { ], write(aggConfig, output) { const inFilters: FilterValue[] = aggConfig.params.filters; - if (!_.size(inFilters)) return; + if (!size(inFilters)) return; inFilters.forEach(filter => { const persistedLog = getQueryLog( @@ -70,10 +71,10 @@ export function getFiltersBucketAgg(deps: { uiSettings: IUiSettingsClient }) { persistedLog.add(filter.input.query); }); - const outFilters = _.transform( + const outFilters = transform( inFilters, function(filters, filter) { - const input = _.cloneDeep(filter.input); + const input = cloneDeep(filter.input); if (!input) { console.log('malformed filter agg params, missing "input" query'); // eslint-disable-line no-console @@ -100,7 +101,7 @@ export function getFiltersBucketAgg(deps: { uiSettings: IUiSettingsClient }) { {} ); - if (!_.size(outFilters)) return; + if (!size(outFilters)) return; const params = output.params || (output.params = {}); params.filters = outFilters; @@ -108,4 +109,3 @@ export function getFiltersBucketAgg(deps: { uiSettings: IUiSettingsClient }) { }, ], }); -} diff --git a/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts b/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts index 09dd03c759155..408cdf22bcbc2 100644 --- a/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts @@ -24,8 +24,6 @@ import { BUCKET_TYPES } from './bucket_agg_types'; import { IBucketAggConfig } from './_bucket_agg_type'; describe('Geohash Agg', () => { - // const typesRegistry = mockAggTypesRegistry([geoHashBucketAgg]); - const typesRegistry = mockAggTypesRegistry(); const getAggConfigs = (params?: Record<string, any>) => { const indexPattern = { id: '1234', @@ -63,7 +61,7 @@ describe('Geohash Agg', () => { }, }, ], - { typesRegistry } + { typesRegistry: mockAggTypesRegistry() } ); }; diff --git a/src/plugins/data/public/search/aggs/buckets/histogram.test.ts b/src/plugins/data/public/search/aggs/buckets/histogram.test.ts index 07cf022dca83c..c61b4ff37935a 100644 --- a/src/plugins/data/public/search/aggs/buckets/histogram.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/histogram.test.ts @@ -17,21 +17,29 @@ * under the License. */ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths import { coreMock } from '../../../../../../../src/core/public/mocks'; -import { setUiSettings } from '../../../../public/services'; import { AggConfigs } from '../agg_configs'; -import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; +import { mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { IBucketHistogramAggConfig, histogramBucketAgg, AutoBounds } from './histogram'; +import { + IBucketHistogramAggConfig, + getHistogramBucketAgg, + AutoBounds, + HistogramBucketAggDependencies, +} from './histogram'; import { BucketAggType } from './_bucket_agg_type'; describe('Histogram Agg', () => { + let aggTypesDependencies: HistogramBucketAggDependencies; + beforeEach(() => { - mockDataServices(); - }); + const { uiSettings, notifications } = coreMock.createSetup(); - const typesRegistry = mockAggTypesRegistry([histogramBucketAgg]); + aggTypesDependencies = { + uiSettings, + notifications, + }; + }); const getAggConfigs = (params: Record<string, any>) => { const indexPattern = { @@ -58,7 +66,7 @@ describe('Histogram Agg', () => { params, }, ], - { typesRegistry } + { typesRegistry: mockAggTypesRegistry([getHistogramBucketAgg(aggTypesDependencies)]) } ); }; @@ -76,7 +84,7 @@ describe('Histogram Agg', () => { let histogramType: BucketAggType<IBucketHistogramAggConfig>; beforeEach(() => { - histogramType = histogramBucketAgg; + histogramType = getHistogramBucketAgg(aggTypesDependencies); }); it('is ordered', () => { @@ -150,6 +158,14 @@ describe('Histogram Agg', () => { params?: Record<string, any>, autoBounds?: AutoBounds ) => { + aggTypesDependencies = { + ...aggTypesDependencies, + uiSettings: { + ...aggTypesDependencies.uiSettings, + get: () => maxBars as any, + }, + }; + const aggConfigs = getAggConfigs({ ...params, field: { @@ -162,15 +178,7 @@ describe('Histogram Agg', () => { aggConfig.setAutoBounds(autoBounds); } - const core = coreMock.createStart(); - setUiSettings({ - ...core.uiSettings, - get: () => maxBars as any, - }); - - const interval = aggConfig.write(aggConfigs).params; - setUiSettings(core.uiSettings); // clean up - return interval; + return aggConfig.write(aggConfigs).params; }; it('will respect the histogram:maxBars setting', () => { diff --git a/src/plugins/data/public/search/aggs/buckets/histogram.ts b/src/plugins/data/public/search/aggs/buckets/histogram.ts index 7ccd5ae4bf98c..bbffc0912bf0d 100644 --- a/src/plugins/data/public/search/aggs/buckets/histogram.ts +++ b/src/plugins/data/public/search/aggs/buckets/histogram.ts @@ -17,182 +17,190 @@ * under the License. */ -import _ from 'lodash'; +import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; +import { IUiSettingsClient, NotificationsSetup } from 'src/core/public'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { createFilterHistogram } from './create_filter/histogram'; import { BUCKET_TYPES } from './bucket_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; -import { getNotifications, getUiSettings } from '../../../../public/services'; export interface AutoBounds { min: number; max: number; } +export interface HistogramBucketAggDependencies { + uiSettings: IUiSettingsClient; + notifications: NotificationsSetup; +} + export interface IBucketHistogramAggConfig extends IBucketAggConfig { setAutoBounds: (bounds: AutoBounds) => void; getAutoBounds: () => AutoBounds; } -export const histogramBucketAgg = new BucketAggType<IBucketHistogramAggConfig>({ - name: BUCKET_TYPES.HISTOGRAM, - title: i18n.translate('data.search.aggs.buckets.histogramTitle', { - defaultMessage: 'Histogram', - }), - ordered: {}, - makeLabel(aggConfig) { - return aggConfig.getFieldDisplayName(); - }, - createFilter: createFilterHistogram, - decorateAggConfig() { - let autoBounds: AutoBounds; - - return { - setAutoBounds: { - configurable: true, - value(newValue: AutoBounds) { - autoBounds = newValue; +export const getHistogramBucketAgg = ({ + uiSettings, + notifications, +}: HistogramBucketAggDependencies) => + new BucketAggType<IBucketHistogramAggConfig>({ + name: BUCKET_TYPES.HISTOGRAM, + title: i18n.translate('data.search.aggs.buckets.histogramTitle', { + defaultMessage: 'Histogram', + }), + ordered: {}, + makeLabel(aggConfig) { + return aggConfig.getFieldDisplayName(); + }, + createFilter: createFilterHistogram, + decorateAggConfig() { + let autoBounds: AutoBounds; + + return { + setAutoBounds: { + configurable: true, + value(newValue: AutoBounds) { + autoBounds = newValue; + }, }, - }, - getAutoBounds: { - configurable: true, - value() { - return autoBounds; + getAutoBounds: { + configurable: true, + value() { + return autoBounds; + }, }, - }, - }; - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.NUMBER, + }; }, - { - /* - * This parameter can be set if you want the auto scaled interval to always - * be a multiple of a specific base. - */ - name: 'intervalBase', - default: null, - write: () => {}, - }, - { - name: 'interval', - modifyAggConfigOnSearchRequestStart( - aggConfig: IBucketHistogramAggConfig, - searchSource: any, - options: any - ) { - const field = aggConfig.getField(); - const aggBody = field.scripted - ? { script: { source: field.script, lang: field.lang } } - : { field: field.name }; - - const childSearchSource = searchSource - .createChild() - .setField('size', 0) - .setField('aggs', { - maxAgg: { - max: aggBody, - }, - minAgg: { - min: aggBody, - }, - }); - - return childSearchSource - .fetch(options) - .then((resp: any) => { - aggConfig.setAutoBounds({ - min: _.get(resp, 'aggregations.minAgg.value'), - max: _.get(resp, 'aggregations.maxAgg.value'), - }); - }) - .catch((e: Error) => { - if (e.name === 'AbortError') return; - getNotifications().toasts.addWarning( - i18n.translate('data.search.aggs.histogram.missingMaxMinValuesWarning', { - defaultMessage: - 'Unable to retrieve max and min values to auto-scale histogram buckets. This may lead to poor visualization performance.', - }) - ); - }); + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.NUMBER, }, - write(aggConfig, output) { - let interval = parseFloat(aggConfig.params.interval); - if (interval <= 0) { - interval = 1; - } - const autoBounds = aggConfig.getAutoBounds(); - - // ensure interval does not create too many buckets and crash browser - if (autoBounds) { - const range = autoBounds.max - autoBounds.min; - const bars = range / interval; - - const config = getUiSettings(); - if (bars > config.get('histogram:maxBars')) { - const minInterval = range / config.get('histogram:maxBars'); - - // Round interval by order of magnitude to provide clean intervals - // Always round interval up so there will always be less buckets than histogram:maxBars - const orderOfMagnitude = Math.pow(10, Math.floor(Math.log10(minInterval))); - let roundInterval = orderOfMagnitude; - - while (roundInterval < minInterval) { - roundInterval += orderOfMagnitude; + { + /* + * This parameter can be set if you want the auto scaled interval to always + * be a multiple of a specific base. + */ + name: 'intervalBase', + default: null, + write: () => {}, + }, + { + name: 'interval', + modifyAggConfigOnSearchRequestStart( + aggConfig: IBucketHistogramAggConfig, + searchSource: any, + options: any + ) { + const field = aggConfig.getField(); + const aggBody = field.scripted + ? { script: { source: field.script, lang: field.lang } } + : { field: field.name }; + + const childSearchSource = searchSource + .createChild() + .setField('size', 0) + .setField('aggs', { + maxAgg: { + max: aggBody, + }, + minAgg: { + min: aggBody, + }, + }); + + return childSearchSource + .fetch(options) + .then((resp: any) => { + aggConfig.setAutoBounds({ + min: get(resp, 'aggregations.minAgg.value'), + max: get(resp, 'aggregations.maxAgg.value'), + }); + }) + .catch((e: Error) => { + if (e.name === 'AbortError') return; + notifications.toasts.addWarning( + i18n.translate('data.search.aggs.histogram.missingMaxMinValuesWarning', { + defaultMessage: + 'Unable to retrieve max and min values to auto-scale histogram buckets. This may lead to poor visualization performance.', + }) + ); + }); + }, + write(aggConfig, output) { + let interval = parseFloat(aggConfig.params.interval); + if (interval <= 0) { + interval = 1; + } + const autoBounds = aggConfig.getAutoBounds(); + + // ensure interval does not create too many buckets and crash browser + if (autoBounds) { + const range = autoBounds.max - autoBounds.min; + const bars = range / interval; + + if (bars > uiSettings.get('histogram:maxBars')) { + const minInterval = range / uiSettings.get('histogram:maxBars'); + + // Round interval by order of magnitude to provide clean intervals + // Always round interval up so there will always be less buckets than histogram:maxBars + const orderOfMagnitude = Math.pow(10, Math.floor(Math.log10(minInterval))); + let roundInterval = orderOfMagnitude; + + while (roundInterval < minInterval) { + roundInterval += orderOfMagnitude; + } + interval = roundInterval; } - interval = roundInterval; } - } - const base = aggConfig.params.intervalBase; - - if (base) { - if (interval < base) { - // In case the specified interval is below the base, just increase it to it's base - interval = base; - } else if (interval % base !== 0) { - // In case the interval is not a multiple of the base round it to the next base - interval = Math.round(interval / base) * base; + const base = aggConfig.params.intervalBase; + + if (base) { + if (interval < base) { + // In case the specified interval is below the base, just increase it to it's base + interval = base; + } else if (interval % base !== 0) { + // In case the interval is not a multiple of the base round it to the next base + interval = Math.round(interval / base) * base; + } } - } - output.params.interval = interval; + output.params.interval = interval; + }, }, - }, - { - name: 'min_doc_count', - default: false, - write(aggConfig, output) { - if (aggConfig.params.min_doc_count) { - output.params.min_doc_count = 0; - } else { - output.params.min_doc_count = 1; - } + { + name: 'min_doc_count', + default: false, + write(aggConfig, output) { + if (aggConfig.params.min_doc_count) { + output.params.min_doc_count = 0; + } else { + output.params.min_doc_count = 1; + } + }, }, - }, - { - name: 'has_extended_bounds', - default: false, - write: () => {}, - }, - { - name: 'extended_bounds', - default: { - min: '', - max: '', + { + name: 'has_extended_bounds', + default: false, + write: () => {}, }, - write(aggConfig, output) { - const { min, max } = aggConfig.params.extended_bounds; + { + name: 'extended_bounds', + default: { + min: '', + max: '', + }, + write(aggConfig, output) { + const { min, max } = aggConfig.params.extended_bounds; - if (aggConfig.params.has_extended_bounds && (min || min === 0) && (max || max === 0)) { - output.params.extended_bounds = { min, max }; - } + if (aggConfig.params.has_extended_bounds && (min || min === 0) && (max || max === 0)) { + output.params.extended_bounds = { min, max }; + } + }, + shouldShow: (aggConfig: IBucketAggConfig) => aggConfig.params.has_extended_bounds, }, - shouldShow: (aggConfig: IBucketAggConfig) => aggConfig.params.has_extended_bounds, - }, - ], -}); + ], + }); diff --git a/src/plugins/data/public/search/aggs/buckets/range.test.ts b/src/plugins/data/public/search/aggs/buckets/range.test.ts index d9e1af149524c..bf3711543ae88 100644 --- a/src/plugins/data/public/search/aggs/buckets/range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/range.test.ts @@ -48,8 +48,6 @@ describe('Range Agg', () => { mockDataServices(); }); - const typesRegistry = mockAggTypesRegistry([rangeBucketAgg]); - const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { @@ -86,7 +84,7 @@ describe('Range Agg', () => { }, }, ], - { typesRegistry } + { typesRegistry: mockAggTypesRegistry([rangeBucketAgg]) } ); }; diff --git a/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts b/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts index cee3ed506c29c..1c221126c35e0 100644 --- a/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts @@ -26,7 +26,6 @@ import { IBucketAggConfig } from './_bucket_agg_type'; describe('Significant Terms Agg', () => { describe('order agg editor UI', () => { describe('convert include/exclude from old format', () => { - const typesRegistry = mockAggTypesRegistry([significantTermsBucketAgg]); const getAggConfigs = (params: Record<string, any> = {}) => { const indexPattern = { id: '1234', @@ -52,12 +51,12 @@ describe('Significant Terms Agg', () => { params, }, ], - { typesRegistry } + { typesRegistry: mockAggTypesRegistry([significantTermsBucketAgg]) } ); }; const testSerializeAndWrite = (aggs: IAggConfigs) => { - const agg = aggs.aggs[0]; + const [agg] = aggs.aggs; const { [BUCKET_TYPES.SIGNIFICANT_TERMS]: params } = agg.toDsl(); expect(params.field).toBe('field'); diff --git a/src/plugins/data/public/search/aggs/buckets/terms.test.ts b/src/plugins/data/public/search/aggs/buckets/terms.test.ts index 9a4f28afd3edf..280d78f6620bd 100644 --- a/src/plugins/data/public/search/aggs/buckets/terms.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/terms.test.ts @@ -23,7 +23,6 @@ import { BUCKET_TYPES } from './bucket_agg_types'; describe('Terms Agg', () => { describe('order agg editor UI', () => { - const typesRegistry = mockAggTypesRegistry(); const getAggConfigs = (params: Record<string, any> = {}) => { const indexPattern = { id: '1234', @@ -48,7 +47,7 @@ describe('Terms Agg', () => { type: BUCKET_TYPES.TERMS, }, ], - { typesRegistry } + { typesRegistry: mockAggTypesRegistry() } ); }; diff --git a/src/plugins/data/public/search/aggs/index.test.ts b/src/plugins/data/public/search/aggs/index.test.ts index b5dedc9d45e84..8c0e47763c295 100644 --- a/src/plugins/data/public/search/aggs/index.test.ts +++ b/src/plugins/data/public/search/aggs/index.test.ts @@ -22,24 +22,29 @@ import { getAggTypes } from './index'; import { isBucketAggType } from './buckets/_bucket_agg_type'; import { isMetricAggType } from './metrics/metric_agg_type'; +import { QueryStart } from '../../query'; -const aggTypes = getAggTypes({ uiSettings: coreMock.createStart().uiSettings }); +describe('AggTypesComponent', () => { + const core = coreMock.createSetup(); + const aggTypes = getAggTypes({ + uiSettings: core.uiSettings, + notifications: core.notifications, + query: {} as QueryStart, + }); -const bucketAggs = aggTypes.buckets; -const metricAggs = aggTypes.metrics; + const { buckets, metrics } = aggTypes; -describe('AggTypesComponent', () => { describe('bucket aggs', () => { - it('all extend BucketAggType', () => { - bucketAggs.forEach(bucketAgg => { + test('all extend BucketAggType', () => { + buckets.forEach(bucketAgg => { expect(isBucketAggType(bucketAgg)).toBeTruthy(); }); }); }); describe('metric aggs', () => { - it('all extend MetricAggType', () => { - metricAggs.forEach(metricAgg => { + test('all extend MetricAggType', () => { + metrics.forEach(metricAgg => { expect(isMetricAggType(metricAgg)).toBeTruthy(); }); }); diff --git a/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts b/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts index 1ebd0ea29c9ff..57d27b7da6313 100644 --- a/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts +++ b/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts @@ -23,6 +23,7 @@ import { AggTypesRegistry, AggTypesRegistryStart } from '../agg_types_registry'; import { getAggTypes } from '../agg_types'; import { BucketAggType } from '../buckets/_bucket_agg_type'; import { MetricAggType } from '../metrics/metric_agg_type'; +import { queryServiceMock } from '../../../query/mocks'; /** * Testing utility which creates a new instance of AggTypesRegistry, @@ -51,7 +52,13 @@ export function mockAggTypesRegistry<T extends BucketAggType<any> | MetricAggTyp } }); } else { - const aggTypes = getAggTypes({ uiSettings: coreMock.createSetup().uiSettings }); + const core = coreMock.createSetup(); + const aggTypes = getAggTypes({ + uiSettings: core.uiSettings, + notifications: core.notifications, + query: queryServiceMock.createSetupContract(), + }); + aggTypes.buckets.forEach(type => registrySetup.registerBucket(type)); aggTypes.metrics.forEach(type => registrySetup.registerMetric(type)); } diff --git a/src/plugins/data/public/search/search_service.test.ts b/src/plugins/data/public/search/search_service.test.ts index 4bf027b07b833..19308dd387d3a 100644 --- a/src/plugins/data/public/search/search_service.test.ts +++ b/src/plugins/data/public/search/search_service.test.ts @@ -34,7 +34,7 @@ describe('Search service', () => { describe('setup()', () => { it('exposes proper contract', async () => { const setup = searchService.setup(mockCoreSetup, { - version: '8', + packageInfo: { version: '8' }, } as any); expect(setup).toHaveProperty('registerSearchStrategyProvider'); }); diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index 311a8a2fc6f60..dc1c99f76d59a 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -25,6 +25,7 @@ import { TStrategyTypes } from './strategy_types'; import { getEsClient, LegacyApiCaller } from './es_client'; import { ES_SEARCH_STRATEGY, DEFAULT_SEARCH_STRATEGY } from '../../common/search'; import { esSearchStrategyProvider } from './es_search/es_search_strategy'; +import { QuerySetup } from '../query/query_service'; import { SearchInterceptor } from './search_interceptor'; import { getAggTypes, @@ -40,6 +41,11 @@ import { siblingPipelineAggHelper, } from './aggs'; +interface SearchServiceSetupDependencies { + packageInfo: PackageInfo; + query: QuerySetup; +} + /** * The search plugin exposes two registration methods for other plugins: * - registerSearchStrategyProvider for plugins to add their own custom @@ -73,13 +79,21 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> { return strategyProvider; }; - public setup(core: CoreSetup, packageInfo: PackageInfo): ISearchSetup { + public setup( + core: CoreSetup, + { packageInfo, query }: SearchServiceSetupDependencies + ): ISearchSetup { this.esClient = getEsClient(core.injectedMetadata, core.http, packageInfo); this.registerSearchStrategyProvider(SYNC_SEARCH_STRATEGY, syncSearchStrategyProvider); this.registerSearchStrategyProvider(ES_SEARCH_STRATEGY, esSearchStrategyProvider); const aggTypesSetup = this.aggTypesRegistry.setup(); - const aggTypes = getAggTypes({ uiSettings: core.uiSettings }); + const aggTypes = getAggTypes({ + query, + uiSettings: core.uiSettings, + notifications: core.notifications, + }); + aggTypes.buckets.forEach(b => aggTypesSetup.registerBucket(b)); aggTypes.metrics.forEach(m => aggTypesSetup.registerMetric(m)); diff --git a/src/plugins/data/public/services.ts b/src/plugins/data/public/services.ts index 2af87d84b780e..199ba17b3b81b 100644 --- a/src/plugins/data/public/services.ts +++ b/src/plugins/data/public/services.ts @@ -17,8 +17,7 @@ * under the License. */ -import { NotificationsStart } from 'src/core/public'; -import { CoreSetup, CoreStart } from 'kibana/public'; +import { NotificationsStart, CoreSetup, CoreStart } from 'src/core/public'; import { FieldFormatsStart } from './field_formats'; import { createGetterSetter } from '../../kibana_utils/public'; import { IndexPatternsContract } from './index_patterns'; diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index 5038b4226fad8..47bef4255347c 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -173,6 +173,7 @@ export { ISearchContext, TSearchStrategyProvider, getDefaultSearchParams, + getTotalLoaded, } from './search'; // Search namespace diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.ts b/src/plugins/data/server/search/es_search/es_search_strategy.ts index b4ee02eefaf84..47cad7aa6b4d7 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.ts @@ -21,7 +21,7 @@ import { APICaller } from 'kibana/server'; import { SearchResponse } from 'elasticsearch'; import { ES_SEARCH_STRATEGY } from '../../../common/search'; import { ISearchStrategy, TSearchStrategyProvider } from '../i_search_strategy'; -import { getDefaultSearchParams, ISearchContext } from '..'; +import { getDefaultSearchParams, getTotalLoaded, ISearchContext } from '..'; export const esSearchStrategyProvider: TSearchStrategyProvider<typeof ES_SEARCH_STRATEGY> = ( context: ISearchContext, @@ -46,9 +46,7 @@ export const esSearchStrategyProvider: TSearchStrategyProvider<typeof ES_SEARCH_ // The above query will either complete or timeout and throw an error. // There is no progress indication on this api. - const { total, failed, successful } = rawResponse._shards; - const loaded = failed + successful; - return { total, loaded, rawResponse }; + return { rawResponse, ...getTotalLoaded(rawResponse._shards) }; }, }; }; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy.ts b/src/plugins/data/server/search/es_search/get_total_loaded.test.ts similarity index 67% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/legacy.ts rename to src/plugins/data/server/search/es_search/get_total_loaded.test.ts index 216e523b07141..74e2873ede762 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy.ts +++ b/src/plugins/data/server/search/es_search/get_total_loaded.test.ts @@ -17,12 +17,20 @@ * under the License. */ -// eslint-disable-next-line -import { npSetup, npStart } from 'ui/new_platform'; -import { PluginInitializerContext } from '../../../../../../core/public'; -import { plugin } from '.'; +import { getTotalLoaded } from './get_total_loaded'; -const pluginInstance = plugin({} as PluginInitializerContext); +describe('getTotalLoaded', () => { + it('returns the total/loaded, not including skipped', () => { + const result = getTotalLoaded({ + successful: 10, + failed: 5, + skipped: 5, + total: 100, + }); -export const setup = pluginInstance.setup(npSetup.core, npSetup.plugins); -export const start = pluginInstance.start(npStart.core, npStart.plugins); + expect(result).toEqual({ + total: 100, + loaded: 15, + }); + }); +}); diff --git a/src/plugins/data/server/search/es_search/get_total_loaded.ts b/src/plugins/data/server/search/es_search/get_total_loaded.ts new file mode 100644 index 0000000000000..b5af600e84eff --- /dev/null +++ b/src/plugins/data/server/search/es_search/get_total_loaded.ts @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ShardsResponse } from 'elasticsearch'; + +/** + * Get the `total`/`loaded` for this response (see `IKibanaSearchResponse`). Note that `skipped` is + * not included as it is already included in `successful`. + * @internal + */ +export function getTotalLoaded({ total, failed, successful }: ShardsResponse) { + const loaded = failed + successful; + return { total, loaded }; +} diff --git a/src/plugins/data/server/search/es_search/index.ts b/src/plugins/data/server/search/es_search/index.ts index 5a8b3bc94c679..20006b70730d8 100644 --- a/src/plugins/data/server/search/es_search/index.ts +++ b/src/plugins/data/server/search/es_search/index.ts @@ -20,3 +20,4 @@ export { ES_SEARCH_STRATEGY, IEsSearchRequest, IEsSearchResponse } from '../../../common/search'; export { esSearchStrategyProvider } from './es_search_strategy'; export { getDefaultSearchParams } from './get_default_search_params'; +export { getTotalLoaded } from './get_total_loaded'; diff --git a/src/plugins/data/server/search/index.ts b/src/plugins/data/server/search/index.ts index 15738a3befb27..e08eba1cad831 100644 --- a/src/plugins/data/server/search/index.ts +++ b/src/plugins/data/server/search/index.ts @@ -33,4 +33,4 @@ export { TStrategyTypes } from './strategy_types'; export { TSearchStrategyProvider } from './i_search_strategy'; -export { getDefaultSearchParams } from './es_search'; +export { getDefaultSearchParams, getTotalLoaded } from './es_search'; diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 1abc74fe07ccc..c41023eab6d20 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -125,6 +125,7 @@ import { SearchResponse } from 'elasticsearch'; import { SearchShardsParams } from 'elasticsearch'; import { SearchTemplateParams } from 'elasticsearch'; import { ShallowPromise } from '@kbn/utility-types'; +import { ShardsResponse } from 'elasticsearch'; import { SnapshotCreateParams } from 'elasticsearch'; import { SnapshotCreateRepositoryParams } from 'elasticsearch'; import { SnapshotDeleteParams } from 'elasticsearch'; @@ -330,6 +331,12 @@ export function getDefaultSearchParams(config: SharedGlobalConfig): { restTotalHitsAsInt: boolean; }; +// @internal +export function getTotalLoaded({ total, failed, successful }: ShardsResponse): { + total: number; + loaded: number; +}; + // Warning: (ae-missing-release-tag) "IFieldFormatsRegistry" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -730,12 +737,12 @@ export type TSearchStrategyProvider<T extends TStrategyTypes> = (context: ISearc // src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:130:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:130:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:181:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:182:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:183:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:184:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:185:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:188:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:182:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:183:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:184:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:185:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:186:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:189:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/server/plugin.ts:64:14 - (ae-forgotten-export) The symbol "ISearchSetup" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/kibana_utils/public/history/redirect_when_missing.tsx b/src/plugins/kibana_utils/public/history/redirect_when_missing.tsx index cbdeef6fbe96c..8a4cc88999bfe 100644 --- a/src/plugins/kibana_utils/public/history/redirect_when_missing.tsx +++ b/src/plugins/kibana_utils/public/history/redirect_when_missing.tsx @@ -37,6 +37,7 @@ export function redirectWhenMissing({ history, mapping, toastNotifications, + onBeforeRedirect, }: { history: History; /** @@ -48,6 +49,10 @@ export function redirectWhenMissing({ * Toast notifications service to show toasts in error cases. */ toastNotifications: ToastsSetup; + /** + * Optional callback invoked directly before a redirect is triggered + */ + onBeforeRedirect?: (error: SavedObjectNotFound) => void; }) { let localMappingObject: Mapping; @@ -75,6 +80,9 @@ export function redirectWhenMissing({ text: toMountPoint(<MarkdownSimple>{error.message}</MarkdownSimple>), }); + if (onBeforeRedirect) { + onBeforeRedirect(error); + } history.replace(url); }; } diff --git a/src/plugins/saved_objects/kibana.json b/src/plugins/saved_objects/kibana.json index 4081c9a4b21b9..0792955b7c5f1 100644 --- a/src/plugins/saved_objects/kibana.json +++ b/src/plugins/saved_objects/kibana.json @@ -3,5 +3,5 @@ "version": "kibana", "server": false, "ui": true, - "requiredPlugins": [] + "requiredPlugins": ["data"] } diff --git a/src/plugins/saved_objects/public/plugin.ts b/src/plugins/saved_objects/public/plugin.ts index 5092f7a0b7b33..0f5773c00283e 100644 --- a/src/plugins/saved_objects/public/plugin.ts +++ b/src/plugins/saved_objects/public/plugin.ts @@ -17,11 +17,31 @@ * under the License. */ -import { Plugin } from 'src/core/public'; +import { CoreStart, Plugin } from 'src/core/public'; import './index.scss'; +import { createSavedObjectClass } from './saved_object'; +import { DataPublicPluginStart } from '../../data/public'; -export class SavedObjectsPublicPlugin implements Plugin { +export interface SavedObjectsStart { + SavedObjectClass: any; +} + +export interface SavedObjectsStartDeps { + data: DataPublicPluginStart; +} + +export class SavedObjectsPublicPlugin + implements Plugin<void, SavedObjectsStart, object, SavedObjectsStartDeps> { public setup() {} - public start() {} + public start(core: CoreStart, { data }: SavedObjectsStartDeps) { + return { + SavedObjectClass: createSavedObjectClass({ + indexPatterns: data.indexPatterns, + savedObjectsClient: core.savedObjects.client, + chrome: core.chrome, + overlays: core.overlays, + }), + }; + } } diff --git a/src/plugins/visualizations/kibana.json b/src/plugins/visualizations/kibana.json index 8e63ea7833327..cd22b1375ae1b 100644 --- a/src/plugins/visualizations/kibana.json +++ b/src/plugins/visualizations/kibana.json @@ -3,7 +3,5 @@ "version": "kibana", "server": true, "ui": true, - "requiredPlugins": [ - "expressions" - ] + "requiredPlugins": ["data", "expressions", "uiActions", "embeddable", "usageCollection"] } diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/__snapshots__/visualization_noresults.test.js.snap b/src/plugins/visualizations/public/components/__snapshots__/visualization_noresults.test.js.snap similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/components/__snapshots__/visualization_noresults.test.js.snap rename to src/plugins/visualizations/public/components/__snapshots__/visualization_noresults.test.js.snap diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/__snapshots__/visualization_requesterror.test.js.snap b/src/plugins/visualizations/public/components/__snapshots__/visualization_requesterror.test.js.snap similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/components/__snapshots__/visualization_requesterror.test.js.snap rename to src/plugins/visualizations/public/components/__snapshots__/visualization_requesterror.test.js.snap diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/_index.scss b/src/plugins/visualizations/public/components/_index.scss similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/components/_index.scss rename to src/plugins/visualizations/public/components/_index.scss diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/_visualization.scss b/src/plugins/visualizations/public/components/_visualization.scss similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/components/_visualization.scss rename to src/plugins/visualizations/public/components/_visualization.scss diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/index.ts b/src/plugins/visualizations/public/components/index.ts similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/components/index.ts rename to src/plugins/visualizations/public/components/index.ts diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization.test.js b/src/plugins/visualizations/public/components/visualization.test.js similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization.test.js rename to src/plugins/visualizations/public/components/visualization.test.js diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization.tsx b/src/plugins/visualizations/public/components/visualization.tsx similarity index 96% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization.tsx rename to src/plugins/visualizations/public/components/visualization.tsx index 5296de365daec..c17e088d7635b 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization.tsx +++ b/src/plugins/visualizations/public/components/visualization.tsx @@ -19,7 +19,7 @@ import { get } from 'lodash'; import React from 'react'; -import { PersistedState } from '../../../../../../../plugins/visualizations/public'; +import { PersistedState } from '../../../../plugins/visualizations/public'; import { memoizeLast } from '../legacy/memoize'; import { VisualizationChart } from './visualization_chart'; import { VisualizationNoResults } from './visualization_noresults'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_chart.test.js b/src/plugins/visualizations/public/components/visualization_chart.test.js similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_chart.test.js rename to src/plugins/visualizations/public/components/visualization_chart.test.js diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_chart.tsx b/src/plugins/visualizations/public/components/visualization_chart.tsx similarity index 96% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_chart.tsx rename to src/plugins/visualizations/public/components/visualization_chart.tsx index fcfbc8445952c..7d163d2067ee5 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_chart.tsx +++ b/src/plugins/visualizations/public/components/visualization_chart.tsx @@ -20,9 +20,9 @@ import React from 'react'; import * as Rx from 'rxjs'; import { debounceTime, filter, share, switchMap } from 'rxjs/operators'; -import { PersistedState } from '../../../../../../../plugins/visualizations/public'; +import { PersistedState } from '../../../../plugins/visualizations/public'; import { VisualizationController } from '../types'; -import { ResizeChecker } from '../../../../../../../plugins/kibana_utils/public'; +import { ResizeChecker } from '../../../../plugins/kibana_utils/public'; import { ExprVis } from '../expressions/vis'; interface VisualizationChartProps { diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_noresults.test.js b/src/plugins/visualizations/public/components/visualization_noresults.test.js similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_noresults.test.js rename to src/plugins/visualizations/public/components/visualization_noresults.test.js diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_noresults.tsx b/src/plugins/visualizations/public/components/visualization_noresults.tsx similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_noresults.tsx rename to src/plugins/visualizations/public/components/visualization_noresults.tsx diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_requesterror.test.js b/src/plugins/visualizations/public/components/visualization_requesterror.test.js similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_requesterror.test.js rename to src/plugins/visualizations/public/components/visualization_requesterror.test.js diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_requesterror.tsx b/src/plugins/visualizations/public/components/visualization_requesterror.tsx similarity index 96% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_requesterror.tsx rename to src/plugins/visualizations/public/components/visualization_requesterror.tsx index 406f24741c911..3e677e609ad9a 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_requesterror.tsx +++ b/src/plugins/visualizations/public/components/visualization_requesterror.tsx @@ -19,7 +19,7 @@ import { EuiIcon, EuiSpacer, EuiText } from '@elastic/eui'; import React from 'react'; -import { SearchError } from '../../../../../../../plugins/data/public'; +import { SearchError } from '../../../../plugins/data/public'; interface VisualizationRequestErrorProps { onInit?: () => void; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/_embeddables.scss b/src/plugins/visualizations/public/embeddable/_embeddables.scss similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/_embeddables.scss rename to src/plugins/visualizations/public/embeddable/_embeddables.scss diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/_index.scss b/src/plugins/visualizations/public/embeddable/_index.scss similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/_index.scss rename to src/plugins/visualizations/public/embeddable/_index.scss diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/_visualize_lab_disabled.scss b/src/plugins/visualizations/public/embeddable/_visualize_lab_disabled.scss similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/_visualize_lab_disabled.scss rename to src/plugins/visualizations/public/embeddable/_visualize_lab_disabled.scss diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/constants.ts b/src/plugins/visualizations/public/embeddable/constants.ts similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/constants.ts rename to src/plugins/visualizations/public/embeddable/constants.ts diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/disabled_lab_embeddable.tsx b/src/plugins/visualizations/public/embeddable/disabled_lab_embeddable.tsx similarity index 94% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/disabled_lab_embeddable.tsx rename to src/plugins/visualizations/public/embeddable/disabled_lab_embeddable.tsx index fbb2eba3afe79..af8121d8bf033 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/disabled_lab_embeddable.tsx +++ b/src/plugins/visualizations/public/embeddable/disabled_lab_embeddable.tsx @@ -19,7 +19,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { Embeddable, EmbeddableOutput } from '../../../../../../../plugins/embeddable/public'; +import { Embeddable, EmbeddableOutput } from '../../../../plugins/embeddable/public'; import { DisabledLabVisualization } from './disabled_lab_visualization'; import { VisualizeInput } from './visualize_embeddable'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/disabled_lab_visualization.tsx b/src/plugins/visualizations/public/embeddable/disabled_lab_visualization.tsx similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/disabled_lab_visualization.tsx rename to src/plugins/visualizations/public/embeddable/disabled_lab_visualization.tsx diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/events.ts b/src/plugins/visualizations/public/embeddable/events.ts similarity index 90% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/events.ts rename to src/plugins/visualizations/public/embeddable/events.ts index 53d04bf6eb04a..0957895a21403 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/events.ts +++ b/src/plugins/visualizations/public/embeddable/events.ts @@ -17,10 +17,7 @@ * under the License. */ -import { - SELECT_RANGE_TRIGGER, - VALUE_CLICK_TRIGGER, -} from '../../../../../../../plugins/ui_actions/public'; +import { SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER } from '../../../../plugins/ui_actions/public'; export interface VisEventToTrigger { ['brush']: typeof SELECT_RANGE_TRIGGER; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/get_index_pattern.ts b/src/plugins/visualizations/public/embeddable/get_index_pattern.ts similarity index 97% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/get_index_pattern.ts rename to src/plugins/visualizations/public/embeddable/get_index_pattern.ts index 05ce68221eaf0..c12c95145fe44 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/get_index_pattern.ts +++ b/src/plugins/visualizations/public/embeddable/get_index_pattern.ts @@ -22,7 +22,7 @@ import { indexPatterns, IIndexPattern, IndexPatternAttributes, -} from '../../../../../../../plugins/data/public'; +} from '../../../../plugins/data/public'; import { getUISettings, getSavedObjects } from '../services'; export async function getIndexPattern( diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/index.ts b/src/plugins/visualizations/public/embeddable/index.ts similarity index 95% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/index.ts rename to src/plugins/visualizations/public/embeddable/index.ts index a1cd31eebef20..78f9827ffde3e 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/index.ts +++ b/src/plugins/visualizations/public/embeddable/index.ts @@ -20,3 +20,4 @@ export { DisabledLabEmbeddable } from './disabled_lab_embeddable'; export { VisualizeEmbeddable, VisualizeInput } from './visualize_embeddable'; export { VisualizeEmbeddableFactory } from './visualize_embeddable_factory'; export { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; +export { VIS_EVENT_TO_TRIGGER } from './events'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts similarity index 97% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts rename to src/plugins/visualizations/public/embeddable/visualize_embeddable.ts index bcca4bdf67dcf..0c7e732f0b185 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts @@ -28,19 +28,16 @@ import { esFilters, Filter, TimefilterContract, -} from '../../../../../../../plugins/data/public'; +} from '../../../../plugins/data/public'; import { EmbeddableInput, EmbeddableOutput, Embeddable, Container, EmbeddableVisTriggerContext, -} from '../../../../../../../plugins/embeddable/public'; -import { dispatchRenderComplete } from '../../../../../../../plugins/kibana_utils/public'; -import { - IExpressionLoaderParams, - ExpressionsStart, -} from '../../../../../../../plugins/expressions/public'; +} from '../../../../plugins/embeddable/public'; +import { dispatchRenderComplete } from '../../../../plugins/kibana_utils/public'; +import { IExpressionLoaderParams, ExpressionsStart } from '../../../../plugins/expressions/public'; import { buildPipeline } from '../legacy/build_pipeline'; import { Vis } from '../vis'; import { getExpressions, getUiActions } from '../services'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable_factory.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx similarity index 97% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable_factory.tsx rename to src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx index 36d3d8bb07d29..428f1865e6566 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable_factory.tsx +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx @@ -18,13 +18,13 @@ */ import { i18n } from '@kbn/i18n'; -import { SavedObjectAttributes } from '../../../../../../../core/public'; +import { SavedObjectAttributes } from '../../../../core/public'; import { Container, EmbeddableFactory, EmbeddableOutput, ErrorEmbeddable, -} from '../../../../../../../plugins/embeddable/public'; +} from '../../../../plugins/embeddable/public'; import { DisabledLabEmbeddable } from './disabled_lab_embeddable'; import { VisualizeEmbeddable, VisualizeInput, VisualizeOutput } from './visualize_embeddable'; import { Vis } from '../types'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/vis.ts b/src/plugins/visualizations/public/expressions/vis.ts similarity index 95% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/vis.ts rename to src/plugins/visualizations/public/expressions/vis.ts index 3b0458a6c8dcc..a7d4a28070620 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/vis.ts +++ b/src/plugins/visualizations/public/expressions/vis.ts @@ -29,11 +29,10 @@ import { EventEmitter } from 'events'; import _ from 'lodash'; -import { PersistedState } from '../../../../../../../plugins/visualizations/public'; +import { VisParams, PersistedState } from '../../../../plugins/visualizations/public'; import { getTypes } from '../services'; import { VisType } from '../vis_types'; -import { VisParams } from '../types'; export interface ExprVisState { title?: string; @@ -82,10 +81,11 @@ export class ExprVis extends EventEmitter { private getType(type: string | VisType) { if (_.isString(type)) { - return getTypes().get(type); - if (!this.type) { + const newType = getTypes().get(type); + if (!newType) { throw new Error(`Invalid type "${type}"`); } + return newType; } else { return type; } diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_function.ts b/src/plugins/visualizations/public/expressions/visualization_function.ts similarity index 95% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_function.ts rename to src/plugins/visualizations/public/expressions/visualization_function.ts index d98eda4c50ef9..52177f6ec381a 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_function.ts +++ b/src/plugins/visualizations/public/expressions/visualization_function.ts @@ -19,14 +19,8 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { - VisResponseValue, - PersistedState, -} from '../../../../../../../plugins/visualizations/public'; -import { - ExpressionFunctionDefinition, - Render, -} from '../../../../../../../plugins/expressions/public'; +import { VisResponseValue, PersistedState } from '../../../../plugins/visualizations/public'; +import { ExpressionFunctionDefinition, Render } from '../../../../plugins/expressions/public'; import { getTypes, getIndexPatterns, getFilterManager } from '../services'; interface Arguments { diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_renderer.tsx b/src/plugins/visualizations/public/expressions/visualization_renderer.tsx similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_renderer.tsx rename to src/plugins/visualizations/public/expressions/visualization_renderer.tsx diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/_index.scss b/src/plugins/visualizations/public/index.scss similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/_index.scss rename to src/plugins/visualizations/public/index.scss diff --git a/src/plugins/visualizations/public/index.ts b/src/plugins/visualizations/public/index.ts index c08dbf890b8da..7df420e7ba585 100644 --- a/src/plugins/visualizations/public/index.ts +++ b/src/plugins/visualizations/public/index.ts @@ -17,15 +17,37 @@ * under the License. */ -import { PluginInitializerContext } from '../../../core/public'; -import { VisualizationsPublicPlugin } from './plugin'; +import './index.scss'; + +import { PublicContract } from '@kbn/utility-types'; +import { PluginInitializerContext } from 'src/core/public'; +import { VisualizationsPlugin, VisualizationsSetup, VisualizationsStart } from './plugin'; +import { VisualizeEmbeddableFactory, VisualizeEmbeddable } from './embeddable'; +import { ExprVis as ExprVisClass } from './expressions/vis'; export function plugin(initializerContext: PluginInitializerContext) { - return new VisualizationsPublicPlugin(initializerContext); + return new VisualizationsPlugin(initializerContext); } -export { VisualizationsPublicPlugin as Plugin }; -export * from './plugin'; -export * from './types'; +/** @public static code */ +export { Vis } from './vis'; +export { TypesService } from './vis_types/types_service'; +export { VISUALIZE_EMBEDDABLE_TYPE, VIS_EVENT_TO_TRIGGER } from './embeddable'; +/** @public types */ +export { VisualizationsSetup, VisualizationsStart }; +export { VisTypeAlias, VisType } from './vis_types'; +export { VisParams, SerializedVis, SerializedVisData, VisData } from './vis'; +export type VisualizeEmbeddableFactoryContract = PublicContract<VisualizeEmbeddableFactory>; +export type VisualizeEmbeddableContract = PublicContract<VisualizeEmbeddable>; +export { VisualizeInput } from './embeddable'; +export type ExprVis = ExprVisClass; +export { SchemaConfig } from './legacy/build_pipeline'; export { PersistedState } from './persisted_state'; +export { + VisualizationController, + SavedVisState, + ISavedVis, + VisSavedObject, + VisResponseValue, +} from './types'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__snapshots__/build_pipeline.test.ts.snap b/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__snapshots__/build_pipeline.test.ts.snap rename to src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_update_objs/gauge_objs.js b/src/plugins/visualizations/public/legacy/__tests__/vis_update_objs/gauge_objs.js similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_update_objs/gauge_objs.js rename to src/plugins/visualizations/public/legacy/__tests__/vis_update_objs/gauge_objs.js diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.test.ts b/src/plugins/visualizations/public/legacy/build_pipeline.test.ts similarity index 98% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.test.ts rename to src/plugins/visualizations/public/legacy/build_pipeline.test.ts index d5c532b53a53e..5476ce6df0390 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.test.ts +++ b/src/plugins/visualizations/public/legacy/build_pipeline.test.ts @@ -27,10 +27,8 @@ import { Schemas, } from './build_pipeline'; import { Vis } from '..'; -import { dataPluginMock } from '../../../../../../../plugins/data/public/mocks'; -import { IAggConfig } from '../../../../../../../plugins/data/public'; - -jest.mock('ui/new_platform'); +import { dataPluginMock } from '../../../../plugins/data/public/mocks'; +import { IAggConfig } from '../../../../plugins/data/public'; describe('visualize loader pipeline helpers: build pipeline', () => { describe('prepareJson', () => { diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts b/src/plugins/visualizations/public/legacy/build_pipeline.ts similarity index 99% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts rename to src/plugins/visualizations/public/legacy/build_pipeline.ts index 05ae26892b9bd..18af94c919247 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts +++ b/src/plugins/visualizations/public/legacy/build_pipeline.ts @@ -19,13 +19,13 @@ import { get } from 'lodash'; import moment from 'moment'; -import { SerializedFieldFormat } from '../../../../../../../plugins/expressions/public'; +import { SerializedFieldFormat } from '../../../../plugins/expressions/public'; import { IAggConfig, fieldFormats, search, TimefilterContract, -} from '../../../../../../../plugins/data/public'; +} from '../../../../plugins/data/public'; import { Vis, VisParams } from '../types'; const { isDateHistogramBucketAggConfig } = search.aggs; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/memoize.test.ts b/src/plugins/visualizations/public/legacy/memoize.test.ts similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/memoize.test.ts rename to src/plugins/visualizations/public/legacy/memoize.test.ts diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/memoize.ts b/src/plugins/visualizations/public/legacy/memoize.ts similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/memoize.ts rename to src/plugins/visualizations/public/legacy/memoize.ts diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/vis_update.js b/src/plugins/visualizations/public/legacy/vis_update.js similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/vis_update.js rename to src/plugins/visualizations/public/legacy/vis_update.js diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/vis_update_state.js b/src/plugins/visualizations/public/legacy/vis_update_state.js similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/vis_update_state.js rename to src/plugins/visualizations/public/legacy/vis_update_state.js diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/vis_update_state.test.js b/src/plugins/visualizations/public/legacy/vis_update_state.test.js similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/vis_update_state.test.js rename to src/plugins/visualizations/public/legacy/vis_update_state.test.js diff --git a/src/plugins/visualizations/public/mocks.ts b/src/plugins/visualizations/public/mocks.ts index af7688d019f65..f4983a4313c4d 100644 --- a/src/plugins/visualizations/public/mocks.ts +++ b/src/plugins/visualizations/public/mocks.ts @@ -16,22 +16,60 @@ * specific language governing permissions and limitations * under the License. */ -import { VisualizationsSetup, VisualizationsStart } from '.'; -export type Setup = jest.Mocked<VisualizationsSetup>; -export type Start = jest.Mocked<VisualizationsStart>; +import { PluginInitializerContext } from '../../../core/public'; +import { VisualizationsSetup, VisualizationsStart } from './'; +import { VisualizationsPlugin } from './plugin'; +import { coreMock } from '../../../core/public/mocks'; +import { embeddablePluginMock } from '../../../plugins/embeddable/public/mocks'; +import { expressionsPluginMock } from '../../../plugins/expressions/public/mocks'; +import { dataPluginMock } from '../../../plugins/data/public/mocks'; +import { usageCollectionPluginMock } from '../../../plugins/usage_collection/public/mocks'; +import { uiActionsPluginMock } from '../../../plugins/ui_actions/public/mocks'; -const createSetupContract = (): Setup => { - const setupContract: Setup = undefined; - return setupContract; -}; +const createSetupContract = (): VisualizationsSetup => ({ + createBaseVisualization: jest.fn(), + createReactVisualization: jest.fn(), + registerAlias: jest.fn(), + hideTypes: jest.fn(), +}); + +const createStartContract = (): VisualizationsStart => ({ + get: jest.fn(), + all: jest.fn(), + getAliases: jest.fn(), + savedVisualizationsLoader: {} as any, + showNewVisModal: jest.fn(), + createVis: jest.fn(), + convertFromSerializedVis: jest.fn(), + convertToSerializedVis: jest.fn(), +}); + +const createInstance = async () => { + const plugin = new VisualizationsPlugin({} as PluginInitializerContext); + + const setup = plugin.setup(coreMock.createSetup(), { + data: dataPluginMock.createSetupContract(), + expressions: expressionsPluginMock.createSetupContract(), + embeddable: embeddablePluginMock.createSetupContract(), + usageCollection: usageCollectionPluginMock.createSetupContract(), + }); + const doStart = () => + plugin.start(coreMock.createStart(), { + data: dataPluginMock.createStartContract(), + expressions: expressionsPluginMock.createStartContract(), + uiActions: uiActionsPluginMock.createStartContract(), + }); -const createStartContract = (): Start => { - const startContract: Start = undefined; - return startContract; + return { + plugin, + setup, + doStart, + }; }; -export const expressionsPluginMock = { +export const visualizationsPluginMock = { createSetupContract, createStartContract, + createInstance, }; diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts index cceb63122820d..d3e7b759a4416 100644 --- a/src/plugins/visualizations/public/plugin.ts +++ b/src/plugins/visualizations/public/plugin.ts @@ -18,23 +18,81 @@ */ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public'; -import { ExpressionsSetup, ExpressionsStart } from '../../expressions/public'; +import { TypesService, TypesSetup, TypesStart } from './vis_types'; +import { + setUISettings, + setTypes, + setI18n, + setCapabilities, + setHttp, + setIndexPatterns, + setSavedObjects, + setUsageCollector, + setFilterManager, + setExpressions, + setUiActions, + setSavedVisualizationsLoader, + setTimeFilter, + setAggs, + setChrome, + setOverlays, +} from './services'; +import { VISUALIZE_EMBEDDABLE_TYPE, VisualizeEmbeddableFactory } from './embeddable'; +import { ExpressionsSetup, ExpressionsStart } from '../../../plugins/expressions/public'; +import { EmbeddableSetup } from '../../../plugins/embeddable/public'; +import { visualization as visualizationFunction } from './expressions/visualization_function'; +import { visualization as visualizationRenderer } from './expressions/visualization_renderer'; import { range as rangeExpressionFunction } from './expression_functions/range'; import { visDimension as visDimensionExpressionFunction } from './expression_functions/vis_dimension'; +import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../plugins/data/public'; +import { UsageCollectionSetup } from '../../../plugins/usage_collection/public'; +import { createSavedVisLoader, SavedVisualizationsLoader } from './saved_visualizations'; +import { SerializedVis, Vis } from './vis'; +import { showNewVisModal } from './wizard'; +import { UiActionsStart } from '../../../plugins/ui_actions/public'; +import { + convertFromSerializedVis, + convertToSerializedVis, +} from './saved_visualizations/_saved_vis'; + +/** + * Interface for this plugin's returned setup/start contracts. + * + * @public + */ + +export type VisualizationsSetup = TypesSetup; + +export interface VisualizationsStart extends TypesStart { + savedVisualizationsLoader: SavedVisualizationsLoader; + createVis: (visType: string, visState?: SerializedVis) => Vis; + convertToSerializedVis: typeof convertToSerializedVis; + convertFromSerializedVis: typeof convertFromSerializedVis; + showNewVisModal: typeof showNewVisModal; +} export interface VisualizationsSetupDeps { expressions: ExpressionsSetup; + embeddable: EmbeddableSetup; + usageCollection: UsageCollectionSetup; + data: DataPublicPluginSetup; } export interface VisualizationsStartDeps { + data: DataPublicPluginStart; expressions: ExpressionsStart; + uiActions: UiActionsStart; } -export type VisualizationsSetup = void; - -export type VisualizationsStart = void; - -export class VisualizationsPublicPlugin +/** + * Visualizations Plugin - public + * + * This plugin's stateful contracts are returned from the `setup` and `start` methods + * below. The interfaces for these contracts are provided above. + * + * @internal + */ +export class VisualizationsPlugin implements Plugin< VisualizationsSetup, @@ -42,18 +100,73 @@ export class VisualizationsPublicPlugin VisualizationsSetupDeps, VisualizationsStartDeps > { + private readonly types: TypesService = new TypesService(); + constructor(initializerContext: PluginInitializerContext) {} - public setup(core: CoreSetup, { expressions }: VisualizationsSetupDeps): VisualizationsSetup { + public setup( + core: CoreSetup, + { expressions, embeddable, usageCollection, data }: VisualizationsSetupDeps + ): VisualizationsSetup { + setUISettings(core.uiSettings); + setUsageCollector(usageCollection); + + expressions.registerFunction(visualizationFunction); + expressions.registerRenderer(visualizationRenderer); expressions.registerFunction(rangeExpressionFunction); expressions.registerFunction(visDimensionExpressionFunction); - return undefined; + const embeddableFactory = new VisualizeEmbeddableFactory(); + embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); + + return { + ...this.types.setup(), + }; } - public start(core: CoreStart, { expressions }: VisualizationsStartDeps): VisualizationsStart { - return undefined; + public start( + core: CoreStart, + { data, expressions, uiActions }: VisualizationsStartDeps + ): VisualizationsStart { + const types = this.types.start(); + setI18n(core.i18n); + setTypes(types); + setCapabilities(core.application.capabilities); + setHttp(core.http); + setSavedObjects(core.savedObjects); + setIndexPatterns(data.indexPatterns); + setFilterManager(data.query.filterManager); + setExpressions(expressions); + setUiActions(uiActions); + setTimeFilter(data.query.timefilter.timefilter); + setAggs(data.search.aggs); + setOverlays(core.overlays); + setChrome(core.chrome); + const savedVisualizationsLoader = createSavedVisLoader({ + savedObjectsClient: core.savedObjects.client, + indexPatterns: data.indexPatterns, + chrome: core.chrome, + overlays: core.overlays, + visualizationTypes: types, + }); + setSavedVisualizationsLoader(savedVisualizationsLoader); + + return { + ...types, + showNewVisModal, + /** + * creates new instance of Vis + * @param {IIndexPattern} indexPattern - index pattern to use + * @param {VisState} visState - visualization configuration + */ + createVis: (visType: string, visState?: SerializedVis) => new Vis(visType, visState), + convertToSerializedVis, + convertFromSerializedVis, + savedVisualizationsLoader, + }; } - public stop() {} + public stop() { + this.types.stop(); + } } diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/_saved_vis.ts b/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts similarity index 94% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/_saved_vis.ts rename to src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts index c9906428ccb31..bc96e08f4b9da 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/_saved_vis.ts +++ b/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts @@ -28,17 +28,13 @@ import { createSavedObjectClass, SavedObject, SavedObjectKibanaServices, -} from '../../../../../../../plugins/saved_objects/public'; +} from '../../../../plugins/saved_objects/public'; // @ts-ignore import { updateOldState } from '../legacy/vis_update_state'; import { extractReferences, injectReferences } from './saved_visualization_references'; -import { - IIndexPattern, - ISearchSource, - SearchSource, -} from '../../../../../../../plugins/data/public'; +import { IIndexPattern, ISearchSource, SearchSource } from '../../../../plugins/data/public'; import { ISavedVis, SerializedVis } from '../types'; -import { createSavedSearchesLoader } from '../../../../../../../plugins/discover/public'; +import { createSavedSearchesLoader } from '../../../../plugins/discover/public'; import { getChrome, getOverlays, getIndexPatterns, getSavedObjects } from '../services'; export const convertToSerializedVis = async (savedVis: ISavedVis): Promise<SerializedVis> => { @@ -159,5 +155,5 @@ export function createSavedVisClass(services: SavedObjectKibanaServices) { } } - return SavedVis; + return SavedVis as new (opts: Record<string, unknown> | string) => SavedObject; } diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/find_list_items.test.ts b/src/plugins/visualizations/public/saved_visualizations/find_list_items.test.ts similarity index 97% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/find_list_items.test.ts rename to src/plugins/visualizations/public/saved_visualizations/find_list_items.test.ts index d1def09978dbb..4a50590e26251 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/find_list_items.test.ts +++ b/src/plugins/visualizations/public/saved_visualizations/find_list_items.test.ts @@ -18,8 +18,8 @@ */ import { findListItems } from './find_list_items'; -import { coreMock } from '../../../../../../../core/public/mocks'; -import { SavedObjectsClientContract } from '../../../../../../../core/public'; +import { coreMock } from '../../../../core/public/mocks'; +import { SavedObjectsClientContract } from '../../../../core/public'; import { VisTypeAlias } from '../vis_types'; describe('saved_visualizations', () => { diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/find_list_items.ts b/src/plugins/visualizations/public/saved_visualizations/find_list_items.ts similarity index 92% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/find_list_items.ts rename to src/plugins/visualizations/public/saved_visualizations/find_list_items.ts index 02db90a762e89..c0203a7441a61 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/find_list_items.ts +++ b/src/plugins/visualizations/public/saved_visualizations/find_list_items.ts @@ -18,11 +18,8 @@ */ import _ from 'lodash'; -import { - SavedObjectAttributes, - SavedObjectsClientContract, -} from '../../../../../../../core/public'; -import { SavedObjectLoader } from '../../../../../../../plugins/saved_objects/public'; +import { SavedObjectAttributes, SavedObjectsClientContract } from '../../../../core/public'; +import { SavedObjectLoader } from '../../../../plugins/saved_objects/public'; import { VisTypeAlias } from '../vis_types'; import { VisualizationsAppExtension } from '../vis_types/vis_type_alias_registry'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/index.ts b/src/plugins/visualizations/public/saved_visualizations/index.ts similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/index.ts rename to src/plugins/visualizations/public/saved_visualizations/index.ts diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/saved_visualization_references.test.ts b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references.test.ts similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/saved_visualization_references.test.ts rename to src/plugins/visualizations/public/saved_visualizations/saved_visualization_references.test.ts diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/saved_visualization_references.ts b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references.ts similarity index 99% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/saved_visualization_references.ts rename to src/plugins/visualizations/public/saved_visualizations/saved_visualization_references.ts index b995d340d44d9..a14595524100b 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/saved_visualization_references.ts +++ b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { SavedObjectAttributes, SavedObjectReference } from '../../../../../../../core/public'; +import { SavedObjectAttributes, SavedObjectReference } from '../../../../core/public'; import { VisSavedObject } from '../types'; export function extractReferences({ diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/saved_visualizations.ts b/src/plugins/visualizations/public/saved_visualizations/saved_visualizations.ts similarity index 92% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/saved_visualizations.ts rename to src/plugins/visualizations/public/saved_visualizations/saved_visualizations.ts index fc0f77d54059c..e5d0c41712a91 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/saved_visualizations.ts +++ b/src/plugins/visualizations/public/saved_visualizations/saved_visualizations.ts @@ -19,7 +19,7 @@ import { SavedObjectLoader, SavedObjectKibanaServices, -} from '../../../../../../../plugins/saved_objects/public'; +} from '../../../../plugins/saved_objects/public'; import { findListItems } from './find_list_items'; import { createSavedVisClass } from './_saved_vis'; import { TypesStart } from '../vis_types'; @@ -76,5 +76,9 @@ export function createSavedVisLoader(services: SavedObjectKibanaServicesWithVisu } } const SavedVis = createSavedVisClass(services); - return new SavedObjectLoaderVisualize(SavedVis, savedObjectsClient, services.chrome); + return new SavedObjectLoaderVisualize( + SavedVis, + savedObjectsClient, + services.chrome + ) as SavedObjectLoader & { findListItems: (search: string, size: number) => any }; } diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts b/src/plugins/visualizations/public/services.ts similarity index 83% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts rename to src/plugins/visualizations/public/services.ts index 23cdeae7d15ff..c4668fa4b0c79 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts +++ b/src/plugins/visualizations/public/services.ts @@ -18,7 +18,6 @@ */ import { - ApplicationStart, Capabilities, ChromeStart, HttpStart, @@ -26,18 +25,18 @@ import { IUiSettingsClient, OverlayStart, SavedObjectsStart, -} from '../../../../../../core/public'; +} from '../../../core/public'; import { TypesStart } from './vis_types'; -import { createGetterSetter } from '../../../../../../plugins/kibana_utils/public'; +import { createGetterSetter } from '../../../plugins/kibana_utils/public'; import { DataPublicPluginStart, FilterManager, IndexPatternsContract, TimefilterContract, -} from '../../../../../../plugins/data/public'; -import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/public'; -import { ExpressionsStart } from '../../../../../../plugins/expressions/public'; -import { UiActionsStart } from '../../../../../../plugins/ui_actions/public'; +} from '../../../plugins/data/public'; +import { UsageCollectionSetup } from '../../../plugins/usage_collection/public'; +import { ExpressionsStart } from '../../../plugins/expressions/public'; +import { UiActionsStart } from '../../../plugins/ui_actions/public'; import { SavedVisualizationsLoader } from './saved_visualizations'; export const [getUISettings, setUISettings] = createGetterSetter<IUiSettingsClient>('UISettings'); @@ -83,5 +82,3 @@ export const [getAggs, setAggs] = createGetterSetter<DataPublicPluginStart['sear export const [getOverlays, setOverlays] = createGetterSetter<OverlayStart>('Overlays'); export const [getChrome, setChrome] = createGetterSetter<ChromeStart>('Chrome'); - -export const [getApplication, setApplication] = createGetterSetter<ApplicationStart>('Application'); diff --git a/src/plugins/visualizations/public/types.ts b/src/plugins/visualizations/public/types.ts index 6487266956119..54528a33414c3 100644 --- a/src/plugins/visualizations/public/types.ts +++ b/src/plugins/visualizations/public/types.ts @@ -17,6 +17,42 @@ * under the License. */ +import { SavedObject } from '../../../plugins/saved_objects/public'; +import { ISearchSource, AggConfigOptions } from '../../../plugins/data/public'; +import { SerializedVis, Vis, VisParams } from './vis'; + +export { Vis, SerializedVis, VisParams }; + +export interface VisualizationController { + render(visData: any, visParams: any): Promise<void>; + destroy(): void; + isLoaded?(): Promise<void> | void; +} + +export type VisualizationControllerConstructor = new ( + el: HTMLElement, + vis: Vis +) => VisualizationController; + +export interface SavedVisState { + type: string; + params: VisParams; + aggs: AggConfigOptions[]; +} + +export interface ISavedVis { + id?: string; + title: string; + description?: string; + visState: SavedVisState; + searchSource?: ISearchSource; + uiStateJSON?: string; + savedSearchRefName?: string; + savedSearchId?: string; +} + +export interface VisSavedObject extends SavedObject, ISavedVis {} + export interface VisResponseValue { visType: string; visData: object; diff --git a/src/plugins/visualizations/public/vis.test.ts b/src/plugins/visualizations/public/vis.test.ts new file mode 100644 index 0000000000000..fc9327903fc90 --- /dev/null +++ b/src/plugins/visualizations/public/vis.test.ts @@ -0,0 +1,124 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Vis } from './vis'; +// @ts-ignore +import fixturesStubbedLogstashIndexPatternProvider from '../../../fixtures/stubbed_logstash_index_pattern'; + +jest.mock('./services', () => { + class MockVisualizationController { + constructor() {} + + render(): Promise<void> { + return new Promise(resolve => { + resolve(); + }); + } + + destroy() {} + } + + // eslint-disable-next-line + const { BaseVisType } = require('./vis_types/base_vis_type'); + + const visType = new BaseVisType({ + name: 'pie', + title: 'pie', + icon: 'pie-icon', + visualization: MockVisualizationController, + }); + + return { + getTypes: () => ({ get: () => visType }), + getAggs: () => ({ + createAggConfigs: (indexPattern: any, cfg: any) => ({ + aggs: cfg.map((aggConfig: any) => ({ ...aggConfig, toJSON: () => aggConfig })), + }), + }), + }; +}); + +describe('Vis Class', function() { + let vis: Vis; + const stateFixture = { + type: 'pie', + title: 'pie', + data: { + aggs: [ + { type: 'avg' as any, schema: 'metric', params: { field: 'bytes' } }, + { type: 'terms' as any, schema: 'segment', params: { field: 'machine.os' } }, + { type: 'terms' as any, schema: 'segment', params: { field: 'geo.src' } }, + ], + searchSource: { + getField: (name: string) => { + if (name === 'index') { + return fixturesStubbedLogstashIndexPatternProvider(); + } + }, + createCopy: jest.fn(), + }, + }, + params: { isDonut: true }, + }; + + beforeEach(function() { + vis = new Vis('test', stateFixture as any); + }); + + const verifyVis = function(visToVerify: Vis) { + expect(visToVerify).toHaveProperty('data'); + expect(visToVerify.data).toHaveProperty('aggs'); + expect(visToVerify.data.aggs!.aggs).toHaveLength(3); + + expect(visToVerify).toHaveProperty('type'); + + expect(visToVerify).toHaveProperty('params'); + expect(visToVerify.params).toHaveProperty('isDonut', true); + }; + + describe('initialization', function() { + it('should set the state', function() { + verifyVis(vis); + }); + }); + + describe('getState()', function() { + it('should get a state that represents the... er... state', function() { + const state = vis.serialize(); + expect(state).toHaveProperty('type', 'pie'); + + expect(state).toHaveProperty('params'); + expect(state.params).toHaveProperty('isDonut', true); + + expect(state.data).toHaveProperty('aggs'); + expect(state.data.aggs).toHaveLength(3); + }); + }); + + describe('isHierarchical()', function() { + it('should return false for non-hierarchical vis (like histogram)', function() { + expect(vis.isHierarchical()).toBe(false); + }); + + it('should return true for hierarchical vis (like pie)', function() { + vis.type.hierarchicalData = true; + expect(vis.isHierarchical()).toBe(true); + }); + }); +}); diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.ts b/src/plugins/visualizations/public/vis.ts similarity index 97% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/vis.ts rename to src/plugins/visualizations/public/vis.ts index 91b6a2368f5ef..3cab4faf2a27f 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.ts +++ b/src/plugins/visualizations/public/vis.ts @@ -28,7 +28,7 @@ */ import { isFunction, defaults, cloneDeep } from 'lodash'; -import { PersistedState } from '../../../../../../../src/plugins/visualizations/public'; +import { PersistedState } from './persisted_state'; // @ts-ignore import { updateVisualizationConfig } from './legacy/vis_update'; import { getTypes, getAggs } from './services'; @@ -38,7 +38,7 @@ import { IndexPattern, ISearchSource, AggConfigOptions, -} from '../../../../../../plugins/data/public'; +} from '../../../plugins/data/public'; export interface SerializedVisData { expression?: string; @@ -49,7 +49,7 @@ export interface SerializedVisData { } export interface SerializedVis { - id: string; + id?: string; title: string; description?: string; type: string; @@ -72,7 +72,7 @@ export interface VisParams { export class Vis { public readonly type: VisType; - public readonly id: string; + public readonly id?: string; public title: string = ''; public description: string = ''; public params: VisParams = {}; diff --git a/src/legacy/core_plugins/visualizations/index.ts b/src/plugins/visualizations/public/vis_types/base_vis_type.test.ts similarity index 64% rename from src/legacy/core_plugins/visualizations/index.ts rename to src/plugins/visualizations/public/vis_types/base_vis_type.test.ts index a2779cfe4346d..ac1242b2a1321 100644 --- a/src/legacy/core_plugins/visualizations/index.ts +++ b/src/plugins/visualizations/public/vis_types/base_vis_type.test.ts @@ -17,18 +17,19 @@ * under the License. */ -import { resolve } from 'path'; -import { LegacyPluginInitializer } from '../../../../src/legacy/types'; +import { BaseVisType } from './base_vis_type'; -export const visualizations: LegacyPluginInitializer = kibana => - new kibana.Plugin({ - id: 'visualizations', - publicDir: resolve(__dirname, 'public'), - require: [], - uiExports: { - styleSheetPaths: resolve(__dirname, 'public/index.scss'), - }, +describe('BaseVisType', () => { + describe('constructor', () => { + test('should throw if image and icon are missing', () => { + expect(() => { + new BaseVisType({ + name: 'test', + title: 'test', + description: 'test', + visualization: {} as any, + }); + }).toThrow(); + }); }); - -// eslint-disable-next-line import/no-default-export -export default visualizations; +}); diff --git a/src/plugins/visualizations/public/vis_types/base_vis_type.ts b/src/plugins/visualizations/public/vis_types/base_vis_type.ts new file mode 100644 index 0000000000000..2464bb72d2695 --- /dev/null +++ b/src/plugins/visualizations/public/vis_types/base_vis_type.ts @@ -0,0 +1,110 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; +import { VisualizationControllerConstructor } from '../types'; + +export interface BaseVisTypeOptions { + name: string; + title: string; + description?: string; + icon?: string; + image?: string; + stage?: 'experimental' | 'beta' | 'production'; + feedbackMessage?: string; + options?: Record<string, any>; + visualization: VisualizationControllerConstructor; + visConfig?: Record<string, any>; + editor?: any; + editorConfig?: Record<string, any>; + hidden?: boolean; + requestHandler?: string | unknown; + responseHandler?: string | unknown; + hierarchicalData?: boolean | unknown; + setup?: unknown; + useCustomNoDataScreen?: boolean; +} + +export class BaseVisType { + name: string; + title: string; + description: string; + icon?: string; + image?: string; + stage: 'experimental' | 'beta' | 'production'; + feedbackMessage: string; + options: Record<string, any>; + visualization: VisualizationControllerConstructor; + visConfig: Record<string, any>; + editor: any; + editorConfig: Record<string, any>; + hidden: boolean; + requiresSearch: boolean; + requestHandler: string | unknown; + responseHandler: string | unknown; + hierarchicalData: boolean | unknown; + setup?: unknown; + useCustomNoDataScreen: boolean; + + constructor(opts: BaseVisTypeOptions) { + if (!opts.icon && !opts.image) { + throw new Error('vis_type must define its icon or image'); + } + + const defaultOptions = { + // controls the visualize editor + showTimePicker: true, + showQueryBar: true, + showFilterBar: true, + showIndexSelection: true, + hierarchicalData: false, // we should get rid of this i guess ? + }; + + this.name = opts.name; + this.description = opts.description || ''; + this.title = opts.title; + this.icon = opts.icon; + this.image = opts.image; + this.visualization = opts.visualization; + this.visConfig = _.defaultsDeep({}, opts.visConfig, { defaults: {} }); + this.editor = opts.editor; + this.editorConfig = _.defaultsDeep({}, opts.editorConfig, { collections: {} }); + this.options = _.defaultsDeep({}, opts.options, defaultOptions); + this.stage = opts.stage || 'production'; + this.feedbackMessage = opts.feedbackMessage || ''; + this.hidden = opts.hidden || false; + this.requestHandler = opts.requestHandler || 'courier'; + this.responseHandler = opts.responseHandler || 'none'; + this.setup = opts.setup; + this.requiresSearch = this.requestHandler !== 'none'; + this.hierarchicalData = opts.hierarchicalData || false; + this.useCustomNoDataScreen = opts.useCustomNoDataScreen || false; + } + + shouldMarkAsExperimentalInUI() { + return this.stage === 'experimental'; + } + + public get schemas() { + if (this.editorConfig && this.editorConfig.schemas) { + return this.editorConfig.schemas; + } + return []; + } +} diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/index.ts b/src/plugins/visualizations/public/vis_types/index.ts similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/index.ts rename to src/plugins/visualizations/public/vis_types/index.ts diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/react_vis_type.js b/src/plugins/visualizations/public/vis_types/react_vis_controller.tsx similarity index 70% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/react_vis_type.js rename to src/plugins/visualizations/public/vis_types/react_vis_controller.tsx index e8ee7bc6e5445..643e6ffcb730b 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/react_vis_type.js +++ b/src/plugins/visualizations/public/vis_types/react_vis_controller.tsx @@ -19,21 +19,26 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { getUISettings, getI18n } from '../services'; -import { BaseVisType } from './base_vis_type'; +import { Vis, VisualizationController } from '../types'; +import { getI18n, getUISettings } from '../services'; -class ReactVisController { - constructor(element, vis) { +export class ReactVisController implements VisualizationController { + private el: HTMLElement; + private vis: Vis; + + constructor(element: HTMLElement, vis: Vis) { this.el = element; this.vis = vis; } - render(visData, visParams, updateStatus) { - this.visData = visData; - + public render(visData: any, visParams: any): Promise<void> { const I18nContext = getI18n().Context; - return new Promise(resolve => { + return new Promise((resolve, reject) => { + if (!this.vis.type || !this.vis.type.visConfig || !this.vis.type.visConfig.component) { + reject('Missing component for ReactVisType'); + } + const Component = this.vis.type.visConfig.component; const config = getUISettings(); render( @@ -44,7 +49,6 @@ class ReactVisController { visData={visData} visParams={visParams} renderComplete={resolve} - updateStatus={updateStatus} /> </I18nContext>, this.el @@ -52,20 +56,7 @@ class ReactVisController { }); } - destroy() { + public destroy() { unmountComponentAtNode(this.el); } } - -export class ReactVisType extends BaseVisType { - constructor(opts) { - super({ - ...opts, - visualization: ReactVisController, - }); - - if (!this.visConfig.component) { - throw new Error('Missing component for ReactVisType'); - } - } -} diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_types/react_vis_type.js b/src/plugins/visualizations/public/vis_types/react_vis_type.test.ts similarity index 55% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_types/react_vis_type.js rename to src/plugins/visualizations/public/vis_types/react_vis_type.test.ts index 2474a58870424..134106bb3d42a 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_types/react_vis_type.js +++ b/src/plugins/visualizations/public/vis_types/react_vis_type.test.ts @@ -17,57 +17,30 @@ * under the License. */ -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { ReactVisType } from '../../../vis_types/react_vis_type'; +import { cloneDeep } from 'lodash'; +import { ReactVisType } from './react_vis_type'; -describe('React Vis Type', function() { +describe('React Vis Type', () => { const visConfig = { name: 'test', title: 'test', description: 'test', icon: 'test', visConfig: { component: 'test' }, - type: { visConfig: { component: 'test' } }, }; - beforeEach(ngMock.module('kibana')); - describe('initialization', () => { it('should throw if component is not set', () => { expect(() => { - new ReactVisType({}); - }).to.throwError(); + const missingConfig = cloneDeep(visConfig); + delete missingConfig.visConfig.component; + new ReactVisType(missingConfig); + }).toThrow(); }); it('creates react controller', () => { const visType = new ReactVisType(visConfig); - expect(visType.visualization).to.not.be.an('undefined'); - }); - }); - - describe('controller render method', () => { - let vis; - beforeEach(() => { - const visType = new ReactVisType(visConfig); - const Vis = visType.visualization; - - vis = new Vis(window.document.body, {}); - }); - - it('rejects if data is not provided', () => { - vis - .render() - .then(() => { - expect('promise was not rejected').to.equal(false); - }) - .catch(() => {}); - }); - - it('renders the component', () => { - expect(() => { - vis.render({}); - }).to.not.throwError(); + expect(visType.visualization).not.toBeUndefined(); }); }); }); diff --git a/src/plugins/visualizations/public/vis_types/react_vis_type.ts b/src/plugins/visualizations/public/vis_types/react_vis_type.ts new file mode 100644 index 0000000000000..68979abe52a3c --- /dev/null +++ b/src/plugins/visualizations/public/vis_types/react_vis_type.ts @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { BaseVisType, BaseVisTypeOptions } from './base_vis_type'; +import { ReactVisController } from './react_vis_controller'; + +export class ReactVisType extends BaseVisType { + constructor(opts: Omit<BaseVisTypeOptions, 'visualization'>) { + super({ + ...opts, + visualization: ReactVisController, + }); + + if (!this.visConfig.component) { + throw new Error('Missing component for ReactVisType'); + } + } +} diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/types_service.ts b/src/plugins/visualizations/public/vis_types/types_service.ts similarity index 98% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/types_service.ts rename to src/plugins/visualizations/public/vis_types/types_service.ts index 6bcaa9a3e1dac..321f96180fd68 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/types_service.ts +++ b/src/plugins/visualizations/public/vis_types/types_service.ts @@ -30,8 +30,8 @@ export interface VisType { description?: string; visualization: any; isAccessible?: boolean; - requestHandler: string; - responseHandler: string; + requestHandler: string | unknown; + responseHandler: string | unknown; icon?: IconType; image?: string; stage: 'experimental' | 'beta' | 'production'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/vis_type_alias_registry.ts b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts similarity index 98% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/vis_type_alias_registry.ts rename to src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts index 12b02ee9e6b32..040fa22352a3a 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/vis_type_alias_registry.ts +++ b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts @@ -17,7 +17,7 @@ * under the License. */ -interface VisualizationListItem { +export interface VisualizationListItem { editUrl: string; icon: string; id: string; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap b/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap rename to src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/_dialog.scss b/src/plugins/visualizations/public/wizard/_dialog.scss similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/_dialog.scss rename to src/plugins/visualizations/public/wizard/_dialog.scss diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/_index.scss b/src/plugins/visualizations/public/wizard/_index.scss similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/_index.scss rename to src/plugins/visualizations/public/wizard/_index.scss diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/index.ts b/src/plugins/visualizations/public/wizard/index.ts similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/index.ts rename to src/plugins/visualizations/public/wizard/index.ts diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/new_vis_modal.test.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx similarity index 99% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/new_vis_modal.test.tsx rename to src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx index 2712019e42609..5637aeafc6f14 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/new_vis_modal.test.tsx +++ b/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { TypesStart, VisType } from '../vis_types'; import { NewVisModal } from './new_vis_modal'; -import { SavedObjectsStart } from '../../../../../../../core/public'; +import { SavedObjectsStart } from '../../../../core/public'; describe('NewVisModal', () => { const { location } = window; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/new_vis_modal.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx similarity index 97% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/new_vis_modal.tsx rename to src/plugins/visualizations/public/wizard/new_vis_modal.tsx index 7c10001eddb50..448077819bb8d 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/new_vis_modal.tsx +++ b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx @@ -23,11 +23,11 @@ import { EuiModal, EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { METRIC_TYPE, UiStatsMetricType } from '@kbn/analytics'; -import { IUiSettingsClient, SavedObjectsStart } from '../../../../../../../core/public'; +import { IUiSettingsClient, SavedObjectsStart } from '../../../../core/public'; import { SearchSelection } from './search_selection'; import { TypeSelection } from './type_selection'; import { TypesStart, VisType, VisTypeAlias } from '../vis_types'; -import { UsageCollectionSetup } from '../../../../../../../plugins/usage_collection/public'; +import { UsageCollectionSetup } from '../../../../plugins/usage_collection/public'; interface TypeSelectionProps { isOpen: boolean; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/search_selection/index.ts b/src/plugins/visualizations/public/wizard/search_selection/index.ts similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/search_selection/index.ts rename to src/plugins/visualizations/public/wizard/search_selection/index.ts diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/search_selection/search_selection.tsx b/src/plugins/visualizations/public/wizard/search_selection/search_selection.tsx similarity index 96% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/search_selection/search_selection.tsx rename to src/plugins/visualizations/public/wizard/search_selection/search_selection.tsx index f8eb191dd5f92..c9fb592d1f936 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/search_selection/search_selection.tsx +++ b/src/plugins/visualizations/public/wizard/search_selection/search_selection.tsx @@ -21,9 +21,9 @@ import { EuiModalBody, EuiModalHeader, EuiModalHeaderTitle } from '@elastic/eui' import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; -import { IUiSettingsClient, SavedObjectsStart } from '../../../../../../../../core/public'; +import { IUiSettingsClient, SavedObjectsStart } from '../../../../../core/public'; -import { SavedObjectFinderUi } from '../../../../../../../../plugins/saved_objects/public'; +import { SavedObjectFinderUi } from '../../../../../plugins/saved_objects/public'; import { VisType } from '../../vis_types'; interface SearchSelectionProps { diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/show_new_vis.tsx b/src/plugins/visualizations/public/wizard/show_new_vis.tsx similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/show_new_vis.tsx rename to src/plugins/visualizations/public/wizard/show_new_vis.tsx diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/type_selection/index.ts b/src/plugins/visualizations/public/wizard/type_selection/index.ts similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/type_selection/index.ts rename to src/plugins/visualizations/public/wizard/type_selection/index.ts diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/type_selection/new_vis_help.test.tsx b/src/plugins/visualizations/public/wizard/type_selection/new_vis_help.test.tsx similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/type_selection/new_vis_help.test.tsx rename to src/plugins/visualizations/public/wizard/type_selection/new_vis_help.test.tsx diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/type_selection/new_vis_help.tsx b/src/plugins/visualizations/public/wizard/type_selection/new_vis_help.tsx similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/type_selection/new_vis_help.tsx rename to src/plugins/visualizations/public/wizard/type_selection/new_vis_help.tsx diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/type_selection/type_selection.tsx b/src/plugins/visualizations/public/wizard/type_selection/type_selection.tsx similarity index 98% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/type_selection/type_selection.tsx rename to src/plugins/visualizations/public/wizard/type_selection/type_selection.tsx index 81dcecfee2613..bb5037545cc82 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/type_selection/type_selection.tsx +++ b/src/plugins/visualizations/public/wizard/type_selection/type_selection.tsx @@ -35,8 +35,8 @@ import { EuiTitle, } from '@elastic/eui'; -import { memoizeLast } from '../../../../../../visualizations/public/np_ready/public/legacy/memoize'; -import { VisTypeAlias } from '../../../../../../visualizations/public'; +import { memoizeLast } from '../../legacy/memoize'; +import { VisTypeAlias } from '../../vis_types/vis_type_alias_registry'; import { NewVisHelp } from './new_vis_help'; import { VisHelpText } from './vis_help_text'; import { VisTypeIcon } from './vis_type_icon'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/type_selection/vis_help_text.tsx b/src/plugins/visualizations/public/wizard/type_selection/vis_help_text.tsx similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/type_selection/vis_help_text.tsx rename to src/plugins/visualizations/public/wizard/type_selection/vis_help_text.tsx diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/type_selection/vis_type_icon.tsx b/src/plugins/visualizations/public/wizard/type_selection/vis_type_icon.tsx similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/type_selection/vis_type_icon.tsx rename to src/plugins/visualizations/public/wizard/type_selection/vis_type_icon.tsx diff --git a/test/api_integration/services/supertest.js b/test/api_integration/services/supertest.ts similarity index 83% rename from test/api_integration/services/supertest.js rename to test/api_integration/services/supertest.ts index b53b4ae0ef32f..d5505c080468a 100644 --- a/test/api_integration/services/supertest.js +++ b/test/api_integration/services/supertest.ts @@ -16,18 +16,18 @@ * specific language governing permissions and limitations * under the License. */ - +import { FtrProviderContext } from 'test/functional/ftr_provider_context'; import { format as formatUrl } from 'url'; import supertestAsPromised from 'supertest-as-promised'; -export function KibanaSupertestProvider({ getService }) { +export function KibanaSupertestProvider({ getService }: FtrProviderContext) { const config = getService('config'); const kibanaServerUrl = formatUrl(config.get('servers.kibana')); return supertestAsPromised(kibanaServerUrl); } -export function ElasticsearchSupertestProvider({ getService }) { +export function ElasticsearchSupertestProvider({ getService }: FtrProviderContext) { const config = getService('config'); const elasticSearchServerUrl = formatUrl(config.get('servers.elasticsearch')); return supertestAsPromised(elasticSearchServerUrl); diff --git a/test/common/fixtures/plugins/newsfeed/newsfeed_simulation.ts b/test/common/fixtures/plugins/newsfeed/newsfeed_simulation.ts index 4bf92868b0eca..5aa44b48f9d59 100644 --- a/test/common/fixtures/plugins/newsfeed/newsfeed_simulation.ts +++ b/test/common/fixtures/plugins/newsfeed/newsfeed_simulation.ts @@ -44,7 +44,7 @@ export async function initPlugin(server: Hapi.Server, path: string) { ], }, }, - handler: newsfeedHandler, + handler: newsfeedHandler as Hapi.Lifecycle.Method, }); server.route({ diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js index 6cba92349ffc0..850b2773b5025 100644 --- a/test/functional/apps/discover/_discover.js +++ b/test/functional/apps/discover/_discover.js @@ -199,5 +199,22 @@ export default function({ getService, getPageObjects }) { expect(rowData.startsWith('Sep 22, 2015 @ 16:50:13.253')).to.be.ok(); }); }); + describe('usage of discover:searchOnPageLoad', () => { + it('should fetch data from ES initially when discover:searchOnPageLoad is false', async function() { + await kibanaServer.uiSettings.replace({ 'discover:searchOnPageLoad': false }); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.header.awaitKibanaChrome(); + + expect(await PageObjects.discover.getNrOfFetches()).to.be(0); + }); + + it('should not fetch data from ES initially when discover:searchOnPageLoad is true', async function() { + await kibanaServer.uiSettings.replace({ 'discover:searchOnPageLoad': true }); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.header.awaitKibanaChrome(); + + expect(await PageObjects.discover.getNrOfFetches()).to.be(1); + }); + }); }); } diff --git a/test/functional/apps/discover/_field_visualize.ts b/test/functional/apps/discover/_field_visualize.ts index 4a72d5bb34716..46238bf143290 100644 --- a/test/functional/apps/discover/_field_visualize.ts +++ b/test/functional/apps/discover/_field_visualize.ts @@ -17,13 +17,16 @@ * under the License. */ +import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); + const filterBar = getService('filterBar'); const inspector = getService('inspector'); const kibanaServer = getService('kibanaServer'); const log = getService('log'); + const queryBar = getService('queryBar'); const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']); const defaultSettings = { defaultIndex: 'logstash-*', @@ -76,5 +79,76 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { await inspector.expectTableData(expectedTableData); await inspector.close(); }); + + it('should preserve app filters in visualize', async () => { + await filterBar.addFilter('bytes', 'is between', '3500', '4000'); + await PageObjects.discover.clickFieldListItem('geo.src'); + log.debug('visualize a geo.src field with filter applied'); + await PageObjects.discover.clickFieldListItemVisualize('geo.src'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + expect(await filterBar.hasFilter('bytes', '3,500 to 4,000')).to.be(true); + const expectedTableData = [ + ['CN', '133'], + ['IN', '120'], + ['US', '58'], + ['ID', '28'], + ['BD', '25'], + ['BR', '22'], + ['EG', '14'], + ['NG', '14'], + ['PK', '13'], + ['IR', '12'], + ['PH', '12'], + ['JP', '11'], + ['RU', '11'], + ['DE', '8'], + ['FR', '8'], + ['MX', '8'], + ['TH', '8'], + ['TR', '8'], + ['CA', '6'], + ['SA', '6'], + ]; + await inspector.open(); + await inspector.expectTableData(expectedTableData); + await inspector.close(); + }); + + it('should preserve query in visualize', async () => { + await queryBar.setQuery('machine.os : ios'); + await queryBar.submitQuery(); + await PageObjects.discover.clickFieldListItem('geo.dest'); + log.debug('visualize a geo.dest field with query applied'); + await PageObjects.discover.clickFieldListItemVisualize('geo.dest'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + expect(await queryBar.getQueryString()).to.equal('machine.os : ios'); + const expectedTableData = [ + ['CN', '519'], + ['IN', '495'], + ['US', '275'], + ['ID', '82'], + ['PK', '75'], + ['BR', '71'], + ['NG', '54'], + ['BD', '51'], + ['JP', '47'], + ['MX', '47'], + ['IR', '44'], + ['PH', '44'], + ['RU', '42'], + ['ET', '33'], + ['TH', '33'], + ['EG', '32'], + ['VN', '32'], + ['DE', '31'], + ['FR', '30'], + ['GB', '30'], + ]; + await inspector.open(); + await inspector.expectTableData(expectedTableData); + await inspector.close(); + }); }); } diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index a126cfb1bce4b..1d4e51360f319 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -300,6 +300,11 @@ export function DiscoverPageProvider({ getService, getPageObjects }: FtrProvider 'true' ); } + public async getNrOfFetches() { + const el = await find.byCssSelector('[data-fetch-counter]'); + const nr = await el.getAttribute('data-fetch-counter'); + return Number(nr); + } } return new DiscoverPage(); diff --git a/test/plugin_functional/plugins/core_plugin_static_assets/kibana.json b/test/plugin_functional/plugins/core_plugin_static_assets/kibana.json new file mode 100644 index 0000000000000..6f9fb94e9b49c --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_static_assets/kibana.json @@ -0,0 +1,7 @@ +{ + "id": "corePluginStaticAssets", + "version": "0.0.1", + "kibanaVersion": "kibana", + "server": false, + "ui": true +} diff --git a/test/plugin_functional/plugins/core_plugin_static_assets/package.json b/test/plugin_functional/plugins/core_plugin_static_assets/package.json new file mode 100644 index 0000000000000..304e1b11fde42 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_static_assets/package.json @@ -0,0 +1,17 @@ +{ + "name": "corePluginStaticAssets", + "version": "1.0.0", + "main": "target/test/plugin_functional/plugins/core_plugin_static_assets", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "Apache-2.0", + "scripts": { + "kbn": "node ../../../../scripts/kbn.js", + "build": "rm -rf './target' && tsc" + }, + "devDependencies": { + "typescript": "3.7.2" + } +} diff --git a/test/plugin_functional/plugins/core_plugin_static_assets/public/assets/chart.svg b/test/plugin_functional/plugins/core_plugin_static_assets/public/assets/chart.svg new file mode 100644 index 0000000000000..44553960a5cce --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_static_assets/public/assets/chart.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="36" height="24" viewBox="0 0 36 24"> + <g fill="none" fill-rule="evenodd"> + <path fill="#69707D" fill-rule="nonzero" d="M8,8 C8.55228,8 9,8.44772 9,9 L9,22 C9,22.5523 8.55228,23 8,23 L4,23 C3.44772,23 3,22.5523 3,22 L3,9 C3,8.44772 3.44772,8 4,8 L8,8 Z M24,1 C24.5523,1 25,1.44772 25,2 L25,22 C25,22.5523 24.5523,23 24,23 L20,23 C19.4477,23 19,22.5523 19,22 L19,2 C19,1.44772 19.4477,1 20,1 L24,1 Z"/> + <path fill="#54B399" fill-rule="nonzero" d="M16,12 C16.5523,12 17,12.4477 17,13 L17,22 C17,22.5523 16.5523,23 16,23 L12,23 C11.4477,23 11,22.5523 11,22 L11,13 C11,12.4477 11.4477,12 12,12 L16,12 Z M32,5 C32.5523,5 33,5.44772 33,6 L33,22 C33,22.5523 32.5523,23 32,23 L28,23 C27.4477,23 27,22.5523 27,22 L27,6 C27,5.44772 27.4477,5 28,5 L32,5 Z"/> + </g> +</svg> diff --git a/src/legacy/core_plugins/visualizations/public/index.ts b/test/plugin_functional/plugins/core_plugin_static_assets/public/index.ts similarity index 81% rename from src/legacy/core_plugins/visualizations/public/index.ts rename to test/plugin_functional/plugins/core_plugin_static_assets/public/index.ts index f5590c745b3f9..2bdb40cf19cb5 100644 --- a/src/legacy/core_plugins/visualizations/public/index.ts +++ b/test/plugin_functional/plugins/core_plugin_static_assets/public/index.ts @@ -17,10 +17,6 @@ * under the License. */ -/** - * Static np-ready code, re-exported here so consumers can import from - * `src/legacy/core_plugins/visualizations/public` - * - * @public - */ -export * from './np_ready/public'; +import { CorePluginStaticAssets } from './plugin'; + +export const plugin = () => new CorePluginStaticAssets(); diff --git a/test/plugin_functional/plugins/core_plugin_static_assets/public/plugin.tsx b/test/plugin_functional/plugins/core_plugin_static_assets/public/plugin.tsx new file mode 100644 index 0000000000000..d9f3d62937584 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_static_assets/public/plugin.tsx @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Plugin, CoreSetup } from 'kibana/public'; + +export class CorePluginStaticAssets implements Plugin { + public setup(core: CoreSetup, deps: {}) {} + + public start() {} + public stop() {} +} diff --git a/test/plugin_functional/plugins/core_plugin_static_assets/tsconfig.json b/test/plugin_functional/plugins/core_plugin_static_assets/tsconfig.json new file mode 100644 index 0000000000000..4a564ee1e5578 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_static_assets/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "../../../../typings/**/*" + ], + "exclude": [] +} diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_vis.js b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_vis.js index 643d15c982792..7aa12ea7a1130 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_vis.js +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_vis.js @@ -20,9 +20,9 @@ import { SelfChangingEditor } from './self_changing_editor'; import { SelfChangingComponent } from './self_changing_components'; -import { setup as visualizations } from '../../../../../../src/legacy/core_plugins/visualizations/public/np_ready/public/legacy'; +import { npSetup } from '../../../../../../src/legacy/ui/public/new_platform'; -visualizations.createReactVisualization({ +npSetup.plugins.visualizations.createReactVisualization({ name: 'self_changing_vis', title: 'Self Changing Vis', icon: 'controlsHorizontal', diff --git a/test/plugin_functional/test_suites/core_plugins/ui_plugins.ts b/test/plugin_functional/test_suites/core_plugins/ui_plugins.ts index 82267d73782af..8ddd0ff96ba8f 100644 --- a/test/plugin_functional/test_suites/core_plugins/ui_plugins.ts +++ b/test/plugin_functional/test_suites/core_plugins/ui_plugins.ts @@ -25,6 +25,7 @@ import '../../../../test/plugin_functional/plugins/core_provider_plugin/types'; export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) { const PageObjects = getPageObjects(['common']); const browser = getService('browser'); + const supertest = getService('supertest'); describe('ui plugins', function() { describe('loading', function describeIndexTests() { @@ -97,5 +98,47 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider ).to.be('/core_plugin_b/system_request says: "System request? false"'); }); }); + + describe('Plugin static assets', function() { + it('exposes static assets from "public/assets" folder', async () => { + await supertest.get('/plugins/corePluginStaticAssets/assets/chart.svg').expect(200); + }); + + it('returns 404 if not found', async function() { + await supertest.get('/plugins/corePluginStaticAssets/assets/not-a-chart.svg').expect(404); + }); + + it('does not expose folder content', async function() { + await supertest.get('/plugins/corePluginStaticAssets/assets/').expect(403); + }); + + it('does not allow file tree traversing', async function() { + await supertest.get('/plugins/corePluginStaticAssets/assets/../../kibana.json').expect(404); + }); + + it('generates "etag" & "last-modified" headers', async () => { + const response = await supertest + .get('/plugins/corePluginStaticAssets/assets/chart.svg') + .expect(200); + + expect(response.header).to.have.property('etag'); + expect(response.header).to.have.property('last-modified'); + }); + + it('generates the same "etag" & "last-modified" for the same asset', async () => { + const firstResponse = await supertest + .get('/plugins/corePluginStaticAssets/assets/chart.svg') + .expect(200); + + expect(firstResponse.header).to.have.property('etag'); + + const secondResponse = await supertest + .get('/plugins/corePluginStaticAssets/assets/chart.svg') + .expect(200); + + expect(secondResponse.header.etag).to.be(firstResponse.header.etag); + expect(secondResponse.header['last-modified']).to.be(firstResponse.header['last-modified']); + }); + }); }); } diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx index 54a1b4347e29b..7bdc6aebbd9a0 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx @@ -13,6 +13,7 @@ import React, { useRef, useState } from 'react'; +import { debounce } from 'lodash'; import { isRumAgentName } from '../../../../../../../plugins/apm/common/agent_name'; import { AGENT_NAME } from '../../../../../../../plugins/apm/common/elasticsearch_fieldnames'; import { @@ -171,8 +172,13 @@ export function Cytoscape({ } }); }; + // debounce hover tracking so it doesn't spam telemetry with redundant events + const trackNodeEdgeHover = debounce( + () => trackApmEvent({ metric: 'service_map_node_or_edge_hover' }), + 1000 + ); const mouseoverHandler: cytoscape.EventHandler = event => { - trackApmEvent({ metric: 'service_map_node_or_edge_hover' }); + trackNodeEdgeHover(); event.target.addClass('hover'); event.target.connectedEdges().addClass('nodeHover'); }; diff --git a/x-pack/legacy/plugins/canvas/.storybook/middleware.js b/x-pack/legacy/plugins/canvas/.storybook/middleware.js index 46ae7ac90f364..8bbd2b6c1a22f 100644 --- a/x-pack/legacy/plugins/canvas/.storybook/middleware.js +++ b/x-pack/legacy/plugins/canvas/.storybook/middleware.js @@ -9,5 +9,5 @@ const path = require('path'); // Extend the Storybook Middleware to include a route to access Legacy UI assets module.exports = function(router) { - router.get('/ui', serve(path.resolve(__dirname, '../../../../../src/legacy/ui/public/assets'))); + router.get('/ui', serve(path.resolve(__dirname, '../../../../../src/core/server/core_app/assets'))); }; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts index 538aa9f74e2a6..f5836fe91e040 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore -import { MAP_SAVED_OBJECT_TYPE } from '../../../maps/common/constants'; -import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/visualizations/public'; +import { MAP_SAVED_OBJECT_TYPE } from '../../../../../plugins/maps/public'; +import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../../../src/plugins/visualizations/public'; import { LENS_EMBEDDABLE_TYPE } from '../../../../../plugins/lens/common/constants'; import { SEARCH_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/constants'; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts index 9777eaebb36ed..d98fea2ec1be8 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts @@ -5,7 +5,7 @@ */ import { ExpressionFunctionDefinition } from 'src/plugins/expressions'; -import { VisualizeInput } from 'src/legacy/core_plugins/visualizations/public'; +import { VisualizeInput } from 'src/plugins/visualizations/public'; import { EmbeddableTypes, EmbeddableExpressionType, diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.ts index be0dd6a79292f..4c8de2afd81ad 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { VisualizeInput } from 'src/legacy/core_plugins/visualizations/public'; +import { VisualizeInput } from 'src/plugins/visualizations/public'; export function toExpression(input: VisualizeInput): string { const expressionParts = [] as string[]; diff --git a/x-pack/legacy/plugins/grokdebugger/public/services/grokdebugger/grokdebugger_service.js b/x-pack/legacy/plugins/grokdebugger/public/services/grokdebugger/grokdebugger_service.js index 7a656b33d59ff..c6d28adcefd78 100644 --- a/x-pack/legacy/plugins/grokdebugger/public/services/grokdebugger/grokdebugger_service.js +++ b/x-pack/legacy/plugins/grokdebugger/public/services/grokdebugger/grokdebugger_service.js @@ -4,19 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import chrome from 'ui/chrome'; import { ROUTES } from '../../../common/constants'; import { GrokdebuggerResponse } from 'plugins/grokdebugger/models/grokdebugger_response'; export class GrokdebuggerService { constructor(http) { this.http = http; - this.basePath = chrome.addBasePath(ROUTES.API_ROOT); } simulate(grokdebuggerRequest) { return this.http - .post(`${this.basePath}/simulate`, { body: JSON.stringify(grokdebuggerRequest.upstreamJSON) }) + .post(`${ROUTES.API_ROOT}/simulate`, { + body: JSON.stringify(grokdebuggerRequest.upstreamJSON), + }) .then(response => { return GrokdebuggerResponse.fromUpstreamJSON(response.grokdebuggerResponse); }) diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx index d18174baacdb9..c2ab1c72af545 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx @@ -17,7 +17,7 @@ import { import { Subscription } from 'rxjs'; import { ReactExpressionRendererType } from '../../../../../../../src/plugins/expressions/public'; -import { VIS_EVENT_TO_TRIGGER } from '../../../../../../../src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/events'; +import { VIS_EVENT_TO_TRIGGER } from '../../../../../../../src/plugins/visualizations/public'; import { Embeddable as AbstractEmbeddable, diff --git a/x-pack/legacy/plugins/lens/public/legacy_imports.ts b/x-pack/legacy/plugins/lens/public/legacy_imports.ts index d53842d209e2b..5c5afc1a87df0 100644 --- a/x-pack/legacy/plugins/lens/public/legacy_imports.ts +++ b/x-pack/legacy/plugins/lens/public/legacy_imports.ts @@ -4,5 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { setup as visualizations } from '../../../../../src/legacy/core_plugins/visualizations/public/np_ready/public/legacy'; -export { VisualizationsSetup } from '../../../../../src/legacy/core_plugins/visualizations/public'; +import { npSetup } from 'ui/new_platform'; +export const { visualizations } = npSetup.plugins; +export { VisualizationsSetup } from '../../../../../src/plugins/visualizations/public'; diff --git a/x-pack/legacy/plugins/lens/public/vis_type_alias.ts b/x-pack/legacy/plugins/lens/public/vis_type_alias.ts index c4e0a20110c81..123b994e6ccce 100644 --- a/x-pack/legacy/plugins/lens/public/vis_type_alias.ts +++ b/x-pack/legacy/plugins/lens/public/vis_type_alias.ts @@ -5,8 +5,8 @@ */ import { i18n } from '@kbn/i18n'; +import { VisTypeAlias } from 'src/plugins/visualizations/public'; import { getBasePath, getEditPath } from '../../../../plugins/lens/common'; -import { VisTypeAlias } from '../../../../../src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types'; export const getLensAliasConfig = (): VisTypeAlias => ({ aliasUrl: getBasePath(), diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.test.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.test.tsx index d6abee101db31..cdc5fc2ff1c17 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.test.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.test.tsx @@ -39,7 +39,12 @@ function sampleArgs() { formatHint: { id: 'number', params: { pattern: '0,0.000' } }, }, { id: 'b', name: 'b', formatHint: { id: 'number', params: { pattern: '000,0' } } }, - { id: 'c', name: 'c', formatHint: { id: 'string' } }, + { + id: 'c', + name: 'c', + formatHint: { id: 'string' }, + meta: { type: 'date-histogram', aggConfigParams: { interval: '10s' } }, + }, { id: 'd', name: 'ColD', formatHint: { id: 'string' } }, ], rows: [ @@ -179,6 +184,7 @@ describe('xy_expression', () => { Object { "max": 1546491600000, "min": 1546405200000, + "minInterval": 10000, } `); }); diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.tsx index eaf3acf7bb2a7..a7d4b2a217f37 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization/xy_expression.tsx @@ -29,12 +29,13 @@ import { EuiIcon, EuiText, IconType, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { EmbeddableVisTriggerContext } from '../../../../../../src/plugins/embeddable/public'; -import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/events'; +import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/plugins/visualizations/public'; import { LensMultiTable, FormatFactory } from '../types'; import { XYArgs, SeriesType, visualizationTypes } from './types'; import { VisualizationContainer } from '../visualization_container'; import { isHorizontalChart } from './state_helpers'; import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public'; +import { parseInterval } from '../../../../../../src/plugins/data/common'; import { getExecuteTriggerActions } from './services'; type InferPropType<T> = T extends React.FunctionComponent<infer P> ? P : T; @@ -210,11 +211,14 @@ export function XYChart({ const shouldRotate = isHorizontalChart(layers); const xTitle = (xAxisColumn && xAxisColumn.name) || args.xTitle; + const interval = parseInterval(xAxisColumn?.meta?.aggConfigParams?.interval); + const xDomain = data.dateRange && layers.every(l => l.xScaleType === 'time') ? { min: data.dateRange.fromDate.getTime(), max: data.dateRange.toDate.getTime(), + minInterval: interval?.asMilliseconds(), } : undefined; return ( diff --git a/x-pack/legacy/plugins/maps/public/actions/map_actions.d.ts b/x-pack/legacy/plugins/maps/public/actions/map_actions.d.ts index 48ab957089361..c1f5c31eb4210 100644 --- a/x-pack/legacy/plugins/maps/public/actions/map_actions.d.ts +++ b/x-pack/legacy/plugins/maps/public/actions/map_actions.d.ts @@ -5,6 +5,8 @@ */ /* eslint-disable @typescript-eslint/consistent-type-definitions */ +import { Filter, Query } from 'src/plugins/data/public'; +import { AnyAction } from 'redux'; import { LAYER_TYPE } from '../../common/constants'; import { DataMeta, MapFilters } from '../../common/descriptor_types'; @@ -24,3 +26,45 @@ export function updateSourceProp( value: unknown, newLayerType?: LAYER_TYPE ): void; + +export interface MapCenter { + lat: number; + lon: number; + zoom: number; +} + +export function setGotoWithCenter(config: MapCenter): AnyAction; + +export function replaceLayerList(layerList: unknown[]): AnyAction; + +export interface QueryGroup { + filters: Filter[]; + query?: Query; + timeFilters: unknown; + refresh: unknown; +} + +export function setQuery(query: QueryGroup): AnyAction; + +export interface RefreshConfig { + isPaused: boolean; + interval: number; +} + +export function setRefreshConfig(config: RefreshConfig): AnyAction; + +export function disableScrollZoom(): AnyAction; + +export function disableInteractive(): AnyAction; + +export function disableTooltipControl(): AnyAction; + +export function hideToolbarOverlay(): AnyAction; + +export function hideLayerControl(): AnyAction; + +export function hideViewControl(): AnyAction; + +export function setHiddenLayers(hiddenLayerIds: string[]): AnyAction; + +export function addLayerWithoutDataSync(layerDescriptor: unknown): AnyAction; diff --git a/x-pack/legacy/plugins/maps/public/actions/ui_actions.d.ts b/x-pack/legacy/plugins/maps/public/actions/ui_actions.d.ts new file mode 100644 index 0000000000000..233918847de08 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/actions/ui_actions.d.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AnyAction } from 'redux'; + +export function setOpenTOCDetails(layerIds?: string[]): AnyAction; + +export function setIsLayerTOCOpen(open: boolean): AnyAction; + +export function setReadOnly(readOnly: boolean): AnyAction; diff --git a/x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestion/index.js b/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.d.ts similarity index 71% rename from x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestion/index.js rename to x-pack/legacy/plugins/maps/public/angular/get_initial_layers.d.ts index 98aedf068a987..920888404b97d 100644 --- a/x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestion/index.js +++ b/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.d.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { Suggestion } from './suggestion'; +export function getInitialLayers(layerListJSON?: string, initialLayers?: unknown[]): unknown[]; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/gis_map/index.d.ts b/x-pack/legacy/plugins/maps/public/connected_components/gis_map/index.d.ts new file mode 100644 index 0000000000000..00a9400109dc1 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/connected_components/gis_map/index.d.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { Filter } from 'src/plugins/data/public'; +import { RenderToolTipContent } from '../../layers/tooltips/tooltip_property'; + +export const GisMap: React.ComponentType<{ + addFilters: ((filters: Filter[]) => void) | null; + renderTooltipContent?: RenderToolTipContent; +}>; diff --git a/x-pack/plugins/ml/public/application/components/kql_filter_bar/index.js b/x-pack/legacy/plugins/maps/public/embeddable/index.ts similarity index 76% rename from x-pack/plugins/ml/public/application/components/kql_filter_bar/index.js rename to x-pack/legacy/plugins/maps/public/embeddable/index.ts index d229943f6afe7..a410a6699a01f 100644 --- a/x-pack/plugins/ml/public/application/components/kql_filter_bar/index.js +++ b/x-pack/legacy/plugins/maps/public/embeddable/index.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { KqlFilterBar } from './kql_filter_bar'; +export * from './map_embeddable'; +export * from './map_embeddable_factory'; diff --git a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx similarity index 70% rename from x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js rename to x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx index 9af1a135794c0..69f55815d16a0 100644 --- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js +++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx @@ -10,16 +10,29 @@ import { Provider } from 'react-redux'; import { render, unmountComponentAtNode } from 'react-dom'; import 'mapbox-gl/dist/mapbox-gl.css'; -import { Embeddable } from '../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public'; -import { APPLY_FILTER_TRIGGER } from '../../../../../../src/plugins/ui_actions/public'; -import { esFilters } from '../../../../../../src/plugins/data/public'; - import { I18nContext } from 'ui/i18n'; +import { npStart } from 'ui/new_platform'; +import { Subscription } from 'rxjs'; +import { Unsubscribe } from 'redux'; +import { + Embeddable, + IContainer, + EmbeddableInput, + EmbeddableOutput, +} from '../../../../../../src/plugins/embeddable/public'; +import { APPLY_FILTER_TRIGGER } from '../../../../../../src/plugins/ui_actions/public'; +import { + esFilters, + IIndexPattern, + TimeRange, + Filter, + Query, + RefreshInterval, +} from '../../../../../../src/plugins/data/public'; import { GisMap } from '../connected_components/gis_map'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { createMapStore } from '../../../../../plugins/maps/public/reducers/store'; -import { npStart } from 'ui/new_platform'; +import { createMapStore, MapStore } from '../../../../../plugins/maps/public/reducers/store'; import { setGotoWithCenter, replaceLayerList, @@ -32,21 +45,72 @@ import { hideLayerControl, hideViewControl, setHiddenLayers, + MapCenter, } from '../actions/map_actions'; import { setReadOnly, setIsLayerTOCOpen, setOpenTOCDetails } from '../actions/ui_actions'; import { getIsLayerTOCOpen, getOpenTOCDetails } from '../selectors/ui_selectors'; import { getInspectorAdapters, setEventHandlers, + EventHandlers, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../../../plugins/maps/public/reducers/non_serializable_instances'; import { getMapCenter, getMapZoom, getHiddenLayerIds } from '../selectors/map_selectors'; import { MAP_SAVED_OBJECT_TYPE } from '../../common/constants'; +import { RenderToolTipContent } from '../layers/tooltips/tooltip_property'; + +interface MapEmbeddableConfig { + editUrl?: string; + indexPatterns: IIndexPattern[]; + editable: boolean; + title?: string; + layerList: unknown[]; +} + +export interface MapEmbeddableInput extends EmbeddableInput { + timeRange?: TimeRange; + filters: Filter[]; + query?: Query; + refresh?: unknown; + refreshConfig: RefreshInterval; + isLayerTOCOpen: boolean; + openTOCDetails?: string[]; + disableTooltipControl?: boolean; + disableInteractive?: boolean; + hideToolbarOverlay?: boolean; + hideLayerControl?: boolean; + hideViewControl?: boolean; + mapCenter?: MapCenter; + hiddenLayers?: string[]; + hideFilterActions?: boolean; +} -export class MapEmbeddable extends Embeddable { +export interface MapEmbeddableOutput extends EmbeddableOutput { + indexPatterns: IIndexPattern[]; +} + +export class MapEmbeddable extends Embeddable<MapEmbeddableInput, MapEmbeddableOutput> { type = MAP_SAVED_OBJECT_TYPE; - constructor(config, initialInput, parent, renderTooltipContent, eventHandlers) { + private _renderTooltipContent?: RenderToolTipContent; + private _eventHandlers?: EventHandlers; + private _layerList: unknown[]; + private _store: MapStore; + private _subscription: Subscription; + private _prevTimeRange?: TimeRange; + private _prevQuery?: Query; + private _prevRefreshConfig?: RefreshInterval; + private _prevFilters?: Filter[]; + private _domNode?: HTMLElement; + private _unsubscribeFromStore?: Unsubscribe; + + constructor( + config: MapEmbeddableConfig, + initialInput: MapEmbeddableInput, + parent?: IContainer, + renderTooltipContent?: RenderToolTipContent, + eventHandlers?: EventHandlers + ) { super( initialInput, { @@ -70,7 +134,7 @@ export class MapEmbeddable extends Embeddable { return getInspectorAdapters(this._store.getState()); } - onContainerStateChanged(containerState) { + onContainerStateChanged(containerState: MapEmbeddableInput) { if ( !_.isEqual(containerState.timeRange, this._prevTimeRange) || !_.isEqual(containerState.query, this._prevQuery) || @@ -84,7 +148,12 @@ export class MapEmbeddable extends Embeddable { } } - _dispatchSetQuery({ query, timeRange, filters, refresh }) { + _dispatchSetQuery({ + query, + timeRange, + filters, + refresh, + }: Pick<MapEmbeddableInput, 'query' | 'timeRange' | 'filters' | 'refresh'>) { this._prevTimeRange = timeRange; this._prevQuery = query; this._prevFilters = filters; @@ -98,7 +167,7 @@ export class MapEmbeddable extends Embeddable { ); } - _dispatchSetRefreshConfig({ refreshConfig }) { + _dispatchSetRefreshConfig({ refreshConfig }: Pick<MapEmbeddableInput, 'refreshConfig'>) { this._prevRefreshConfig = refreshConfig; this._store.dispatch( setRefreshConfig({ @@ -113,7 +182,7 @@ export class MapEmbeddable extends Embeddable { * @param {HTMLElement} domNode * @param {ContainerState} containerState */ - render(domNode) { + render(domNode: HTMLElement) { this._store.dispatch(setEventHandlers(this._eventHandlers)); this._store.dispatch(setReadOnly(true)); this._store.dispatch(disableScrollZoom()); @@ -127,23 +196,22 @@ export class MapEmbeddable extends Embeddable { } if (_.has(this.input, 'disableInteractive') && this.input.disableInteractive) { - this._store.dispatch(disableInteractive(this.input.disableInteractive)); + this._store.dispatch(disableInteractive()); } if (_.has(this.input, 'disableTooltipControl') && this.input.disableTooltipControl) { - this._store.dispatch(disableTooltipControl(this.input.disableTooltipControl)); + this._store.dispatch(disableTooltipControl()); } - if (_.has(this.input, 'hideToolbarOverlay') && this.input.hideToolbarOverlay) { - this._store.dispatch(hideToolbarOverlay(this.input.hideToolbarOverlay)); + this._store.dispatch(hideToolbarOverlay()); } if (_.has(this.input, 'hideLayerControl') && this.input.hideLayerControl) { - this._store.dispatch(hideLayerControl(this.input.hideLayerControl)); + this._store.dispatch(hideLayerControl()); } if (_.has(this.input, 'hideViewControl') && this.input.hideViewControl) { - this._store.dispatch(hideViewControl(this.input.hideViewControl)); + this._store.dispatch(hideViewControl()); } if (this.input.mapCenter) { @@ -182,12 +250,12 @@ export class MapEmbeddable extends Embeddable { }); } - async setLayerList(layerList) { + async setLayerList(layerList: unknown[]) { this._layerList = layerList; return await this._store.dispatch(replaceLayerList(this._layerList)); } - addFilters = filters => { + addFilters = (filters: Filter[]) => { npStart.plugins.uiActions.executeTriggerActions(APPLY_FILTER_TRIGGER, { embeddable: this, filters, @@ -213,7 +281,7 @@ export class MapEmbeddable extends Embeddable { this._dispatchSetQuery({ query: this._prevQuery, timeRange: this._prevTimeRange, - filters: this._prevFilters, + filters: this._prevFilters ?? [], refresh: true, }); } @@ -222,7 +290,7 @@ export class MapEmbeddable extends Embeddable { const center = getMapCenter(this._store.getState()); const zoom = getMapZoom(this._store.getState()); - const mapCenter = this.input.mapCenter || {}; + const mapCenter = this.input.mapCenter || undefined; if ( !mapCenter || mapCenter.lat !== center.lat || @@ -233,7 +301,7 @@ export class MapEmbeddable extends Embeddable { mapCenter: { lat: center.lat, lon: center.lon, - zoom: zoom, + zoom, }, }); } diff --git a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.js b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts similarity index 78% rename from x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.js rename to x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts index fcbae894dffa4..ddb937dd98926 100644 --- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.js +++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts @@ -8,12 +8,16 @@ import _ from 'lodash'; import chrome from 'ui/chrome'; import { capabilities } from 'ui/capabilities'; import { i18n } from '@kbn/i18n'; +import { npSetup, npStart } from 'ui/new_platform'; +import { SavedObjectLoader } from 'src/plugins/saved_objects/public'; +import { IIndexPattern } from 'src/plugins/data/public'; import { EmbeddableFactory, ErrorEmbeddable, + IContainer, } from '../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public'; import { setup } from '../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/legacy'; -import { MapEmbeddable } from './map_embeddable'; +import { MapEmbeddable, MapEmbeddableInput } from './map_embeddable'; import { getIndexPatternService } from '../kibana_services'; import { createMapPath, MAP_SAVED_OBJECT_TYPE, APP_ICON } from '../../common/constants'; @@ -25,7 +29,11 @@ import { getInitialLayers } from '../angular/get_initial_layers'; import { mergeInputWithSavedMap } from './merge_input_with_saved_map'; import '../angular/services/gis_map_saved_object_loader'; import { bindSetupCoreAndPlugins, bindStartCoreAndPlugins } from '../plugin'; -import { npSetup, npStart } from 'ui/new_platform'; +import { RenderToolTipContent } from '../layers/tooltips/tooltip_property'; +import { + EventHandlers, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../plugins/maps/public/reducers/non_serializable_instances'; export class MapEmbeddableFactory extends EmbeddableFactory { type = MAP_SAVED_OBJECT_TYPE; @@ -44,8 +52,9 @@ export class MapEmbeddableFactory extends EmbeddableFactory { bindSetupCoreAndPlugins(npSetup.core, npSetup.plugins); bindStartCoreAndPlugins(npStart.core, npStart.plugins); } - isEditable() { - return capabilities.get().maps.save; + + async isEditable() { + return capabilities.get().maps.save as boolean; } // Not supported yet for maps types. @@ -59,12 +68,12 @@ export class MapEmbeddableFactory extends EmbeddableFactory { }); } - async _getIndexPatterns(layerList) { + async _getIndexPatterns(layerList: unknown[]): Promise<IIndexPattern[]> { // Need to extract layerList from store to get queryable index pattern ids const store = createMapStore(); let queryableIndexPatternIds; try { - layerList.forEach(layerDescriptor => { + layerList.forEach((layerDescriptor: unknown) => { store.dispatch(addLayerWithoutDataSync(layerDescriptor)); }); queryableIndexPatternIds = getQueryableUniqueIndexPatternIds(store.getState()); @@ -86,16 +95,20 @@ export class MapEmbeddableFactory extends EmbeddableFactory { } }); const indexPatterns = await Promise.all(promises); - return _.compact(indexPatterns); + return _.compact(indexPatterns) as IIndexPattern[]; } - async _fetchSavedMap(savedObjectId) { + async _fetchSavedMap(savedObjectId: string) { const $injector = await chrome.dangerouslyGetActiveInjector(); - const savedObjectLoader = $injector.get('gisMapSavedObjectLoader'); + const savedObjectLoader = $injector.get<SavedObjectLoader>('gisMapSavedObjectLoader'); return await savedObjectLoader.get(savedObjectId); } - async createFromSavedObject(savedObjectId, input, parent) { + async createFromSavedObject( + savedObjectId: string, + input: MapEmbeddableInput, + parent?: IContainer + ) { const savedMap = await this._fetchSavedMap(savedObjectId); const layerList = getInitialLayers(savedMap.layerListJSON); const indexPatterns = await this._getIndexPatterns(layerList); @@ -106,7 +119,7 @@ export class MapEmbeddableFactory extends EmbeddableFactory { title: savedMap.title, editUrl: chrome.addBasePath(createMapPath(savedObjectId)), indexPatterns, - editable: this.isEditable(), + editable: await this.isEditable(), }, input, parent @@ -125,7 +138,13 @@ export class MapEmbeddableFactory extends EmbeddableFactory { return embeddable; } - async createFromState(state, input, parent, renderTooltipContent, eventHandlers) { + async createFromState( + state: { title?: string; layerList?: unknown[] }, + input: MapEmbeddableInput, + parent: IContainer, + renderTooltipContent: RenderToolTipContent, + eventHandlers: EventHandlers + ) { const layerList = state && state.layerList ? state.layerList : getInitialLayers(); const indexPatterns = await this._getIndexPatterns(layerList); @@ -133,7 +152,6 @@ export class MapEmbeddableFactory extends EmbeddableFactory { { layerList, title: state && state.title ? state.title : '', - editUrl: null, indexPatterns, editable: false, }, @@ -144,7 +162,7 @@ export class MapEmbeddableFactory extends EmbeddableFactory { ); } - async create(input) { + async create(input: MapEmbeddableInput) { window.location.href = chrome.addBasePath(createMapPath('')); return new ErrorEmbeddable( 'Maps can only be created with createFromSavedObject or createFromState', diff --git a/x-pack/legacy/plugins/maps/public/embeddable/merge_input_with_saved_map.d.ts b/x-pack/legacy/plugins/maps/public/embeddable/merge_input_with_saved_map.d.ts new file mode 100644 index 0000000000000..4ce4df02f6a39 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/embeddable/merge_input_with_saved_map.d.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { MapEmbeddableInput } from './map_embeddable'; + +export function mergeInputWithSavedMap( + input: MapEmbeddableInput, + savedmap: unknown +): Partial<MapEmbeddableInput>; diff --git a/x-pack/legacy/plugins/maps/public/index.ts b/x-pack/legacy/plugins/maps/public/index.ts index 27cd64103eec9..2d13f005f1a70 100644 --- a/x-pack/legacy/plugins/maps/public/index.ts +++ b/x-pack/legacy/plugins/maps/public/index.ts @@ -25,3 +25,6 @@ import { MapsPlugin } from './plugin'; export const plugin = (initializerContext: PluginInitializerContext) => { return new MapsPlugin(); }; + +export { RenderTooltipContentParams, ITooltipProperty } from './layers/tooltips/tooltip_property'; +export { MapEmbeddable, MapEmbeddableInput } from './embeddable'; diff --git a/x-pack/legacy/plugins/maps/public/kibana_services.d.ts b/x-pack/legacy/plugins/maps/public/kibana_services.d.ts new file mode 100644 index 0000000000000..89b1fee1aa842 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/kibana_services.d.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IIndexPattern } from 'src/plugins/data/public'; + +export function getIndexPatternService(): { + get: (id: string) => IIndexPattern | undefined; +}; + +export function setLicenseId(args: unknown): void; +export function setInspector(args: unknown): void; +export function setFileUpload(args: unknown): void; +export function setIndexPatternSelect(args: unknown): void; +export function setHttp(args: unknown): void; +export function setTimeFilter(args: unknown): void; +export function setUiSettings(args: unknown): void; +export function setInjectedVarFunc(args: unknown): void; +export function setToasts(args: unknown): void; +export function setIndexPatternService(args: unknown): void; +export function setAutocompleteService(args: unknown): void; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js index 0d4cf322d2a40..bf57306df5697 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js @@ -123,6 +123,7 @@ export class ColorMapSelect extends Component { { value: CUSTOM_COLOR_MAP, inputDisplay: this.props.customOptionLabel, + 'data-test-subj': `colorMapSelectOption_${CUSTOM_COLOR_MAP}`, }, ...this.props.colorMapOptions, ]; @@ -150,6 +151,7 @@ export class ColorMapSelect extends Component { onChange={this._onColorMapSelect} valueOfSelected={valueOfSelected} hasDividers={true} + data-test-subj={`colorMapSelect_${this.props.styleProperty.getStyleName()}`} /> </EuiFlexItem> </EuiFlexGroup> diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js index 124c2bf0cff55..edf230b0a945c 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js @@ -63,6 +63,7 @@ export const ColorStopsCategorical = ({ getValueSuggestions={getValueSuggestions} value={stopValue} onChange={onStopChange} + dataTestSubj={`colorStopInput${index}`} /> ); }; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js index c6b68b7e94409..a1c15e27c9eb3 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js @@ -123,6 +123,7 @@ export function DynamicColorForm({ </EuiFlexItem> <EuiFlexItem> <FieldSelect + styleName={styleProperty.getStyleName()} fields={fields} selectedFieldName={styleProperty.getFieldName()} onChange={onFieldChange} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js index cf0ec5589d6bc..2f5de507657a5 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js @@ -72,7 +72,7 @@ function groupFieldsByOrigin(fields) { return optionGroups; } -export function FieldSelect({ fields, selectedFieldName, onChange, ...rest }) { +export function FieldSelect({ fields, selectedFieldName, onChange, styleName, ...rest }) { const onFieldChange = selectedFields => { onChange({ field: selectedFields.length > 0 ? selectedFields[0].value : null, @@ -98,6 +98,7 @@ export function FieldSelect({ fields, selectedFieldName, onChange, ...rest }) { defaultMessage: 'Select a field', })} renderOption={renderOption} + data-test-subj={`styleFieldSelect_${styleName}`} {...rest} /> ); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js index 0c9011b811f38..bbba6705f7de7 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js @@ -27,6 +27,7 @@ export function DynamicLabelForm({ </EuiFlexItem> <EuiFlexItem> <FieldSelect + styleName={styleProperty.getStyleName()} fields={fields} selectedFieldName={styleProperty.getFieldName()} onChange={onFieldChange} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js index 5879e94991054..70f10e8e25675 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js @@ -30,6 +30,7 @@ export function DynamicOrientationForm({ </EuiFlexItem> <EuiFlexItem> <FieldSelect + styleName={styleProperty.getStyleName()} fields={fields} selectedFieldName={styleProperty.getFieldName()} onChange={onFieldChange} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js index 4918c2d906eef..6d8a88e1195d3 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js @@ -50,6 +50,7 @@ export function DynamicSizeForm({ </EuiFlexItem> <EuiFlexItem> <FieldSelect + styleName={styleProperty.getStyleName()} fields={fields} selectedFieldName={styleProperty.getFieldName()} onChange={onFieldChange} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/stop_input.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/stop_input.js index d12a3d77d0b29..93d9375972545 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/stop_input.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/stop_input.js @@ -119,6 +119,7 @@ export class StopInput extends Component { isClearable={false} isLoading={this.state.isLoadingSuggestions} onFocus={this._onFocus} + data-test-subj={this.props.dataTestSubj} compressed /> ); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js index 752e0e4213217..005bc11aa1bd8 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js @@ -77,6 +77,7 @@ export class StylePropEditor extends Component { defaultMessage: 'Select to style by fixed value or by data value', })} compressed + data-test-subj={`staticDynamicSelect_${this.props.styleProperty.getStyleName()}`} /> ); } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js index 9065102dc8bd7..6b79ac17f2e22 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js @@ -59,6 +59,7 @@ export function DynamicIconForm({ </EuiFlexItem> <EuiFlexItem> <FieldSelect + styleName={styleProperty.getStyleName()} fields={fields} selectedFieldName={_.get(styleOptions, 'field.name')} onChange={onFieldChange} diff --git a/x-pack/legacy/plugins/maps/public/layers/tooltips/tooltip_property.ts b/x-pack/legacy/plugins/maps/public/layers/tooltips/tooltip_property.ts index 3428cb9589267..c77af11d0ae24 100644 --- a/x-pack/legacy/plugins/maps/public/layers/tooltips/tooltip_property.ts +++ b/x-pack/legacy/plugins/maps/public/layers/tooltips/tooltip_property.ts @@ -16,10 +16,37 @@ export interface ITooltipProperty { getESFilters(): Promise<PhraseFilter[]>; } +export interface MapFeature { + id: number; + layerId: string; +} + +export interface LoadFeatureProps { + layerId: string; + featureId: number; +} + +export interface FeatureGeometry { + coordinates: [number]; + type: string; +} + +export interface RenderTooltipContentParams { + addFilters(filter: object): void; + closeTooltip(): void; + features: MapFeature[]; + isLocked: boolean; + getLayerName(layerId: string): Promise<string>; + loadFeatureProperties({ layerId, featureId }: LoadFeatureProps): Promise<ITooltipProperty[]>; + loadFeatureGeometry({ layerId, featureId }: LoadFeatureProps): FeatureGeometry; +} + +export type RenderToolTipContent = (params: RenderTooltipContentParams) => JSX.Element; + export class TooltipProperty implements ITooltipProperty { private readonly _propertyKey: string; - private readonly _propertyName: string; private readonly _rawValue: string | undefined; + private readonly _propertyName: string; constructor(propertyKey: string, propertyName: string, rawValue: string | undefined) { this._propertyKey = propertyKey; diff --git a/x-pack/legacy/plugins/maps/public/plugin.ts b/x-pack/legacy/plugins/maps/public/plugin.ts index 1f5817aa33dcc..53c951ac787e1 100644 --- a/x-pack/legacy/plugins/maps/public/plugin.ts +++ b/x-pack/legacy/plugins/maps/public/plugin.ts @@ -24,7 +24,6 @@ import { setToasts, setIndexPatternService, setAutocompleteService, - // @ts-ignore } from './kibana_services'; // @ts-ignore import { setInjectedVarFunc as npSetInjectedVarFunc } from '../../../../plugins/maps/public/kibana_services'; // eslint-disable-line @kbn/eslint/no-restricted-paths diff --git a/x-pack/legacy/plugins/maps/public/register_vis_type_alias.js b/x-pack/legacy/plugins/maps/public/register_vis_type_alias.js index 4d87b6a055802..64a42173098ee 100644 --- a/x-pack/legacy/plugins/maps/public/register_vis_type_alias.js +++ b/x-pack/legacy/plugins/maps/public/register_vis_type_alias.js @@ -5,7 +5,7 @@ */ import chrome from 'ui/chrome'; -import { setup as visualizationsSetup } from '../../../../../src/legacy/core_plugins/visualizations/public/np_ready/public/legacy'; +import { npSetup } from '../../../../../src/legacy/ui/public/new_platform'; import { i18n } from '@kbn/i18n'; import { APP_ID, APP_ICON, MAP_BASE_URL } from '../common/constants'; @@ -23,7 +23,7 @@ The Maps app offers more functionality and is easier to use.`, } ); -visualizationsSetup.registerAlias({ +npSetup.plugins.visualizations.registerAlias({ aliasUrl: MAP_BASE_URL, name: APP_ID, title: i18n.translate('xpack.maps.visTypeAlias.title', { @@ -37,5 +37,5 @@ visualizationsSetup.registerAlias({ }); if (!showMapVisualizationTypes) { - visualizationsSetup.hideTypes(['region_map', 'tile_map']); + npSetup.plugins.visualizations.hideTypes(['region_map', 'tile_map']); } diff --git a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.d.ts b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.d.ts new file mode 100644 index 0000000000000..237a04027e21b --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.d.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AnyAction } from 'redux'; +import { MapCenter } from '../actions/map_actions'; + +export function getHiddenLayerIds(state: unknown): string[]; + +export function getMapZoom(state: unknown): number; + +export function getMapCenter(state: unknown): MapCenter; + +export function getQueryableUniqueIndexPatternIds(state: unknown): string[]; diff --git a/x-pack/plugins/ml/public/application/components/kql_filter_bar/filter_bar/index.js b/x-pack/legacy/plugins/maps/public/selectors/ui_selectors.d.ts similarity index 66% rename from x-pack/plugins/ml/public/application/components/kql_filter_bar/filter_bar/index.js rename to x-pack/legacy/plugins/maps/public/selectors/ui_selectors.d.ts index e8153037e0c10..812e2082241bd 100644 --- a/x-pack/plugins/ml/public/application/components/kql_filter_bar/filter_bar/index.js +++ b/x-pack/legacy/plugins/maps/public/selectors/ui_selectors.d.ts @@ -4,4 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { FilterBar } from './filter_bar'; +export function getOpenTOCDetails(state: unknown): string[]; + +export function getIsLayerTOCOpen(state: unknown): boolean; diff --git a/x-pack/legacy/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts index 3f509c35b64ea..86b8ca1ff3894 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts @@ -10,6 +10,7 @@ import { ABOUT_FALSE_POSITIVES, ABOUT_MITRE, ABOUT_RISK, + ABOUT_RULE_DESCRIPTION, ABOUT_SEVERITY, ABOUT_STEP, ABOUT_TAGS, @@ -22,7 +23,6 @@ import { SCHEDULE_LOOPBACK, SCHEDULE_RUNS, SCHEDULE_STEP, - ABOUT_RULE_DESCRIPTION, } from '../screens/rule_details'; import { CUSTOM_RULES_BTN, @@ -31,6 +31,7 @@ import { RULES_ROW, RULES_TABLE, SEVERITY, + SHOWING_RULES_TEXT, } from '../screens/signal_detection_rules'; import { @@ -45,9 +46,12 @@ import { } from '../tasks/detections'; import { changeToThreeHundredRowsPerPage, + deleteFirstRule, + deleteSelectedRules, filterByCustomRules, goToCreateNewRule, goToRuleDetails, + selectNumberOfRules, waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRulesToBeLoaded, } from '../tasks/signal_detection_rules'; @@ -192,3 +196,67 @@ describe('Signal detection rules, custom', () => { .should('eql', '1m'); }); }); + +describe('Deletes custom rules', () => { + beforeEach(() => { + esArchiverLoad('custom_rules'); + loginAndWaitForPageWithoutDateRange(DETECTIONS); + waitForSignalsPanelToBeLoaded(); + waitForSignalsIndexToBeCreated(); + goToManageSignalDetectionRules(); + }); + + after(() => { + esArchiverUnload('custom_rules'); + }); + + it('Deletes one rule', () => { + cy.get(RULES_TABLE) + .find(RULES_ROW) + .then(rules => { + const initialNumberOfRules = rules.length; + const expectedNumberOfRulesAfterDeletion = initialNumberOfRules - 1; + + cy.get(SHOWING_RULES_TEXT) + .invoke('text') + .should('eql', `Showing ${initialNumberOfRules} rules`); + + deleteFirstRule(); + waitForRulesToBeLoaded(); + + cy.get(RULES_TABLE).then($table => { + cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRulesAfterDeletion); + }); + cy.get(SHOWING_RULES_TEXT) + .invoke('text') + .should('eql', `Showing ${expectedNumberOfRulesAfterDeletion} rules`); + cy.get(CUSTOM_RULES_BTN) + .invoke('text') + .should('eql', `Custom rules (${expectedNumberOfRulesAfterDeletion})`); + }); + }); + + it('Deletes more than one rule', () => { + cy.get(RULES_TABLE) + .find(RULES_ROW) + .then(rules => { + const initialNumberOfRules = rules.length; + const numberOfRulesToBeDeleted = 3; + const expectedNumberOfRulesAfterDeletion = initialNumberOfRules - numberOfRulesToBeDeleted; + + selectNumberOfRules(numberOfRulesToBeDeleted); + deleteSelectedRules(); + waitForRulesToBeLoaded(); + + cy.get(RULES_TABLE).then($table => { + cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRulesAfterDeletion); + }); + cy.get(SHOWING_RULES_TEXT) + .invoke('text') + .should('eql', `Showing ${expectedNumberOfRulesAfterDeletion} rule`); + cy.get(CUSTOM_RULES_BTN) + .invoke('text') + .should('eql', `Custom rules (${expectedNumberOfRulesAfterDeletion})`); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/cypress/screens/signal_detection_rules.ts b/x-pack/legacy/plugins/siem/cypress/screens/signal_detection_rules.ts index 65f24e3a765ea..09fbc2132302c 100644 --- a/x-pack/legacy/plugins/siem/cypress/screens/signal_detection_rules.ts +++ b/x-pack/legacy/plugins/siem/cypress/screens/signal_detection_rules.ts @@ -43,4 +43,6 @@ export const RULES_ROW = '.euiTableRow'; export const SEVERITY = '[data-test-subj="severity"]'; +export const SHOWING_RULES_TEXT = '[data-test-subj="showingRules"]'; + export const THREE_HUNDRED_ROWS = '[data-test-subj="tablePagination-300-rows"]'; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx index d0b1d8ffcb5ae..a3c4a655a4937 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx @@ -22,7 +22,8 @@ import { createEmbeddable, findMatchingIndexPatterns } from './embedded_map_help import { IndexPatternsMissingPrompt } from './index_patterns_missing_prompt'; import { MapToolTip } from './map_tool_tip/map_tool_tip'; import * as i18n from './translations'; -import { MapEmbeddable, SetQuery } from './types'; +import { SetQuery } from './types'; +import { MapEmbeddable } from '../../../../../plugins/maps/public'; import { Query, Filter } from '../../../../../../../src/plugins/data/public'; import { useKibana, useUiSetting$ } from '../../lib/kibana'; import { getSavedObjectFinder } from '../../../../../../../src/plugins/saved_objects/public'; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx index 888df8447a728..4b32fd8299ef7 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx @@ -9,10 +9,10 @@ import React from 'react'; import { OutPortal, PortalNode } from 'react-reverse-portal'; import minimatch from 'minimatch'; import { ViewMode } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public'; -import { IndexPatternMapping, MapEmbeddable, RenderTooltipContentParams, SetQuery } from './types'; +import { IndexPatternMapping, SetQuery } from './types'; import { getLayerList } from './map_config'; -// @ts-ignore Missing type defs as maps moves to Typescript -import { MAP_SAVED_OBJECT_TYPE } from '../../../../maps/common/constants'; +import { MAP_SAVED_OBJECT_TYPE } from '../../../../../../plugins/maps/public'; +import { MapEmbeddable, RenderTooltipContentParams } from '../../../../maps/public'; import * as i18n from './translations'; import { Query, Filter } from '../../../../../../../src/plugins/data/public'; import { EmbeddableStart } from '../../../../../../../src/plugins/embeddable/public'; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.tsx index 15c423a3b3dc1..fc55e3437dc21 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.tsx @@ -64,7 +64,7 @@ export const MapToolTipComponent = ({ getLayerName(layerId), ]); - setFeatureProps(featureProperties); + setFeatureProps((featureProperties as unknown) as FeatureProperty[]); setFeatureGeometry(featureGeo); setLayerName(layerNameString); } catch (e) { diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/types.ts b/x-pack/legacy/plugins/siem/public/components/embeddables/types.ts index cc253beb08eae..216fe9105327c 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/types.ts @@ -4,26 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { TimeRange } from 'src/plugins/data/public'; -import { - EmbeddableInput, - EmbeddableOutput, - IEmbeddable, -} from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public'; +import { RenderTooltipContentParams } from '../../../../maps/public'; import { inputsModel } from '../../store/inputs'; -import { Query, Filter } from '../../../../../../../src/plugins/data/public'; - -export interface MapEmbeddableInput extends EmbeddableInput { - filters: Filter[]; - query: Query; - refreshConfig: { - isPaused: boolean; - interval: number; - }; - timeRange?: TimeRange; -} - -export type MapEmbeddable = IEmbeddable<MapEmbeddableInput, EmbeddableOutput>; export interface IndexPatternMapping { title: string; @@ -73,14 +55,4 @@ export interface FeatureGeometry { type: string; } -export interface RenderTooltipContentParams { - addFilters(filter: object): void; - closeTooltip(): void; - features: MapFeature[]; - isLocked: boolean; - getLayerName(layerId: string): Promise<string>; - loadFeatureProperties({ layerId, featureId }: LoadFeatureProps): Promise<FeatureProperty[]>; - loadFeatureGeometry({ layerId, featureId }: LoadFeatureProps): FeatureGeometry; -} - export type MapToolTipProps = Partial<RenderTooltipContentParams>; diff --git a/x-pack/legacy/plugins/siem/public/components/import_data_modal/index.tsx b/x-pack/legacy/plugins/siem/public/components/import_data_modal/index.tsx index 503710f1ee8aa..c827411a41e2e 100644 --- a/x-pack/legacy/plugins/siem/public/components/import_data_modal/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/import_data_modal/index.tsx @@ -21,7 +21,7 @@ import { } from '@elastic/eui'; import React, { useCallback, useState } from 'react'; -import { ImportRulesResponse, ImportRulesProps } from '../../containers/detection_engine/rules'; +import { ImportDataResponse, ImportDataProps } from '../../containers/detection_engine/rules'; import { displayErrorToast, displaySuccessToast, @@ -37,7 +37,7 @@ interface ImportDataModalProps { errorMessage: string; failedDetailed: (id: string, statusCode: number, message: string) => string; importComplete: () => void; - importData: (arg: ImportRulesProps) => Promise<ImportRulesResponse>; + importData: (arg: ImportDataProps) => Promise<ImportDataResponse>; showCheckBox: boolean; showModal: boolean; submitBtnText: string; @@ -75,7 +75,7 @@ export const ImportDataModalComponent = ({ closeModal(); }, [setIsImporting, setSelectedFiles, closeModal]); - const importRulesCallback = useCallback(async () => { + const importDataCallback = useCallback(async () => { if (selectedFiles != null) { setIsImporting(true); const abortCtrl = new AbortController(); @@ -152,7 +152,7 @@ export const ImportDataModalComponent = ({ <EuiModalFooter> <EuiButtonEmpty onClick={handleCloseModal}>{i18n.CANCEL_BUTTON}</EuiButtonEmpty> <EuiButton - onClick={importRulesCallback} + onClick={importDataCallback} disabled={selectedFiles == null || isImporting} fill > diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx index 6c2cd21d808b7..c27a6039da29d 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx @@ -54,7 +54,7 @@ interface OwnProps<TCache = object> { export type OpenTimelineOwnProps = OwnProps & Pick< OpenTimelineProps, - 'defaultPageSize' | 'title' | 'importCompleteToggle' | 'setImportCompleteToggle' + 'defaultPageSize' | 'title' | 'importDataModalToggle' | 'setImportDataModalToggle' > & PropsFromRedux; @@ -77,9 +77,9 @@ export const StatefulOpenTimelineComponent = React.memo<OpenTimelineOwnProps>( defaultPageSize, hideActions = [], isModal = false, - importCompleteToggle, + importDataModalToggle, onOpenTimeline, - setImportCompleteToggle, + setImportDataModalToggle, timeline, title, updateTimeline, @@ -269,7 +269,7 @@ export const StatefulOpenTimelineComponent = React.memo<OpenTimelineOwnProps>( defaultPageSize={defaultPageSize} isLoading={loading} itemIdToExpandedNotesRowMap={itemIdToExpandedNotesRowMap} - importCompleteToggle={importCompleteToggle} + importDataModalToggle={importDataModalToggle} onAddTimelinesToFavorites={undefined} onDeleteSelected={onDeleteSelected} onlyFavorites={onlyFavorites} @@ -284,7 +284,7 @@ export const StatefulOpenTimelineComponent = React.memo<OpenTimelineOwnProps>( query={search} refetch={refetch} searchResults={timelines} - setImportCompleteToggle={setImportCompleteToggle} + setImportDataModalToggle={setImportDataModalToggle} selectedItems={selectedItems} sortDirection={sortDirection} sortField={sortField} diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.tsx index 8b3da4427a362..6b2f953b82de4 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.tsx @@ -33,7 +33,7 @@ export const OpenTimeline = React.memo<OpenTimelineProps>( defaultPageSize, isLoading, itemIdToExpandedNotesRowMap, - importCompleteToggle, + importDataModalToggle, onAddTimelinesToFavorites, onDeleteSelected, onlyFavorites, @@ -50,7 +50,7 @@ export const OpenTimeline = React.memo<OpenTimelineProps>( searchResults, selectedItems, sortDirection, - setImportCompleteToggle, + setImportDataModalToggle, sortField, title, totalSearchResultsCount, @@ -103,18 +103,18 @@ export const OpenTimeline = React.memo<OpenTimelineProps>( }, [refetch]); const handleCloseModal = useCallback(() => { - if (setImportCompleteToggle != null) { - setImportCompleteToggle(false); + if (setImportDataModalToggle != null) { + setImportDataModalToggle(false); } - }, [setImportCompleteToggle]); + }, [setImportDataModalToggle]); const handleComplete = useCallback(() => { - if (setImportCompleteToggle != null) { - setImportCompleteToggle(false); + if (setImportDataModalToggle != null) { + setImportDataModalToggle(false); } if (refetch != null) { refetch(); } - }, [setImportCompleteToggle, refetch]); + }, [setImportDataModalToggle, refetch]); return ( <> @@ -136,7 +136,7 @@ export const OpenTimeline = React.memo<OpenTimelineProps>( importData={importTimelines} successMessage={i18n.SUCCESSFULLY_IMPORTED_TIMELINES} showCheckBox={false} - showModal={importCompleteToggle ?? false} + showModal={importDataModalToggle ?? false} submitBtnText={i18n.IMPORT_TIMELINE_BTN_TITLE} subtitle={i18n.INITIAL_PROMPT_TEXT} title={i18n.IMPORT_TIMELINE} diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx index ca82e30798d82..8805037ecc4ca 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx @@ -156,4 +156,72 @@ describe('#getActionsColumns', () => { expect(onOpenTimeline).toBeCalledWith({ duplicate: true, timelineId: 'saved-timeline-11' }); }); + + test('it renders the export icon when enableExportTimelineDownloader is including the action export', () => { + const testProps: TimelinesTableProps = { + ...getMockTimelinesTableProps(mockResults), + actionTimelineToShow: ['export'], + }; + const wrapper = mountWithIntl( + <ThemeProvider theme={theme}> + <TimelinesTable {...testProps} /> + </ThemeProvider> + ); + expect(wrapper.find('[data-test-subj="export-timeline"]').exists()).toBe(true); + }); + + test('it renders No export icon when export is not included in the action ', () => { + const testProps: TimelinesTableProps = { + ...getMockTimelinesTableProps(mockResults), + }; + const wrapper = mountWithIntl( + <ThemeProvider theme={theme}> + <TimelinesTable {...testProps} /> + </ThemeProvider> + ); + expect(wrapper.find('[data-test-subj="export-timeline"]').exists()).toBe(false); + }); + + test('it renders a disabled the export button if the timeline does not have a saved object id', () => { + const missingSavedObjectId: OpenTimelineResult[] = [ + omit('savedObjectId', { ...mockResults[0] }), + ]; + + const testProps: TimelinesTableProps = { + ...getMockTimelinesTableProps(missingSavedObjectId), + actionTimelineToShow: ['export'], + }; + const wrapper = mountWithIntl( + <ThemeProvider theme={theme}> + <TimelinesTable {...testProps} /> + </ThemeProvider> + ); + + const props = wrapper + .find('[data-test-subj="export-timeline"]') + .first() + .props() as EuiButtonIconProps; + expect(props.isDisabled).toBe(true); + }); + + test('it invokes enableExportTimelineDownloader with the expected params when the button is clicked', () => { + const enableExportTimelineDownloader = jest.fn(); + const testProps: TimelinesTableProps = { + ...getMockTimelinesTableProps(mockResults), + actionTimelineToShow: ['export'], + enableExportTimelineDownloader, + }; + const wrapper = mountWithIntl( + <ThemeProvider theme={theme}> + <TimelinesTable {...testProps} /> + </ThemeProvider> + ); + + wrapper + .find('[data-test-subj="export-timeline"]') + .first() + .simulate('click'); + + expect(enableExportTimelineDownloader).toBeCalledWith(mockResults[0]); + }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx index 4bbf98dafe38d..8588beed64b79 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx @@ -55,6 +55,7 @@ export const getActionsColumns = ({ }, enabled: ({ savedObjectId }: OpenTimelineResult) => savedObjectId != null, description: i18n.EXPORT_SELECTED, + 'data-test-subj': 'export-timeline', }; const deleteTimelineColumn = { diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts b/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts index 1265c056ec506..51c72681c0863 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts @@ -121,7 +121,7 @@ export interface OpenTimelineProps { /** Required by EuiTable for expandable rows: a map of `TimelineResult.savedObjectId` to rendered notes */ itemIdToExpandedNotesRowMap: Record<string, JSX.Element>; /** Display import timelines modal*/ - importCompleteToggle?: boolean; + importDataModalToggle?: boolean; /** If this callback is specified, a "Favorite Selected" button will be displayed, and this callback will be invoked when the button is clicked */ onAddTimelinesToFavorites?: OnAddTimelinesToFavorites; /** If this callback is specified, a "Delete Selected" button will be displayed, and this callback will be invoked when the button is clicked */ @@ -153,7 +153,7 @@ export interface OpenTimelineProps { /** the currently-selected timelines in the table */ selectedItems: OpenTimelineResult[]; /** Toggle export timelines modal*/ - setImportCompleteToggle?: React.Dispatch<React.SetStateAction<boolean>>; + setImportDataModalToggle?: React.Dispatch<React.SetStateAction<boolean>>; /** the requested sort direction of the query results */ sortDirection: 'asc' | 'desc'; /** the requested field to sort on */ diff --git a/x-pack/legacy/plugins/siem/public/containers/case/api.ts b/x-pack/legacy/plugins/siem/public/containers/case/api.ts index 16ee294224bb9..e98c258690486 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/case/api.ts @@ -8,10 +8,10 @@ import { CaseResponse, CasesResponse, CasesFindResponse, - CaseRequest, + CasePatchRequest, + CasePostRequest, CasesStatusResponse, CommentRequest, - CommentResponse, User, CaseUserActionsResponse, CaseExternalServiceRequest, @@ -26,7 +26,6 @@ import { BulkUpdateStatus, Case, CasesStatus, - Comment, FetchCasesProps, SortFieldCase, CaseUserActions, @@ -40,7 +39,6 @@ import { decodeCasesResponse, decodeCasesFindResponse, decodeCasesStatusResponse, - decodeCommentResponse, decodeCaseUserActionsResponse, decodeServiceConnectorCaseResponse, } from './utils'; @@ -123,7 +121,7 @@ export const getCases = async ({ return convertAllCasesToCamel(decodeCasesFindResponse(response)); }; -export const postCase = async (newCase: CaseRequest): Promise<Case> => { +export const postCase = async (newCase: CasePostRequest): Promise<Case> => { const response = await KibanaServices.get().http.fetch<CaseResponse>(CASES_URL, { method: 'POST', body: JSON.stringify(newCase), @@ -133,7 +131,7 @@ export const postCase = async (newCase: CaseRequest): Promise<Case> => { export const patchCase = async ( caseId: string, - updatedCase: Partial<CaseRequest>, + updatedCase: Pick<CasePatchRequest, 'description' | 'status' | 'tags' | 'title'>, version: string ): Promise<Case[]> => { const response = await KibanaServices.get().http.fetch<CasesResponse>(CASES_URL, { @@ -151,15 +149,15 @@ export const patchCasesStatus = async (cases: BulkUpdateStatus[]): Promise<Case[ return convertToCamelCase<CasesResponse, Case[]>(decodeCasesResponse(response)); }; -export const postComment = async (newComment: CommentRequest, caseId: string): Promise<Comment> => { - const response = await KibanaServices.get().http.fetch<CommentResponse>( +export const postComment = async (newComment: CommentRequest, caseId: string): Promise<Case> => { + const response = await KibanaServices.get().http.fetch<CaseResponse>( `${CASES_URL}/${caseId}/comments`, { method: 'POST', body: JSON.stringify(newComment), } ); - return convertToCamelCase<CommentResponse, Comment>(decodeCommentResponse(response)); + return convertToCamelCase<CaseResponse, Case>(decodeCaseResponse(response)); }; export const patchComment = async ( @@ -167,15 +165,15 @@ export const patchComment = async ( commentId: string, commentUpdate: string, version: string -): Promise<Partial<Comment>> => { - const response = await KibanaServices.get().http.fetch<CommentResponse>( +): Promise<Case> => { + const response = await KibanaServices.get().http.fetch<CaseResponse>( `${CASES_URL}/${caseId}/comments`, { method: 'PATCH', body: JSON.stringify({ comment: commentUpdate, id: commentId, version }), } ); - return convertToCamelCase<CommentResponse, Comment>(decodeCommentResponse(response)); + return convertToCamelCase<CaseResponse, Case>(decodeCaseResponse(response)); }; export const deleteCases = async (caseIds: string[]): Promise<boolean> => { diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx index 02b41c9fc720f..506be62614c14 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useEffect, useReducer } from 'react'; +import { useEffect, useReducer, useCallback } from 'react'; import { Case } from './types'; import * as i18n from './translations'; @@ -20,7 +20,8 @@ interface CaseState { type Action = | { type: 'FETCH_INIT' } | { type: 'FETCH_SUCCESS'; payload: Case } - | { type: 'FETCH_FAILURE' }; + | { type: 'FETCH_FAILURE' } + | { type: 'UPDATE_CASE'; payload: Case }; const dataFetchReducer = (state: CaseState, action: Action): CaseState => { switch (action.type) { @@ -43,6 +44,11 @@ const dataFetchReducer = (state: CaseState, action: Action): CaseState => { isLoading: false, isError: true, }; + case 'UPDATE_CASE': + return { + ...state, + data: action.payload, + }; default: return state; } @@ -67,7 +73,12 @@ const initialData: Case = { version: '', }; -export const useGetCase = (caseId: string): CaseState => { +interface UseGetCase extends CaseState { + fetchCase: () => void; + updateCase: (newCase: Case) => void; +} + +export const useGetCase = (caseId: string): UseGetCase => { const [state, dispatch] = useReducer(dataFetchReducer, { isLoading: true, isError: false, @@ -75,7 +86,11 @@ export const useGetCase = (caseId: string): CaseState => { }); const [, dispatchToaster] = useStateToaster(); - const callFetch = () => { + const updateCase = useCallback((newCase: Case) => { + dispatch({ type: 'UPDATE_CASE', payload: newCase }); + }, []); + + const callFetch = useCallback(async () => { let didCancel = false; const fetchData = async () => { dispatch({ type: 'FETCH_INIT' }); @@ -99,10 +114,10 @@ export const useGetCase = (caseId: string): CaseState => { return () => { didCancel = true; }; - }; + }, [caseId]); useEffect(() => { callFetch(); }, [caseId]); - return state; + return { ...state, fetchCase: callFetch, updateCase }; }; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_post_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_post_case.tsx index 817101cf5e663..2ad7a08473200 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_post_case.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_post_case.tsx @@ -6,7 +6,7 @@ import { useReducer, useCallback } from 'react'; -import { CaseRequest } from '../../../../../../plugins/case/common/api'; +import { CasePostRequest } from '../../../../../../plugins/case/common/api'; import { errorToToaster, useStateToaster } from '../../components/toasters'; import { postCase } from './api'; import * as i18n from './translations'; @@ -49,7 +49,7 @@ const dataFetchReducer = (state: NewCaseState, action: Action): NewCaseState => }; interface UsePostCase extends NewCaseState { - postCase: (data: CaseRequest) => void; + postCase: (data: CasePostRequest) => void; } export const usePostCase = (): UsePostCase => { const [state, dispatch] = useReducer(dataFetchReducer, { @@ -59,11 +59,11 @@ export const usePostCase = (): UsePostCase => { }); const [, dispatchToaster] = useStateToaster(); - const postMyCase = useCallback(async (data: CaseRequest) => { + const postMyCase = useCallback(async (data: CasePostRequest) => { let cancel = false; try { dispatch({ type: 'FETCH_INIT' }); - const response = await postCase({ ...data, status: 'open' }); + const response = await postCase(data); if (!cancel) { dispatch({ type: 'FETCH_SUCCESS', diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_post_comment.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_post_comment.tsx index a96cb97d7cc7b..86b7f92ace5ad 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_post_comment.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_post_comment.tsx @@ -11,43 +11,28 @@ import { errorToToaster, useStateToaster } from '../../components/toasters'; import { postComment } from './api'; import * as i18n from './translations'; -import { Comment } from './types'; +import { Case } from './types'; interface NewCommentState { - commentData: Comment | null; isLoading: boolean; isError: boolean; - caseId: string; } -type Action = - | { type: 'RESET_COMMENT_DATA' } - | { type: 'FETCH_INIT' } - | { type: 'FETCH_SUCCESS'; payload: Comment } - | { type: 'FETCH_FAILURE' }; +type Action = { type: 'FETCH_INIT' } | { type: 'FETCH_SUCCESS' } | { type: 'FETCH_FAILURE' }; const dataFetchReducer = (state: NewCommentState, action: Action): NewCommentState => { switch (action.type) { - case 'RESET_COMMENT_DATA': - return { - ...state, - commentData: null, - }; case 'FETCH_INIT': return { - ...state, isLoading: true, isError: false, }; case 'FETCH_SUCCESS': return { - ...state, isLoading: false, isError: false, - commentData: action.payload ?? null, }; case 'FETCH_FAILURE': return { - ...state, isLoading: false, isError: true, }; @@ -57,43 +42,42 @@ const dataFetchReducer = (state: NewCommentState, action: Action): NewCommentSta }; interface UsePostComment extends NewCommentState { - postComment: (data: CommentRequest) => void; - resetCommentData: () => void; + postComment: (data: CommentRequest, updateCase: (newCase: Case) => void) => void; } export const usePostComment = (caseId: string): UsePostComment => { const [state, dispatch] = useReducer(dataFetchReducer, { - commentData: null, isLoading: false, isError: false, - caseId, }); const [, dispatchToaster] = useStateToaster(); - const postMyComment = useCallback(async (data: CommentRequest) => { - let cancel = false; - try { - dispatch({ type: 'FETCH_INIT' }); - const response = await postComment(data, state.caseId); - if (!cancel) { - dispatch({ type: 'FETCH_SUCCESS', payload: response }); - } - } catch (error) { - if (!cancel) { - errorToToaster({ - title: i18n.ERROR_TITLE, - error: error.body && error.body.message ? new Error(error.body.message) : error, - dispatchToaster, - }); - dispatch({ type: 'FETCH_FAILURE' }); + const postMyComment = useCallback( + async (data: CommentRequest, updateCase: (newCase: Case) => void) => { + let cancel = false; + try { + dispatch({ type: 'FETCH_INIT' }); + const response = await postComment(data, caseId); + if (!cancel) { + dispatch({ type: 'FETCH_SUCCESS' }); + updateCase(response); + } + } catch (error) { + if (!cancel) { + errorToToaster({ + title: i18n.ERROR_TITLE, + error: error.body && error.body.message ? new Error(error.body.message) : error, + dispatchToaster, + }); + dispatch({ type: 'FETCH_FAILURE' }); + } } - } - return () => { - cancel = true; - }; - }, []); - - const resetCommentData = useCallback(() => dispatch({ type: 'RESET_COMMENT_DATA' }), []); + return () => { + cancel = true; + }; + }, + [caseId] + ); - return { ...state, postComment: postMyComment, resetCommentData }; + return { ...state, postComment: postMyComment }; }; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_post_push_to_service.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_post_push_to_service.tsx index b6fb15f4fa083..d00755a9f2426 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_post_push_to_service.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_post_push_to_service.tsx @@ -143,7 +143,6 @@ const formatServiceRequestData = (myCase: Case): ServiceConnectorCaseParams => { updatedAt, updatedBy, } = myCase; - return { caseId, createdAt, @@ -151,23 +150,35 @@ const formatServiceRequestData = (myCase: Case): ServiceConnectorCaseParams => { fullName: createdBy.fullName ?? null, username: createdBy?.username, }, - comments: comments.map(c => ({ - commentId: c.id, - comment: c.comment, - createdAt: c.createdAt, - createdBy: { - fullName: c.createdBy.fullName ?? null, - username: c.createdBy.username, - }, - updatedAt: c.updatedAt, - updatedBy: - c.updatedBy != null - ? { - fullName: c.updatedBy.fullName ?? null, - username: c.updatedBy.username, - } - : null, - })), + comments: comments + .filter(c => { + const lastPush = c.pushedAt != null ? new Date(c.pushedAt) : null; + const lastUpdate = c.updatedAt != null ? new Date(c.updatedAt) : null; + if ( + lastPush === null || + (lastPush != null && lastUpdate != null && lastPush.getTime() < lastUpdate?.getTime()) + ) { + return true; + } + return false; + }) + .map(c => ({ + commentId: c.id, + comment: c.comment, + createdAt: c.createdAt, + createdBy: { + fullName: c.createdBy.fullName ?? null, + username: c.createdBy.username, + }, + updatedAt: c.updatedAt, + updatedBy: + c.updatedBy != null + ? { + fullName: c.updatedBy.fullName ?? null, + username: c.updatedBy.username, + } + : null, + })), description, incidentId: externalService?.externalId ?? null, title, diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx index f8af088f7e03b..d1015d3f74250 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx @@ -5,18 +5,16 @@ */ import { useReducer, useCallback } from 'react'; -import { cloneDeep } from 'lodash/fp'; -import { CaseRequest } from '../../../../../../plugins/case/common/api'; +import { CasePatchRequest } from '../../../../../../plugins/case/common/api'; import { errorToToaster, useStateToaster } from '../../components/toasters'; import { patchCase } from './api'; import * as i18n from './translations'; import { Case } from './types'; -type UpdateKey = keyof CaseRequest; +type UpdateKey = keyof Pick<CasePatchRequest, 'description' | 'status' | 'tags' | 'title'>; interface NewCaseState { - caseData: Case; isLoading: boolean; isError: boolean; updateKey: UpdateKey | null; @@ -24,13 +22,15 @@ interface NewCaseState { export interface UpdateByKey { updateKey: UpdateKey; - updateValue: CaseRequest[UpdateKey]; + updateValue: CasePatchRequest[UpdateKey]; fetchCaseUserActions?: (caseId: string) => void; + updateCase?: (newCase: Case) => void; + version: string; } type Action = | { type: 'FETCH_INIT'; payload: UpdateKey } - | { type: 'FETCH_SUCCESS'; payload: Case } + | { type: 'FETCH_SUCCESS' } | { type: 'FETCH_FAILURE' }; const dataFetchReducer = (state: NewCaseState, action: Action): NewCaseState => { @@ -48,7 +48,6 @@ const dataFetchReducer = (state: NewCaseState, action: Action): NewCaseState => ...state, isLoading: false, isError: false, - caseData: cloneDeep(action.payload), updateKey: null, }; case 'FETCH_FAILURE': @@ -65,36 +64,29 @@ const dataFetchReducer = (state: NewCaseState, action: Action): NewCaseState => interface UseUpdateCase extends NewCaseState { updateCaseProperty: (updates: UpdateByKey) => void; - updateCase: (newCase: Case) => void; } -export const useUpdateCase = (caseId: string, initialData: Case): UseUpdateCase => { +export const useUpdateCase = ({ caseId }: { caseId: string }): UseUpdateCase => { const [state, dispatch] = useReducer(dataFetchReducer, { isLoading: false, isError: false, - caseData: initialData, updateKey: null, }); const [, dispatchToaster] = useStateToaster(); - const updateCase = useCallback((newCase: Case) => { - dispatch({ type: 'FETCH_SUCCESS', payload: newCase }); - }, []); - const dispatchUpdateCaseProperty = useCallback( - async ({ fetchCaseUserActions, updateKey, updateValue }: UpdateByKey) => { + async ({ fetchCaseUserActions, updateKey, updateValue, updateCase, version }: UpdateByKey) => { let cancel = false; try { dispatch({ type: 'FETCH_INIT', payload: updateKey }); - const response = await patchCase( - caseId, - { [updateKey]: updateValue }, - state.caseData.version - ); + const response = await patchCase(caseId, { [updateKey]: updateValue }, version); if (!cancel) { if (fetchCaseUserActions != null) { fetchCaseUserActions(caseId); } - dispatch({ type: 'FETCH_SUCCESS', payload: response[0] }); + if (updateCase != null) { + updateCase(response[0]); + } + dispatch({ type: 'FETCH_SUCCESS' }); } } catch (error) { if (!cancel) { @@ -110,8 +102,8 @@ export const useUpdateCase = (caseId: string, initialData: Case): UseUpdateCase cancel = true; }; }, - [state] + [] ); - return { ...state, updateCase, updateCaseProperty: dispatchUpdateCaseProperty }; + return { ...state, updateCaseProperty: dispatchUpdateCaseProperty }; }; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_update_comment.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_update_comment.tsx index c1b2bfde30126..b83ed2c01708a 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_update_comment.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_update_comment.tsx @@ -4,38 +4,29 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useReducer, useCallback, Dispatch } from 'react'; +import { useReducer, useCallback } from 'react'; import { errorToToaster, useStateToaster } from '../../components/toasters'; import { patchComment } from './api'; import * as i18n from './translations'; -import { Comment } from './types'; +import { Case } from './types'; interface CommentUpdateState { - comments: Comment[]; isLoadingIds: string[]; isError: boolean; } - interface CommentUpdate { - update: Partial<Comment>; commentId: string; } type Action = - | { type: 'APPEND_COMMENT'; payload: Comment } | { type: 'FETCH_INIT'; payload: string } | { type: 'FETCH_SUCCESS'; payload: CommentUpdate } | { type: 'FETCH_FAILURE'; payload: string }; const dataFetchReducer = (state: CommentUpdateState, action: Action): CommentUpdateState => { switch (action.type) { - case 'APPEND_COMMENT': - return { - ...state, - comments: [...state.comments, action.payload], - }; case 'FETCH_INIT': return { ...state, @@ -44,20 +35,10 @@ const dataFetchReducer = (state: CommentUpdateState, action: Action): CommentUpd }; case 'FETCH_SUCCESS': - const updatePayload = action.payload; - const foundIndex = state.comments.findIndex( - comment => comment.id === updatePayload.commentId - ); - const newComments = state.comments; - if (foundIndex !== -1) { - newComments[foundIndex] = { ...state.comments[foundIndex], ...updatePayload.update }; - } - return { ...state, - isLoadingIds: state.isLoadingIds.filter(id => updatePayload.commentId !== id), + isLoadingIds: state.isLoadingIds.filter(id => action.payload.commentId !== id), isError: false, - comments: newComments, }; case 'FETCH_FAILURE': return { @@ -75,38 +56,38 @@ interface UpdateComment { commentId: string; commentUpdate: string; fetchUserActions: () => void; + updateCase: (newCase: Case) => void; + version: string; } interface UseUpdateComment extends CommentUpdateState { - updateComment: ({ caseId, commentId, commentUpdate, fetchUserActions }: UpdateComment) => void; - addPostedComment: Dispatch<Comment>; + patchComment: ({ caseId, commentId, commentUpdate, fetchUserActions }: UpdateComment) => void; } -export const useUpdateComment = (comments: Comment[]): UseUpdateComment => { +export const useUpdateComment = (): UseUpdateComment => { const [state, dispatch] = useReducer(dataFetchReducer, { isLoadingIds: [], isError: false, - comments, }); const [, dispatchToaster] = useStateToaster(); const dispatchUpdateComment = useCallback( - async ({ caseId, commentId, commentUpdate, fetchUserActions }: UpdateComment) => { + async ({ + caseId, + commentId, + commentUpdate, + fetchUserActions, + updateCase, + version, + }: UpdateComment) => { let cancel = false; try { dispatch({ type: 'FETCH_INIT', payload: commentId }); - const currentComment = state.comments.find(comment => comment.id === commentId) ?? { - version: '', - }; - const response = await patchComment( - caseId, - commentId, - commentUpdate, - currentComment.version - ); + const response = await patchComment(caseId, commentId, commentUpdate, version); if (!cancel) { + updateCase(response); fetchUserActions(); - dispatch({ type: 'FETCH_SUCCESS', payload: { update: response, commentId } }); + dispatch({ type: 'FETCH_SUCCESS', payload: { commentId } }); } } catch (error) { if (!cancel) { @@ -122,12 +103,8 @@ export const useUpdateComment = (comments: Comment[]): UseUpdateComment => { cancel = true; }; }, - [state] - ); - const addPostedComment = useCallback( - (comment: Comment) => dispatch({ type: 'APPEND_COMMENT', payload: comment }), [] ); - return { ...state, updateComment: dispatchUpdateComment, addPostedComment }; + return { ...state, patchComment: dispatchUpdateComment }; }; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/utils.ts b/x-pack/legacy/plugins/siem/public/containers/case/utils.ts index ce23ac6c440b6..1ec98bf5b5f1f 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/utils.ts +++ b/x-pack/legacy/plugins/siem/public/containers/case/utils.ts @@ -19,8 +19,6 @@ import { CasesStatusResponseRt, CasesStatusResponse, throwErrors, - CommentResponse, - CommentResponseRt, CasesConfigureResponse, CaseConfigureResponseRt, CaseUserActionsResponse, @@ -82,9 +80,6 @@ export const decodeCasesResponse = (respCase?: CasesResponse) => export const decodeCasesFindResponse = (respCases?: CasesFindResponse) => pipe(CasesFindResponseRt.decode(respCases), fold(throwErrors(createToasterPlainError), identity)); -export const decodeCommentResponse = (respComment?: CommentResponse) => - pipe(CommentResponseRt.decode(respComment), fold(throwErrors(createToasterPlainError), identity)); - export const decodeCaseConfigureResponse = (respCase?: CasesConfigureResponse) => pipe( CaseConfigureResponseRt.decode(respCase), diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts index 4b0e0030be53d..2dd6955581eff 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts @@ -15,10 +15,10 @@ import { Rule, FetchRuleProps, BasicFetchProps, - ImportRulesProps, + ImportDataProps, ExportDocumentsProps, RuleStatusResponse, - ImportRulesResponse, + ImportDataResponse, PrePackagedRulesStatusResponse, BulkRuleResponse, } from './types'; @@ -204,11 +204,11 @@ export const importRules = async ({ fileToImport, overwrite = false, signal, -}: ImportRulesProps): Promise<ImportRulesResponse> => { +}: ImportDataProps): Promise<ImportDataResponse> => { const formData = new FormData(); formData.append('file', fileToImport); - return KibanaServices.get().http.fetch<ImportRulesResponse>( + return KibanaServices.get().http.fetch<ImportDataResponse>( `${DETECTION_ENGINE_RULES_URL}/_import`, { method: 'POST', diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts index 53a1c0770028c..f676ab944fce4 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts @@ -194,7 +194,7 @@ export interface BasicFetchProps { signal: AbortSignal; } -export interface ImportRulesProps { +export interface ImportDataProps { fileToImport: File; overwrite?: boolean; signal: AbortSignal; @@ -208,7 +208,7 @@ export interface ImportRulesResponseError { }; } -export interface ImportRulesResponse { +export interface ImportDataResponse { success: boolean; success_count: number; errors: ImportRulesResponseError[]; diff --git a/x-pack/legacy/plugins/siem/public/containers/timeline/all/api.ts b/x-pack/legacy/plugins/siem/public/containers/timeline/all/api.ts index 0479851fc5b55..4c8e2384de585 100644 --- a/x-pack/legacy/plugins/siem/public/containers/timeline/all/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/timeline/all/api.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ImportRulesProps, ImportRulesResponse } from '../../detection_engine/rules'; +import { ImportDataProps, ImportDataResponse } from '../../detection_engine/rules'; import { KibanaServices } from '../../../lib/kibana'; import { TIMELINE_IMPORT_URL, TIMELINE_EXPORT_URL } from '../../../../common/constants'; import { ExportSelectedData } from '../../../components/generic_downloader'; @@ -13,11 +13,11 @@ export const importTimelines = async ({ fileToImport, overwrite = false, signal, -}: ImportRulesProps): Promise<ImportRulesResponse> => { +}: ImportDataProps): Promise<ImportDataResponse> => { const formData = new FormData(); formData.append('file', fileToImport); - return KibanaServices.get().http.fetch<ImportRulesResponse>(`${TIMELINE_IMPORT_URL}`, { + return KibanaServices.get().http.fetch<ImportDataResponse>(`${TIMELINE_IMPORT_URL}`, { method: 'POST', headers: { 'Content-Type': undefined }, query: { overwrite }, diff --git a/x-pack/legacy/plugins/siem/public/lib/connectors/servicenow.tsx b/x-pack/legacy/plugins/siem/public/lib/connectors/servicenow.tsx index 8e947fbc0f9bb..d67007399abea 100644 --- a/x-pack/legacy/plugins/siem/public/lib/connectors/servicenow.tsx +++ b/x-pack/legacy/plugins/siem/public/lib/connectors/servicenow.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, ChangeEvent } from 'react'; +import React, { useCallback, ChangeEvent, useEffect } from 'react'; import { EuiFieldText, EuiFlexGroup, @@ -98,6 +98,21 @@ const ServiceNowConnectorFields: React.FunctionComponent<ActionConnectorFieldsPr const isUsernameInvalid: boolean = errors.username.length > 0 && username != null; const isPasswordInvalid: boolean = errors.password.length > 0 && password != null; + /** + * We need to distinguish between the add flyout and the edit flyout. + * useEffect will run only once on component mount. + * This guarantees that the function below will run only once. + * On the first render of the component the apiUrl can be either undefined or filled. + * If it is filled then we are on the edit flyout. Otherwise we are on the add flyout. + */ + + useEffect(() => { + if (!isEmpty(apiUrl)) { + editActionSecrets('username', ''); + editActionSecrets('password', ''); + } + }, []); + if (isEmpty(mapping)) { editActionConfig('casesConfiguration', { ...action.config.casesConfiguration, @@ -169,7 +184,7 @@ const ServiceNowConnectorFields: React.FunctionComponent<ActionConnectorFieldsPr <EuiFlexGroup> <EuiFlexItem> <EuiFormRow - id="username" + id="connector-servicenow-username" fullWidth error={errors.username} isInvalid={isUsernameInvalid} @@ -178,7 +193,7 @@ const ServiceNowConnectorFields: React.FunctionComponent<ActionConnectorFieldsPr <EuiFieldText fullWidth isInvalid={isUsernameInvalid} - name="username" + name="connector-servicenow-username" value={username || ''} // Needed to prevent uncontrolled input error when value is undefined data-test-subj="usernameFromInput" onChange={handleOnChangeSecretConfig.bind(null, 'username')} @@ -190,7 +205,7 @@ const ServiceNowConnectorFields: React.FunctionComponent<ActionConnectorFieldsPr <EuiFlexGroup> <EuiFlexItem> <EuiFormRow - id="password" + id="connector-servicenow-password" fullWidth error={errors.password} isInvalid={isPasswordInvalid} @@ -199,7 +214,7 @@ const ServiceNowConnectorFields: React.FunctionComponent<ActionConnectorFieldsPr <EuiFieldPassword fullWidth isInvalid={isPasswordInvalid} - name="password" + name="connector-servicenow-password" value={password || ''} // Needed to prevent uncontrolled input error when value is undefined data-test-subj="passwordFromInput" onChange={handleOnChangeSecretConfig.bind(null, 'password')} @@ -212,7 +227,7 @@ const ServiceNowConnectorFields: React.FunctionComponent<ActionConnectorFieldsPr <EuiFlexItem> <EuiSpacer size="xs" /> <FieldMapping - disabled={false} + disabled={true} mapping={mapping as CasesConfigurationMapping[]} onChangeMapping={handleOnChangeMappingConfig} /> diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx index 21e4724797c5d..46a777984c6e0 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx @@ -10,13 +10,14 @@ import styled from 'styled-components'; import { CommentRequest } from '../../../../../../../../plugins/case/common/api'; import { usePostComment } from '../../../../containers/case/use_post_comment'; +import { Case } from '../../../../containers/case/types'; import { MarkdownEditorForm } from '../../../../components/markdown_editor/form'; +import { InsertTimelinePopover } from '../../../../components/timeline/insert_timeline_popover'; +import { useInsertTimeline } from '../../../../components/timeline/insert_timeline_popover/use_insert_timeline'; import { Form, useForm, UseField } from '../../../../shared_imports'; + import * as i18n from '../../translations'; import { schema } from './schema'; -import { InsertTimelinePopover } from '../../../../components/timeline/insert_timeline_popover'; -import { useInsertTimeline } from '../../../../components/timeline/insert_timeline_popover/use_insert_timeline'; -import { Comment } from '../../../../containers/case/types'; const MySpinner = styled(EuiLoadingSpinner)` position: absolute; @@ -32,13 +33,13 @@ interface AddCommentProps { caseId: string; insertQuote: string | null; onCommentSaving?: () => void; - onCommentPosted: (commentResponse: Comment) => void; + onCommentPosted: (newCase: Case) => void; showLoading?: boolean; } export const AddComment = React.memo<AddCommentProps>( ({ caseId, insertQuote, showLoading = true, onCommentPosted, onCommentSaving }) => { - const { commentData, isLoading, postComment, resetCommentData } = usePostComment(caseId); + const { isLoading, postComment } = usePostComment(caseId); const { form } = useForm<CommentRequest>({ defaultValue: initialCommentValue, options: { stripEmptyFields: false }, @@ -59,23 +60,16 @@ export const AddComment = React.memo<AddCommentProps>( } }, [insertQuote]); - useEffect(() => { - if (commentData !== null) { - onCommentPosted(commentData); - form.reset(); - resetCommentData(); - } - }, [commentData]); - const onSubmit = useCallback(async () => { const { isValid, data } = await form.submit(); if (isValid) { if (onCommentSaving != null) { onCommentSaving(); } - await postComment(data); + await postComment(data, onCommentPosted); + form.reset(); } - }, [form]); + }, [form, onCommentPosted, onCommentSaving]); return ( <span id="add-comment-permLink"> diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx index 5ca54c7f429d2..7c61d7eb034dc 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx @@ -11,7 +11,6 @@ import { EuiTableActionsColumnType, EuiAvatar, EuiLink, - EuiLoadingSpinner, } from '@elastic/eui'; import styled from 'styled-components'; import { DefaultItemIconButtonAction } from '@elastic/eui/src/components/basic_table/action_types'; @@ -21,7 +20,6 @@ import { FormattedRelativePreferenceDate } from '../../../../components/formatte import { CaseDetailsLink } from '../../../../components/links'; import { TruncatableText } from '../../../../components/truncatable_text'; import * as i18n from './translations'; -import { useGetCaseUserActions } from '../../../../containers/case/use_get_case_user_actions'; export type CasesColumns = | EuiTableFieldDataColumnType<Case> @@ -169,25 +167,30 @@ interface Props { } const ServiceNowColumn: React.FC<Props> = ({ theCase }) => { - const { hasDataToPush, isLoading } = useGetCaseUserActions(theCase.id); - const handleRenderDataToPush = useCallback( - () => - isLoading ? ( - <EuiLoadingSpinner /> - ) : ( - <p> - <EuiLink - data-test-subj={`case-table-column-external`} - href={theCase.externalService?.externalUrl} - target="_blank" - > - {theCase.externalService?.externalTitle} - </EuiLink> - {hasDataToPush ? i18n.REQUIRES_UPDATE : i18n.UP_TO_DATE} - </p> - ), - [hasDataToPush, isLoading, theCase.externalService] - ); + const handleRenderDataToPush = useCallback(() => { + const lastCaseUpdate = theCase.updatedAt != null ? new Date(theCase.updatedAt) : null; + const lastCasePush = + theCase.externalService?.pushedAt != null + ? new Date(theCase.externalService?.pushedAt) + : null; + const hasDataToPush = + lastCasePush === null || + (lastCasePush != null && + lastCaseUpdate != null && + lastCasePush.getTime() < lastCaseUpdate?.getTime()); + return ( + <p> + <EuiLink + data-test-subj={`case-table-column-external`} + href={theCase.externalService?.externalUrl} + target="_blank" + > + {theCase.externalService?.externalTitle} + </EuiLink> + {hasDataToPush ? i18n.REQUIRES_UPDATE : i18n.UP_TO_DATE} + </p> + ); + }, [theCase]); if (theCase.externalService !== null) { return handleRenderDataToPush(); } diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_status/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_status/index.tsx index 0420a71fea907..5037987845326 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_status/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_status/index.tsx @@ -8,6 +8,7 @@ import React from 'react'; import styled, { css } from 'styled-components'; import { EuiBadge, + EuiButtonEmpty, EuiButtonToggle, EuiDescriptionList, EuiDescriptionListDescription, @@ -18,6 +19,7 @@ import { import * as i18n from '../case_view/translations'; import { FormattedRelativePreferenceDate } from '../../../../components/formatted_date'; import { CaseViewActions } from '../case_view/actions'; +import { Case } from '../../../../containers/case/types'; const MyDescriptionList = styled(EuiDescriptionList)` ${({ theme }) => css` @@ -32,11 +34,11 @@ interface CaseStatusProps { 'data-test-subj': string; badgeColor: string; buttonLabel: string; - caseId: string; - caseTitle: string; + caseData: Case; icon: string; isLoading: boolean; isSelected: boolean; + onRefresh: () => void; status: string; title: string; toggleStatusCase: (evt: unknown) => void; @@ -46,11 +48,11 @@ const CaseStatusComp: React.FC<CaseStatusProps> = ({ 'data-test-subj': dataTestSubj, badgeColor, buttonLabel, - caseId, - caseTitle, + caseData, icon, isLoading, isSelected, + onRefresh, status, title, toggleStatusCase, @@ -79,6 +81,11 @@ const CaseStatusComp: React.FC<CaseStatusProps> = ({ </EuiFlexItem> <EuiFlexItem grow={false}> <EuiFlexGroup gutterSize="l" alignItems="center"> + <EuiFlexItem> + <EuiButtonEmpty iconType="refresh" onClick={onRefresh}> + {i18n.CASE_REFRESH} + </EuiButtonEmpty> + </EuiFlexItem> <EuiFlexItem> <EuiButtonToggle data-test-subj="toggle-case-status" @@ -90,7 +97,7 @@ const CaseStatusComp: React.FC<CaseStatusProps> = ({ /> </EuiFlexItem> <EuiFlexItem grow={false}> - <CaseViewActions caseId={caseId} caseTitle={caseTitle} /> + <CaseViewActions caseData={caseData} /> </EuiFlexItem> </EuiFlexGroup> </EuiFlexItem> diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx index 7aadea1a453a7..c4f1888df39e9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx @@ -7,9 +7,12 @@ import { CaseProps } from '../index'; import { Case } from '../../../../../containers/case/types'; +const updateCase = jest.fn(); +const fetchCase = jest.fn(); + export const caseProps: CaseProps = { caseId: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15', - initialData: { + caseData: { closedAt: null, closedBy: null, id: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15', @@ -46,12 +49,14 @@ export const caseProps: CaseProps = { }, version: 'WzQ3LDFd', }, + fetchCase, + updateCase, }; export const caseClosedProps: CaseProps = { ...caseProps, - initialData: { - ...caseProps.initialData, + caseData: { + ...caseProps.caseData, closedAt: '2020-02-20T23:06:33.798Z', closedBy: { username: 'elastic', @@ -61,11 +66,11 @@ export const caseClosedProps: CaseProps = { }; export const data: Case = { - ...caseProps.initialData, + ...caseProps.caseData, }; export const dataClosed: Case = { - ...caseClosedProps.initialData, + ...caseClosedProps.caseData, }; export const caseUserActions = [ diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx index 4e1e5ba753c36..1be0d6a3b5fcc 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx @@ -6,15 +6,15 @@ import React from 'react'; import { mount } from 'enzyme'; -import { CaseViewActions } from './actions'; -import { TestProviders } from '../../../../mock'; + import { useDeleteCases } from '../../../../containers/case/use_delete_cases'; +import { TestProviders } from '../../../../mock'; +import { data } from './__mock__'; +import { CaseViewActions } from './actions'; jest.mock('../../../../containers/case/use_delete_cases'); const useDeleteCasesMock = useDeleteCases as jest.Mock; describe('CaseView actions', () => { - const caseTitle = 'Cool title'; - const caseId = 'cool-id'; const handleOnDeleteConfirm = jest.fn(); const handleToggleModal = jest.fn(); const dispatchResetIsDeleted = jest.fn(); @@ -34,7 +34,7 @@ describe('CaseView actions', () => { it('clicking trash toggles modal', () => { const wrapper = mount( <TestProviders> - <CaseViewActions caseTitle={caseTitle} caseId={caseId} /> + <CaseViewActions caseData={data} /> </TestProviders> ); @@ -54,12 +54,12 @@ describe('CaseView actions', () => { })); const wrapper = mount( <TestProviders> - <CaseViewActions caseTitle={caseTitle} caseId={caseId} /> + <CaseViewActions caseData={data} /> </TestProviders> ); expect(wrapper.find('[data-test-subj="confirm-delete-case-modal"]').exists()).toBeTruthy(); wrapper.find('button[data-test-subj="confirmModalConfirmButton"]').simulate('click'); - expect(handleOnDeleteConfirm.mock.calls[0][0]).toEqual([caseId]); + expect(handleOnDeleteConfirm.mock.calls[0][0]).toEqual([data.id]); }); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.tsx index 88a717ac5fa6a..1d90470eab0e1 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.tsx @@ -12,13 +12,13 @@ import { useDeleteCases } from '../../../../containers/case/use_delete_cases'; import { ConfirmDeleteCaseModal } from '../confirm_delete_case'; import { SiemPageName } from '../../../home/types'; import { PropertyActions } from '../property_actions'; +import { Case } from '../../../../containers/case/types'; interface CaseViewActions { - caseId: string; - caseTitle: string; + caseData: Case; } -const CaseViewActionsComponent: React.FC<CaseViewActions> = ({ caseId, caseTitle }) => { +const CaseViewActionsComponent: React.FC<CaseViewActions> = ({ caseData }) => { // Delete case const { handleToggleModal, @@ -30,14 +30,14 @@ const CaseViewActionsComponent: React.FC<CaseViewActions> = ({ caseId, caseTitle const confirmDeleteModal = useMemo( () => ( <ConfirmDeleteCaseModal - caseTitle={caseTitle} + caseTitle={caseData.title} isModalVisible={isDisplayConfirmDeleteModal} isPlural={false} onCancel={handleToggleModal} - onConfirm={handleOnDeleteConfirm.bind(null, [caseId])} + onConfirm={handleOnDeleteConfirm.bind(null, [caseData.id])} /> ), - [isDisplayConfirmDeleteModal] + [isDisplayConfirmDeleteModal, caseData] ); // TO DO refactor each of these const's into their own components const propertyActions = useMemo( @@ -47,18 +47,17 @@ const CaseViewActionsComponent: React.FC<CaseViewActions> = ({ caseId, caseTitle label: i18n.DELETE_CASE, onClick: handleToggleModal, }, - { - iconType: 'popout', - label: 'View ServiceNow incident', - onClick: () => null, - }, - { - iconType: 'importAction', - label: 'Update ServiceNow incident', - onClick: () => null, - }, + ...(caseData.externalService?.externalUrl !== null + ? [ + { + iconType: 'popout', + label: i18n.VIEW_INCIDENT(caseData.externalService?.externalTitle ?? ''), + onClick: () => window.open(caseData.externalService?.externalUrl, '_blank'), + }, + ] + : []), ], - [handleToggleModal] + [handleToggleModal, caseData] ); if (isDeleted) { diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx index 18cc33d8a6d4d..92fc43eff53e9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx @@ -67,7 +67,6 @@ describe('CaseView ', () => { /* eslint-enable no-console */ const defaultUpdateCaseState = { - caseData: data, isLoading: false, isError: false, updateKey: null, @@ -186,12 +185,7 @@ describe('CaseView ', () => { wrapper .find('input[data-test-subj="toggle-case-status"]') .simulate('change', { target: { checked: true } }); - - expect(updateCaseProperty).toBeCalledWith({ - fetchCaseUserActions, - updateKey: 'status', - updateValue: 'closed', - }); + expect(updateCaseProperty).toHaveBeenCalled(); }); it('should render comments', async () => { diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx index 5c20b53f5fcb9..07834c3fb0678 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx @@ -57,230 +57,252 @@ const MyEuiHorizontalRule = styled(EuiHorizontalRule)` export interface CaseProps { caseId: string; - initialData: Case; + fetchCase: () => void; + caseData: Case; + updateCase: (newCase: Case) => void; } -export const CaseComponent = React.memo<CaseProps>(({ caseId, initialData }) => { - const basePath = window.location.origin + useBasePath(); - const caseLink = `${basePath}/app/siem#/case/${caseId}`; - const search = useGetUrlSearch(navTabs.case); +export const CaseComponent = React.memo<CaseProps>( + ({ caseId, caseData, fetchCase, updateCase }) => { + const basePath = window.location.origin + useBasePath(); + const caseLink = `${basePath}/app/siem#/case/${caseId}`; + const search = useGetUrlSearch(navTabs.case); - const [initLoadingData, setInitLoadingData] = useState(true); - const { - caseUserActions, - fetchCaseUserActions, - firstIndexPushToService, - hasDataToPush, - isLoading: isLoadingUserActions, - lastIndexPushToService, - participants, - } = useGetCaseUserActions(caseId); - const { caseData, isLoading, updateKey, updateCase, updateCaseProperty } = useUpdateCase( - caseId, - initialData - ); + const [initLoadingData, setInitLoadingData] = useState(true); + const { + caseUserActions, + fetchCaseUserActions, + firstIndexPushToService, + hasDataToPush, + isLoading: isLoadingUserActions, + lastIndexPushToService, + participants, + } = useGetCaseUserActions(caseId); + const { isLoading, updateKey, updateCaseProperty } = useUpdateCase({ + caseId, + }); - // Update Fields - const onUpdateField = useCallback( - (newUpdateKey: keyof Case, updateValue: Case[keyof Case]) => { - switch (newUpdateKey) { - case 'title': - const titleUpdate = getTypedPayload<string>(updateValue); - if (titleUpdate.length > 0) { - updateCaseProperty({ - fetchCaseUserActions, - updateKey: 'title', - updateValue: titleUpdate, - }); - } - break; - case 'description': - const descriptionUpdate = getTypedPayload<string>(updateValue); - if (descriptionUpdate.length > 0) { + // Update Fields + const onUpdateField = useCallback( + (newUpdateKey: keyof Case, updateValue: Case[keyof Case]) => { + const handleUpdateNewCase = (newCase: Case) => + updateCase({ ...newCase, comments: caseData.comments }); + switch (newUpdateKey) { + case 'title': + const titleUpdate = getTypedPayload<string>(updateValue); + if (titleUpdate.length > 0) { + updateCaseProperty({ + fetchCaseUserActions, + updateKey: 'title', + updateValue: titleUpdate, + updateCase: handleUpdateNewCase, + version: caseData.version, + }); + } + break; + case 'description': + const descriptionUpdate = getTypedPayload<string>(updateValue); + if (descriptionUpdate.length > 0) { + updateCaseProperty({ + fetchCaseUserActions, + updateKey: 'description', + updateValue: descriptionUpdate, + updateCase: handleUpdateNewCase, + version: caseData.version, + }); + } + break; + case 'tags': + const tagsUpdate = getTypedPayload<string[]>(updateValue); updateCaseProperty({ fetchCaseUserActions, - updateKey: 'description', - updateValue: descriptionUpdate, + updateKey: 'tags', + updateValue: tagsUpdate, + updateCase: handleUpdateNewCase, + version: caseData.version, }); - } - break; - case 'tags': - const tagsUpdate = getTypedPayload<string[]>(updateValue); - updateCaseProperty({ - fetchCaseUserActions, - updateKey: 'tags', - updateValue: tagsUpdate, - }); - break; - case 'status': - const statusUpdate = getTypedPayload<string>(updateValue); - if (caseData.status !== updateValue) { - updateCaseProperty({ - fetchCaseUserActions, - updateKey: 'status', - updateValue: statusUpdate, - }); - } - default: - return null; - } - }, - [fetchCaseUserActions, updateCaseProperty, caseData.status] - ); - const handleUpdateCase = useCallback( - (newCase: Case) => { - updateCase(newCase); - fetchCaseUserActions(newCase.id); - }, - [updateCase, fetchCaseUserActions] - ); + break; + case 'status': + const statusUpdate = getTypedPayload<string>(updateValue); + if (caseData.status !== updateValue) { + updateCaseProperty({ + fetchCaseUserActions, + updateKey: 'status', + updateValue: statusUpdate, + updateCase: handleUpdateNewCase, + version: caseData.version, + }); + } + default: + return null; + } + }, + [fetchCaseUserActions, updateCaseProperty, updateCase, caseData] + ); + const handleUpdateCase = useCallback( + (newCase: Case) => { + updateCase(newCase); + fetchCaseUserActions(newCase.id); + }, + [updateCase, fetchCaseUserActions] + ); - const { pushButton, pushCallouts } = usePushToService({ - caseId: caseData.id, - caseStatus: caseData.status, - isNew: caseUserActions.filter(cua => cua.action === 'push-to-service').length === 0, - updateCase: handleUpdateCase, - }); + const { pushButton, pushCallouts } = usePushToService({ + caseId: caseData.id, + caseStatus: caseData.status, + isNew: caseUserActions.filter(cua => cua.action === 'push-to-service').length === 0, + updateCase: handleUpdateCase, + }); - const onSubmitTags = useCallback(newTags => onUpdateField('tags', newTags), [onUpdateField]); - const onSubmitTitle = useCallback(newTitle => onUpdateField('title', newTitle), [onUpdateField]); - const toggleStatusCase = useCallback( - e => onUpdateField('status', e.target.checked ? 'closed' : 'open'), - [onUpdateField] - ); - const spyState = useMemo(() => ({ caseTitle: caseData.title }), [caseData.title]); + const onSubmitTags = useCallback(newTags => onUpdateField('tags', newTags), [onUpdateField]); + const onSubmitTitle = useCallback(newTitle => onUpdateField('title', newTitle), [ + onUpdateField, + ]); + const toggleStatusCase = useCallback( + e => onUpdateField('status', e.target.checked ? 'closed' : 'open'), + [onUpdateField] + ); + const handleRefresh = useCallback(() => { + fetchCaseUserActions(caseData.id); + fetchCase(); + }, [caseData.id, fetchCase, fetchCaseUserActions]); - const caseStatusData = useMemo( - () => - caseData.status === 'open' - ? { - 'data-test-subj': 'case-view-createdAt', - value: caseData.createdAt, - title: i18n.CASE_OPENED, - buttonLabel: i18n.CLOSE_CASE, - status: caseData.status, - icon: 'folderCheck', - badgeColor: 'secondary', - isSelected: false, - } - : { - 'data-test-subj': 'case-view-closedAt', - value: caseData.closedAt ?? '', - title: i18n.CASE_CLOSED, - buttonLabel: i18n.REOPEN_CASE, - status: caseData.status, - icon: 'folderExclamation', - badgeColor: 'danger', - isSelected: true, - }, - [caseData.closedAt, caseData.createdAt, caseData.status] - ); - const emailContent = useMemo( - () => ({ - subject: i18n.EMAIL_SUBJECT(caseData.title), - body: i18n.EMAIL_BODY(caseLink), - }), - [caseLink, caseData.title] - ); + const spyState = useMemo(() => ({ caseTitle: caseData.title }), [caseData.title]); - useEffect(() => { - if (initLoadingData && !isLoadingUserActions) { - setInitLoadingData(false); - } - }, [initLoadingData, isLoadingUserActions]); + const caseStatusData = useMemo( + () => + caseData.status === 'open' + ? { + 'data-test-subj': 'case-view-createdAt', + value: caseData.createdAt, + title: i18n.CASE_OPENED, + buttonLabel: i18n.CLOSE_CASE, + status: caseData.status, + icon: 'folderCheck', + badgeColor: 'secondary', + isSelected: false, + } + : { + 'data-test-subj': 'case-view-closedAt', + value: caseData.closedAt ?? '', + title: i18n.CASE_CLOSED, + buttonLabel: i18n.REOPEN_CASE, + status: caseData.status, + icon: 'folderExclamation', + badgeColor: 'danger', + isSelected: true, + }, + [caseData.closedAt, caseData.createdAt, caseData.status] + ); + const emailContent = useMemo( + () => ({ + subject: i18n.EMAIL_SUBJECT(caseData.title), + body: i18n.EMAIL_BODY(caseLink), + }), + [caseLink, caseData.title] + ); - return ( - <> - <MyWrapper> - <HeaderPage - backOptions={{ - href: getCaseUrl(search), - text: i18n.BACK_TO_ALL, - }} - data-test-subj="case-view-title" - titleNode={ - <EditableTitle - isLoading={isLoading && updateKey === 'title'} - title={caseData.title} - onSubmit={onSubmitTitle} - /> - } - title={caseData.title} - > - <CaseStatus - caseId={caseData.id} - caseTitle={caseData.title} - isLoading={isLoading && updateKey === 'status'} - toggleStatusCase={toggleStatusCase} - {...caseStatusData} - /> - </HeaderPage> - </MyWrapper> - <WhitePageWrapper> + useEffect(() => { + if (initLoadingData && !isLoadingUserActions) { + setInitLoadingData(false); + } + }, [initLoadingData, isLoadingUserActions]); + + return ( + <> <MyWrapper> - {pushCallouts != null && pushCallouts} - <EuiFlexGroup> - <EuiFlexItem grow={6}> - {initLoadingData && <EuiLoadingContent lines={8} />} - {!initLoadingData && ( - <> - <UserActionTree - caseUserActions={caseUserActions} - data={caseData} - fetchUserActions={fetchCaseUserActions.bind(null, caseData.id)} - firstIndexPushToService={firstIndexPushToService} - isLoadingDescription={isLoading && updateKey === 'description'} - isLoadingUserActions={isLoadingUserActions} - lastIndexPushToService={lastIndexPushToService} - onUpdateField={onUpdateField} - /> - <MyEuiHorizontalRule margin="s" /> - <EuiFlexGroup alignItems="center" gutterSize="s" justifyContent="flexEnd"> - <EuiFlexItem grow={false}> - <EuiButtonToggle - data-test-subj={caseStatusData['data-test-subj']} - iconType={caseStatusData.icon} - isSelected={caseStatusData.isSelected} - isLoading={isLoading && updateKey === 'status'} - label={caseStatusData.buttonLabel} - onChange={toggleStatusCase} - /> - </EuiFlexItem> - {hasDataToPush && <EuiFlexItem grow={false}>{pushButton}</EuiFlexItem>} - </EuiFlexGroup> - </> - )} - </EuiFlexItem> - <EuiFlexItem grow={2}> - <UserList - data-test-subj="case-view-user-list-reporter" - email={emailContent} - headline={i18n.REPORTER} - users={[caseData.createdBy]} - /> - <UserList - data-test-subj="case-view-user-list-participants" - email={emailContent} - headline={i18n.PARTICIPANTS} - users={participants} + <HeaderPage + backOptions={{ + href: getCaseUrl(search), + text: i18n.BACK_TO_ALL, + }} + data-test-subj="case-view-title" + titleNode={ + <EditableTitle + isLoading={isLoading && updateKey === 'title'} + title={caseData.title} + onSubmit={onSubmitTitle} /> - <TagList - data-test-subj="case-view-tag-list" - tags={caseData.tags} - onSubmit={onSubmitTags} - isLoading={isLoading && updateKey === 'tags'} - /> - </EuiFlexItem> - </EuiFlexGroup> + } + title={caseData.title} + > + <CaseStatus + caseData={caseData} + isLoading={isLoading && updateKey === 'status'} + onRefresh={handleRefresh} + toggleStatusCase={toggleStatusCase} + {...caseStatusData} + /> + </HeaderPage> </MyWrapper> - </WhitePageWrapper> - <SpyRoute state={spyState} /> - </> - ); -}); + <WhitePageWrapper> + <MyWrapper> + {pushCallouts != null && pushCallouts} + <EuiFlexGroup> + <EuiFlexItem grow={6}> + {initLoadingData && <EuiLoadingContent lines={8} />} + {!initLoadingData && ( + <> + <UserActionTree + caseUserActions={caseUserActions} + data={caseData} + fetchUserActions={fetchCaseUserActions.bind(null, caseData.id)} + firstIndexPushToService={firstIndexPushToService} + isLoadingDescription={isLoading && updateKey === 'description'} + isLoadingUserActions={isLoadingUserActions} + lastIndexPushToService={lastIndexPushToService} + onUpdateField={onUpdateField} + updateCase={updateCase} + /> + <MyEuiHorizontalRule margin="s" /> + <EuiFlexGroup alignItems="center" gutterSize="s" justifyContent="flexEnd"> + <EuiFlexItem grow={false}> + <EuiButtonToggle + data-test-subj={caseStatusData['data-test-subj']} + iconType={caseStatusData.icon} + isSelected={caseStatusData.isSelected} + isLoading={isLoading && updateKey === 'status'} + label={caseStatusData.buttonLabel} + onChange={toggleStatusCase} + /> + </EuiFlexItem> + {hasDataToPush && <EuiFlexItem grow={false}>{pushButton}</EuiFlexItem>} + </EuiFlexGroup> + </> + )} + </EuiFlexItem> + <EuiFlexItem grow={2}> + <UserList + data-test-subj="case-view-user-list-reporter" + email={emailContent} + headline={i18n.REPORTER} + users={[caseData.createdBy]} + /> + <UserList + data-test-subj="case-view-user-list-participants" + email={emailContent} + headline={i18n.PARTICIPANTS} + loading={isLoadingUserActions} + users={participants} + /> + <TagList + data-test-subj="case-view-tag-list" + tags={caseData.tags} + onSubmit={onSubmitTags} + isLoading={isLoading && updateKey === 'tags'} + /> + </EuiFlexItem> + </EuiFlexGroup> + </MyWrapper> + </WhitePageWrapper> + <SpyRoute state={spyState} /> + </> + ); + } +); export const CaseView = React.memo(({ caseId }: Props) => { - const { data, isLoading, isError } = useGetCase(caseId); + const { data, isLoading, isError, fetchCase, updateCase } = useGetCase(caseId); if (isError) { return null; } @@ -294,7 +316,9 @@ export const CaseView = React.memo(({ caseId }: Props) => { ); } - return <CaseComponent caseId={caseId} initialData={data} />; + return ( + <CaseComponent caseId={caseId} fetchCase={fetchCase} caseData={data} updateCase={updateCase} /> + ); }); CaseComponent.displayName = 'CaseComponent'; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts index c081567e3be72..3fc963fc23102 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts @@ -34,6 +34,14 @@ export const REMOVED_FIELD = i18n.translate('xpack.siem.case.caseView.actionLabe defaultMessage: 'removed', }); +export const VIEW_INCIDENT = (incidentNumber: string) => + i18n.translate('xpack.siem.case.caseView.actionLabel.viewIncident', { + defaultMessage: 'View {incidentNumber}', + values: { + incidentNumber, + }, + }); + export const PUSHED_NEW_INCIDENT = i18n.translate( 'xpack.siem.case.caseView.actionLabel.pushedNewIncident', { @@ -95,6 +103,10 @@ export const CASE_CLOSED = i18n.translate('xpack.siem.case.caseView.caseClosed', defaultMessage: 'Case closed', }); +export const CASE_REFRESH = i18n.translate('xpack.siem.case.caseView.caseRefresh', { + defaultMessage: 'Refresh case', +}); + export const EMAIL_SUBJECT = (caseTitle: string) => i18n.translate('xpack.siem.case.caseView.emailSubject', { values: { caseTitle }, diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/translations.ts index 33784cfe0058c..d1f04a34b7bad 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/translations.ts @@ -104,7 +104,7 @@ export const FIELD_MAPPING_FIRST_COL = i18n.translate( export const FIELD_MAPPING_SECOND_COL = i18n.translate( 'xpack.siem.case.configureCases.fieldMappingSecondCol', { - defaultMessage: 'Third-party incident field', + defaultMessage: 'External incident field', } ); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/create/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/create/index.tsx index 20712c3c5a815..740909db408ec 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/create/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/create/index.tsx @@ -15,7 +15,7 @@ import { import styled, { css } from 'styled-components'; import { Redirect } from 'react-router-dom'; -import { CaseRequest } from '../../../../../../../../plugins/case/common/api'; +import { CasePostRequest } from '../../../../../../../../plugins/case/common/api'; import { Field, Form, getUseField, useForm, UseField } from '../../../../shared_imports'; import { usePostCase } from '../../../../containers/case/use_post_case'; import { schema } from './schema'; @@ -45,9 +45,8 @@ const MySpinner = styled(EuiLoadingSpinner)` z-index: 99; `; -const initialCaseValue: CaseRequest = { +const initialCaseValue: CasePostRequest = { description: '', - status: 'open', tags: [], title: '', }; @@ -55,12 +54,12 @@ const initialCaseValue: CaseRequest = { export const Create = React.memo(() => { const { caseData, isLoading, postCase } = usePostCase(); const [isCancel, setIsCancel] = useState(false); - const { form } = useForm<CaseRequest>({ + const { form } = useForm<CasePostRequest>({ defaultValue: initialCaseValue, options: { stripEmptyFields: false }, schema, }); - const { handleCursorChange, handleOnTimelineChange } = useInsertTimeline<CaseRequest>( + const { handleCursorChange, handleOnTimelineChange } = useInsertTimeline<CasePostRequest>( form, 'description' ); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/create/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/create/schema.tsx index 91d3b77493b03..4653dbc67d5a1 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/create/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/create/schema.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CaseRequest } from '../../../../../../../../plugins/case/common/api'; +import { CasePostRequest } from '../../../../../../../../plugins/case/common/api'; import { FIELD_TYPES, fieldValidators, FormSchema } from '../../../../shared_imports'; import * as i18n from '../../translations'; @@ -18,7 +18,7 @@ export const schemaTags = { labelAppend: OptionalFieldLabel, }; -export const schema: FormSchema<CaseRequest> = { +export const schema: FormSchema<CasePostRequest> = { title: { type: FIELD_TYPES.TEXT, label: i18n.NAME, diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx index d8b9ac115426a..b3a4b07712857 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx @@ -11,7 +11,7 @@ import styled from 'styled-components'; import * as i18n from '../case_view/translations'; -import { Case, CaseUserActions, Comment } from '../../../../containers/case/types'; +import { Case, CaseUserActions } from '../../../../containers/case/types'; import { useUpdateComment } from '../../../../containers/case/use_update_comment'; import { useCurrentUser } from '../../../../lib/kibana'; import { AddComment } from '../add_comment'; @@ -28,6 +28,7 @@ export interface UserActionTreeProps { isLoadingUserActions: boolean; lastIndexPushToService: number; onUpdateField: (updateKey: keyof Case, updateValue: string | string[]) => void; + updateCase: (newCase: Case) => void; } const MyEuiFlexGroup = styled(EuiFlexGroup)` @@ -47,14 +48,13 @@ export const UserActionTree = React.memo( isLoadingUserActions, lastIndexPushToService, onUpdateField, + updateCase, }: UserActionTreeProps) => { const { commentId } = useParams(); const handlerTimeoutId = useRef(0); const [initLoading, setInitLoading] = useState(true); const [selectedOutlineCommentId, setSelectedOutlineCommentId] = useState(''); - const { comments, isLoadingIds, updateComment, addPostedComment } = useUpdateComment( - caseData.comments - ); + const { isLoadingIds, patchComment } = useUpdateComment(); const currentUser = useCurrentUser(); const [manageMarkdownEditIds, setManangeMardownEditIds] = useState<string[]>([]); const [insertQuote, setInsertQuote] = useState<string | null>(null); @@ -73,14 +73,16 @@ export const UserActionTree = React.memo( const handleSaveComment = useCallback( (id: string, content: string) => { handleManageMarkdownEditId(id); - updateComment({ + patchComment({ caseId: caseData.id, commentId: id, commentUpdate: content, fetchUserActions, + version: caseData.version, + updateCase, }); }, - [handleManageMarkdownEditId, updateComment] + [caseData, handleManageMarkdownEditId, patchComment, updateCase] ); const handleOutlineComment = useCallback( @@ -117,11 +119,11 @@ export const UserActionTree = React.memo( ); const handleUpdate = useCallback( - (comment: Comment) => { - addPostedComment(comment); + (newCase: Case) => { + updateCase(newCase); fetchUserActions(); }, - [addPostedComment, fetchUserActions] + [fetchUserActions, updateCase] ); const MarkdownDescription = useMemo( @@ -181,7 +183,7 @@ export const UserActionTree = React.memo( {caseUserActions.map((action, index) => { if (action.commentId != null && action.action === 'create') { - const comment = comments.find(c => c.id === action.commentId); + const comment = caseData.comments.find(c => c.id === action.commentId); if (comment != null) { return ( <UserActionItem diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_list/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_list/index.tsx index 9ace36eea1e9e..3109f2382c362 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_list/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_list/index.tsx @@ -12,6 +12,7 @@ import { EuiAvatar, EuiFlexGroup, EuiFlexItem, + EuiLoadingSpinner, } from '@elastic/eui'; import styled, { css } from 'styled-components'; import { ElasticUser } from '../../../../containers/case/types'; @@ -22,6 +23,7 @@ interface UserListProps { body: string; }; headline: string; + loading?: boolean; users: ElasticUser[]; } @@ -67,7 +69,7 @@ const renderUsers = ( )); }; -export const UserList = React.memo(({ email, headline, users }: UserListProps) => { +export const UserList = React.memo(({ email, headline, loading, users }: UserListProps) => { const handleSendEmail = useCallback( (emailAddress: string | undefined | null) => { if (emailAddress && emailAddress != null) { @@ -80,6 +82,13 @@ export const UserList = React.memo(({ email, headline, users }: UserListProps) = <EuiText> <h4>{headline}</h4> <EuiHorizontalRule margin="xs" /> + {loading && ( + <EuiFlexGroup> + <EuiFlexItem> + <EuiLoadingSpinner /> + </EuiFlexItem> + </EuiFlexGroup> + )} {renderUsers(users, handleSendEmail)} </EuiText> ); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx index 8458b32368525..4003b71b95d77 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx @@ -300,7 +300,9 @@ export const AllRules = React.memo<AllRulesProps>( <UtilityBar border> <UtilityBarSection> <UtilityBarGroup> - <UtilityBarText>{i18n.SHOWING_RULES(pagination.total ?? 0)}</UtilityBarText> + <UtilityBarText dataTestSubj="showingRules"> + {i18n.SHOWING_RULES(pagination.total ?? 0)} + </UtilityBarText> </UtilityBarGroup> <UtilityBarGroup> diff --git a/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx index 38462e6526454..75bef7a04a4c9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx @@ -14,6 +14,7 @@ import { StatefulOpenTimeline } from '../../components/open_timeline'; import { WrapperPage } from '../../components/wrapper_page'; import { SpyRoute } from '../../utils/route/spy_routes'; import * as i18n from './translations'; +import { useKibana } from '../../lib/kibana'; const TimelinesContainer = styled.div` width: 100%; @@ -28,17 +29,24 @@ type OwnProps = TimelinesProps; export const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10; const TimelinesPageComponent: React.FC<OwnProps> = ({ apolloClient }) => { - const [importCompleteToggle, setImportCompleteToggle] = useState<boolean>(false); + const [importDataModalToggle, setImportDataModalToggle] = useState<boolean>(false); const onImportTimelineBtnClick = useCallback(() => { - setImportCompleteToggle(true); - }, [setImportCompleteToggle]); + setImportDataModalToggle(true); + }, [setImportDataModalToggle]); + + const uiCapabilities = useKibana().services.application.capabilities; + const capabilitiesCanUserCRUD: boolean = + typeof uiCapabilities.siem.crud === 'boolean' ? uiCapabilities.siem.crud : false; + return ( <> <WrapperPage> <HeaderPage border title={i18n.PAGE_TITLE}> - <EuiButton iconType="indexOpen" onClick={onImportTimelineBtnClick}> - {i18n.ALL_TIMELINES_IMPORT_TIMELINE_TITLE} - </EuiButton> + {capabilitiesCanUserCRUD && ( + <EuiButton iconType="indexOpen" onClick={onImportTimelineBtnClick}> + {i18n.ALL_TIMELINES_IMPORT_TIMELINE_TITLE} + </EuiButton> + )} </HeaderPage> <TimelinesContainer> @@ -46,8 +54,8 @@ const TimelinesPageComponent: React.FC<OwnProps> = ({ apolloClient }) => { apolloClient={apolloClient} defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} isModal={false} - importCompleteToggle={importCompleteToggle} - setImportCompleteToggle={setImportCompleteToggle} + importDataModalToggle={importDataModalToggle && capabilitiesCanUserCRUD} + setImportDataModalToggle={setImportDataModalToggle} title={i18n.ALL_TIMELINES_PANEL_TITLE} /> </TimelinesContainer> diff --git a/x-pack/legacy/plugins/uptime/common/graphql/types.ts b/x-pack/legacy/plugins/uptime/common/graphql/types.ts index 9e720ed6f7999..506966ec6b5c9 100644 --- a/x-pack/legacy/plugins/uptime/common/graphql/types.ts +++ b/x-pack/legacy/plugins/uptime/common/graphql/types.ts @@ -8,7 +8,6 @@ // Scalars // ==================================================== - export type UnsignedInteger = any; // ==================================================== @@ -200,7 +199,6 @@ export interface SummaryHistogramPoint { down: number; } - export interface GetMonitorStatesQueryArgs { dateRangeStart: string; diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/pings/ping_list.tsx b/x-pack/legacy/plugins/uptime/public/components/connected/pings/ping_list.tsx index cd12a06e2041f..94a67c9847457 100644 --- a/x-pack/legacy/plugins/uptime/public/components/connected/pings/ping_list.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/connected/pings/ping_list.tsx @@ -19,7 +19,7 @@ export interface PingListProps { export const PingList = (props: PingListProps) => { const { loading, - pingList: { locations, pings }, + pingList: { locations, pings, total }, } = useSelector(selectPingList); const { dateRangeStart: from, dateRangeEnd: to } = useContext(UptimeSettingsContext); @@ -48,6 +48,7 @@ export const PingList = (props: PingListProps) => { loading={loading} locations={locations} pings={pings} + total={total} {...props} /> ); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx index 11f6565734782..cfed52f4e5d27 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx @@ -8,13 +8,13 @@ import React, { useEffect, useState, useContext, useRef } from 'react'; import uuid from 'uuid'; import styled from 'styled-components'; +import { ViewMode } from '../../../../../../../../../src/plugins/embeddable/public'; import { start } from '../../../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/legacy'; import * as i18n from './translations'; -// @ts-ignore -import { MAP_SAVED_OBJECT_TYPE } from '../../../../../../maps/common/constants'; +import { MapEmbeddable, MapEmbeddableInput } from '../../../../../../maps/public'; +import { MAP_SAVED_OBJECT_TYPE } from '../../../../../../../../plugins/maps/public'; import { Location } from '../../../../../common/runtime_types'; -import { MapEmbeddable } from './types'; import { getLayerList } from './map_config'; import { UptimeThemeContext } from '../../../../contexts'; @@ -49,7 +49,7 @@ export const EmbeddedMap = React.memo(({ upPoints, downPoints }: EmbeddedMapProp const embeddableRoot: React.RefObject<HTMLDivElement> = useRef<HTMLDivElement>(null); const factory = start.getEmbeddableFactory(MAP_SAVED_OBJECT_TYPE); - const input = { + const input: MapEmbeddableInput = { id: uuid.v4(), filters: [], hidePanelTitles: true, @@ -57,7 +57,7 @@ export const EmbeddedMap = React.memo(({ upPoints, downPoints }: EmbeddedMapProp value: 0, pause: false, }, - viewMode: 'view', + viewMode: ViewMode.VIEW, isLayerTOCOpen: false, hideFilterActions: true, // Zoom Lat/Lon values are set to make sure map is in center in the panel diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/types.ts b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/types.ts deleted file mode 100644 index 03cb33c3459d2..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/types.ts +++ /dev/null @@ -1,31 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Query } from 'src/plugins/data/common'; -import { TimeRange } from 'src/plugins/data/public'; -import { - EmbeddableInput, - EmbeddableOutput, - IEmbeddable, -} from '../../../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public'; - -import { Filter } from '../../../../../../../../../src/plugins/data/public'; - -export interface MapEmbeddableInput extends EmbeddableInput { - filters: Filter[]; - query: Query; - refreshConfig: { - isPaused: boolean; - interval: number; - }; - timeRange?: TimeRange; -} - -export interface CustomProps { - setLayerList: Function; -} - -export type MapEmbeddable = IEmbeddable<MapEmbeddableInput, EmbeddableOutput> & CustomProps; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap index 4202c760e6efa..528ca072e56cd 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap @@ -51,7 +51,7 @@ exports[`PingList component renders sorted list without errors 1`] = ` }, ] } - value="down" + value="" /> </EuiFormRow> </EuiFlexItem> @@ -314,17 +314,16 @@ exports[`PingList component renders sorted list without errors 1`] = ` onChange={[Function]} pagination={ Object { - "initialPageSize": 20, + "initialPageSize": 10, "pageIndex": 0, - "pageSize": 30, + "pageSize": 10, "pageSizeOptions": Array [ - 5, 10, - 20, + 25, 50, 100, ], - "totalItemCount": 30, + "totalItemCount": 10, } } responsive={true} diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/ping_list.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/ping_list.test.tsx index bdfa59e63ed15..658c3a9143de5 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/ping_list.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/ping_list.test.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { PingListComponent, AllLocationOption, toggleDetails } from '../ping_list'; +import { PingListComponent, toggleDetails } from '../ping_list'; import { ExpandedRowMap } from '../../monitor_list/types'; import { Ping, PingsResponse } from '../../../../../common/runtime_types'; @@ -173,15 +173,8 @@ describe('PingList component', () => { loading={false} locations={[]} monitorId="foo" - onPageCountChange={jest.fn()} - onSelectedLocationChange={(_loc: any[]) => {}} - onSelectedStatusChange={jest.fn()} - pageSize={30} pings={response.pings} - size={10} - status="all" - selectedOption="down" - selectedLocation={AllLocationOption.value} + total={10} /> ); expect(component).toMatchSnapshot(); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx index 72dbe32b10c85..8c955dac838c0 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx @@ -64,6 +64,7 @@ interface Props extends PingListProps { loading: boolean; locations: string[]; pings: Ping[]; + total: number; } const DEFAULT_PAGE_SIZE = 10; @@ -92,8 +93,18 @@ const statusOptions = [ export const PingListComponent = (props: Props) => { const [selectedLocation, setSelectedLocation] = useState<string>(''); const [status, setStatus] = useState<string>(''); - const [size, setSize] = useState(DEFAULT_PAGE_SIZE); - const { dateRangeStart, dateRangeEnd, getPings, loading, locations, monitorId, pings } = props; + const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE); + const [pageIndex, setPageIndex] = useState(0); + const { + dateRangeStart, + dateRangeEnd, + getPings, + loading, + locations, + monitorId, + pings, + total, + } = props; useEffect(() => { getPings({ @@ -101,10 +112,10 @@ export const PingListComponent = (props: Props) => { dateRangeEnd, location: selectedLocation, monitorId, - size, + size: pageSize, status: status !== 'all' ? status : '', }); - }, [dateRangeStart, dateRangeEnd, getPings, monitorId, selectedLocation, size, status]); + }, [dateRangeStart, dateRangeEnd, getPings, monitorId, selectedLocation, pageSize, status]); const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState<ExpandedRowMap>({}); @@ -231,15 +242,15 @@ export const PingListComponent = (props: Props) => { ]; const pagination: Pagination = { - initialPageSize: 20, - pageIndex: 0, - pageSize: size, - pageSizeOptions: [5, 10, 20, 50, 100], + initialPageSize: DEFAULT_PAGE_SIZE, + pageIndex, + pageSize, + pageSizeOptions: [10, 25, 50, 100], /** * we're not currently supporting pagination in this component * so the first page is the only page */ - totalItemCount: size, + totalItemCount: total, }; return ( @@ -300,7 +311,10 @@ export const PingListComponent = (props: Props) => { itemId="@timestamp" itemIdToExpandedRowMap={itemIdToExpandedRowMap} pagination={pagination} - onChange={(criteria: any) => setSize(criteria.page!.size)} + onChange={(criteria: any) => { + setPageSize(criteria.page!.size); + setPageIndex(criteria.page!.index); + }} /> </EuiPanel> ); diff --git a/x-pack/package.json b/x-pack/package.json index 1677748e4c9e1..2072b6d8d46e9 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -232,7 +232,7 @@ "dedent": "^0.7.0", "del": "^5.1.0", "dragselect": "1.13.1", - "elasticsearch": "^16.5.0", + "elasticsearch": "^16.7.0", "extract-zip": "^1.7.0", "file-saver": "^1.3.8", "file-type": "^10.9.0", diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/helpers.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/helpers.ts index 46d4789e0bd53..6dd3cc7baa760 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/helpers.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/helpers.ts @@ -112,8 +112,8 @@ export const transformComments = ( ...c, comment: flow(...pipes.map(p => t[p]))({ value: c.comment, - date: params.createdAt, - user: params.createdBy.fullName ?? '', + date: c.createdAt, + user: c.createdBy.fullName ?? '', }).value, })); }; diff --git a/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts b/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts new file mode 100644 index 0000000000000..1b0fe03633531 --- /dev/null +++ b/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getTotalCount } from './actions_telemetry'; + +describe('actions telemetry', () => { + test('getTotalCount should replace action types names with . to __', async () => { + const mockEsClient = jest.fn(); + mockEsClient.mockReturnValue({ + aggregations: { + byActionTypeId: { + value: { + types: { '.index': 1, '.server-log': 1 }, + }, + }, + }, + hits: { + hits: [ + { + _id: 'action:541efb3d-f82a-4d2c-a5c3-636d1ce49b53', + _index: '.kibana_1', + _score: 0, + _source: { + action: { + actionTypeId: '.index', + config: { + index: 'kibana_sample_data_ecommerce', + refresh: true, + executionTimeField: null, + }, + name: 'test', + secrets: + 'UPyn6cit6zBTPMmldfKh/8S2JWypwaLhhEQWBXp+OyTc6TtLHOnW92wehCqTq1FhIY3vA8hwVsggj+tbIoCcfPArpzP5SO7hh8vd6pY13x5TkiM083UgjjaAxbPvKQ==', + }, + references: [], + type: 'action', + updated_at: '2020-03-26T18:46:44.449Z', + }, + }, + { + _id: 'action:00000000-f82a-4d2c-a5c3-636d1ce49b53', + _index: '.kibana_1', + _score: 0, + _source: { + action: { + actionTypeId: '.server-log', + config: {}, + name: 'test server log', + secrets: '', + }, + references: [], + type: 'action', + updated_at: '2020-03-26T18:46:44.449Z', + }, + }, + ], + }, + }); + + const telemetry = await getTotalCount(mockEsClient, 'test'); + + expect(mockEsClient).toHaveBeenCalledTimes(1); + + expect(telemetry).toMatchInlineSnapshot(` +Object { + "countByType": Object { + "__index": 1, + "__server-log": 1, + }, + "countTotal": 2, +} +`); + }); +}); diff --git a/x-pack/plugins/actions/server/usage/actions_telemetry.ts b/x-pack/plugins/actions/server/usage/actions_telemetry.ts index ccdb4ecec2012..eabb38e61d17d 100644 --- a/x-pack/plugins/actions/server/usage/actions_telemetry.ts +++ b/x-pack/plugins/actions/server/usage/actions_telemetry.ts @@ -54,7 +54,13 @@ export async function getTotalCount(callCluster: APICaller, kibanaIndex: string) parseInt(searchResult.aggregations.byActionTypeId.value.types[key], 0) + total, 0 ), - countByType: searchResult.aggregations.byActionTypeId.value.types, + countByType: Object.keys(searchResult.aggregations.byActionTypeId.value.types).reduce( + (obj: any, key: string) => ({ + ...obj, + [key.replace('.', '__')]: searchResult.aggregations.byActionTypeId.value.types[key], + }), + {} + ), }; } diff --git a/x-pack/plugins/alerting/server/usage/alerts_telemetry.test.ts b/x-pack/plugins/alerting/server/usage/alerts_telemetry.test.ts new file mode 100644 index 0000000000000..171f80cf11cb8 --- /dev/null +++ b/x-pack/plugins/alerting/server/usage/alerts_telemetry.test.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getTotalCountInUse } from './alerts_telemetry'; + +describe('alerts telemetry', () => { + test('getTotalCountInUse should replace action types names with . to __', async () => { + const mockEsClient = jest.fn(); + mockEsClient.mockReturnValue({ + aggregations: { + byAlertTypeId: { + value: { + types: { '.index-threshold': 2 }, + }, + }, + }, + hits: { + hits: [], + }, + }); + + const telemetry = await getTotalCountInUse(mockEsClient, 'test'); + + expect(mockEsClient).toHaveBeenCalledTimes(1); + + expect(telemetry).toMatchInlineSnapshot(` +Object { + "countByType": Object { + "__index-threshold": 2, + }, + "countTotal": 2, +} +`); + }); +}); diff --git a/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts b/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts index 9ab63b7755500..9c710fa3b3b8e 100644 --- a/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts +++ b/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts @@ -245,7 +245,13 @@ export async function getTotalCountAggregations(callCluster: APICaller, kibanaIn return { count_total: totalAlertsCount, - count_by_type: results.aggregations.byAlertTypeId.value.types, + count_by_type: Object.keys(results.aggregations.byAlertTypeId.value.types).reduce( + (obj: any, key: string) => ({ + ...obj, + [key.replace('.', '__')]: results.aggregations.byAlertTypeId.value.types[key], + }), + {} + ), throttle_time: { min: `${results.aggregations.throttleTime.value.min}s`, avg: `${ @@ -298,7 +304,13 @@ export async function getTotalCountInUse(callCluster: APICaller, kibanaInex: str parseInt(searchResult.aggregations.byAlertTypeId.value.types[key], 0) + total, 0 ), - countByType: searchResult.aggregations.byAlertTypeId.value.types, + countByType: Object.keys(searchResult.aggregations.byAlertTypeId.value.types).reduce( + (obj: any, key: string) => ({ + ...obj, + [key.replace('.', '__')]: searchResult.aggregations.byAlertTypeId.value.types[key], + }), + {} + ), }; } diff --git a/x-pack/plugins/case/common/api/cases/case.ts b/x-pack/plugins/case/common/api/cases/case.ts index ee244dd205113..3c5d3405f395e 100644 --- a/x-pack/plugins/case/common/api/cases/case.ts +++ b/x-pack/plugins/case/common/api/cases/case.ts @@ -54,7 +54,11 @@ export const CaseAttributesRt = rt.intersection([ }), ]); -export const CaseRequestRt = CaseBasicRt; +export const CasePostRequestRt = rt.type({ + description: rt.string, + tags: rt.array(rt.string), + title: rt.string, +}); export const CaseExternalServiceRequestRt = CaseExternalServiceBasicRt; @@ -95,7 +99,7 @@ export const CasesFindResponseRt = rt.intersection([ ]); export const CasePatchRequestRt = rt.intersection([ - rt.partial(CaseRequestRt.props), + rt.partial(CaseBasicRt.props), rt.type({ id: rt.string, version: rt.string }), ]); @@ -157,7 +161,7 @@ export const ServiceConnectorCaseResponseRt = rt.intersection([ ]); export type CaseAttributes = rt.TypeOf<typeof CaseAttributesRt>; -export type CaseRequest = rt.TypeOf<typeof CaseRequestRt>; +export type CasePostRequest = rt.TypeOf<typeof CasePostRequestRt>; export type CaseResponse = rt.TypeOf<typeof CaseResponseRt>; export type CasesResponse = rt.TypeOf<typeof CasesResponseRt>; export type CasesFindResponse = rt.TypeOf<typeof CasesFindResponseRt>; diff --git a/x-pack/plugins/case/common/api/runtime_types.ts b/x-pack/plugins/case/common/api/runtime_types.ts index d5b858df38def..4717d7c3c48c4 100644 --- a/x-pack/plugins/case/common/api/runtime_types.ts +++ b/x-pack/plugins/case/common/api/runtime_types.ts @@ -4,22 +4,55 @@ * you may not use this file except in compliance with the Elastic License. */ -import { fold } from 'fp-ts/lib/Either'; +import { either, fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; import { pipe } from 'fp-ts/lib/pipeable'; -import { Errors, Type } from 'io-ts'; +import * as rt from 'io-ts'; import { failure } from 'io-ts/lib/PathReporter'; type ErrorFactory = (message: string) => Error; export const createPlainError = (message: string) => new Error(message); -export const throwErrors = (createError: ErrorFactory) => (errors: Errors) => { +export const throwErrors = (createError: ErrorFactory) => (errors: rt.Errors) => { throw createError(failure(errors).join('\n')); }; export const decodeOrThrow = <A, O, I>( - runtimeType: Type<A, O, I>, + runtimeType: rt.Type<A, O, I>, createError: ErrorFactory = createPlainError ) => (inputValue: I) => pipe(runtimeType.decode(inputValue), fold(throwErrors(createError), identity)); + +const getExcessProps = (props: rt.Props, r: Record<string, unknown>): string[] => { + const ex: string[] = []; + for (const k of Object.keys(r)) { + if (!props.hasOwnProperty(k)) { + ex.push(k); + } + } + return ex; +}; + +export function excess<C extends rt.InterfaceType<rt.Props>>(codec: C): C { + const r = new rt.InterfaceType( + codec.name, + codec.is, + (i, c) => + either.chain(rt.UnknownRecord.validate(i, c), (s: Record<string, unknown>) => { + const ex = getExcessProps(codec.props, s); + return ex.length > 0 + ? rt.failure( + i, + c, + `Invalid value ${JSON.stringify(i)} supplied to : ${ + codec.name + }, excess properties: ${JSON.stringify(ex)}` + ) + : codec.validate(i, c); + }), + codec.encode, + codec.props + ); + return r as any; +} diff --git a/x-pack/plugins/case/common/api/user.ts b/x-pack/plugins/case/common/api/user.ts index 651cd08f08a02..3adb78ccdac07 100644 --- a/x-pack/plugins/case/common/api/user.ts +++ b/x-pack/plugins/case/common/api/user.ts @@ -7,8 +7,8 @@ import * as rt from 'io-ts'; export const UserRT = rt.type({ - email: rt.union([rt.undefined, rt.string]), - full_name: rt.union([rt.undefined, rt.string]), + email: rt.union([rt.undefined, rt.null, rt.string]), + full_name: rt.union([rt.undefined, rt.null, rt.string]), username: rt.string, }); diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts index 5051f78a47cce..95cd66a9c51a2 100644 --- a/x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts @@ -29,7 +29,7 @@ export const createMockSavedObjectsRepository = ({ if (!result.length) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } - return result[0]; + return result; } const result = caseSavedObject.filter(s => s.id === id); if (!result.length) { @@ -100,8 +100,9 @@ export const createMockSavedObjectsRepository = ({ if (attributes.description === 'Throw an error' || attributes.comment === 'Throw an error') { throw SavedObjectsErrorHelpers.createBadRequestError('Error thrown for testing'); } + if (type === CASE_COMMENT_SAVED_OBJECT) { - return { + const newCommentObj = { type, id: 'mock-comment', attributes, @@ -109,6 +110,8 @@ export const createMockSavedObjectsRepository = ({ updated_at: '2019-12-02T22:48:08.327Z', version: 'WzksMV0=', }; + caseCommentSavedObject = [...caseCommentSavedObject, newCommentObj]; + return newCommentObj; } return { type, @@ -124,6 +127,16 @@ export const createMockSavedObjectsRepository = ({ if (!caseCommentSavedObject.find(s => s.id === id)) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } + caseCommentSavedObject = [ + ...caseCommentSavedObject, + { + id, + type, + updated_at: '2019-11-22T22:50:55.191Z', + version: 'WzE3LDFd', + attributes, + }, + ]; } else if (type === CASE_SAVED_OBJECT) { if (!caseSavedObject.find(s => s.id === id)) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.test.ts b/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.test.ts index 4e7e266f326a2..8d9906c2abe7f 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.test.ts @@ -43,7 +43,9 @@ describe('PATCH comment', () => { const response = await routeHandler(theContext, request, kibanaResponseFactory); expect(response.status).toEqual(200); - expect(response.payload.comment).toEqual('Update my comment'); + expect(response.payload.comments[response.payload.comments.length - 1].comment).toEqual( + 'Update my comment' + ); }); it(`Fails with 409 if version does not match`, async () => { diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.ts b/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.ts index c67ad1bdaea71..3b38afc02ed81 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.ts @@ -10,11 +10,11 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; -import { CommentPatchRequestRt, CommentResponseRt, throwErrors } from '../../../../../common/api'; +import { CommentPatchRequestRt, CaseResponseRt, throwErrors } from '../../../../../common/api'; import { CASE_SAVED_OBJECT } from '../../../../saved_object_types'; import { buildCommentUserActionItem } from '../../../../services/user_actions/helpers'; import { RouteDeps } from '../../types'; -import { escapeHatch, wrapError, flattenCommentSavedObject } from '../../utils'; +import { escapeHatch, wrapError, flattenCaseSavedObject } from '../../utils'; export function initPatchCommentApi({ caseService, router, userActionService }: RouteDeps) { router.patch( @@ -30,11 +30,17 @@ export function initPatchCommentApi({ caseService, router, userActionService }: async (context, request, response) => { try { const client = context.core.savedObjects.client; + const caseId = request.params.case_id; const query = pipe( CommentPatchRequestRt.decode(request.body), fold(throwErrors(Boom.badRequest), identity) ); + const myCase = await caseService.getCase({ + client, + caseId, + }); + const myComment = await caseService.getComment({ client, commentId: query.id, @@ -45,10 +51,8 @@ export function initPatchCommentApi({ caseService, router, userActionService }: } const caseRef = myComment.references.find(c => c.type === CASE_SAVED_OBJECT); - if (caseRef == null || (caseRef != null && caseRef.id !== request.params.case_id)) { - throw Boom.notFound( - `This comment ${query.id} does not exist in ${request.params.case_id}).` - ); + if (caseRef == null || (caseRef != null && caseRef.id !== caseId)) { + throw Boom.notFound(`This comment ${query.id} does not exist in ${caseId}).`); } if (query.version !== myComment.version) { @@ -59,40 +63,77 @@ export function initPatchCommentApi({ caseService, router, userActionService }: const { username, full_name, email } = await caseService.getUser({ request, response }); const updatedDate = new Date().toISOString(); - const updatedComment = await caseService.patchComment({ + const [updatedComment, updatedCase] = await Promise.all([ + caseService.patchComment({ + client, + commentId: query.id, + updatedAttributes: { + comment: query.comment, + updated_at: updatedDate, + updated_by: { email, full_name, username }, + }, + version: query.version, + }), + caseService.patchCase({ + client, + caseId, + updatedAttributes: { + updated_at: updatedDate, + updated_by: { username, full_name, email }, + }, + version: myCase.version, + }), + ]); + + const totalCommentsFindByCases = await caseService.getAllCaseComments({ client, - commentId: query.id, - updatedAttributes: { - comment: query.comment, - updated_at: updatedDate, - updated_by: { email, full_name, username }, + caseId, + options: { + fields: [], + page: 1, + perPage: 1, }, - version: query.version, }); - await userActionService.postUserActions({ - client, - actions: [ - buildCommentUserActionItem({ - action: 'update', - actionAt: updatedDate, - actionBy: { username, full_name, email }, - caseId: request.params.case_id, - commentId: updatedComment.id, - fields: ['comment'], - newValue: query.comment, - oldValue: myComment.attributes.comment, - }), - ], - }); + const [comments] = await Promise.all([ + caseService.getAllCaseComments({ + client, + caseId: request.params.case_id, + options: { + fields: [], + page: 1, + perPage: totalCommentsFindByCases.total, + }, + }), + userActionService.postUserActions({ + client, + actions: [ + buildCommentUserActionItem({ + action: 'update', + actionAt: updatedDate, + actionBy: { username, full_name, email }, + caseId: request.params.case_id, + commentId: updatedComment.id, + fields: ['comment'], + newValue: query.comment, + oldValue: myComment.attributes.comment, + }), + ], + }), + ]); return response.ok({ - body: CommentResponseRt.encode( - flattenCommentSavedObject({ - ...updatedComment, - attributes: { ...myComment.attributes, ...updatedComment.attributes }, - references: myComment.references, - }) + body: CaseResponseRt.encode( + flattenCaseSavedObject( + { + ...myCase, + ...updatedCase, + attributes: { ...myCase.attributes, ...updatedCase.attributes }, + version: updatedCase.version ?? myCase.version, + references: myCase.references, + }, + comments.saved_objects + ) ), }); } catch (error) { diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.test.ts b/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.test.ts index e51ec7c894d08..af6f8bf223ee5 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.test.ts @@ -42,7 +42,9 @@ describe('POST comment', () => { const response = await routeHandler(theContext, request, kibanaResponseFactory); expect(response.status).toEqual(200); - expect(response.payload.id).toEqual('mock-comment'); + expect(response.payload.comments[response.payload.comments.length - 1].id).toEqual( + 'mock-comment' + ); }); it(`Returns an error if the case does not exist`, async () => { const request = httpServerMock.createKibanaRequest({ diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.ts b/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.ts index 2410505872a3a..70405af26f576 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.ts @@ -10,15 +10,10 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; -import { CommentRequestRt, CommentResponseRt, throwErrors } from '../../../../../common/api'; +import { CaseResponseRt, CommentRequestRt, excess, throwErrors } from '../../../../../common/api'; import { CASE_SAVED_OBJECT } from '../../../../saved_object_types'; import { buildCommentUserActionItem } from '../../../../services/user_actions/helpers'; -import { - escapeHatch, - transformNewComment, - wrapError, - flattenCommentSavedObject, -} from '../../utils'; +import { escapeHatch, transformNewComment, wrapError, flattenCaseSavedObject } from '../../utils'; import { RouteDeps } from '../../types'; export function initPostCommentApi({ caseService, router, userActionService }: RouteDeps) { @@ -35,54 +30,98 @@ export function initPostCommentApi({ caseService, router, userActionService }: R async (context, request, response) => { try { const client = context.core.savedObjects.client; + const caseId = request.params.case_id; const query = pipe( - CommentRequestRt.decode(request.body), + excess(CommentRequestRt).decode(request.body), fold(throwErrors(Boom.badRequest), identity) ); const myCase = await caseService.getCase({ client, - caseId: request.params.case_id, + caseId, }); const { username, full_name, email } = await caseService.getUser({ request, response }); const createdDate = new Date().toISOString(); - const newComment = await caseService.postNewComment({ - client, - attributes: transformNewComment({ - createdDate, - ...query, - username, - full_name, - email, + const [newComment, updatedCase] = await Promise.all([ + caseService.postNewComment({ + client, + attributes: transformNewComment({ + createdDate, + ...query, + username, + full_name, + email, + }), + references: [ + { + type: CASE_SAVED_OBJECT, + name: `associated-${CASE_SAVED_OBJECT}`, + id: myCase.id, + }, + ], }), - references: [ - { - type: CASE_SAVED_OBJECT, - name: `associated-${CASE_SAVED_OBJECT}`, - id: myCase.id, + caseService.patchCase({ + client, + caseId, + updatedAttributes: { + updated_at: createdDate, + updated_by: { username, full_name, email }, }, - ], - }); + version: myCase.version, + }), + ]); - await userActionService.postUserActions({ + const totalCommentsFindByCases = await caseService.getAllCaseComments({ client, - actions: [ - buildCommentUserActionItem({ - action: 'create', - actionAt: createdDate, - actionBy: { username, full_name, email }, - caseId: myCase.id, - commentId: newComment.id, - fields: ['comment'], - newValue: query.comment, - }), - ], + caseId, + options: { + fields: [], + page: 1, + perPage: 1, + }, }); + const [comments] = await Promise.all([ + caseService.getAllCaseComments({ + client, + caseId, + options: { + fields: [], + page: 1, + perPage: totalCommentsFindByCases.total, + }, + }), + userActionService.postUserActions({ + client, + actions: [ + buildCommentUserActionItem({ + action: 'create', + actionAt: createdDate, + actionBy: { username, full_name, email }, + caseId: myCase.id, + commentId: newComment.id, + fields: ['comment'], + newValue: query.comment, + }), + ], + }), + ]); + return response.ok({ - body: CommentResponseRt.encode(flattenCommentSavedObject(newComment)), + body: CaseResponseRt.encode( + flattenCaseSavedObject( + { + ...myCase, + ...updatedCase, + attributes: { ...myCase.attributes, ...updatedCase.attributes }, + version: updatedCase.version ?? myCase.version, + references: myCase.references, + }, + comments.saved_objects + ) + ), }); } catch (error) { return response.customError(wrapError(error)); diff --git a/x-pack/plugins/case/server/routes/api/cases/helpers.ts b/x-pack/plugins/case/server/routes/api/cases/helpers.ts index 747b5195da7ec..46c2209d79f7d 100644 --- a/x-pack/plugins/case/server/routes/api/cases/helpers.ts +++ b/x-pack/plugins/case/server/routes/api/cases/helpers.ts @@ -62,11 +62,14 @@ export const getCaseToUpdate = ( Object.entries(queryCase).reduce( (acc, [key, value]) => { const currentValue = get(currentCase, key); - if (isTwoArraysDifference(value, currentValue)) { - return { - ...acc, - [key]: value, - }; + if (Array.isArray(currentValue) && Array.isArray(value)) { + if (isTwoArraysDifference(value, currentValue)) { + return { + ...acc, + [key]: value, + }; + } + return acc; } else if (currentValue != null && value !== currentValue) { return { ...acc, diff --git a/x-pack/plugins/case/server/routes/api/cases/patch_cases.ts b/x-pack/plugins/case/server/routes/api/cases/patch_cases.ts index 3d0b7bc79f88b..c36ea8964dc80 100644 --- a/x-pack/plugins/case/server/routes/api/cases/patch_cases.ts +++ b/x-pack/plugins/case/server/routes/api/cases/patch_cases.ts @@ -11,9 +11,10 @@ import { identity } from 'fp-ts/lib/function'; import { CasesPatchRequestRt, - throwErrors, CasesResponseRt, CasePatchRequest, + excess, + throwErrors, } from '../../../../common/api'; import { escapeHatch, wrapError, flattenCaseSavedObject } from '../utils'; import { RouteDeps } from '../types'; @@ -32,7 +33,7 @@ export function initPatchCasesApi({ caseService, router, userActionService }: Ro try { const client = context.core.savedObjects.client; const query = pipe( - CasesPatchRequestRt.decode(request.body), + excess(CasesPatchRequestRt).decode(request.body), fold(throwErrors(Boom.badRequest), identity) ); const myCases = await caseService.getCases({ @@ -116,6 +117,7 @@ export function initPatchCasesApi({ caseService, router, userActionService }: Ro ...updatedCase, attributes: { ...myCase.attributes, ...updatedCase?.attributes }, references: myCase.references, + version: updatedCase?.version ?? myCase.version, }); }); diff --git a/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts b/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts index 5b716e5a2d490..96ce3c1a7eead 100644 --- a/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts @@ -27,7 +27,6 @@ describe('POST cases', () => { body: { description: 'This is a brand new case of a bad meanie defacing data', title: 'Super Bad Security Issue', - status: 'open', tags: ['defacement'], }, }); @@ -43,6 +42,28 @@ describe('POST cases', () => { expect(response.payload.id).toEqual('mock-it'); expect(response.payload.created_by.username).toEqual('awesome'); }); + + it(`Error if you passing status for a new case`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases', + method: 'post', + body: { + description: 'This is a brand new case of a bad meanie defacing data', + title: 'Super Bad Security Issue', + status: 'open', + tags: ['defacement'], + }, + }); + + const theContext = createRouteContext( + createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + }) + ); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(400); + }); it(`Returns an error if postNewCase throws`, async () => { const request = httpServerMock.createKibanaRequest({ path: '/api/cases', @@ -50,7 +71,6 @@ describe('POST cases', () => { body: { description: 'Throw an error', title: 'Super Bad Security Issue', - status: 'open', tags: ['error'], }, }); @@ -74,7 +94,6 @@ describe('POST cases', () => { body: { description: 'This is a brand new case of a bad meanie defacing data', title: 'Super Bad Security Issue', - status: 'open', tags: ['defacement'], }, }); diff --git a/x-pack/plugins/case/server/routes/api/cases/post_case.ts b/x-pack/plugins/case/server/routes/api/cases/post_case.ts index 75be68013bcd4..239b8bfdf9b29 100644 --- a/x-pack/plugins/case/server/routes/api/cases/post_case.ts +++ b/x-pack/plugins/case/server/routes/api/cases/post_case.ts @@ -11,7 +11,7 @@ import { identity } from 'fp-ts/lib/function'; import { flattenCaseSavedObject, transformNewCase, wrapError, escapeHatch } from '../utils'; -import { CaseRequestRt, throwErrors, CaseResponseRt } from '../../../../common/api'; +import { CasePostRequestRt, throwErrors, excess, CaseResponseRt } from '../../../../common/api'; import { buildCaseUserActionItem } from '../../../services/user_actions/helpers'; import { RouteDeps } from '../types'; @@ -27,7 +27,7 @@ export function initPostCaseApi({ caseService, router, userActionService }: Rout try { const client = context.core.savedObjects.client; const query = pipe( - CaseRequestRt.decode(request.body), + excess(CasePostRequestRt).decode(request.body), fold(throwErrors(Boom.badRequest), identity) ); diff --git a/x-pack/plugins/case/server/routes/api/cases/push_case.ts b/x-pack/plugins/case/server/routes/api/cases/push_case.ts index 6ae3df180d9e4..1b24904ce03b7 100644 --- a/x-pack/plugins/case/server/routes/api/cases/push_case.ts +++ b/x-pack/plugins/case/server/routes/api/cases/push_case.ts @@ -15,6 +15,7 @@ import { flattenCaseSavedObject, wrapError, escapeHatch } from '../utils'; import { CaseExternalServiceRequestRt, CaseResponseRt, throwErrors } from '../../../../common/api'; import { buildCaseUserActionItem } from '../../../services/user_actions/helpers'; import { RouteDeps } from '../types'; +import { CASE_COMMENT_SAVED_OBJECT } from '../../../saved_object_types'; export function initPushCaseUserActionApi({ caseConfigureService, @@ -53,6 +54,7 @@ export function initPushCaseUserActionApi({ client, caseId, options: { + filter: `not ${CASE_COMMENT_SAVED_OBJECT}.attributes.pushed_at: *`, fields: [], page: 1, perPage: 1, @@ -70,6 +72,7 @@ export function initPushCaseUserActionApi({ client, caseId, options: { + filter: `not ${CASE_COMMENT_SAVED_OBJECT}.attributes.pushed_at: *`, fields: [], page: 1, perPage: totalCommentsFindByCases.total, @@ -140,7 +143,6 @@ export function initPushCaseUserActionApi({ ], }), ]); - return response.ok({ body: CaseResponseRt.encode( flattenCaseSavedObject( diff --git a/x-pack/plugins/case/server/routes/api/utils.ts b/x-pack/plugins/case/server/routes/api/utils.ts index 9d90eb8ef4a6d..822d6d70c7d61 100644 --- a/x-pack/plugins/case/server/routes/api/utils.ts +++ b/x-pack/plugins/case/server/routes/api/utils.ts @@ -14,7 +14,7 @@ import { } from 'kibana/server'; import { - CaseRequest, + CasePostRequest, CaseResponse, CasesFindResponse, CaseAttributes, @@ -35,17 +35,18 @@ export const transformNewCase = ({ createdDate: string; email?: string; full_name?: string; - newCase: CaseRequest; + newCase: CasePostRequest; username: string; }): CaseAttributes => ({ + ...newCase, closed_at: null, closed_by: null, created_at: createdDate, created_by: { email, full_name, username }, external_service: null, + status: 'open', updated_at: null, updated_by: null, - ...newCase, }); interface NewCommentArgs { diff --git a/x-pack/plugins/case/server/saved_object_types/comments.ts b/x-pack/plugins/case/server/saved_object_types/comments.ts index 8776dd39b11fa..73b1852bafe58 100644 --- a/x-pack/plugins/case/server/saved_object_types/comments.ts +++ b/x-pack/plugins/case/server/saved_object_types/comments.ts @@ -44,6 +44,9 @@ export const caseCommentSavedObjectType: SavedObjectsType = { full_name: { type: 'keyword', }, + email: { + type: 'keyword', + }, }, }, updated_at: { diff --git a/x-pack/plugins/case/server/services/index.ts b/x-pack/plugins/case/server/services/index.ts index 09d726228d309..52f41aae293ab 100644 --- a/x-pack/plugins/case/server/services/index.ts +++ b/x-pack/plugins/case/server/services/index.ts @@ -75,13 +75,13 @@ interface PatchCasesArgs extends ClientArgs { } interface UpdateCommentArgs extends ClientArgs { commentId: string; - updatedAttributes: Partial<CommentAttributes & PushedArgs>; + updatedAttributes: Partial<CommentAttributes>; version?: string; } interface PatchComment { commentId: string; - updatedAttributes: Partial<CommentAttributes & PushedArgs>; + updatedAttributes: Partial<CommentAttributes>; version?: string; } diff --git a/x-pack/plugins/case/server/services/user_actions/helpers.ts b/x-pack/plugins/case/server/services/user_actions/helpers.ts index 59d193f0f30d5..95d35d5a57a57 100644 --- a/x-pack/plugins/case/server/services/user_actions/helpers.ts +++ b/x-pack/plugins/case/server/services/user_actions/helpers.ts @@ -31,8 +31,8 @@ export const transformNewUserAction = ({ actionField: UserActionField; action: UserAction; actionAt: string; - email?: string; - full_name?: string; + email?: string | null; + full_name?: string | null; newValue?: string | null; oldValue?: string | null; username: string; diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts new file mode 100644 index 0000000000000..88c576c70bdf0 --- /dev/null +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts @@ -0,0 +1,144 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { coreMock, pluginInitializerContextConfigMock } from '../../../../../src/core/server/mocks'; +import { enhancedEsSearchStrategyProvider } from './es_search_strategy'; + +const mockAsyncResponse = { + id: 'foo', + response: { + _shards: { + total: 10, + failed: 1, + skipped: 2, + successful: 7, + }, + }, +}; + +const mockRollupResponse = { + _shards: { + total: 10, + failed: 1, + skipped: 2, + successful: 7, + }, +}; + +describe('ES search strategy', () => { + const mockCoreSetup = coreMock.createSetup(); + const mockApiCaller = jest.fn(); + const mockSearch = jest.fn(); + const mockConfig$ = pluginInitializerContextConfigMock<any>({}).legacy.globalConfig$; + + beforeEach(() => { + mockApiCaller.mockClear(); + mockSearch.mockClear(); + }); + + it('returns a strategy with `search`', () => { + const esSearch = enhancedEsSearchStrategyProvider( + { + core: mockCoreSetup, + config$: mockConfig$, + }, + mockApiCaller, + mockSearch + ); + + expect(typeof esSearch.search).toBe('function'); + }); + + it('makes a POST request to async search with params when no ID is provided', async () => { + mockApiCaller.mockResolvedValueOnce(mockAsyncResponse); + + const params = { index: 'logstash-*', body: { query: {} } }; + const esSearch = enhancedEsSearchStrategyProvider( + { + core: mockCoreSetup, + config$: mockConfig$, + }, + mockApiCaller, + mockSearch + ); + + await esSearch.search({ params }); + + expect(mockApiCaller).toBeCalled(); + expect(mockApiCaller.mock.calls[0][0]).toBe('transport.request'); + const { method, path, body } = mockApiCaller.mock.calls[0][1]; + expect(method).toBe('POST'); + expect(path).toBe('logstash-*/_async_search'); + expect(body).toEqual({ query: {} }); + }); + + it('makes a GET request to async search with ID when ID is provided', async () => { + mockApiCaller.mockResolvedValueOnce(mockAsyncResponse); + + const params = { index: 'logstash-*', body: { query: {} } }; + const esSearch = enhancedEsSearchStrategyProvider( + { + core: mockCoreSetup, + config$: mockConfig$, + }, + mockApiCaller, + mockSearch + ); + + await esSearch.search({ id: 'foo', params }); + + expect(mockApiCaller).toBeCalled(); + expect(mockApiCaller.mock.calls[0][0]).toBe('transport.request'); + const { method, path, body } = mockApiCaller.mock.calls[0][1]; + expect(method).toBe('GET'); + expect(path).toBe('_async_search/foo'); + expect(body).toEqual(undefined); + }); + + it('encodes special characters in the path', async () => { + mockApiCaller.mockResolvedValueOnce(mockAsyncResponse); + + const params = { index: 'foo-程', body: {} }; + const esSearch = enhancedEsSearchStrategyProvider( + { + core: mockCoreSetup, + config$: mockConfig$, + }, + mockApiCaller, + mockSearch + ); + + await esSearch.search({ params }); + + expect(mockApiCaller).toBeCalled(); + expect(mockApiCaller.mock.calls[0][0]).toBe('transport.request'); + const { method, path } = mockApiCaller.mock.calls[0][1]; + expect(method).toBe('POST'); + expect(path).toBe('foo-%E7%A8%8B/_async_search'); + }); + + it('calls the rollup API if the index is a rollup type', async () => { + mockApiCaller.mockResolvedValueOnce(mockRollupResponse); + + const params = { index: 'foo-程', body: {} }; + const esSearch = enhancedEsSearchStrategyProvider( + { + core: mockCoreSetup, + config$: mockConfig$, + }, + mockApiCaller, + mockSearch + ); + + await esSearch.search({ indexType: 'rollup', params }); + + expect(mockApiCaller).toBeCalled(); + expect(mockApiCaller.mock.calls[0][0]).toBe('transport.request'); + const { method, path } = mockApiCaller.mock.calls[0][1]; + expect(method).toBe('POST'); + expect(path).toBe('foo-%E7%A8%8B/_rollup_search'); + }); +}); diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index 004e57f71464a..612c4fdbac037 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -16,6 +16,7 @@ import { ISearchOptions, ISearchCancel, getDefaultSearchParams, + getTotalLoaded, } from '../../../../../src/plugins/data/server'; import { IEnhancedEsSearchRequest } from '../../common'; @@ -36,31 +37,21 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider<typeof ES const defaultParams = getDefaultSearchParams(config); const params = { ...defaultParams, ...request.params }; - const response = await (request.indexType === 'rollup' + return request.indexType === 'rollup' ? rollupSearch(caller, { ...request, params }, options) - : asyncSearch(caller, { ...request, params }, options)); - - const rawResponse = - request.indexType === 'rollup' - ? (response as SearchResponse<any>) - : (response as AsyncSearchResponse<any>).response; - - const id = (response as AsyncSearchResponse<any>).id; - const { total, failed, successful } = rawResponse._shards; - const loaded = failed + successful; - return { id, total, loaded, rawResponse }; + : asyncSearch(caller, { ...request, params }, options); }; const cancel: ISearchCancel<typeof ES_SEARCH_STRATEGY> = async id => { const method = 'DELETE'; - const path = `_async_search/${id}`; + const path = encodeURI(`_async_search/${id}`); await caller('transport.request', { method, path }); }; return { search, cancel }; }; -function asyncSearch( +async function asyncSearch( caller: APICaller, request: IEnhancedEsSearchRequest, options?: ISearchOptions @@ -69,12 +60,18 @@ function asyncSearch( // If we have an ID, then just poll for that ID, otherwise send the entire request body const method = request.id ? 'GET' : 'POST'; - const path = request.id ? `_async_search/${request.id}` : `${index}/_async_search`; + const path = encodeURI(request.id ? `_async_search/${request.id}` : `${index}/_async_search`); // Wait up to 1s for the response to return const query = toSnakeCase({ waitForCompletion: '1s', ...params }); - return caller('transport.request', { method, path, body, query }, options); + const { response: rawResponse, id } = (await caller( + 'transport.request', + { method, path, body, query }, + options + )) as AsyncSearchResponse<any>; + + return { id, rawResponse, ...getTotalLoaded(rawResponse._shards) }; } async function rollupSearch( @@ -84,9 +81,16 @@ async function rollupSearch( ) { const { body, index, ...params } = request.params; const method = 'POST'; - const path = `${index}/_rollup_search`; + const path = encodeURI(`${index}/_rollup_search`); const query = toSnakeCase(params); - return caller('transport.request', { method, path, body, query }, options); + + const rawResponse = await ((caller( + 'transport.request', + { method, path, body, query }, + options + ) as unknown) as SearchResponse<any>); + + return { rawResponse, ...getTotalLoaded(rawResponse._shards) }; } function toSnakeCase(obj: Record<string, any>) { diff --git a/x-pack/plugins/endpoint/common/schema/alert_index.ts b/x-pack/plugins/endpoint/common/schema/alert_index.ts index ca27bb646d625..7b48780f2d86b 100644 --- a/x-pack/plugins/endpoint/common/schema/alert_index.ts +++ b/x-pack/plugins/endpoint/common/schema/alert_index.ts @@ -18,6 +18,7 @@ export const alertingIndexGetQuerySchema = schema.object( schema.number({ min: 1, max: 100, + defaultValue: EndpointAppConstants.ALERT_LIST_DEFAULT_PAGE_SIZE, }) ), page_index: schema.maybe( @@ -37,6 +38,7 @@ export const alertingIndexGetQuerySchema = schema.object( maxSize: 2, }) as Type<[string, string]> // Cast this to a string tuple. `@kbn/config-schema` doesn't do this automatically ), + empty_string_is_undefined: schema.maybe(schema.boolean()), sort: schema.maybe(schema.string()), order: schema.maybe(schema.oneOf([schema.literal('asc'), schema.literal('desc')])), query: schema.maybe( @@ -103,18 +105,6 @@ export const alertingIndexGetQuerySchema = schema.object( defaultMessage: '[before] cannot be used with [after]', }); } - if ( - value.before !== undefined && - value.sort !== undefined && - value.sort !== EndpointAppConstants.ALERT_LIST_DEFAULT_SORT - ) { - return i18n.translate( - 'xpack.endpoint.alerts.errors.before_cannot_be_used_with_custom_sort', - { - defaultMessage: '[before] cannot be used with custom sort', - } - ); - } }, } ); diff --git a/x-pack/plugins/endpoint/common/types.ts b/x-pack/plugins/endpoint/common/types.ts index 7e4cf3d700ec8..b3eb518e35ae3 100644 --- a/x-pack/plugins/endpoint/common/types.ts +++ b/x-pack/plugins/endpoint/common/types.ts @@ -44,6 +44,7 @@ export class EndpointAppConstants { **/ static ALERT_LIST_DEFAULT_PAGE_SIZE = 10; static ALERT_LIST_DEFAULT_SORT = '@timestamp'; + static MAX_LONG_INT = '9223372036854775807'; // 2^63-1 } export interface AlertResultList { @@ -238,11 +239,19 @@ interface AlertMetadata { prev: string | null; } +interface AlertState { + state: { + host_metadata: HostMetadata; + }; +} + /** * Union of alert data and metadata. */ export type AlertData = AlertEvent & AlertMetadata; +export type AlertDetails = AlertData & AlertState; + export type HostMetadata = Immutable<{ '@timestamp': number; event: { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/action.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/action.ts index 42c24400d12d3..80b7fd87e13be 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/action.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/action.ts @@ -5,7 +5,7 @@ */ import { IIndexPattern } from 'src/plugins/data/public'; -import { Immutable, AlertData } from '../../../../../common/types'; +import { Immutable, AlertDetails } from '../../../../../common/types'; import { AlertListData } from '../../types'; interface ServerReturnedAlertsData { @@ -15,7 +15,7 @@ interface ServerReturnedAlertsData { interface ServerReturnedAlertDetailsData { readonly type: 'serverReturnedAlertDetailsData'; - readonly payload: Immutable<AlertData>; + readonly payload: Immutable<AlertDetails>; } interface ServerReturnedSearchBarIndexPatterns { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts index b37ba0c0983d3..2c6ebf52189f5 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts @@ -5,7 +5,7 @@ */ import { IIndexPattern } from 'src/plugins/data/public'; -import { AlertResultList, AlertData } from '../../../../../common/types'; +import { AlertResultList, AlertDetails } from '../../../../../common/types'; import { AppAction } from '../action'; import { MiddlewareFactory, AlertListState } from '../../types'; import { isOnAlertPage, apiQueryParams, hasSelectedAlert, uiQueryParams } from './selectors'; @@ -40,7 +40,7 @@ export const alertMiddlewareFactory: MiddlewareFactory<AlertListState> = (coreSt if (action.type === 'userChangedUrl' && isOnAlertPage(state) && hasSelectedAlert(state)) { const uiParams = uiQueryParams(state); - const response: AlertData = await coreStart.http.get( + const response: AlertDetails = await coreStart.http.get( `/api/endpoint/alerts/${uiParams.selected_alert}` ); api.dispatch({ type: 'serverReturnedAlertDetailsData', payload: response }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/mock_alert_result_list.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/mock_alert_result_list.ts index 3931723a55505..6a13e0f92471b 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/mock_alert_result_list.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/mock_alert_result_list.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertResultList } from '../../../../../common/types'; +import { AlertResultList, AlertDetails } from '../../../../../common/types'; import { EndpointDocGenerator } from '../../../../../common/generate_data'; export const mockAlertResultList: (options?: { @@ -47,3 +47,18 @@ export const mockAlertResultList: (options?: { }; return mock; }; + +export const mockAlertDetailsResult = (): AlertDetails => { + const generator = new EndpointDocGenerator(); + return { + ...generator.generateAlert(new Date().getTime()), + ...{ + id: 'xDUYMHABAKk0XnHd8rrd', + prev: null, + next: null, + state: { + host_metadata: generator.generateHostMetadata(), + }, + }, + }; +}; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts index 6c5330bd78baf..7947a35068234 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts @@ -12,6 +12,7 @@ import { AlertResultList, Immutable, ImmutableArray, + AlertDetails, } from '../../../common/types'; import { EndpointPluginStartDependencies } from '../../plugin'; import { AppAction } from './store/action'; @@ -196,7 +197,7 @@ export interface AlertListState { readonly location?: Immutable<EndpointAppLocation>; /** Specific Alert data to be shown in the details view */ - readonly alertDetails?: Immutable<AlertData>; + readonly alertDetails?: Immutable<AlertDetails>; /** Search bar state including indexPatterns */ readonly searchBar: AlertsSearchBarState; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/alert_details.test.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/alert_details.test.tsx index 0f5a9dd7fed17..e3639bf1cacbc 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/alert_details.test.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/alert_details.test.tsx @@ -9,7 +9,7 @@ import { appStoreFactory } from '../../store'; import { fireEvent } from '@testing-library/react'; import { MemoryHistory } from 'history'; import { AppAction } from '../../types'; -import { mockAlertResultList } from '../../store/alerts/mock_alert_result_list'; +import { mockAlertDetailsResult } from '../../store/alerts/mock_alert_result_list'; import { alertPageTestRender } from './test_helpers/render_alert_page'; describe('when the alert details flyout is open', () => { @@ -34,7 +34,7 @@ describe('when the alert details flyout is open', () => { reactTestingLibrary.act(() => { const action: AppAction = { type: 'serverReturnedAlertDetailsData', - payload: mockAlertResultList().alerts[0], + payload: mockAlertDetailsResult(), }; store.dispatch(action); }); diff --git a/x-pack/plugins/endpoint/server/routes/alerts/details/handlers.ts b/x-pack/plugins/endpoint/server/routes/alerts/details/handlers.ts index 3497573918fac..b95c1aaf87c14 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/details/handlers.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/details/handlers.ts @@ -9,6 +9,7 @@ import { AlertEvent, EndpointAppConstants } from '../../../../common/types'; import { EndpointAppContext } from '../../../types'; import { AlertDetailsRequestParams } from '../types'; import { AlertDetailsPagination } from './lib'; +import { getHostData } from '../../../routes/metadata'; export const alertDetailsHandlerWrapper = function( endpointAppContext: EndpointAppContext @@ -33,10 +34,15 @@ export const alertDetailsHandlerWrapper = function( response ); + const currentHostInfo = await getHostData(ctx, response._source.host.id); + return res.ok({ body: { id: response._id, ...response._source, + state: { + host_metadata: currentHostInfo, + }, next: await pagination.getNextUrl(), prev: await pagination.getPrevUrl(), }, diff --git a/x-pack/plugins/endpoint/server/routes/alerts/lib/index.ts b/x-pack/plugins/endpoint/server/routes/alerts/lib/index.ts index d3ed7e7b953c3..dc1ce767a715b 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/lib/index.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/lib/index.ts @@ -13,6 +13,7 @@ import { AlertSearchRequest, AlertSearchRequestWrapper, AlertSort, + UndefinedResultPosition, } from '../types'; export { Pagination } from './pagination'; @@ -68,6 +69,8 @@ function buildSort(query: AlertSearchQuery): AlertSort { { [query.sort]: { order: query.order, + missing: + query.order === 'asc' ? UndefinedResultPosition.last : UndefinedResultPosition.first, }, }, // Secondary sort for tie-breaking @@ -82,6 +85,8 @@ function buildSort(query: AlertSearchQuery): AlertSort { // Reverse sort order for search_before functionality const newDirection = reverseSortDirection(query.order); sort[0][query.sort].order = newDirection; + sort[0][query.sort].missing = + newDirection === 'asc' ? UndefinedResultPosition.last : UndefinedResultPosition.first; sort[1]['event.id'].order = newDirection; } diff --git a/x-pack/plugins/endpoint/server/routes/alerts/list/lib/index.ts b/x-pack/plugins/endpoint/server/routes/alerts/list/lib/index.ts index b076561ddbee2..95c8e4662cfce 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/list/lib/index.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/list/lib/index.ts @@ -49,6 +49,7 @@ export const getRequestData = async ( pageIndex: request.query.page_index, searchAfter: request.query.after, searchBefore: request.query.before, + emptyStringIsUndefined: request.query.empty_string_is_undefined, }; if (reqData.searchAfter === undefined && reqData.searchBefore === undefined) { @@ -59,6 +60,24 @@ export const getRequestData = async ( reqData.fromIndex = reqData.pageIndex * reqData.pageSize; } + // See: https://github.com/elastic/elasticsearch-js/issues/662 + // and https://github.com/elastic/endpoint-app-team/issues/221 + if ( + reqData.searchBefore !== undefined && + reqData.searchBefore[0] === '' && + reqData.emptyStringIsUndefined + ) { + reqData.searchBefore[0] = EndpointAppConstants.MAX_LONG_INT; + } + + if ( + reqData.searchAfter !== undefined && + reqData.searchAfter[0] === '' && + reqData.emptyStringIsUndefined + ) { + reqData.searchAfter[0] = EndpointAppConstants.MAX_LONG_INT; + } + return reqData; }; diff --git a/x-pack/plugins/endpoint/server/routes/alerts/types.ts b/x-pack/plugins/endpoint/server/routes/alerts/types.ts index 3d099447a9bd0..cf4eac5d25f80 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/types.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/types.ts @@ -13,6 +13,7 @@ import { Direction } from '../../../common/types'; export interface AlertSortParam { [key: string]: { order: Direction; + missing?: UndefinedResultPosition; }; } @@ -40,6 +41,7 @@ export interface AlertSearchQuery { order: Direction; searchAfter?: SearchCursor; searchBefore?: SearchCursor; + emptyStringIsUndefined?: boolean; } /** @@ -84,4 +86,14 @@ export interface AlertListRequestQuery { order: Direction; after?: SearchCursor; before?: SearchCursor; + empty_string_is_undefined?: boolean; +} + +/** + * Indicates whether undefined results are sorted to the beginning (_first) or end (_last) + * of a result set. + */ +export enum UndefinedResultPosition { + first = '_first', + last = '_last', } diff --git a/x-pack/plugins/endpoint/server/routes/metadata.ts b/x-pack/plugins/endpoint/server/routes/metadata.ts index 463a071ab0c77..787ffe58a5372 100644 --- a/x-pack/plugins/endpoint/server/routes/metadata.ts +++ b/x-pack/plugins/endpoint/server/routes/metadata.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'kibana/server'; +import { IRouter, RequestHandlerContext } from 'kibana/server'; import { SearchResponse } from 'elasticsearch'; import { schema } from '@kbn/config-schema'; import { kibanaRequestToMetadataListESQuery, - kibanaRequestToMetadataGetESQuery, + getESQueryHostMetadataByID, } from '../services/endpoint/metadata_query_builders'; import { HostMetadata, HostResultList } from '../../common/types'; import { EndpointAppContext } from '../types'; @@ -75,17 +75,11 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp }, async (context, req, res) => { try { - const query = kibanaRequestToMetadataGetESQuery(req, endpointAppContext); - const response = (await context.core.elasticsearch.dataClient.callAsCurrentUser( - 'search', - query - )) as SearchResponse<HostMetadata>; - - if (response.hits.hits.length === 0) { - return res.notFound({ body: 'Endpoint Not Found' }); + const doc = await getHostData(context, req.params.id); + if (doc) { + return res.ok({ body: doc }); } - - return res.ok({ body: response.hits.hits[0]._source }); + return res.notFound({ body: 'Endpoint Not Found' }); } catch (err) { return res.internalError({ body: err }); } @@ -93,6 +87,23 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp ); } +export async function getHostData( + context: RequestHandlerContext, + id: string +): Promise<HostMetadata | undefined> { + const query = getESQueryHostMetadataByID(id); + const response = (await context.core.elasticsearch.dataClient.callAsCurrentUser( + 'search', + query + )) as SearchResponse<HostMetadata>; + + if (response.hits.hits.length === 0) { + return undefined; + } + + return response.hits.hits[0]._source; +} + function mapToHostResultList( queryParams: Record<string, any>, searchResponse: SearchResponse<HostMetadata> diff --git a/x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.test.ts b/x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.test.ts index a3090361d4965..0966b52c79f7d 100644 --- a/x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.test.ts +++ b/x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.test.ts @@ -7,7 +7,7 @@ import { httpServerMock, loggingServiceMock } from '../../../../../../src/core/s import { EndpointConfigSchema } from '../../config'; import { kibanaRequestToMetadataListESQuery, - kibanaRequestToMetadataGetESQuery, + getESQueryHostMetadataByID, } from './metadata_query_builders'; import { EndpointAppConstants } from '../../../common/types'; @@ -118,15 +118,7 @@ describe('query builder', () => { describe('MetadataGetQuery', () => { it('searches for the correct ID', () => { const mockID = 'AABBCCDD-0011-2233-AA44-DEADBEEF8899'; - const mockRequest = httpServerMock.createKibanaRequest({ - params: { - id: mockID, - }, - }); - const query = kibanaRequestToMetadataGetESQuery(mockRequest, { - logFactory: loggingServiceMock.create(), - config: () => Promise.resolve(EndpointConfigSchema.validate({})), - }); + const query = getESQueryHostMetadataByID(mockID); expect(query).toEqual({ body: { query: { match: { 'host.id.keyword': mockID } }, diff --git a/x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.ts b/x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.ts index 300e837c4af1e..57b0a4ef10519 100644 --- a/x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.ts +++ b/x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.ts @@ -74,15 +74,12 @@ function buildQueryBody(request: KibanaRequest<any, any, any>): Record<string, a }; } -export const kibanaRequestToMetadataGetESQuery = ( - request: KibanaRequest<any, any, any>, - endpointAppContext: EndpointAppContext -) => { +export function getESQueryHostMetadataByID(hostID: string) { return { body: { query: { match: { - 'host.id.keyword': request.params.id, + 'host.id.keyword': hostID, }, }, sort: [ @@ -96,4 +93,4 @@ export const kibanaRequestToMetadataGetESQuery = ( }, index: EndpointAppConstants.ENDPOINT_INDEX_NAME, }; -}; +} diff --git a/x-pack/plugins/maps/public/index.ts b/x-pack/plugins/maps/public/index.ts index c465700a4f9c5..e3feb47691877 100644 --- a/x-pack/plugins/maps/public/index.ts +++ b/x-pack/plugins/maps/public/index.ts @@ -10,3 +10,5 @@ import { MapsPlugin, MapsPluginSetup, MapsPluginStart } from './plugin'; export const plugin: PluginInitializer<MapsPluginSetup, MapsPluginStart> = () => { return new MapsPlugin(); }; + +export { MAP_SAVED_OBJECT_TYPE } from '../common/constants'; diff --git a/x-pack/plugins/maps/public/reducers/non_serializable_instances.d.ts b/x-pack/plugins/maps/public/reducers/non_serializable_instances.d.ts new file mode 100644 index 0000000000000..6d216eb60c45d --- /dev/null +++ b/x-pack/plugins/maps/public/reducers/non_serializable_instances.d.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Adapters } from 'src/plugins/inspector/public'; +import { AnyAction } from 'redux'; + +interface EventHandlers { + /** + * Take action on data load. + */ + onDataLoad: (layerId: string, dataId: string) => void; + /** + * Take action on data load end. + */ + onDataLoadEnd: (layerId: string, dataId: string, resultMeta: object) => void; + /** + * Take action on data load error. + */ + onDataLoadError: (layerId: string, dataId: string, errorMessage: string) => void; +} + +export function setEventHandlers(eventHandlers?: EventHandlers): AnyAction; + +export function getInspectorAdapters(args: unknown): Adapters | undefined; diff --git a/x-pack/plugins/ml/public/application/components/kql_filter_bar/click_outside/index.js b/x-pack/plugins/maps/public/reducers/store.d.ts similarity index 69% rename from x-pack/plugins/ml/public/application/components/kql_filter_bar/click_outside/index.js rename to x-pack/plugins/maps/public/reducers/store.d.ts index 884481f9848dd..ebed396e20399 100644 --- a/x-pack/plugins/ml/public/application/components/kql_filter_bar/click_outside/index.js +++ b/x-pack/plugins/maps/public/reducers/store.d.ts @@ -4,4 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -export { ClickOutside } from './click_outside'; +import { Store } from 'redux'; + +export type MapStore = Store; + +export function createMapStore(): MapStore; diff --git a/x-pack/plugins/ml/common/constants/search.ts b/x-pack/plugins/ml/common/constants/search.ts index e17f6b3098421..da65748668a4f 100644 --- a/x-pack/plugins/ml/common/constants/search.ts +++ b/x-pack/plugins/ml/common/constants/search.ts @@ -11,3 +11,8 @@ export enum SEARCH_QUERY_LANGUAGE { KUERY = 'kuery', LUCENE = 'lucene', } + +export interface ErrorMessage { + query: string; + message: string; +} diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js index 7da49a378ec96..2a34f12330a75 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js @@ -21,7 +21,7 @@ import { checkPermission } from '../../privilege/check_privilege'; import { SEARCH_QUERY_LANGUAGE } from '../../../../common/constants/search'; import { isRuleSupported } from '../../../../common/util/anomaly_utils'; import { parseInterval } from '../../../../common/util/parse_interval'; -import { escapeDoubleQuotes } from '../kql_filter_bar/utils'; +import { escapeDoubleQuotes } from '../../explorer/explorer_utils'; import { getFieldTypeFromMapping } from '../../services/mapping_service'; import { ml } from '../../services/ml_api_service'; import { mlJobService } from '../../services/job_service'; diff --git a/x-pack/plugins/ml/public/application/components/kql_filter_bar/__snapshots__/kql_filter_bar.test.js.snap b/x-pack/plugins/ml/public/application/components/kql_filter_bar/__snapshots__/kql_filter_bar.test.js.snap deleted file mode 100644 index f2eeb00b6c643..0000000000000 --- a/x-pack/plugins/ml/public/application/components/kql_filter_bar/__snapshots__/kql_filter_bar.test.js.snap +++ /dev/null @@ -1,15 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`KqlFilterBar snapshot 1`] = ` -<Fragment> - <FilterBar - disabled={false} - initialValue="" - isLoading={false} - onChange={[Function]} - onSubmit={[Function]} - placeholder="tag : engineering OR tag : marketing" - suggestions={Array []} - /> -</Fragment> -`; diff --git a/x-pack/plugins/ml/public/application/components/kql_filter_bar/__tests__/utils.js b/x-pack/plugins/ml/public/application/components/kql_filter_bar/__tests__/utils.js deleted file mode 100644 index 6029799ffe8b8..0000000000000 --- a/x-pack/plugins/ml/public/application/components/kql_filter_bar/__tests__/utils.js +++ /dev/null @@ -1,123 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { removeFilterFromQueryString, getQueryPattern, escapeRegExp } from '../utils'; - -describe('ML - KqlFilterBar utils', () => { - const fieldName = 'http.response.status_code'; - const fieldValue = '200'; - const speciaCharFieldName = 'normal(brackets)name'; - const speciaCharFieldValue = '<>:;[})'; - - describe('removeFilterFromQueryString', () => { - it('removes selected fieldName/fieldValue from query string with one value', () => { - const currentQueryString = 'http.response.status_code : "200"'; - const expectedOutput = ''; - const result = removeFilterFromQueryString(currentQueryString, fieldName, fieldValue); - expect(result).to.be(expectedOutput); - }); - - it('removes selected fieldName/fieldValue of type number from existing query string with one value', () => { - const currentQueryString = 'http.response.status_code : 200'; - const expectedOutput = ''; - const result = removeFilterFromQueryString(currentQueryString, fieldName, fieldValue); - expect(result).to.be(expectedOutput); - }); - - it('removes selected fieldName/fieldValue from query string with multiple values', () => { - const currentQueryString = 'test_field : test_value or http.response.status_code : "200"'; - const expectedOutput = 'test_field : test_value'; - const result = removeFilterFromQueryString(currentQueryString, fieldName, fieldValue); - expect(result).to.be(expectedOutput); - }); - - it('removes selected fieldName/fieldValue of type number from existing query string with multiple values', () => { - const currentQueryString = 'http.response.status_code : 200 or test_field : test_value'; - const expectedOutput = 'test_field : test_value'; - const result = removeFilterFromQueryString(currentQueryString, fieldName, fieldValue); - expect(result).to.be(expectedOutput); - }); - - it("removes 'and' from end of the query to ensure kuery syntax is valid", () => { - const currentQueryString = 'http.response.status_code : "200" and'; - const expectedOutput = ''; - const result = removeFilterFromQueryString(currentQueryString, fieldName, fieldValue); - expect(result).to.be(expectedOutput); - }); - - it("removes 'or' from end of the query to ensure kuery syntax is valid", () => { - const currentQueryString = 'http.response.status_code : "200" or'; - const expectedOutput = ''; - const result = removeFilterFromQueryString(currentQueryString, fieldName, fieldValue); - expect(result).to.be(expectedOutput); - }); - - it("removes 'and' from start of the query to ensure kuery syntax is valid", () => { - const currentQueryString = ' and http.response.status_code : "200"'; - const expectedOutput = ''; - const result = removeFilterFromQueryString(currentQueryString, fieldName, fieldValue); - expect(result).to.be(expectedOutput); - }); - - it("removes 'or' from start of the query to ensure kuery syntax is valid", () => { - const currentQueryString = ' or http.response.status_code : "200" '; - const expectedOutput = ''; - const result = removeFilterFromQueryString(currentQueryString, fieldName, fieldValue); - expect(result).to.be(expectedOutput); - }); - - it('removes selected fieldName/fieldValue correctly from AND query string when it is the middle value', () => { - const currentQueryString = `http.response.status_code : "400" and http.response.status_code : "200" - and http.response.status_code : "300"`; - const expectedOutput = - 'http.response.status_code : "400" and http.response.status_code : "300"'; - const result = removeFilterFromQueryString(currentQueryString, fieldName, fieldValue); - expect(result).to.be(expectedOutput); - }); - - it('removes selected fieldName/fieldValue correctly from OR query string when it is the middle value', () => { - const currentQueryString = `http.response.status_code : "400" or http.response.status_code : "200" - or http.response.status_code : "300"`; - const expectedOutput = - 'http.response.status_code : "400" or http.response.status_code : "300"'; - const result = removeFilterFromQueryString(currentQueryString, fieldName, fieldValue); - expect(result).to.be(expectedOutput); - }); - }); - - describe('getQueryPattern', () => { - it('creates a regular expression pattern for given fieldName/fieldValue', () => { - // The source property returns a String containing the source text of the regexp object - // and it doesn't contain the two forward slashes on both sides and any flags. - const expectedOutput = /(http\.response\.status_code)\s?:\s?(")?(200)(")?/i.source; - const result = getQueryPattern(fieldName, fieldValue).source; - expect(result).to.be(expectedOutput); - }); - - it('creates a regular expression pattern for given fieldName/fieldValue containing special characters', () => { - // The source property returns a String containing the source text of the regexp object - // and it doesn't contain the two forward slashes on both sides and any flags. - const expectedOutput = /(normal\(brackets\)name)\s?:\s?(")?(<>:;\[\}\))(")?/i.source; - const result = getQueryPattern(speciaCharFieldName, speciaCharFieldValue).source; - expect(result).to.be(expectedOutput); - }); - }); - - describe('escapeRegExp', () => { - it('escapes regex special characters for given fieldName/fieldValue', () => { - // The source property returns a String containing the source text of the regexp object - // and it doesn't contain the two forward slashes on both sides and any flags. - const expectedFieldName = 'normal\\(brackets\\)name'; - const expectedFieldValue = '<>:;\\[\\}\\)'; - const resultFieldName = escapeRegExp(speciaCharFieldName); - const resultFieldValue = escapeRegExp(speciaCharFieldValue); - - expect(resultFieldName).to.be(expectedFieldName); - expect(resultFieldValue).to.be(expectedFieldValue); - }); - }); -}); diff --git a/x-pack/plugins/ml/public/application/components/kql_filter_bar/click_outside/__snapshots__/click_outside.test.js.snap b/x-pack/plugins/ml/public/application/components/kql_filter_bar/click_outside/__snapshots__/click_outside.test.js.snap deleted file mode 100644 index eb3e5e6005dee..0000000000000 --- a/x-pack/plugins/ml/public/application/components/kql_filter_bar/click_outside/__snapshots__/click_outside.test.js.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ClickOutside snapshot 1`] = `<div />`; diff --git a/x-pack/plugins/ml/public/application/components/kql_filter_bar/click_outside/click_outside.js b/x-pack/plugins/ml/public/application/components/kql_filter_bar/click_outside/click_outside.js deleted file mode 100644 index 02d6750dca965..0000000000000 --- a/x-pack/plugins/ml/public/application/components/kql_filter_bar/click_outside/click_outside.js +++ /dev/null @@ -1,42 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; - -export class ClickOutside extends Component { - componentDidMount() { - document.addEventListener('mousedown', this.onClick); - } - - componentWillUnmount() { - document.removeEventListener('mousedown', this.onClick); - } - - setNodeRef = node => { - this.nodeRef = node; - }; - - onClick = event => { - if (this.nodeRef && !this.nodeRef.contains(event.target)) { - this.props.onClickOutside(); - } - }; - - render() { - const { style, children } = this.props; - - return ( - <div ref={this.setNodeRef} style={style}> - {children} - </div> - ); - } -} - -ClickOutside.propTypes = { - onClickOutside: PropTypes.func.isRequired, -}; diff --git a/x-pack/plugins/ml/public/application/components/kql_filter_bar/click_outside/click_outside.test.js b/x-pack/plugins/ml/public/application/components/kql_filter_bar/click_outside/click_outside.test.js deleted file mode 100644 index 1cd1dc6e4d715..0000000000000 --- a/x-pack/plugins/ml/public/application/components/kql_filter_bar/click_outside/click_outside.test.js +++ /dev/null @@ -1,16 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { shallow } from 'enzyme'; -import { ClickOutside } from './click_outside'; - -describe('ClickOutside', () => { - test('snapshot', () => { - const wrapper = shallow(<ClickOutside onClickOutside={() => {}} />); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/x-pack/plugins/ml/public/application/components/kql_filter_bar/filter_bar/__snapshots__/filter_bar.test.js.snap b/x-pack/plugins/ml/public/application/components/kql_filter_bar/filter_bar/__snapshots__/filter_bar.test.js.snap deleted file mode 100644 index f3c825a66ee2f..0000000000000 --- a/x-pack/plugins/ml/public/application/components/kql_filter_bar/filter_bar/__snapshots__/filter_bar.test.js.snap +++ /dev/null @@ -1,133 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`FilterBar snapshot suggestions not shown 1`] = ` -<ClickOutside - onClickOutside={[Function]} - style={ - Object { - "position": "relative", - } - } -> - <div - style={ - Object { - "position": "relative", - } - } - > - <EuiFieldSearch - autoComplete="off" - compressed={false} - disabled={false} - fullWidth={true} - incremental={false} - inputRef={[Function]} - isClearable={true} - isLoading={false} - onChange={[Function]} - onClick={[Function]} - onKeyDown={[Function]} - onKeyUp={[Function]} - placeholder="Test placeholder" - spellCheck={false} - style={ - Object { - "backgroundImage": "none", - } - } - value="" - /> - </div> - <Suggestions - index={null} - onClick={[Function]} - onMouseEnter={[Function]} - show={false} - suggestions={ - Array [ - Object { - "description": "<p>Test description for fieldValueOne</p>", - "end": 1, - "start": 0, - "text": "fieldValueOne", - "type": "field", - }, - Object { - "description": "<p>Test description for fieldValueTwo</p>", - "end": 1, - "start": 0, - "text": "fieldValueTwo", - "type": "field", - }, - ] - } - /> -</ClickOutside> -`; - -exports[`FilterBar snapshot suggestions shown 1`] = ` -<ClickOutside - onClickOutside={[Function]} - style={ - Object { - "position": "relative", - } - } -> - <div - style={ - Object { - "position": "relative", - } - } - > - <EuiFieldSearch - autoComplete="off" - compressed={false} - disabled={false} - fullWidth={true} - incremental={false} - inputRef={[Function]} - isClearable={true} - isLoading={false} - onChange={[Function]} - onClick={[Function]} - onKeyDown={[Function]} - onKeyUp={[Function]} - placeholder="Test placeholder" - spellCheck={false} - style={ - Object { - "backgroundImage": "none", - } - } - value="" - /> - </div> - <Suggestions - index={null} - onClick={[Function]} - onMouseEnter={[Function]} - show={true} - suggestions={ - Array [ - Object { - "description": "<p>Test description for fieldValueOne</p>", - "end": 1, - "start": 0, - "text": "fieldValueOne", - "type": "field", - }, - Object { - "description": "<p>Test description for fieldValueTwo</p>", - "end": 1, - "start": 0, - "text": "fieldValueTwo", - "type": "field", - }, - ] - } - /> -</ClickOutside> -`; diff --git a/x-pack/plugins/ml/public/application/components/kql_filter_bar/filter_bar/filter_bar.js b/x-pack/plugins/ml/public/application/components/kql_filter_bar/filter_bar/filter_bar.js deleted file mode 100644 index 0c1796a6e01ca..0000000000000 --- a/x-pack/plugins/ml/public/application/components/kql_filter_bar/filter_bar/filter_bar.js +++ /dev/null @@ -1,224 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { Suggestions } from '../suggestions'; -import { ClickOutside } from '../click_outside'; -import { EuiFieldSearch, EuiProgress, keyCodes } from '@elastic/eui'; - -export class FilterBar extends Component { - state = { - isSuggestionsVisible: false, - index: null, - value: '', - inputIsPristine: true, - }; - - static getDerivedStateFromProps(props, state) { - if (state.inputIsPristine && props.initialValue) { - return { - value: props.initialValue, - }; - } - - return null; - } - - // Set value to filter created via filter table - componentDidUpdate(oldProps) { - const newProps = this.props; - if (oldProps.valueExternal !== newProps.valueExternal) { - this.setState({ value: newProps.valueExternal, index: null }); - } - } - - incrementIndex = currentIndex => { - let nextIndex = currentIndex + 1; - if (currentIndex === null || nextIndex >= this.props.suggestions.length) { - nextIndex = 0; - } - this.setState({ index: nextIndex }); - }; - - decrementIndex = currentIndex => { - let previousIndex = currentIndex - 1; - if (previousIndex < 0) { - previousIndex = null; - } - this.setState({ index: previousIndex }); - }; - - onKeyUp = event => { - const { selectionStart } = event.target; - const { value } = this.state; - switch (event.keyCode) { - case keyCodes.LEFT: - case keyCodes.RIGHT: - this.setState({ isSuggestionsVisible: true }); - this.props.onChange(value, selectionStart); - break; - } - }; - - onKeyDown = event => { - const { isSuggestionsVisible, index, value } = this.state; - switch (event.keyCode) { - case keyCodes.DOWN: - event.preventDefault(); - if (isSuggestionsVisible) { - this.incrementIndex(index); - } else { - this.setState({ isSuggestionsVisible: true, index: 0 }); - } - break; - case keyCodes.UP: - event.preventDefault(); - if (isSuggestionsVisible) { - this.decrementIndex(index); - } - break; - case keyCodes.ENTER: - event.preventDefault(); - if (isSuggestionsVisible && this.props.suggestions[index]) { - this.selectSuggestion(this.props.suggestions[index]); - } else { - this.setState({ isSuggestionsVisible: false }); - this.props.onSubmit(value); - } - break; - case keyCodes.ESC: - event.preventDefault(); - this.setState({ isSuggestionsVisible: false }); - break; - case keyCodes.TAB: - this.setState({ isSuggestionsVisible: false }); - break; - } - }; - - selectSuggestion = suggestion => { - const nextInputValue = - this.state.value.substr(0, suggestion.start) + - suggestion.text + - this.state.value.substr(suggestion.end); - - this.setState({ value: nextInputValue, index: null }); - this.props.onChange(nextInputValue, nextInputValue.length); - }; - - onClickOutside = () => { - this.setState({ isSuggestionsVisible: false }); - }; - - onChangeInputValue = event => { - const { value, selectionStart } = event.target; - const hasValue = Boolean(value.trim()); - this.setState({ - value, - inputIsPristine: false, - isSuggestionsVisible: hasValue, - index: null, - }); - - if (!hasValue) { - this.props.onSubmit(value); - } - this.props.onChange(value, selectionStart); - }; - - onClickInput = event => { - const { selectionStart } = event.target; - this.props.onChange(this.state.value, selectionStart); - }; - - onClickSuggestion = suggestion => { - this.selectSuggestion(suggestion); - this.inputRef.focus(); - }; - - onMouseEnterSuggestion = index => { - this.setState({ index }); - }; - - onSubmit = () => { - this.props.onSubmit(this.state.value); - this.setState({ isSuggestionsVisible: false }); - }; - - render() { - const { disabled } = this.props; - const { value } = this.state; - - return ( - <ClickOutside onClickOutside={this.onClickOutside} style={{ position: 'relative' }}> - <div style={{ position: 'relative' }}> - <EuiFieldSearch - fullWidth - style={{ - backgroundImage: 'none', - }} - placeholder={this.props.placeholder} - inputRef={node => { - if (node) { - this.inputRef = node; - } - }} - disabled={disabled} - value={value} - onKeyDown={this.onKeyDown} - onKeyUp={this.onKeyUp} - onChange={this.onChangeInputValue} - onClick={this.onClickInput} - autoComplete="off" - spellCheck={false} - data-test-subj={this.props.testSubj} - /> - - {this.props.isLoading && ( - <EuiProgress - size="xs" - color="accent" - position="absolute" - style={{ - bottom: 0, - top: 'initial', - }} - /> - )} - </div> - - <Suggestions - show={this.state.isSuggestionsVisible} - suggestions={this.props.suggestions} - index={this.state.index} - onClick={this.onClickSuggestion} - onMouseEnter={this.onMouseEnterSuggestion} - /> - </ClickOutside> - ); - } -} - -FilterBar.propTypes = { - initialValue: PropTypes.string, - isLoading: PropTypes.bool, - disabled: PropTypes.bool, - onChange: PropTypes.func.isRequired, - placeholder: PropTypes.string, - onSubmit: PropTypes.func.isRequired, - valueExternal: PropTypes.string, - suggestions: PropTypes.array.isRequired, - testSubj: PropTypes.string, -}; - -FilterBar.defaultProps = { - isLoading: false, - disabled: false, - placeholder: 'tag : engineering OR tag : marketing', - suggestions: [], - testSubj: undefined, -}; diff --git a/x-pack/plugins/ml/public/application/components/kql_filter_bar/filter_bar/filter_bar.test.js b/x-pack/plugins/ml/public/application/components/kql_filter_bar/filter_bar/filter_bar.test.js deleted file mode 100644 index 287803f9eb40a..0000000000000 --- a/x-pack/plugins/ml/public/application/components/kql_filter_bar/filter_bar/filter_bar.test.js +++ /dev/null @@ -1,83 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { shallow, mount } from 'enzyme'; -import { keyCodes } from '@elastic/eui'; -import { FilterBar } from './filter_bar'; - -const defaultProps = { - disabled: false, - initialValue: '', - placeholder: 'Test placeholder', - isLoading: false, - onChange: () => {}, - onSubmit: () => {}, - suggestions: [ - { - description: '<p>Test description for fieldValueOne</p>', - end: 1, - start: 0, - text: 'fieldValueOne', - type: 'field', - }, - { - description: '<p>Test description for fieldValueTwo</p>', - end: 1, - start: 0, - text: 'fieldValueTwo', - type: 'field', - }, - ], -}; - -describe('FilterBar', () => { - test('snapshot suggestions not shown', () => { - const wrapper = shallow(<FilterBar {...defaultProps} />); - expect(wrapper).toMatchSnapshot(); - }); - - test('snapshot suggestions shown', () => { - const wrapper = shallow(<FilterBar {...defaultProps} />); - wrapper.setState({ isSuggestionsVisible: true }); - expect(wrapper).toMatchSnapshot(); - }); - - test('index updated in state when suggestion is navigated to via mouse', () => { - const wrapper = mount(<FilterBar {...defaultProps} />); - wrapper.setState({ isSuggestionsVisible: true }); - - expect(wrapper.state('index')).toEqual(null); - - const firstSuggestion = wrapper.find('li').first(); - firstSuggestion.simulate('mouseenter'); - expect(wrapper.state('index')).toEqual(0); - }); - - test('index updated and suggestions set to visible when input added', () => { - const wrapper = shallow(<FilterBar {...defaultProps} />); - // default values - expect(wrapper.state('index')).toEqual(null); - expect(wrapper.state('isSuggestionsVisible')).toBe(false); - - const searchBar = wrapper.find('EuiFieldSearch'); - searchBar.simulate('keydown', { preventDefault: () => {}, keyCode: keyCodes.DOWN }); - wrapper.update(); - // updated values - expect(wrapper.state('index')).toEqual(0); - expect(wrapper.state('isSuggestionsVisible')).toBe(true); - }); - - test('index updated in state when suggestion is navigated to via keyboard', () => { - const wrapper = shallow(<FilterBar {...defaultProps} />); - wrapper.setState({ isSuggestionsVisible: true, value: 'f', index: 0 }); - const searchBar = wrapper.find('EuiFieldSearch'); - searchBar.simulate('keydown', { preventDefault: () => {}, keyCode: keyCodes.DOWN }); - wrapper.update(); - - expect(wrapper.state('index')).toEqual(1); - }); -}); diff --git a/x-pack/plugins/ml/public/application/components/kql_filter_bar/kql_filter_bar.js b/x-pack/plugins/ml/public/application/components/kql_filter_bar/kql_filter_bar.js deleted file mode 100644 index 0f3c6d25fe641..0000000000000 --- a/x-pack/plugins/ml/public/application/components/kql_filter_bar/kql_filter_bar.js +++ /dev/null @@ -1,115 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; -import { uniqueId } from 'lodash'; -import { FilterBar } from './filter_bar'; -import { EuiCallOut, EuiLink, EuiText } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { getSuggestions, getKqlQueryValues } from './utils'; -import { getDocLinks } from '../../util/dependency_cache'; - -function getErrorWithLink(errorMessage) { - const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = getDocLinks(); - return ( - <EuiText> - {`${errorMessage} Input must be valid `} - <EuiLink - href={`${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kuery-query.html`} - target="_blank" - > - {'Kibana Query Language'} - </EuiLink> - {' (KQL) syntax.'} - </EuiText> - ); -} - -export class KqlFilterBar extends Component { - state = { - error: null, - suggestions: [], - isLoadingSuggestions: false, - isLoadingIndexPattern: true, - }; - - onChange = async (inputValue, selectionStart) => { - const { indexPattern } = this.props; - - this.setState({ error: null, suggestions: [], isLoadingSuggestions: true }); - - const currentRequest = uniqueId(); - this.currentRequest = currentRequest; - const boolFilter = []; - - try { - const suggestions = - (await getSuggestions(inputValue, selectionStart, indexPattern, boolFilter)) || []; - - if (currentRequest !== this.currentRequest) { - return; - } - - this.setState({ suggestions, isLoadingSuggestions: false }); - } catch (e) { - console.error('Error while fetching suggestions', e); - const errorMessage = i18n.translate('xpack.ml.explorer.fetchingSuggestionsErrorMessage', { - defaultMessage: 'Error while fetching suggestions', - }); - this.setState({ isLoadingSuggestions: false, error: e.message ? e.message : errorMessage }); - } - }; - - onSubmit = inputValue => { - const { indexPattern } = this.props; - const { onSubmit } = this.props; - - try { - // returns object with properties: { influencersFilterQuery, filteredFields, queryString, isAndOperator } - const kqlQueryValues = getKqlQueryValues(inputValue, indexPattern); - onSubmit(kqlQueryValues); - } catch (e) { - console.log('Invalid kuery syntax', e); // eslint-disable-line no-console - const errorWithLink = getErrorWithLink(e.message); - const errorMessage = i18n.translate('xpack.ml.explorer.invalidKuerySyntaxErrorMessage', { - defaultMessage: 'Invalid kuery syntax', - }); - this.setState({ error: e.message ? errorWithLink : errorMessage }); - } - }; - - render() { - const { error } = this.state; - const { initialValue, placeholder, valueExternal, testSubj } = this.props; - - return ( - <Fragment> - <FilterBar - disabled={false} - isLoading={this.state.isLoadingSuggestions} - initialValue={initialValue || ''} - onChange={this.onChange} - placeholder={placeholder} - onSubmit={this.onSubmit} - suggestions={this.state.suggestions} - valueExternal={valueExternal} - testSubj={testSubj} - /> - {error && <EuiCallOut color="danger">{error}</EuiCallOut>} - </Fragment> - ); - } -} - -KqlFilterBar.propTypes = { - indexPattern: PropTypes.object.isRequired, - initialValue: PropTypes.string, - onSubmit: PropTypes.func.isRequired, - placeholder: PropTypes.string, - valueExternal: PropTypes.string, - testSubj: PropTypes.string, -}; diff --git a/x-pack/plugins/ml/public/application/components/kql_filter_bar/kql_filter_bar.test.js b/x-pack/plugins/ml/public/application/components/kql_filter_bar/kql_filter_bar.test.js deleted file mode 100644 index 610d924651406..0000000000000 --- a/x-pack/plugins/ml/public/application/components/kql_filter_bar/kql_filter_bar.test.js +++ /dev/null @@ -1,63 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { shallow } from 'enzyme'; -import { KqlFilterBar } from './kql_filter_bar'; - -const defaultProps = { - indexPattern: { - title: '.ml-anomalies-*', - fields: [ - { - name: 'nginx.access.geoip.country_iso_code', - type: 'string', - aggregatable: true, - searchable: true, - }, - { - name: 'nginx.access.url', - type: 'string', - aggregatable: true, - searchable: true, - }, - ], - }, - initialValue: '', - onSubmit: () => {}, - placeholder: undefined, -}; - -jest.mock('../../util/dependency_cache', () => ({ - getAutocomplete: () => ({ - getQuerySuggestions: () => {}, - }), -})); - -describe('KqlFilterBar', () => { - test('snapshot', () => { - const wrapper = shallow(<KqlFilterBar {...defaultProps} />); - expect(wrapper).toMatchSnapshot(); - }); - - test('error message callout when error is present', () => { - const wrapper = shallow(<KqlFilterBar {...defaultProps} />); - wrapper.setState({ error: 'Invalid syntax' }); - wrapper.update(); - const callout = wrapper.find('EuiCallOut'); - - expect(callout.contains('Invalid syntax')).toBe(true); - }); - - test('suggestions loading when typing into search bar', () => { - const wrapper = shallow(<KqlFilterBar {...defaultProps} />); - expect(wrapper.state('isLoadingSuggestions')).toBe(false); - // Simulate typing in by triggering change with inputValue and selectionStart - const filterBar = wrapper.find('FilterBar'); - filterBar.simulate('change', 'n', 1); - expect(wrapper.state('isLoadingSuggestions')).toBe(true); - }); -}); diff --git a/x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestion/__snapshots__/suggestion.test.js.snap b/x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestion/__snapshots__/suggestion.test.js.snap deleted file mode 100644 index 4eb236f50be05..0000000000000 --- a/x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestion/__snapshots__/suggestion.test.js.snap +++ /dev/null @@ -1,24 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Suggestion snapshot 1`] = ` -<styled.li - innerRef={[Function]} - onClick={[Function]} - onMouseEnter={[Function]} - selected={true} -> - <styled.div - type="field" - > - <EuiIcon - type="kqlField" - /> - </styled.div> - <styled.div> - fieldValue - </styled.div> - <styled.div> - <p>Test description for fieldValue</p> - </styled.div> -</styled.li> -`; diff --git a/x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestion/suggestion.js b/x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestion/suggestion.js deleted file mode 100644 index 121082a776c80..0000000000000 --- a/x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestion/suggestion.js +++ /dev/null @@ -1,117 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import styled from 'styled-components'; -import { EuiIcon } from '@elastic/eui'; -import { tint } from 'polished'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; - -function getIconColor(type) { - switch (type) { - case 'field': - return theme.euiColorVis7; - case 'value': - return theme.euiColorVis0; - case 'operator': - return theme.euiColorVis1; - case 'conjunction': - return theme.euiColorVis3; - case 'recentSearch': - return theme.euiColorMediumShade; - } -} - -const Description = styled.div` - color: ${theme.euiColorDarkShade}; - - p { - display: inline; - - span { - font-family: ${theme.euiFontFamily}; - color: ${theme.euiColorFullShade}; - padding: 0 ${theme.euiSizeXS}; - display: inline-block; - } - } -`; - -const ListItem = styled.li` - font-size: ${theme.euiFontSizeXS}; - height: ${theme.euiSizeXL}; - align-items: center; - display: flex; - background: ${props => (props.selected ? theme.euiColorLightestShade : 'initial')}; - cursor: pointer; - border-radius: ${theme.euiSizeXS}; - - ${Description} { - p span { - background: ${props => - props.selected ? theme.euiColorEmptyShade : theme.euiColorLightestShade}; - } - } -`; - -const Icon = styled.div` - flex: 0 0 ${theme.euiSizeXL}; - background: ${props => tint(0.1, getIconColor(props.type))}; - color: ${props => getIconColor(props.type)}; - width: 100%; - height: 100%; - text-align: center; - line-height: ${theme.euiSizeXL}; -`; - -const TextValue = styled.div` - flex: 0 0 ${theme.euiSize * 16}px; - color: ${theme.euiColorDarkestShade}; - padding: 0 ${theme.euiSizeS}; -`; - -function getEuiIconType(type) { - switch (type) { - case 'field': - return 'kqlField'; - case 'value': - return 'kqlValue'; - case 'recentSearch': - return 'search'; - case 'conjunction': - return 'kqlSelector'; - case 'operator': - return 'kqlOperand'; - default: - throw new Error('Unknown type', type); - } -} - -export const Suggestion = props => { - return ( - <ListItem - innerRef={props.innerRef} - selected={props.selected} - onClick={() => props.onClick(props.suggestion)} - onMouseEnter={props.onMouseEnter} - > - <Icon type={props.suggestion.type}> - <EuiIcon type={getEuiIconType(props.suggestion.type)} /> - </Icon> - <TextValue>{props.suggestion.text}</TextValue> - <Description>{props.suggestion.description}</Description> - </ListItem> - ); -}; - -Suggestion.propTypes = { - onClick: PropTypes.func.isRequired, - onMouseEnter: PropTypes.func.isRequired, - selected: PropTypes.bool, - suggestion: PropTypes.object.isRequired, - innerRef: PropTypes.func.isRequired, -}; diff --git a/x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestion/suggestion.test.js b/x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestion/suggestion.test.js deleted file mode 100644 index d60f2004db445..0000000000000 --- a/x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestion/suggestion.test.js +++ /dev/null @@ -1,30 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { shallow } from 'enzyme'; -import { Suggestion } from './suggestion'; - -const defaultProps = { - innerRef: () => {}, - onClick: () => {}, - onMouseEnter: () => {}, - selected: true, - suggestion: { - description: '<p>Test description for fieldValue</p>', - end: 1, - start: 0, - text: 'fieldValue', - type: 'field', - }, -}; - -describe('Suggestion', () => { - test('snapshot', () => { - const wrapper = shallow(<Suggestion {...defaultProps} />); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestions/__snapshots__/suggestions.test.js.snap b/x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestions/__snapshots__/suggestions.test.js.snap deleted file mode 100644 index 869e321d4a7af..0000000000000 --- a/x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestions/__snapshots__/suggestions.test.js.snap +++ /dev/null @@ -1,40 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Suggestions snapshot 1`] = ` -<styled.ul - innerRef={[Function]} -> - <Suggestion - innerRef={[Function]} - key="[object Object]_0" - onClick={[Function]} - onMouseEnter={[Function]} - selected={true} - suggestion={ - Object { - "description": "<p>Test description for fieldValueOne</p>", - "end": 1, - "start": 0, - "text": "fieldValueOne", - "type": "field", - } - } - /> - <Suggestion - innerRef={[Function]} - key="[object Object]_1" - onClick={[Function]} - onMouseEnter={[Function]} - selected={false} - suggestion={ - Object { - "description": "<p>Test description for fieldValueTwo</p>", - "end": 1, - "start": 0, - "text": "fieldValueTwo", - "type": "field", - } - } - /> -</styled.ul> -`; diff --git a/x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestions/index.js b/x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestions/index.js deleted file mode 100644 index 70fb46e06bfa0..0000000000000 --- a/x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestions/index.js +++ /dev/null @@ -1,7 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export { Suggestions } from './suggestions'; diff --git a/x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestions/suggestions.js b/x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestions/suggestions.js deleted file mode 100644 index 94960e1fcc865..0000000000000 --- a/x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestions/suggestions.js +++ /dev/null @@ -1,81 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import styled from 'styled-components'; -import { Suggestion } from '../suggestion'; -import { rgba } from 'polished'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; - -const List = styled.ul` - width: 100%; - border: 1px solid ${theme.euiColorLightShade}; - border-radius: ${theme.euiSizeXS}; - box-shadow: 0px ${theme.euiSizeXS} ${theme.euiSizeXL} ${rgba(theme.euiTextColor, 0.1)}; - position: absolute; - background: #fff; - z-index: 10; - left: 0; - max-height: ${theme.euiSize * 20}px; - overflow: scroll; -`; - -export class Suggestions extends Component { - childNodes = []; - - scrollIntoView = () => { - const parent = this.parentNode; - const child = this.childNodes[this.props.index]; - - if (this.props.index == null || !parent || !child) { - return; - } - - const scrollTop = Math.max( - Math.min(parent.scrollTop, child.offsetTop), - child.offsetTop + child.offsetHeight - parent.offsetHeight - ); - - parent.scrollTop = scrollTop; - }; - - componentDidUpdate(prevProps) { - if (prevProps.index !== this.props.index) { - this.scrollIntoView(); - } - } - - render() { - if (!this.props.show || this.props.suggestions.length === 0) { - return null; - } - - const suggestions = this.props.suggestions.map((suggestion, index) => { - const key = `${suggestion}_${index}`; - return ( - <Suggestion - innerRef={node => (this.childNodes[index] = node)} - selected={index === this.props.index} - suggestion={suggestion} - onClick={this.props.onClick} - onMouseEnter={() => this.props.onMouseEnter(index)} - key={key} - /> - ); - }); - - return <List innerRef={node => (this.parentNode = node)}>{suggestions}</List>; - } -} - -Suggestions.propTypes = { - index: PropTypes.number, - onClick: PropTypes.func.isRequired, - onMouseEnter: PropTypes.func.isRequired, - show: PropTypes.bool, - suggestions: PropTypes.array.isRequired, -}; diff --git a/x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestions/suggestions.test.js b/x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestions/suggestions.test.js deleted file mode 100644 index 666bfa4cfa1fa..0000000000000 --- a/x-pack/plugins/ml/public/application/components/kql_filter_bar/suggestions/suggestions.test.js +++ /dev/null @@ -1,57 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { shallow, mount } from 'enzyme'; -import { Suggestions } from './suggestions'; - -const defaultProps = { - index: 0, - onClick: () => {}, - onMouseEnter: () => {}, - show: true, - suggestions: [ - { - description: '<p>Test description for fieldValueOne</p>', - end: 1, - start: 0, - text: 'fieldValueOne', - type: 'field', - }, - { - description: '<p>Test description for fieldValueTwo</p>', - end: 1, - start: 0, - text: 'fieldValueTwo', - type: 'field', - }, - ], -}; - -describe('Suggestions', () => { - test('snapshot', () => { - const wrapper = shallow(<Suggestions {...defaultProps} />); - expect(wrapper).toMatchSnapshot(); - }); - - test('is null when show is false', () => { - const noShowProps = { ...defaultProps, show: false }; - const wrapper = shallow(<Suggestions {...noShowProps} />); - expect(wrapper.isEmptyRender()).toBeTruthy(); - }); - - test('is null when no suggestions are available', () => { - const noSuggestions = { ...defaultProps, suggestions: [] }; - const wrapper = shallow(<Suggestions {...noSuggestions} />); - expect(wrapper.isEmptyRender()).toBeTruthy(); - }); - - test('creates suggestion list item for each suggestion passed in via props', () => { - const wrapper = mount(<Suggestions {...defaultProps} />); - const suggestions = wrapper.find('li'); - expect(suggestions.length).toEqual(2); - }); -}); diff --git a/x-pack/plugins/ml/public/application/components/kql_filter_bar/utils.js b/x-pack/plugins/ml/public/application/components/kql_filter_bar/utils.js deleted file mode 100644 index d632f4079e5b9..0000000000000 --- a/x-pack/plugins/ml/public/application/components/kql_filter_bar/utils.js +++ /dev/null @@ -1,97 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import { esKuery } from '../../../../../../../src/plugins/data/public'; -import { getAutocomplete } from '../../util/dependency_cache'; - -export function getSuggestions(query, selectionStart, indexPattern, boolFilter) { - const autocomplete = getAutocomplete(); - return autocomplete.getQuerySuggestions({ - language: 'kuery', - indexPatterns: [indexPattern], - boolFilter, - query, - selectionStart, - selectionEnd: selectionStart, - }); -} - -function convertKueryToEsQuery(kuery, indexPattern) { - const ast = esKuery.fromKueryExpression(kuery); - return esKuery.toElasticsearchQuery(ast, indexPattern); -} -// Recommended by MDN for escaping user input to be treated as a literal string within a regular expression -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions -export function escapeRegExp(string) { - return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string -} - -export function escapeParens(string) { - return string.replace(/[()]/g, '\\$&'); -} - -export function escapeDoubleQuotes(string) { - return string.replace(/\"/g, '\\$&'); -} - -export function getKqlQueryValues(inputValue, indexPattern) { - const ast = esKuery.fromKueryExpression(inputValue); - const isAndOperator = ast.function === 'and'; - const query = convertKueryToEsQuery(inputValue, indexPattern); - const filteredFields = []; - - if (!query) { - return; - } - - // if ast.type == 'function' then layout of ast.arguments: - // [{ arguments: [ { type: 'literal', value: 'AAL' } ] },{ arguments: [ { type: 'literal', value: 'AAL' } ] }] - if (ast && Array.isArray(ast.arguments)) { - ast.arguments.forEach(arg => { - if (arg.arguments !== undefined) { - arg.arguments.forEach(nestedArg => { - if (typeof nestedArg.value === 'string') { - filteredFields.push(nestedArg.value); - } - }); - } else if (typeof arg.value === 'string') { - filteredFields.push(arg.value); - } - }); - } - - return { - filterQuery: query, - filteredFields, - queryString: inputValue, - isAndOperator, - tableQueryString: inputValue, - }; -} - -export function getQueryPattern(fieldName, fieldValue) { - const sanitizedFieldName = escapeRegExp(fieldName); - const sanitizedFieldValue = escapeRegExp(fieldValue); - - return new RegExp(`(${sanitizedFieldName})\\s?:\\s?(")?(${sanitizedFieldValue})(")?`, 'i'); -} - -export function removeFilterFromQueryString(currentQueryString, fieldName, fieldValue) { - let newQueryString = ''; - // Remove the passed in fieldName and value from the existing filter - const queryPattern = getQueryPattern(fieldName, fieldValue); - newQueryString = currentQueryString.replace(queryPattern, ''); - // match 'and' or 'or' at the start/end of the string - const endPattern = /\s(and|or)\s*$/gi; - const startPattern = /^\s*(and|or)\s/gi; - // If string has a double operator (e.g. tag:thing or or tag:other) remove and replace with the first occurring operator - const invalidOperatorPattern = /\s+(and|or)\s+(and|or)\s+/gi; - newQueryString = newQueryString.replace(invalidOperatorPattern, ' $1 '); - // If string starts/ends with 'and' or 'or' remove that as that is illegal kuery syntax - newQueryString = newQueryString.replace(endPattern, ''); - newQueryString = newQueryString.replace(startPattern, ''); - - return newQueryString; -} diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/search_panel/search_panel.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/search_panel/search_panel.tsx index ef13fec3589fb..16004475eb44f 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/search_panel/search_panel.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/search_panel/search_panel.tsx @@ -6,14 +6,22 @@ import React, { FC, useState } from 'react'; -import { EuiFlexItem, EuiFlexGroup, EuiIconTip, EuiSuperSelect, EuiText } from '@elastic/eui'; +import { + EuiCode, + EuiFlexItem, + EuiFlexGroup, + EuiIconTip, + EuiInputPopover, + EuiSuperSelect, + EuiText, +} from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { IndexPattern } from '../../../../../../../../../src/plugins/data/public'; -import { SEARCH_QUERY_LANGUAGE } from '../../../../../../common/constants/search'; +import { SEARCH_QUERY_LANGUAGE, ErrorMessage } from '../../../../../../common/constants/search'; import { esKuery, @@ -22,8 +30,6 @@ import { QueryStringInput, } from '../../../../../../../../../src/plugins/data/public'; -import { getToastNotifications } from '../../../../util/dependency_cache'; - interface Props { indexPattern: IndexPattern; searchString: Query['query']; @@ -73,6 +79,7 @@ export const SearchPanel: FC<Props> = ({ query: searchString || '', language: searchQueryLanguage, }); + const [errorMessage, setErrorMessage] = useState<ErrorMessage | undefined>(undefined); const searchHandler = (query: Query) => { let filterQuery; @@ -93,13 +100,7 @@ export const SearchPanel: FC<Props> = ({ setSearchQueryLanguage(query.language); } catch (e) { console.log('Invalid syntax', JSON.stringify(e, null, 2)); // eslint-disable-line no-console - const toastNotifications = getToastNotifications(); - toastNotifications.addError(e, { - title: i18n.translate('xpack.ml.datavisualizer.invalidSyntaxErrorMessage', { - defaultMessage: 'Invalid syntax in search bar', - }), - toastMessage: e.message ? e.message : e, - }); + setErrorMessage({ query: query.query as string, message: e.message }); } }; const searchChangeHandler = (query: Query) => setSearchInput(query); @@ -107,22 +108,40 @@ export const SearchPanel: FC<Props> = ({ return ( <EuiFlexGroup gutterSize="m" alignItems="center" data-test-subj="mlDataVisualizerSearchPanel"> <EuiFlexItem> - <QueryStringInput - bubbleSubmitEvent={true} - query={searchInput} - indexPatterns={[indexPattern]} - onChange={searchChangeHandler} - onSubmit={searchHandler} - placeholder={i18n.translate( - 'xpack.ml.datavisualizer.searchPanel.queryBarPlaceholderText', - { - defaultMessage: 'Search… (e.g. status:200 AND extension:"PHP")', - } - )} - disableAutoFocus={true} - dataTestSubj="transformQueryInput" - languageSwitcherPopoverAnchorPosition="rightDown" - /> + <EuiInputPopover + style={{ maxWidth: '100%' }} + closePopover={() => setErrorMessage(undefined)} + input={ + <QueryStringInput + bubbleSubmitEvent={true} + query={searchInput} + indexPatterns={[indexPattern]} + onChange={searchChangeHandler} + onSubmit={searchHandler} + placeholder={i18n.translate( + 'xpack.ml.datavisualizer.searchPanel.queryBarPlaceholderText', + { + defaultMessage: 'Search… (e.g. status:200 AND extension:"PHP")', + } + )} + disableAutoFocus={true} + dataTestSubj="transformQueryInput" + languageSwitcherPopoverAnchorPosition="rightDown" + /> + } + isOpen={errorMessage?.query === searchInput.query && errorMessage?.message !== ''} + > + <EuiCode> + {i18n.translate( + 'xpack.ml.datavisualizer.searchPanel.invalidKuerySyntaxErrorMessageQueryBar', + { + defaultMessage: 'Invalid query', + } + )} + {': '} + {errorMessage?.message.split('\n')[0]} + </EuiCode> + </EuiInputPopover> </EuiFlexItem> <EuiFlexItem grow={false}> diff --git a/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx b/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx index 0789a7f8ef5ff..0263ad08b03cf 100644 --- a/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx +++ b/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx @@ -5,6 +5,8 @@ */ import React, { FC, useState, useEffect } from 'react'; +import { EuiCode, EuiInputPopover } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { Query, esKuery, @@ -12,10 +14,10 @@ import { QueryStringInput, } from '../../../../../../../../src/plugins/data/public'; import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns'; -import { QUERY_LANGUAGE_KUERY, QUERY_LANGUAGE_LUCENE } from '../../explorer_constants'; +import { SEARCH_QUERY_LANGUAGE, ErrorMessage } from '../../../../../common/constants/search'; import { explorerService } from '../../explorer_dashboard_service'; -export const DEFAULT_QUERY_LANG = QUERY_LANGUAGE_KUERY; +export const DEFAULT_QUERY_LANG = SEARCH_QUERY_LANGUAGE.KUERY; export function getKqlQueryValues({ inputString, @@ -25,11 +27,11 @@ export function getKqlQueryValues({ inputString: string | { [key: string]: any }; queryLanguage: string; indexPattern: IIndexPattern; -}) { +}): { clearSettings: boolean; settings: any } { let influencersFilterQuery: any = {}; - const ast = esKuery.fromKueryExpression(inputString); - const isAndOperator = ast.function === 'and'; const filteredFields: string[] = []; + const ast = esKuery.fromKueryExpression(inputString); + const isAndOperator = ast && ast.function === 'and'; // if ast.type == 'function' then layout of ast.arguments: // [{ arguments: [ { type: 'literal', value: 'AAL' } ] },{ arguments: [ { type: 'literal', value: 'AAL' } ] }] if (ast && Array.isArray(ast.arguments)) { @@ -45,12 +47,12 @@ export function getKqlQueryValues({ } }); } - if (queryLanguage === QUERY_LANGUAGE_KUERY) { + if (queryLanguage === SEARCH_QUERY_LANGUAGE.KUERY) { influencersFilterQuery = esKuery.toElasticsearchQuery( esKuery.fromKueryExpression(inputString), indexPattern ); - } else if (queryLanguage === QUERY_LANGUAGE_LUCENE) { + } else if (queryLanguage === SEARCH_QUERY_LANGUAGE.LUCENE) { influencersFilterQuery = esQuery.luceneStringToDsl(inputString); } @@ -78,7 +80,7 @@ function getInitSearchInputState({ }) { if (queryString !== undefined && filterActive === true) { return { - language: QUERY_LANGUAGE_KUERY, + language: SEARCH_QUERY_LANGUAGE.KUERY, query: queryString, }; } else { @@ -110,6 +112,7 @@ export const ExplorerQueryBar: FC<ExplorerQueryBarProps> = ({ const [searchInput, setSearchInput] = useState<Query>( getInitSearchInputState({ filterActive, queryString }) ); + const [errorMessage, setErrorMessage] = useState<ErrorMessage | undefined>(undefined); useEffect(() => { if (filterIconTriggeredQuery !== undefined) { @@ -127,30 +130,50 @@ export const ExplorerQueryBar: FC<ExplorerQueryBarProps> = ({ setSearchInput(query); }; const applyInfluencersFilterQuery = (query: Query) => { - const { clearSettings, settings } = getKqlQueryValues({ - inputString: query.query, - queryLanguage: query.language, - indexPattern, - }); + try { + const { clearSettings, settings } = getKqlQueryValues({ + inputString: query.query, + queryLanguage: query.language, + indexPattern, + }); - if (clearSettings === true) { - explorerService.clearInfluencerFilterSettings(); - } else { - explorerService.setInfluencerFilterSettings(settings); + if (clearSettings === true) { + explorerService.clearInfluencerFilterSettings(); + } else { + explorerService.setInfluencerFilterSettings(settings); + } + } catch (e) { + console.log('Invalid query syntax in search bar', e); // eslint-disable-line no-console + setErrorMessage({ query: query.query as string, message: e.message }); } }; return ( - <QueryStringInput - bubbleSubmitEvent - query={searchInput} - indexPatterns={[indexPattern]} - onChange={searchChangeHandler} - onSubmit={applyInfluencersFilterQuery} - placeholder={filterPlaceHolder} - disableAutoFocus - dataTestSubj="explorerQueryInput" - languageSwitcherPopoverAnchorPosition="rightDown" - /> + <EuiInputPopover + style={{ maxWidth: '100%' }} + closePopover={() => setErrorMessage(undefined)} + input={ + <QueryStringInput + bubbleSubmitEvent + query={searchInput} + indexPatterns={[indexPattern]} + onChange={searchChangeHandler} + onSubmit={applyInfluencersFilterQuery} + placeholder={filterPlaceHolder} + disableAutoFocus + dataTestSubj="explorerQueryInput" + languageSwitcherPopoverAnchorPosition="rightDown" + /> + } + isOpen={errorMessage?.query === searchInput.query && errorMessage?.message !== ''} + > + <EuiCode> + {i18n.translate('xpack.ml.explorer.invalidKuerySyntaxErrorMessageQueryBar', { + defaultMessage: 'Invalid query', + })} + {': '} + {errorMessage?.message.split('\n')[0]} + </EuiCode> + </EuiInputPopover> ); }; diff --git a/x-pack/plugins/ml/public/application/explorer/explorer.js b/x-pack/plugins/ml/public/application/explorer/explorer.js index 9b02150bae9bb..d61d56d07b644 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer.js @@ -58,13 +58,12 @@ import { DEFAULT_QUERY_LANG, } from './components/explorer_query_bar/explorer_query_bar'; import { + getDateFormatTz, removeFilterFromQueryString, getQueryPattern, escapeParens, escapeDoubleQuotes, -} from '../components/kql_filter_bar/utils'; - -import { getDateFormatTz } from './explorer_utils'; +} from './explorer_utils'; import { getSwimlaneContainerWidth } from './legacy_utils'; import { @@ -266,7 +265,7 @@ export class Explorer extends React.Component { explorerService.setInfluencerFilterSettings(settings); } } catch (e) { - console.log('Invalid kuery syntax', e); // eslint-disable-line no-console + console.log('Invalid query syntax from table', e); // eslint-disable-line no-console const toastNotifications = getToastNotifications(); toastNotifications.addDanger( diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts b/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts index afec50eae06aa..b084f503272cc 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts +++ b/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts @@ -25,7 +25,6 @@ export const EXPLORER_ACTION = { SET_EXPLORER_DATA: 'setExplorerData', SET_FILTER_DATA: 'setFilterData', SET_INFLUENCER_FILTER_SETTINGS: 'setInfluencerFilterSettings', - SET_SEARCH_INPUT: 'setSearchInput', SET_SELECTED_CELLS: 'setSelectedCells', SET_SWIMLANE_CONTAINER_WIDTH: 'setSwimlaneContainerWidth', SET_SWIMLANE_LIMIT: 'setSwimlaneLimit', @@ -56,7 +55,3 @@ export const MAX_INFLUENCER_FIELD_NAMES = 50; export const VIEW_BY_JOB_LABEL = i18n.translate('xpack.ml.explorer.jobIdLabel', { defaultMessage: 'job ID', }); - -export const QUERY_LANGUAGE_KUERY = 'kuery'; -export const QUERY_LANGUAGE_LUCENE = 'lucene'; -export type QUERY_LANGUAGE = 'kuery' | 'lucene'; diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts b/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts index 277c1aa6f4566..89e1a908b1ecc 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts +++ b/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts @@ -132,12 +132,6 @@ export const explorerService = { payload, }); }, - setSearchInput: (query: any) => { - explorerAction$.next({ - type: EXPLORER_ACTION.SET_SEARCH_INPUT, - payload: query, - }); - }, setSelectedCells: (payload: AppStateSelectedCells | undefined) => { explorerAction$.next({ type: EXPLORER_ACTION.SET_SELECTED_CELLS, diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_utils.js b/x-pack/plugins/ml/public/application/explorer/explorer_utils.js index 0b41f789bb571..852b16ec581bb 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_utils.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_utils.js @@ -883,3 +883,42 @@ export async function loadTopInfluencers( } }); } + +// Recommended by MDN for escaping user input to be treated as a literal string within a regular expression +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions +export function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +export function escapeParens(string) { + return string.replace(/[()]/g, '\\$&'); +} + +export function escapeDoubleQuotes(string) { + return string.replace(/\"/g, '\\$&'); +} + +export function getQueryPattern(fieldName, fieldValue) { + const sanitizedFieldName = escapeRegExp(fieldName); + const sanitizedFieldValue = escapeRegExp(fieldValue); + + return new RegExp(`(${sanitizedFieldName})\\s?:\\s?(")?(${sanitizedFieldValue})(")?`, 'i'); +} + +export function removeFilterFromQueryString(currentQueryString, fieldName, fieldValue) { + let newQueryString = ''; + // Remove the passed in fieldName and value from the existing filter + const queryPattern = getQueryPattern(fieldName, fieldValue); + newQueryString = currentQueryString.replace(queryPattern, ''); + // match 'and' or 'or' at the start/end of the string + const endPattern = /\s(and|or)\s*$/gi; + const startPattern = /^\s*(and|or)\s/gi; + // If string has a double operator (e.g. tag:thing or or tag:other) remove and replace with the first occurring operator + const invalidOperatorPattern = /\s+(and|or)\s+(and|or)\s+/gi; + newQueryString = newQueryString.replace(invalidOperatorPattern, ' $1 '); + // If string starts/ends with 'and' or 'or' remove that as that is illegal kuery syntax + newQueryString = newQueryString.replace(endPattern, ''); + newQueryString = newQueryString.replace(startPattern, ''); + + return newQueryString; +} diff --git a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/reducer.ts b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/reducer.ts index ff659029e38d7..c31b26b7adb7b 100644 --- a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/reducer.ts +++ b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/reducer.ts @@ -68,13 +68,6 @@ export const explorerReducer = (state: ExplorerState, nextAction: Action): Explo nextState = setInfluencerFilterSettings(state, payload); break; - case EXPLORER_ACTION.SET_SEARCH_INPUT: - nextState = { - ...state, - searchInput: payload, - }; - break; - case EXPLORER_ACTION.SET_SELECTED_CELLS: const selectedCells = payload; nextState = { diff --git a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts index 44e1486508ea3..0a2dbf5bcff35 100644 --- a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts +++ b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts @@ -20,8 +20,6 @@ import { TimeRangeBounds, } from '../../explorer_utils'; -import { QUERY_LANGUAGE_KUERY, QUERY_LANGUAGE } from '../../explorer_constants'; // QUERY_LANGUAGE_LUCENE - export interface ExplorerState { annotationsData: any[]; bounds: TimeRangeBounds | undefined; @@ -39,10 +37,6 @@ export interface ExplorerState { noInfluencersConfigured: boolean; overallSwimlaneData: SwimlaneData; queryString: string; - searchInput: { - query: string; - language: QUERY_LANGUAGE; - }; selectedCells: AppStateSelectedCells | undefined; selectedJobs: ExplorerJob[] | null; swimlaneBucketInterval: any; @@ -79,10 +73,6 @@ export function getExplorerDefaultState(): ExplorerState { noInfluencersConfigured: true, overallSwimlaneData: getDefaultSwimlaneData(), queryString: '', - searchInput: { - query: '', - language: QUERY_LANGUAGE_KUERY, - }, selectedCells: undefined, selectedJobs: null, swimlaneBucketInterval: undefined, diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts index 2800cd362d426..0b45209ca4f37 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts @@ -83,6 +83,10 @@ export class JobCreator { return this._type; } + public get indexPatternTitle(): string { + return this._indexPatternTitle; + } + protected _addDetector(detector: Detector, agg: Aggregation, field: Field) { this._detectors.push(detector); this._aggs.push(agg); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/multi_metric_job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/multi_metric_job_creator.ts index f115c203624eb..035af2d81adbc 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/multi_metric_job_creator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/multi_metric_job_creator.ts @@ -14,12 +14,7 @@ import { } from '../../../../../../common/types/fields'; import { Job, Datafeed, Detector } from '../../../../../../common/types/anomaly_detection_jobs'; import { createBasicDetector } from './util/default_configs'; -import { - JOB_TYPE, - CREATED_BY_LABEL, - DEFAULT_MODEL_MEMORY_LIMIT, -} from '../../../../../../common/constants/new_job'; -import { ml } from '../../../../services/ml_api_service'; +import { JOB_TYPE, CREATED_BY_LABEL } from '../../../../../../common/constants/new_job'; import { getRichDetectors } from './util/general'; import { IndexPattern } from '../../../../../../../../../src/plugins/data/public'; @@ -27,7 +22,7 @@ export class MultiMetricJobCreator extends JobCreator { // a multi metric job has one optional overall partition field // which is the same for all detectors. private _splitField: SplitField = null; - private _lastEstimatedModelMemoryLimit = DEFAULT_MODEL_MEMORY_LIMIT; + protected _type: JOB_TYPE = JOB_TYPE.MULTI_METRIC; constructor( @@ -86,60 +81,6 @@ export class MultiMetricJobCreator extends JobCreator { this._removeDetector(index); } - // called externally to set the model memory limit based current detector configuration - public async calculateModelMemoryLimit() { - if (this.jobConfig.analysis_config.detectors.length === 0) { - this.modelMemoryLimit = DEFAULT_MODEL_MEMORY_LIMIT; - } else { - const { modelMemoryLimit } = await ml.calculateModelMemoryLimit({ - analysisConfig: this.jobConfig.analysis_config, - indexPattern: this._indexPatternTitle, - query: this._datafeed_config.query, - timeFieldName: this._job_config.data_description.time_field, - earliestMs: this._start, - latestMs: this._end, - }); - - try { - if (this.modelMemoryLimit === null) { - this.modelMemoryLimit = modelMemoryLimit; - } else { - // To avoid overwriting a possible custom set model memory limit, - // it only gets set to the estimation if the current limit is either - // the default value or the value of the previous estimation. - // That's our best guess if the value hasn't been customized. - // It doesn't get it if the user intentionally for whatever reason (re)set - // the value to either the default or pervious estimate. - // Because the string based limit could contain e.g. MB/Mb/mb - // all strings get lower cased for comparison. - const currentModelMemoryLimit = this.modelMemoryLimit.toLowerCase(); - const defaultModelMemoryLimit = DEFAULT_MODEL_MEMORY_LIMIT.toLowerCase(); - if ( - currentModelMemoryLimit === defaultModelMemoryLimit || - currentModelMemoryLimit === this._lastEstimatedModelMemoryLimit - ) { - this.modelMemoryLimit = modelMemoryLimit; - } - } - this._lastEstimatedModelMemoryLimit = modelMemoryLimit.toLowerCase(); - } catch (error) { - if (this.modelMemoryLimit === null) { - this.modelMemoryLimit = DEFAULT_MODEL_MEMORY_LIMIT; - } else { - // To avoid overwriting a possible custom set model memory limit, - // the limit is reset to the default only if the current limit matches - // the previous estimated limit. - const currentModelMemoryLimit = this.modelMemoryLimit.toLowerCase(); - if (currentModelMemoryLimit === this._lastEstimatedModelMemoryLimit) { - this.modelMemoryLimit = DEFAULT_MODEL_MEMORY_LIMIT; - } - // eslint-disable-next-line no-console - console.error('Model memory limit could not be calculated', error); - } - } - } - } - public get aggFieldPairs(): AggFieldPair[] { return this.detectors.map((d, i) => ({ field: this._fields[i], diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/model_memory_estimator.test.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/model_memory_estimator.test.ts new file mode 100644 index 0000000000000..f85223db65399 --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/model_memory_estimator.test.ts @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useFakeTimers, SinonFakeTimers } from 'sinon'; +import { CalculatePayload, modelMemoryEstimatorProvider } from './model_memory_estimator'; +import { JobValidator } from '../../job_validator'; +import { DEFAULT_MODEL_MEMORY_LIMIT } from '../../../../../../../common/constants/new_job'; +import { ml } from '../../../../../services/ml_api_service'; + +jest.mock('../../../../../services/ml_api_service', () => { + return { + ml: { + calculateModelMemoryLimit$: jest.fn(() => { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { of } = require('rxjs'); + return of({ modelMemoryLimit: '15MB' }); + }), + }, + }; +}); + +describe('delay', () => { + let clock: SinonFakeTimers; + let modelMemoryEstimator: ReturnType<typeof modelMemoryEstimatorProvider>; + let mockJobValidator: JobValidator; + + beforeEach(() => { + clock = useFakeTimers(); + mockJobValidator = { + isModelMemoryEstimationPayloadValid: true, + } as JobValidator; + modelMemoryEstimator = modelMemoryEstimatorProvider(mockJobValidator); + }); + afterEach(() => { + clock.restore(); + jest.clearAllMocks(); + }); + + test('should emit a default value first', () => { + const spy = jest.fn(); + modelMemoryEstimator.updates$.subscribe(spy); + expect(spy).toHaveBeenCalledWith(DEFAULT_MODEL_MEMORY_LIMIT); + }); + + test('should debounce it for 600 ms', () => { + const spy = jest.fn(); + + modelMemoryEstimator.updates$.subscribe(spy); + + modelMemoryEstimator.update({ analysisConfig: { detectors: [{}] } } as CalculatePayload); + + clock.tick(601); + expect(spy).toHaveBeenCalledWith('15MB'); + }); + + test('should not proceed further if the payload has not been changed', () => { + const spy = jest.fn(); + + modelMemoryEstimator.updates$.subscribe(spy); + + modelMemoryEstimator.update({ + analysisConfig: { detectors: [{ by_field_name: 'test' }] }, + } as CalculatePayload); + + clock.tick(601); + + modelMemoryEstimator.update({ + analysisConfig: { detectors: [{ by_field_name: 'test' }] }, + } as CalculatePayload); + + clock.tick(601); + + modelMemoryEstimator.update({ + analysisConfig: { detectors: [{ by_field_name: 'test' }] }, + } as CalculatePayload); + + clock.tick(601); + + expect(ml.calculateModelMemoryLimit$).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledTimes(2); + }); + + test('should call the endpoint only with a valid payload', () => { + const spy = jest.fn(); + + modelMemoryEstimator.updates$.subscribe(spy); + + modelMemoryEstimator.update(({ + analysisConfig: { detectors: [] }, + } as unknown) as CalculatePayload); + // @ts-ignore + mockJobValidator.isModelMemoryEstimationPayloadValid = false; + + clock.tick(601); + + expect(ml.calculateModelMemoryLimit$).not.toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/model_memory_estimator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/model_memory_estimator.ts new file mode 100644 index 0000000000000..501a63492da56 --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/model_memory_estimator.ts @@ -0,0 +1,130 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { Observable, of, Subject, Subscription } from 'rxjs'; +import { isEqual, cloneDeep } from 'lodash'; +import { + catchError, + debounceTime, + distinctUntilChanged, + pluck, + startWith, + switchMap, + map, + pairwise, +} from 'rxjs/operators'; +import { useEffect, useState } from 'react'; +import { DEFAULT_MODEL_MEMORY_LIMIT } from '../../../../../../../common/constants/new_job'; +import { ml } from '../../../../../services/ml_api_service'; +import { JobValidator, VALIDATION_DELAY_MS } from '../../job_validator/job_validator'; +import { ErrorResponse } from '../../../../../../../common/types/errors'; +import { useMlKibana } from '../../../../../contexts/kibana'; +import { JobCreator } from '../job_creator'; + +export type CalculatePayload = Parameters<typeof ml.calculateModelMemoryLimit$>[0]; + +export const modelMemoryEstimatorProvider = (jobValidator: JobValidator) => { + const modelMemoryCheck$ = new Subject<CalculatePayload>(); + const error$ = new Subject<ErrorResponse['body']>(); + + return { + get error$(): Observable<ErrorResponse['body']> { + return error$.asObservable(); + }, + get updates$(): Observable<string> { + return modelMemoryCheck$.pipe( + // delay the request, making sure the validation is completed + debounceTime(VALIDATION_DELAY_MS + 100), + // clone the object to compare payloads and proceed further only + // if the configuration has been changed + map(cloneDeep), + distinctUntilChanged(isEqual), + switchMap(payload => { + const isPayloadValid = jobValidator.isModelMemoryEstimationPayloadValid; + + return isPayloadValid + ? ml.calculateModelMemoryLimit$(payload).pipe( + pluck('modelMemoryLimit'), + catchError(error => { + // eslint-disable-next-line no-console + console.error('Model memory limit could not be calculated', error.body); + error$.next(error.body); + return of(DEFAULT_MODEL_MEMORY_LIMIT); + }) + ) + : of(DEFAULT_MODEL_MEMORY_LIMIT); + }), + startWith(DEFAULT_MODEL_MEMORY_LIMIT) + ); + }, + update(payload: CalculatePayload) { + modelMemoryCheck$.next(payload); + }, + }; +}; + +export const useModelMemoryEstimator = ( + jobCreator: JobCreator, + jobValidator: JobValidator, + jobCreatorUpdate: Function, + jobCreatorUpdated: number +) => { + const { + services: { notifications }, + } = useMlKibana(); + + // Initialize model memory estimator only once + const [modelMemoryEstimator] = useState(modelMemoryEstimatorProvider(jobValidator)); + + // Listen for estimation results and errors + useEffect(() => { + const subscription = new Subscription(); + + subscription.add( + modelMemoryEstimator.updates$ + .pipe(pairwise()) + .subscribe(([previousEstimation, currentEstimation]) => { + // to make sure we don't overwrite a manual input + if ( + jobCreator.modelMemoryLimit === null || + jobCreator.modelMemoryLimit === previousEstimation + ) { + jobCreator.modelMemoryLimit = currentEstimation; + // required in order to trigger changes on the input + jobCreatorUpdate(); + } + }) + ); + + subscription.add( + modelMemoryEstimator.error$.subscribe(error => { + notifications.toasts.addWarning({ + title: i18n.translate('xpack.ml.newJob.wizard.estimateModelMemoryError', { + defaultMessage: 'Model memory limit could not be calculated', + }), + text: error.message, + }); + }) + ); + + return () => { + subscription.unsubscribe(); + }; + }, []); + + // Update model memory estimation payload on the job creator updates + useEffect(() => { + modelMemoryEstimator.update({ + analysisConfig: jobCreator.jobConfig.analysis_config, + indexPattern: jobCreator.indexPatternTitle, + query: jobCreator.datafeedConfig.query, + timeFieldName: jobCreator.jobConfig.data_description.time_field, + earliestMs: jobCreator.start, + latestMs: jobCreator.end, + }); + }, [jobCreatorUpdated]); +}; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts index 82e5e15a24d5c..2650f89cf25ca 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts @@ -6,7 +6,7 @@ import { ReactElement } from 'react'; import { combineLatest, Observable, ReplaySubject, Subject } from 'rxjs'; -import { map, tap } from 'rxjs/operators'; +import { map, startWith, tap } from 'rxjs/operators'; import { basicJobValidation, basicDatafeedValidation, @@ -17,11 +17,12 @@ import { populateValidationMessages, checkForExistingJobAndGroupIds } from './ut import { ExistingJobsAndGroups } from '../../../../services/job_service'; import { cardinalityValidator, CardinalityValidatorResult } from './validators'; import { CATEGORY_EXAMPLES_VALIDATION_STATUS } from '../../../../../../common/constants/categorization_job'; +import { JOB_TYPE } from '../../../../../../common/constants/new_job'; // delay start of validation to allow the user to make changes // e.g. if they are typing in a new value, try not to validate // after every keystroke -const VALIDATION_DELAY_MS = 500; +export const VALIDATION_DELAY_MS = 500; type AsyncValidatorsResult = Partial<CardinalityValidatorResult>; @@ -109,7 +110,8 @@ export class JobValidator { ...(curr ? curr : {}), }; }, {}); - }) + }), + startWith({}) ); this.validationResult$ = combineLatest([ @@ -270,4 +272,30 @@ export class JobValidator { public set categorizationField(valid: boolean) { this._advancedValidations.categorizationFieldValid.valid = valid; } + + /** + * Indicates if the Pick Fields step has a valid input + */ + public get isPickFieldsStepValid(): boolean { + return ( + this._jobCreator.detectors.length > 0 && + (this._jobCreator.type !== JOB_TYPE.ADVANCED || + (this._jobCreator.type === JOB_TYPE.ADVANCED && this.modelMemoryLimit.valid)) && + this.bucketSpan.valid && + this.duplicateDetectors.valid && + !this.validating && + (this._jobCreator.type !== JOB_TYPE.CATEGORIZATION || + (this._jobCreator.type === JOB_TYPE.CATEGORIZATION && this.categorizationField)) + ); + } + + public get isModelMemoryEstimationPayloadValid(): boolean { + return ( + this._jobCreator.detectors.length > 0 && + this.bucketSpan.valid && + this.duplicateDetectors.valid && + (this._jobCreator.type !== JOB_TYPE.CATEGORIZATION || + (this._jobCreator.type === JOB_TYPE.CATEGORIZATION && this.categorizationField)) + ); + } } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/influencers/influencers.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/influencers/influencers.tsx index 5c5ba38b1c5a1..c55bdeef4dde8 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/influencers/influencers.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/influencers/influencers.tsx @@ -25,9 +25,6 @@ export const Influencers: FC = () => { useEffect(() => { jobCreator.removeAllInfluencers(); influencers.forEach(i => jobCreator.addInfluencer(i)); - if (jobCreator instanceof MultiMetricJobCreator) { - jobCreator.calculateModelMemoryLimit(); - } jobCreatorUpdate(); }, [influencers.join()]); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx index ffa991388fbe2..cbd7c0ab72ffc 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx @@ -79,7 +79,6 @@ export const MultiMetricDetectors: FC<Props> = ({ setIsValid }) => { aggFieldPairList.forEach(pair => { jobCreator.addDetector(pair.agg, pair.field); }); - jobCreator.calculateModelMemoryLimit(); jobCreatorUpdate(); loadCharts(); setIsValid(aggFieldPairList.length > 0); @@ -115,7 +114,6 @@ export const MultiMetricDetectors: FC<Props> = ({ setIsValid }) => { } else { setFieldValues([]); } - jobCreator.calculateModelMemoryLimit(); }, [splitField]); // watch for changes in the split field values diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/pick_fields.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/pick_fields.tsx index bfec49678bc34..6f03b9a3c3321 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/pick_fields.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/pick_fields.tsx @@ -25,16 +25,7 @@ export const PickFieldsStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) const jobType = jobCreator.type; useEffect(() => { - const active = - jobCreator.detectors.length > 0 && - (jobCreator.type !== JOB_TYPE.ADVANCED || - (jobCreator.type === JOB_TYPE.ADVANCED && jobValidator.modelMemoryLimit.valid)) && - jobValidator.bucketSpan.valid && - jobValidator.duplicateDetectors.valid && - jobValidator.validating === false && - (jobCreator.type !== JOB_TYPE.CATEGORIZATION || - (jobCreator.type === JOB_TYPE.CATEGORIZATION && jobValidator.categorizationField)); - setNextActive(active); + setNextActive(jobValidator.isPickFieldsStepValid); }, [jobValidatorUpdated]); return ( diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard.tsx index 6fe27457428b1..2ca0607f81a1e 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard.tsx @@ -5,6 +5,7 @@ */ import React, { FC, useReducer, useState, useEffect } from 'react'; +import { useModelMemoryEstimator } from '../../common/job_creator/util/model_memory_estimator'; import { WIZARD_STEPS } from '../components/step_types'; @@ -78,6 +79,8 @@ export const Wizard: FC<Props> = ({ stringifyConfigs(jobCreator.jobConfig, jobCreator.datafeedConfig) ); + useModelMemoryEstimator(jobCreator, jobValidator, jobCreatorUpdate, jobCreatorUpdated); + useEffect(() => { const subscription = jobValidator.validationResult$.subscribe(() => { setJobValidatorUpdate(jobValidatorUpdated); diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts index df59678452e2f..3be8679830423 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts @@ -535,7 +535,7 @@ export const ml = { }); }, - calculateModelMemoryLimit({ + calculateModelMemoryLimit$({ analysisConfig, indexPattern, query, @@ -559,7 +559,7 @@ export const ml = { latestMs, }); - return http<{ modelMemoryLimit: string }>({ + return http$<{ modelMemoryLimit: string }>({ path: `${basePath()}/validate/calculate_model_memory_limit`, method: 'POST', body, diff --git a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts index 277e1547e4b23..bcdc58e61ad7c 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts +++ b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts @@ -17,39 +17,6 @@ describe('ML - data recognizer', () => { } as never) as SavedObjectsClientContract ); - const moduleIds = [ - 'apache_ecs', - 'apm_jsbase', - 'apm_nodejs', - 'apm_transaction', - 'auditbeat_process_docker_ecs', - 'auditbeat_process_hosts_ecs', - 'logs_ui_analysis', - 'logs_ui_categories', - 'metricbeat_system_ecs', - 'nginx_ecs', - 'sample_data_ecommerce', - 'sample_data_weblogs', - 'siem_auditbeat', - 'siem_auditbeat_auth', - 'siem_packetbeat', - 'siem_winlogbeat', - 'siem_winlogbeat_auth', - 'uptime_heartbeat', - ]; - - // check all module IDs are the same as the list above - it('listModules - check all module IDs', async () => { - const modules = await dr.listModules(); - const ids = modules.map(m => m.id); - expect(ids.join()).toEqual(moduleIds.join()); - }); - - it('getModule - load a single module', async () => { - const module = await dr.getModule(moduleIds[0]); - expect(module.id).toEqual(moduleIds[0]); - }); - describe('jobOverrides', () => { it('should apply job overrides correctly', () => { // arrange diff --git a/x-pack/plugins/painless_lab/public/application/components/main.tsx b/x-pack/plugins/painless_lab/public/application/components/main.tsx index 10907536e9cc2..5aed3f25a51b9 100644 --- a/x-pack/plugins/painless_lab/public/application/components/main.tsx +++ b/x-pack/plugins/painless_lab/public/application/components/main.tsx @@ -55,7 +55,7 @@ export const Main: React.FunctionComponent = () => { return ( <div className="painlessLabMainContainer"> <EuiFlexGroup className="painlessLabPanelsContainer" responsive={false} gutterSize="none"> - <EuiFlexItem> + <EuiFlexItem className="painlessLabLeftPane"> <EuiTitle className="euiScreenReaderOnly"> <h1> {i18n.translate('xpack.painlessLab.title', { diff --git a/x-pack/plugins/painless_lab/public/styles/_index.scss b/x-pack/plugins/painless_lab/public/styles/_index.scss index f68dbe302511a..cad0d8229524b 100644 --- a/x-pack/plugins/painless_lab/public/styles/_index.scss +++ b/x-pack/plugins/painless_lab/public/styles/_index.scss @@ -5,12 +5,17 @@ * This is a very brittle way of preventing the editor and other content from disappearing * behind the bottom bar. */ -$bottomBarHeight: calc(#{$euiSize} * 3); +$bottomBarHeight: calc(#{$euiSize} * 3); .painlessLabBottomBarPlaceholder { height: $bottomBarHeight } +.painlessLabLeftPane { + padding-top: $euiSizeM; + background-color: $euiColorEmptyShade; +} + .painlessLabRightPane { border-right: none; border-top: none; @@ -25,7 +30,7 @@ $bottomBarHeight: calc(#{$euiSize} * 3); flex-direction: column; height: 100%; - [role="tabpanel"] { + [role='tabpanel'] { height: 100%; overflow-y: auto; } diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/init_data.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/init_data.ts index af24a8936c915..1a29e77f74e89 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/init_data.ts +++ b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/init_data.ts @@ -24,6 +24,9 @@ export function mutateAggsTimesTree(shard: Shard) { } for (const agg of shard.aggregations!) { initTree([agg], shardTime); + // To make this data structure consistent with that of search we + // mark each aggregation as it's own tree root. + agg.treeRoot = agg; } shard.time = shardTime; } diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap index ceb0fe751c2c7..6d1e0054078bd 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap @@ -135,7 +135,9 @@ exports[`APIKeysGridPage renders permission denied if user does not have require } iconType="securityApp" title={ - <h2> + <h2 + data-test-subj="apiKeysPermissionDeniedMessage" + > <FormattedMessage defaultMessage="You need permission to manage API keys" id="xpack.security.management.apiKeys.deniedPermissionTitle" @@ -174,6 +176,7 @@ exports[`APIKeysGridPage renders permission denied if user does not have require <EuiTitle> <h2 className="euiTitle euiTitle--medium" + data-test-subj="apiKeysPermissionDeniedMessage" > <FormattedMessage defaultMessage="You need permission to manage API keys" diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/permission_denied/permission_denied.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/permission_denied/permission_denied.tsx index d406b1684b3ff..1d9337ecab27f 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/permission_denied/permission_denied.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/permission_denied/permission_denied.tsx @@ -13,7 +13,7 @@ export const PermissionDenied = () => ( <EuiEmptyPrompt iconType="securityApp" title={ - <h2> + <h2 data-test-subj="apiKeysPermissionDeniedMessage"> <FormattedMessage id="xpack.security.management.apiKeys.deniedPermissionTitle" defaultMessage="You need permission to manage API keys" diff --git a/x-pack/plugins/snapshot_restore/kibana.json b/x-pack/plugins/snapshot_restore/kibana.json index a5e462c84aa83..df72102e52086 100644 --- a/x-pack/plugins/snapshot_restore/kibana.json +++ b/x-pack/plugins/snapshot_restore/kibana.json @@ -10,7 +10,8 @@ ], "optionalPlugins": [ "usageCollection", - "security" + "security", + "cloud" ], "configPath": ["xpack", "snapshot_restore"] } diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx index 1f9a60fa869b7..df22c3f3eb2e2 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx @@ -12,6 +12,8 @@ import { i18n } from '@kbn/i18n'; import { EuiButton, EuiCodeEditor, + EuiCode, + EuiInputPopover, EuiFlexGroup, EuiFlexItem, EuiForm, @@ -76,6 +78,11 @@ export interface StepDefineExposedState { valid: boolean; } +interface ErrorMessage { + query: string; + message: string; +} + const defaultSearch = '*'; const QUERY_LANGUAGE_KUERY = 'kuery'; @@ -256,6 +263,7 @@ export const StepDefineForm: FC<Props> = React.memo(({ overrides = {}, onChange, query: defaults.searchString || '', language: defaults.searchLanguage, }); + const [errorMessage, setErrorMessage] = useState<ErrorMessage | undefined>(undefined); // The state of the input query bar updated on every submit and to be exposed. const [searchLanguage, setSearchLanguage] = useState<StepDefineExposedState['searchLanguage']>( @@ -272,18 +280,23 @@ export const StepDefineForm: FC<Props> = React.memo(({ overrides = {}, onChange, const searchSubmitHandler = (query: Query) => { setSearchLanguage(query.language as QUERY_LANGUAGE); setSearchString(query.query !== '' ? (query.query as string) : undefined); - switch (query.language) { - case QUERY_LANGUAGE_KUERY: - setSearchQuery( - esKuery.toElasticsearchQuery( - esKuery.fromKueryExpression(query.query as string), - indexPattern - ) - ); - return; - case QUERY_LANGUAGE_LUCENE: - setSearchQuery(esQuery.luceneStringToDsl(query.query as string)); - return; + try { + switch (query.language) { + case QUERY_LANGUAGE_KUERY: + setSearchQuery( + esKuery.toElasticsearchQuery( + esKuery.fromKueryExpression(query.query as string), + indexPattern + ) + ); + return; + case QUERY_LANGUAGE_LUCENE: + setSearchQuery(esQuery.luceneStringToDsl(query.query as string)); + return; + } + } catch (e) { + console.log('Invalid syntax', JSON.stringify(e, null, 2)); // eslint-disable-line no-console + setErrorMessage({ query: query.query as string, message: e.message }); } }; @@ -620,33 +633,54 @@ export const StepDefineForm: FC<Props> = React.memo(({ overrides = {}, onChange, defaultMessage: 'Use a query to filter the source data (optional).', })} > - <QueryStringInput - bubbleSubmitEvent={true} - query={searchInput} - indexPatterns={[indexPattern]} - onChange={searchChangeHandler} - onSubmit={searchSubmitHandler} - placeholder={ - searchInput.language === QUERY_LANGUAGE_KUERY - ? i18n.translate( - 'xpack.transform.stepDefineForm.queryPlaceholderKql', - { - defaultMessage: 'e.g. {example}', - values: { example: 'method : "GET" or status : "404"' }, - } - ) - : i18n.translate( - 'xpack.transform.stepDefineForm.queryPlaceholderLucene', - { - defaultMessage: 'e.g. {example}', - values: { example: 'method:GET OR status:404' }, - } - ) + <EuiInputPopover + style={{ maxWidth: '100%' }} + closePopover={() => setErrorMessage(undefined)} + input={ + <QueryStringInput + bubbleSubmitEvent={true} + query={searchInput} + indexPatterns={[indexPattern]} + onChange={searchChangeHandler} + onSubmit={searchSubmitHandler} + placeholder={ + searchInput.language === QUERY_LANGUAGE_KUERY + ? i18n.translate( + 'xpack.transform.stepDefineForm.queryPlaceholderKql', + { + defaultMessage: 'e.g. {example}', + values: { example: 'method : "GET" or status : "404"' }, + } + ) + : i18n.translate( + 'xpack.transform.stepDefineForm.queryPlaceholderLucene', + { + defaultMessage: 'e.g. {example}', + values: { example: 'method:GET OR status:404' }, + } + ) + } + disableAutoFocus={true} + dataTestSubj="transformQueryInput" + languageSwitcherPopoverAnchorPosition="rightDown" + /> + } + isOpen={ + errorMessage?.query === searchInput.query && + errorMessage?.message !== '' } - disableAutoFocus={true} - dataTestSubj="transformQueryInput" - languageSwitcherPopoverAnchorPosition="rightDown" - /> + > + <EuiCode> + {i18n.translate( + 'xpack.transform.stepDefineForm.invalidKuerySyntaxErrorMessageQueryBar', + { + defaultMessage: 'Invalid query', + } + )} + {': '} + {errorMessage?.message.split('\n')[0]} + </EuiCode> + </EuiInputPopover> </EuiFormRow> )} </Fragment> diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 15393535eb8e9..f2ea8f8c6dd0c 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -7638,9 +7638,7 @@ "xpack.ml.explorer.distributionChart.unusualByFieldValuesLabel": "{ numberOfCauses, plural, one {# 個の異常な {byFieldName} 値 } other {#{plusSign}異常な{byFieldName}値}}", "xpack.ml.explorer.distributionChart.valueLabel": "値", "xpack.ml.explorer.distributionChart.valueWithoutAnomalyScoreLabel": "値", - "xpack.ml.explorer.fetchingSuggestionsErrorMessage": "提案の取得中にエラーが発生しました", "xpack.ml.explorer.intervalLabel": "間隔", - "xpack.ml.explorer.invalidKuerySyntaxErrorMessage": "無効な Kuery 構文", "xpack.ml.explorer.invalidKuerySyntaxErrorMessageFromTable": "クエリバーに無効な構文。インプットは有効な Kibana クエリ言語 (KQL) でなければなりません", "xpack.ml.explorer.jobIdLabel": "ジョブ ID", "xpack.ml.explorer.jobScoreAcrossAllInfluencersLabel": "(すべての影響因子のジョブスコア)", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 56dad3286c4ce..0dd584e32a248 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -7638,9 +7638,7 @@ "xpack.ml.explorer.distributionChart.unusualByFieldValuesLabel": "{ numberOfCauses, plural, one {# 个异常 {byFieldName} 值} other {#{plusSign} 个异常 {byFieldName} 值}}", "xpack.ml.explorer.distributionChart.valueLabel": "值", "xpack.ml.explorer.distributionChart.valueWithoutAnomalyScoreLabel": "值", - "xpack.ml.explorer.fetchingSuggestionsErrorMessage": "获取建议时出错", "xpack.ml.explorer.intervalLabel": "时间间隔", - "xpack.ml.explorer.invalidKuerySyntaxErrorMessage": "kuery 语法无效", "xpack.ml.explorer.invalidKuerySyntaxErrorMessageFromTable": "查询栏中的语法无效。输入必须是有效的 Kibana 查询语言 (KQL)", "xpack.ml.explorer.jobIdLabel": "作业 ID", "xpack.ml.explorer.jobScoreAcrossAllInfluencersLabel": "(所有影响因素的作业分数)", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index.tsx index 8a13c461fb313..d631882e1f581 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index.tsx @@ -194,7 +194,7 @@ const IndexActionConnectorFields: React.FunctionComponent<ActionConnectorFieldsP 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.refreshTooltip', { defaultMessage: - 'Should Elasticsearch refresh the affected shards to make this operation visible to search', + 'Refresh the affected shards to make this operation visible to search.', } )} /> @@ -217,7 +217,7 @@ const IndexActionConnectorFields: React.FunctionComponent<ActionConnectorFieldsP <> <FormattedMessage id="xpack.triggersActionsUI.components.builtinActionTypes.indexAction.defineTimeFieldLabel" - defaultMessage="Define time field" + defaultMessage="Define time field for each document" /> <EuiIconTip position="right" @@ -225,7 +225,7 @@ const IndexActionConnectorFields: React.FunctionComponent<ActionConnectorFieldsP content={i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.definedateFieldTooltip', { - defaultMessage: `Should a time field be added to each document automatically when it's indexed`, + defaultMessage: `Automatically add a time field to each document when it's indexed.`, } )} /> diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index 0cb9bbbbfb261..fc07171347e5e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -16,6 +16,8 @@ import { EuiLink, EuiLoadingSpinner, EuiIconTip, + EuiFlexGroup, + EuiFlexItem, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -410,7 +412,13 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { /> <EuiSpacer size="m" /> {/* Render the view based on if there's data or if they can save */} - {(isLoadingActions || isLoadingActionTypes) && <EuiLoadingSpinner size="xl" />} + {(isLoadingActions || isLoadingActionTypes) && ( + <EuiFlexGroup justifyContent="center" alignItems="center"> + <EuiFlexItem grow={false}> + <EuiLoadingSpinner size="xl" /> + </EuiFlexItem> + </EuiFlexGroup> + )} {data.length !== 0 && table} {data.length === 0 && canSave && !isLoadingActions && !isLoadingActionTypes && emptyPrompt} {data.length === 0 && !canSave && noPermissionPrompt} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx index 8d675148690c7..afd3299f0c2bb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx @@ -467,7 +467,11 @@ export const AlertsList: React.FunctionComponent = () => { {loadedItems.length || isFilterApplied ? ( table ) : alertTypesState.isLoading || alertsState.isLoading ? ( - <EuiLoadingSpinner size="xl" /> + <EuiFlexGroup justifyContent="center" alignItems="center"> + <EuiFlexItem grow={false}> + <EuiLoadingSpinner size="xl" /> + </EuiFlexItem> + </EuiFlexGroup> ) : ( emptyPrompt )} diff --git a/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts b/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts index 7e9267b3ed829..bc5b4213644af 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts @@ -73,7 +73,7 @@ export const getLatestMonitor: UMElasticsearchQueryFn<GetLatestMonitorParams, Pi const result = await callES('search', params); const decoded = PingType.decode( - result.aggregations.by_id.buckets?.[0]?.latest.hits?.hits?.[0]?._source ?? {} + result.aggregations?.by_id.buckets?.[0]?.latest.hits?.hits?.[0]._source ?? {} ); if (isRight(decoded)) { return decoded.right; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_ping_histogram.ts b/x-pack/plugins/uptime/server/lib/requests/get_ping_histogram.ts index ebf0457a86ac9..5a8927764ea5c 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_ping_histogram.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_ping_histogram.ts @@ -76,7 +76,7 @@ export const getPingHistogram: UMElasticsearchQueryFn< }; const result = await callES('search', params); - const interval = result.aggregations.timeseries?.interval; + const interval = result.aggregations?.timeseries?.interval; const buckets: HistogramQueryResult[] = result?.aggregations?.timeseries?.buckets ?? []; const histogram = buckets.map(bucket => { const x: number = bucket.key; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_pings.ts b/x-pack/plugins/uptime/server/lib/requests/get_pings.ts index 99b807797c599..690888ccc0a37 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_pings.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_pings.ts @@ -35,6 +35,9 @@ export interface GetPingsParams { /** @member location optional location value for use in filtering*/ location?: string | null; + + /** @member page the number to provide to Elasticsearch as the "from" parameter */ + page?: number; } export const getPings: UMElasticsearchQueryFn<GetPingsParams, PingsResponse> = async ({ @@ -47,6 +50,7 @@ export const getPings: UMElasticsearchQueryFn<GetPingsParams, PingsResponse> = a sort, size, location, + page, }) => { const sortParam = { sort: [{ '@timestamp': { order: sort ?? 'desc' } }] }; const sizeParam = size ? { size } : undefined; @@ -63,7 +67,7 @@ export const getPings: UMElasticsearchQueryFn<GetPingsParams, PingsResponse> = a postFilterClause = { post_filter: { term: { 'observer.geo.name': location } } }; } const queryContext = { bool: { filter } }; - const params = { + const params: any = { index: dynamicSettings.heartbeatIndices, body: { query: { @@ -84,6 +88,10 @@ export const getPings: UMElasticsearchQueryFn<GetPingsParams, PingsResponse> = a }, }; + if (page) { + params.body.from = page * (size ?? 25); + } + const { hits: { hits, total }, aggregations: aggs, diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/aad/index.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/aad/index.ts index 7194c642e7015..05139213b76b9 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/aad/index.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/aad/index.ts @@ -42,7 +42,7 @@ export default function(kibana: any) { .required(), }, }, - async handler(request: CheckAADRequest) { + handler: (async (request: CheckAADRequest) => { let namespace: string | undefined; const spacesPlugin = server.plugins.spaces; if (spacesPlugin && request.payload.spaceId) { @@ -52,7 +52,7 @@ export default function(kibana: any) { namespace, }); return { success: true }; - }, + }) as Hapi.Lifecycle.Method, }); }, }); diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/pagerduty_simulation.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/pagerduty_simulation.ts index 977424aab88b7..1de1476fc4ff2 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/pagerduty_simulation.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/pagerduty_simulation.ts @@ -36,7 +36,7 @@ export function initPlugin(server: Hapi.Server, path: string) { }), }, }, - handler: pagerdutyHandler, + handler: pagerdutyHandler as Hapi.Lifecycle.Method, }); } // Pagerduty simulator: create an action pointing here, and you can get diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/servicenow_simulation.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/servicenow_simulation.ts index 329262044357b..a58738e387aeb 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/servicenow_simulation.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/servicenow_simulation.ts @@ -21,7 +21,7 @@ export function initPlugin(server: Hapi.Server, path: string) { options: { auth: false, }, - handler: createHandler, + handler: createHandler as Hapi.Lifecycle.Method, }); server.route({ @@ -35,7 +35,7 @@ export function initPlugin(server: Hapi.Server, path: string) { }), }, }, - handler: updateHandler, + handler: updateHandler as Hapi.Lifecycle.Method, }); server.route({ @@ -44,7 +44,7 @@ export function initPlugin(server: Hapi.Server, path: string) { options: { auth: false, }, - handler: getHandler, + handler: getHandler as Hapi.Lifecycle.Method, }); } diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/slack_simulation.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/slack_simulation.ts index a5d6df60be3f3..039f0c1b84a15 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/slack_simulation.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/slack_simulation.ts @@ -25,7 +25,7 @@ export function initPlugin(server: Hapi.Server, path: string) { }), }, }, - handler: slackHandler, + handler: slackHandler as Hapi.Lifecycle.Method, }); } // Slack simulator: create a slack action pointing here, and you can get diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/webhook_simulation.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/webhook_simulation.ts index 1b267f6c4976f..355245fc4929a 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/webhook_simulation.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/webhook_simulation.ts @@ -52,7 +52,7 @@ export async function initPlugin(server: Hapi.Server, path: string) { payload: Joi.string(), }, }, - handler: webhookHandler, + handler: webhookHandler as Hapi.Lifecycle.Method, }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/create.ts index 74e8e0f832299..efd707b59cd34 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/create.ts @@ -33,7 +33,7 @@ export default function createActionTests({ getService }: FtrProviderContext) { }, }); - expect(response.statusCode).to.eql(200); + expect(response.status).to.eql(200); objectRemover.add(Spaces.space1.id, response.body.id, 'action'); expect(response.body).to.eql({ id: response.body.id, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts index c2e5aa041055d..3faa54ee0b219 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts @@ -66,7 +66,7 @@ export default function({ getService }: FtrProviderContext) { }, }); - expect(response.statusCode).to.eql(200); + expect(response.status).to.eql(200); expect(response.body).to.be.an('object'); const searchResult = await esTestIndexTool.search('action:test.index-record', reference); expect(searchResult.hits.total.value).to.eql(1); @@ -110,7 +110,7 @@ export default function({ getService }: FtrProviderContext) { }, }); - expect(response.statusCode).to.eql(200); + expect(response.status).to.eql(200); expect(response.body).to.eql({ actionId: createdAction.id, status: 'error', @@ -180,7 +180,7 @@ export default function({ getService }: FtrProviderContext) { }, }); - expect(response.statusCode).to.eql(200); + expect(response.status).to.eql(200); const searchResult = await esTestIndexTool.search('action:test.authorization', reference); expect(searchResult.hits.total.value).to.eql(1); const indexedRecord = searchResult.hits.hits[0]; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/list_action_types.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/list_action_types.ts index fa3941dad3795..dca3769d38e12 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/list_action_types.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/list_action_types.ts @@ -23,7 +23,7 @@ export default function listActionTypesTests({ getService }: FtrProviderContext) }; } - expect(response.statusCode).to.eql(200); + expect(response.status).to.eql(200); // Check for values explicitly in order to avoid this test failing each time plugins register // a new action type expect( diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/type_not_enabled.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/type_not_enabled.ts index 1388108806c0f..18a0ecc23c1e1 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/type_not_enabled.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/type_not_enabled.ts @@ -29,7 +29,7 @@ export default function typeNotEnabledTests({ getService }: FtrProviderContext) actionTypeId: DISABLED_ACTION_TYPE, }); - expect(response.statusCode).to.eql(403); + expect(response.status).to.eql(403); expect(response.body).to.eql({ statusCode: 403, error: 'Forbidden', @@ -46,7 +46,7 @@ export default function typeNotEnabledTests({ getService }: FtrProviderContext) params: {}, }); - expect(response.statusCode).to.eql(403); + expect(response.status).to.eql(403); expect(response.body).to.eql({ statusCode: 403, error: 'Forbidden', @@ -58,7 +58,7 @@ export default function typeNotEnabledTests({ getService }: FtrProviderContext) it('should handle get action request with disabled actionType appropriately', async () => { const response = await supertest.get(`/api/action/${PREWRITTEN_ACTION_ID}`); - expect(response.statusCode).to.eql(200); + expect(response.status).to.eql(200); expect(response.body).to.eql({ actionTypeId: 'test.not-enabled', config: {}, @@ -75,7 +75,7 @@ export default function typeNotEnabledTests({ getService }: FtrProviderContext) name: 'an action created before test.not-enabled was disabled (updated)', }); - expect(responseUpdate.statusCode).to.eql(403); + expect(responseUpdate.status).to.eql(403); expect(responseUpdate.body).to.eql({ statusCode: 403, error: 'Forbidden', @@ -84,7 +84,7 @@ export default function typeNotEnabledTests({ getService }: FtrProviderContext) }); const response = await supertest.get(`/api/action/${PREWRITTEN_ACTION_ID}`); - expect(response.statusCode).to.eql(200); + expect(response.status).to.eql(200); expect(response.body).to.eql({ actionTypeId: 'test.not-enabled', config: {}, @@ -99,10 +99,10 @@ export default function typeNotEnabledTests({ getService }: FtrProviderContext) response = await supertest .delete(`/api/action/${PREWRITTEN_ACTION_ID}`) .set('kbn-xsrf', 'foo'); - expect(response.statusCode).to.eql(204); + expect(response.status).to.eql(204); response = await supertest.get(`/api/action/${PREWRITTEN_ACTION_ID}`); - expect(response.statusCode).to.eql(404); + expect(response.status).to.eql(404); }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts index 8f161cfa37c93..c98e3abe75ab1 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts @@ -341,7 +341,7 @@ export default function alertTests({ getService }: FtrProviderContext) { }, }; - const { statusCode, body: createdAlert } = await supertest + const { status, body: createdAlert } = await supertest .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) .set('kbn-xsrf', 'foo') .send({ @@ -369,7 +369,7 @@ export default function alertTests({ getService }: FtrProviderContext) { // will print the error body, if an error occurred // if (statusCode !== 200) console.log(createdAlert); - expect(statusCode).to.be(200); + expect(status).to.be(200); const alertId = createdAlert.id; objectRemover.add(Spaces.space1.id, alertId, 'alert'); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts index 50e01c65b6a86..319834452a212 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts @@ -53,7 +53,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { }) ); - expect(response.statusCode).to.eql(200); + expect(response.status).to.eql(200); objectRemover.add(Spaces.space1.id, response.body.id, 'alert'); expect(response.body).to.eql({ id: response.body.id, @@ -108,7 +108,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })); - expect(response.statusCode).to.eql(200); + expect(response.status).to.eql(200); objectRemover.add(Spaces.space1.id, response.body.id, 'alert'); expect(response.body.scheduledTaskId).to.eql(undefined); }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts index 1dd0426c97cca..5f50c0d64f353 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts @@ -32,7 +32,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { )}/api/alert/_find?search=test.noop&search_fields=alertTypeId` ); - expect(response.statusCode).to.eql(200); + expect(response.status).to.eql(200); expect(response.body.page).to.equal(1); expect(response.body.perPage).to.be.greaterThan(0); expect(response.body.total).to.be.greaterThan(0); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts index 30b5e43aee585..66cd8a7244081 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts @@ -30,7 +30,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { `${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}` ); - expect(response.statusCode).to.eql(200); + expect(response.status).to.eql(200); expect(response.body).to.eql({ id: createdAlert.id, name: 'abc', diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_state.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_state.ts index 053df3b7199cc..6f1aec901760e 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_state.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_state.ts @@ -31,7 +31,7 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont `${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}/state` ); - expect(response.statusCode).to.eql(200); + expect(response.status).to.eql(200); expect(response.body).to.key('alertInstances', 'previousStartedAt'); }); @@ -59,7 +59,7 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont `${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}/state` ); - expect(response.statusCode).to.eql(200); + expect(response.status).to.eql(200); expect(response.body).to.key('alertInstances', 'alertTypeState', 'previousStartedAt'); expect(response.body.alertTypeState.runCount).to.greaterThan(1); }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts index fbcf744b96916..fabc6884b5f96 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts @@ -16,7 +16,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { describe('list_alert_types', () => { it('should return 200 with list of alert types', async () => { const response = await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/types`); - expect(response.statusCode).to.eql(200); + expect(response.status).to.eql(200); const fixtureAlertType = response.body.find((alertType: any) => alertType.id === 'test.noop'); expect(fixtureAlertType).to.eql({ actionGroups: [{ id: 'default', name: 'Default' }], @@ -32,7 +32,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { it('should return actionVariables with both context and state', async () => { const response = await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/types`); - expect(response.statusCode).to.eql(200); + expect(response.status).to.eql(200); const fixtureAlertType = response.body.find( (alertType: any) => alertType.id === 'test.always-firing' @@ -46,7 +46,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { it('should return actionVariables with just context', async () => { const response = await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/types`); - expect(response.statusCode).to.eql(200); + expect(response.status).to.eql(200); const fixtureAlertType = response.body.find( (alertType: any) => alertType.id === 'test.onlyContextVariables' @@ -60,7 +60,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { it('should return actionVariables with just state', async () => { const response = await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/types`); - expect(response.statusCode).to.eql(200); + expect(response.status).to.eql(200); const fixtureAlertType = response.body.find( (alertType: any) => alertType.id === 'test.onlyStateVariables' diff --git a/x-pack/test/api_integration/apis/apm/agent_configuration.ts b/x-pack/test/api_integration/apis/apm/agent_configuration.ts index 8cabac523791c..41d78995711f2 100644 --- a/x-pack/test/api_integration/apis/apm/agent_configuration.ts +++ b/x-pack/test/api_integration/apis/apm/agent_configuration.ts @@ -83,8 +83,8 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte }); it('can find the created config', async () => { - const { statusCode, body } = await searchConfigurations(searchParams); - expect(statusCode).to.equal(200); + const { status, body } = await searchConfigurations(searchParams); + expect(status).to.equal(200); expect(body._source.service).to.eql({}); expect(body._source.settings).to.eql({ transaction_sample_rate: '0.55' }); }); @@ -92,16 +92,16 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte it('can update the created config', async () => { await updateConfiguration({ service: {}, settings: { transaction_sample_rate: '0.85' } }); - const { statusCode, body } = await searchConfigurations(searchParams); - expect(statusCode).to.equal(200); + const { status, body } = await searchConfigurations(searchParams); + expect(status).to.equal(200); expect(body._source.service).to.eql({}); expect(body._source.settings).to.eql({ transaction_sample_rate: '0.85' }); }); it('can delete the created config', async () => { await deleteConfiguration(newConfig); - const { statusCode } = await searchConfigurations(searchParams); - expect(statusCode).to.equal(404); + const { status } = await searchConfigurations(searchParams); + expect(status).to.equal(404); }); }); @@ -166,12 +166,12 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte for (const agentRequest of agentsRequests) { it(`${agentRequest.service.name} / ${agentRequest.service.environment}`, async () => { - const { statusCode, body } = await searchConfigurations({ + const { status, body } = await searchConfigurations({ service: agentRequest.service, etag: 'abc', }); - expect(statusCode).to.equal(200); + expect(status).to.equal(200); expect(body._source.settings).to.eql(agentRequest.expectedSettings); }); } diff --git a/x-pack/test/api_integration/apis/apm/feature_controls.ts b/x-pack/test/api_integration/apis/apm/feature_controls.ts index afe68f21d9e39..8ce55b8fb1d5f 100644 --- a/x-pack/test/api_integration/apis/apm/feature_controls.ts +++ b/x-pack/test/api_integration/apis/apm/feature_controls.ts @@ -182,8 +182,8 @@ export default function featureControlsTests({ getService }: FtrProviderContext) async function executeAsAdmin({ method = 'get', url, body }: Endpoint['req'], spaceId?: string) { const basePath = spaceId ? `/s/${spaceId}` : ''; - - let request = supertest[method](`${basePath}${url}`); + const fullPath = `${basePath}${url}`; + let request = supertest[method](fullPath); // json body if (body) { @@ -192,10 +192,10 @@ export default function featureControlsTests({ getService }: FtrProviderContext) const response = await request.set('kbn-xsrf', 'foo'); - const { statusCode, req } = response; - if (statusCode !== 200) { - throw new Error(`Endpoint: ${req.method} ${req.path} - Status code: ${statusCode} + const { status } = response; + if (status !== 200) { + throw new Error(`Endpoint: ${method} ${fullPath} + Status code: ${status} Response: ${response.body.message}`); } diff --git a/x-pack/test/api_integration/apis/endpoint/alerts.ts b/x-pack/test/api_integration/apis/endpoint/alerts.ts index 568c30aa5484f..f947520620a8c 100644 --- a/x-pack/test/api_integration/apis/endpoint/alerts.ts +++ b/x-pack/test/api_integration/apis/endpoint/alerts.ts @@ -5,6 +5,7 @@ */ import expect from '@kbn/expect/expect.js'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { AlertData } from '../../../../plugins/endpoint/common/types'; /** * The number of alert documents in the es archive. @@ -16,9 +17,47 @@ const numberOfAlertsInFixture = 12; */ const defaultPageSize = 10; +/** + * `NULLABLE_EVENT_FIELD` should be a field in the fixture that exists for some alerts, + * but not all. + * + * This allows us to test sorting and paging on mixed data that may or may not exist + * for each alert. + */ +const NULLABLE_EVENT_FIELD = 'process.parent.entity_id'; + +/** + * An Elasticsearch query to get the alert (or alerts) without `NULLABLE_EVENT_FIELD`. + */ +const ES_QUERY_MISSING = { + query: { + bool: { + must: [ + { + bool: { + must_not: { + exists: { + field: NULLABLE_EVENT_FIELD, + }, + }, + }, + }, + { + term: { + 'event.kind': { + value: 'alert', + }, + }, + }, + ], + }, + }, +}; + export default function({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); + const es = getService('legacyEs'); const nextPrevPrefixQuery = "query=(language:kuery,query:'')"; const nextPrevPrefixDateRange = "date_range=(from:'2018-01-10T00:00:00.000Z',to:now)"; @@ -27,10 +66,24 @@ export default function({ getService }: FtrProviderContext) { const nextPrevPrefixPageSize = 'page_size=10'; const nextPrevPrefix = `${nextPrevPrefixQuery}&${nextPrevPrefixDateRange}&${nextPrevPrefixSort}&${nextPrevPrefixOrder}&${nextPrevPrefixPageSize}`; + let nullableEventId = ''; + describe('Endpoint alert API', () => { describe('when data is in elasticsearch', () => { - before(() => esArchiver.load('endpoint/alerts/api_feature')); - after(() => esArchiver.unload('endpoint/alerts/api_feature')); + before(async () => { + await esArchiver.load('endpoint/alerts/api_feature'); + await esArchiver.load('endpoint/metadata/api_feature'); + const res = await es.search({ + index: 'events-endpoint-1', + body: ES_QUERY_MISSING, + }); + nullableEventId = res.hits.hits[0]._source.event.id; + }); + + after(async () => { + await esArchiver.unload('endpoint/alerts/api_feature'); + await esArchiver.unload('endpoint/metadata/api_feature'); + }); it('should not support POST requests', async () => { await supertest @@ -155,7 +208,7 @@ export default function({ getService }: FtrProviderContext) { expect(body.alerts.length).to.eql(0); }); - it('should return no results when `after` is requested past end of last page', async () => { + it('should return no results when `after` is requested past end of last page, descending', async () => { const { body } = await supertest .get( `/api/endpoint/alerts?${nextPrevPrefix}&after=1584044338612&after=6d75d498-3cca-45ad-a304-525b95ae0412` @@ -165,16 +218,74 @@ export default function({ getService }: FtrProviderContext) { expect(body.alerts.length).to.eql(0); }); - it('should return 400 when using `before` by custom sort parameter', async () => { - await supertest + it('alerts api should return data using `before` by custom sort parameter, descending', async () => { + const { body } = await supertest .get( - `/api/endpoint/alerts?${nextPrevPrefixDateRange}&${nextPrevPrefixPageSize}&${nextPrevPrefixOrder}&sort=process.pid&before=1&before=66008e21-2493-4b15-a937-939ea228064a` + `/api/endpoint/alerts?${nextPrevPrefixDateRange}&${nextPrevPrefixPageSize}&${nextPrevPrefixOrder}&sort=process.name&before=malware%20writer&before=4d7afd81-26ec-47c0-9741-ae16d331f73d` ) .set('kbn-xsrf', 'xxx') - .expect(400); + .expect(200); + let valid: boolean = true; + (body.alerts as AlertData[]).forEach(alert => { + if (alert.process?.name > 'malware writer') { + valid = false; + } + }); + expect(valid).to.eql(true); }); - it('should return data using `after` by custom sort parameter', async () => { + it('alerts api should return data using `before` on undefined primary sort values by custom sort parameter, descending', async () => { + const { body } = await supertest + .get( + `/api/endpoint/alerts?${nextPrevPrefixDateRange}&${nextPrevPrefixPageSize}&order=desc&sort=${NULLABLE_EVENT_FIELD}&before=&before=${nullableEventId}&empty_string_is_undefined=true` + ) + .set('kbn-xsrf', 'xxx') + .expect(200); + + let lastSeen: string | undefined = 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz'; + let valid: boolean = true; + + for (const alert of body.alerts) { + const entityId = alert.process?.parent?.entity_id; + if (entityId === undefined && alert.event.id > nullableEventId) { + valid = false; + } + if (entityId !== undefined && lastSeen !== undefined && entityId > lastSeen) { + valid = false; + } else { + lastSeen = entityId; + } + } + + expect(valid).to.eql(true); + }); + + it('alerts api should return data using `before` on undefined primary sort values by custom sort parameter, ascending', async () => { + const { body } = await supertest + .get( + `/api/endpoint/alerts?${nextPrevPrefixDateRange}&page_size=25&order=asc&sort=${NULLABLE_EVENT_FIELD}&before=&before=${nullableEventId}&empty_string_is_undefined=true` + ) + .set('kbn-xsrf', 'xxx') + .expect(200); + + let lastSeen: string | undefined = '1'; + let valid: boolean = true; + + for (const alert of body.alerts) { + const entityId = alert.process?.parent?.entity_id; + if (entityId === undefined && alert.event.id < nullableEventId) { + valid = false; + } + if (entityId !== undefined && lastSeen !== undefined && entityId < lastSeen) { + valid = false; + } else { + lastSeen = entityId; + } + } + expect(valid).to.eql(true); + }); + + it('should return data using `after` by custom sort parameter, descending', async () => { const { body } = await supertest .get( `/api/endpoint/alerts?${nextPrevPrefixDateRange}&${nextPrevPrefixPageSize}&${nextPrevPrefixOrder}&sort=process.pid&after=3&after=66008e21-2493-4b15-a937-939ea228064a` @@ -185,6 +296,56 @@ export default function({ getService }: FtrProviderContext) { expect(body.alerts[0].process.pid).to.eql(2); }); + it('alerts api should return data using `after` on undefined primary sort values by custom sort parameter, descending', async () => { + const { body } = await supertest + .get( + `/api/endpoint/alerts?${nextPrevPrefixDateRange}&${nextPrevPrefixPageSize}&sort=${NULLABLE_EVENT_FIELD}&order=desc&after=&after=${nullableEventId}&empty_string_is_undefined=true` + ) + .set('kbn-xsrf', 'xxx') + .expect(200); + + let lastSeen: string | undefined = 'zzzzzzzzzzzzzzzzzzzzzzzzzzz'; + let valid: boolean = true; + + for (const alert of body.alerts) { + const entityId = alert.process?.parent?.entity_id; + if (entityId === undefined && alert.event.id < nullableEventId) { + valid = false; + } + if (entityId !== undefined && lastSeen !== undefined && entityId > lastSeen) { + valid = false; + } else { + lastSeen = entityId; + } + } + expect(valid).to.eql(true); + }); + + it('alerts api should return data using `after` on undefined primary sort values by custom sort parameter, ascending', async () => { + const { body } = await supertest + .get( + `/api/endpoint/alerts?${nextPrevPrefixDateRange}&${nextPrevPrefixPageSize}&sort=${NULLABLE_EVENT_FIELD}&order=asc&after=&after=${nullableEventId}&empty_string_is_undefined=true` + ) + .set('kbn-xsrf', 'xxx') + .expect(200); + + let lastSeen: string | undefined = '1'; + let valid: boolean = true; + + for (const alert of body.alerts) { + const entityId = alert.process?.parent?.entity_id; + if (entityId === undefined && alert.event.id < nullableEventId) { + valid = false; + } + if (entityId !== undefined && lastSeen !== undefined && entityId < lastSeen) { + valid = false; + } else { + lastSeen = entityId; + } + } + expect(valid).to.eql(true); + }); + it('should filter results of alert data using rison-encoded filters', async () => { const hostname = 'Host-abmfhmc5ku'; const { body } = await supertest @@ -225,6 +386,7 @@ export default function({ getService }: FtrProviderContext) { expect(body.id).to.eql(documentID); expect(body.prev).to.eql(`/api/endpoint/alerts/${prevDocumentID}`); expect(body.next).to.eql(null); // last alert, no more beyond this + expect(body.state.host_metadata.host.id).to.eql(body.host.id); }); it('should return alert details by id, getting first alert', async () => { diff --git a/x-pack/test/api_integration/apis/fleet/agents/acks.ts b/x-pack/test/api_integration/apis/fleet/agents/acks.ts index 437a5b58710c1..db925813b90c4 100644 --- a/x-pack/test/api_integration/apis/fleet/agents/acks.ts +++ b/x-pack/test/api_integration/apis/fleet/agents/acks.ts @@ -15,11 +15,11 @@ export default function(providerContext: FtrProviderContext) { const esArchiver = getService('esArchiver'); const esClient = getService('es'); - const supertestWithoutAuth = getSupertestWithoutAuth(providerContext); - const supertest = getService('supertest'); + const supertest = getSupertestWithoutAuth(providerContext); let apiKey: { id: string; api_key: string }; - describe('fleet_agents_acks', () => { + // FLAKY: https://github.com/elastic/kibana/issues/60471 + describe.skip('fleet_agents_acks', () => { before(async () => { await esArchiver.loadIfNeeded('fleet/agents'); @@ -50,7 +50,7 @@ export default function(providerContext: FtrProviderContext) { }); it('should return a 401 if this a not a valid acks access', async () => { - await supertestWithoutAuth + await supertest .post(`/api/ingest_manager/fleet/agents/agent1/acks`) .set('kbn-xsrf', 'xx') .set('Authorization', 'ApiKey NOT_A_VALID_TOKEN') @@ -61,7 +61,7 @@ export default function(providerContext: FtrProviderContext) { }); it('should return a 200 if this a valid acks request', async () => { - const { body: apiResponse } = await supertestWithoutAuth + const { body: apiResponse } = await supertest .post(`/api/ingest_manager/fleet/agents/agent1/acks`) .set('kbn-xsrf', 'xx') .set( @@ -91,15 +91,16 @@ export default function(providerContext: FtrProviderContext) { ], }) .expect(200); - expect(apiResponse.action).to.be('acks'); expect(apiResponse.success).to.be(true); - const { body: eventResponse } = await supertest .get(`/api/ingest_manager/fleet/agents/agent1/events`) .set('kbn-xsrf', 'xx') + .set( + 'Authorization', + `ApiKey ${Buffer.from(`${apiKey.id}:${apiKey.api_key}`).toString('base64')}` + ) .expect(200); - const expectedEvents = eventResponse.list.filter( (item: Record<string, string>) => item.action_id === '48cebde1-c906-4893-b89f-595d943b72a1' || @@ -121,7 +122,7 @@ export default function(providerContext: FtrProviderContext) { }); it('should return a 400 when request event list contains event for another agent id', async () => { - const { body: apiResponse } = await supertestWithoutAuth + const { body: apiResponse } = await supertest .post(`/api/ingest_manager/fleet/agents/agent1/acks`) .set('kbn-xsrf', 'xx') .set( @@ -148,7 +149,7 @@ export default function(providerContext: FtrProviderContext) { }); it('should return a 400 when request event list contains action that does not belong to agent current actions', async () => { - const { body: apiResponse } = await supertestWithoutAuth + const { body: apiResponse } = await supertest .post(`/api/ingest_manager/fleet/agents/agent1/acks`) .set('kbn-xsrf', 'xx') .set( @@ -182,7 +183,7 @@ export default function(providerContext: FtrProviderContext) { }); it('should return a 400 when request event list contains action types that are not allowed for acknowledgement', async () => { - const { body: apiResponse } = await supertestWithoutAuth + const { body: apiResponse } = await supertest .post(`/api/ingest_manager/fleet/agents/agent1/acks`) .set('kbn-xsrf', 'xx') .set( diff --git a/x-pack/test/api_integration/apis/ml/get_module.ts b/x-pack/test/api_integration/apis/ml/get_module.ts new file mode 100644 index 0000000000000..4478236c494a8 --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/get_module.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../ftr_provider_context'; +import { USER } from '../../../functional/services/machine_learning/security_common'; + +const COMMON_HEADERS = { + 'kbn-xsrf': 'some-xsrf-token', +}; + +const moduleIds = [ + 'apache_ecs', + 'apm_jsbase', + 'apm_nodejs', + 'apm_transaction', + 'auditbeat_process_docker_ecs', + 'auditbeat_process_hosts_ecs', + 'logs_ui_analysis', + 'logs_ui_categories', + 'metricbeat_system_ecs', + 'nginx_ecs', + 'sample_data_ecommerce', + 'sample_data_weblogs', + 'siem_auditbeat', + 'siem_auditbeat_auth', + 'siem_packetbeat', + 'siem_winlogbeat', + 'siem_winlogbeat_auth', + 'uptime_heartbeat', +]; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertestWithoutAuth'); + const mlSecurity = getService('mlSecurity'); + + async function executeGetModuleRequest(module: string, user: USER, rspCode: number) { + const { body } = await supertest + .get(`/api/ml/modules/get_module/${module}`) + .auth(user, mlSecurity.getPasswordForUser(user)) + .set(COMMON_HEADERS) + .expect(rspCode); + + return body; + } + + describe('get_module', function() { + it('lists all modules', async () => { + const rspBody = await executeGetModuleRequest('', USER.ML_POWERUSER, 200); + expect(rspBody).to.be.an(Array); + + const responseModuleIds = rspBody.map((module: { id: string }) => module.id); + expect(responseModuleIds).to.eql(moduleIds); + }); + + for (const moduleId of moduleIds) { + it(`loads module ${moduleId}`, async () => { + const rspBody = await executeGetModuleRequest(moduleId, USER.ML_POWERUSER, 200); + expect(rspBody).to.be.an(Object); + + expect(rspBody.id).to.eql(moduleId); + }); + } + }); +}; diff --git a/x-pack/test/api_integration/apis/ml/index.ts b/x-pack/test/api_integration/apis/ml/index.ts index e2000b661367f..78f99d8d9776a 100644 --- a/x-pack/test/api_integration/apis/ml/index.ts +++ b/x-pack/test/api_integration/apis/ml/index.ts @@ -25,5 +25,6 @@ export default function({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./bucket_span_estimator')); loadTestFile(require.resolve('./calculate_model_memory_limit')); loadTestFile(require.resolve('./categorization_field_examples')); + loadTestFile(require.resolve('./get_module')); }); } diff --git a/x-pack/test/api_integration/apis/siem/saved_objects/timeline.ts b/x-pack/test/api_integration/apis/siem/saved_objects/timeline.ts index a7e7cf4476f3f..6fe11bc294795 100644 --- a/x-pack/test/api_integration/apis/siem/saved_objects/timeline.ts +++ b/x-pack/test/api_integration/apis/siem/saved_objects/timeline.ts @@ -175,7 +175,7 @@ export default function({ getService }: FtrProviderContext) { expect(version).to.not.be.empty(); }); - it.skip('Update a timeline with a new title', async () => { + it('Update a timeline with a new title', async () => { const titleToSaved = 'hello title'; const response = await createBasicTimeline(client, titleToSaved); const { savedObjectId, version } = response.data && response.data.persistTimeline.timeline; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules.ts index ee34e5e261987..a886a5fb07a6c 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules.ts @@ -48,7 +48,6 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await supertest .delete(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`) .set('kbn-xsrf', 'true') - .query() .expect(200); const bodyToCompare = removeServerGeneratedProperties(body); @@ -67,7 +66,6 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await supertest .delete(`${DETECTION_ENGINE_RULES_URL}?rule_id=${bodyWithCreatedRule.rule_id}`) .set('kbn-xsrf', 'true') - .query() .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body); @@ -86,7 +84,6 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await supertest .delete(`${DETECTION_ENGINE_RULES_URL}?id=${bodyWithCreatedRule.id}`) .set('kbn-xsrf', 'true') - .query() .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body); @@ -97,7 +94,6 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await supertest .delete(`${DETECTION_ENGINE_RULES_URL}?id=fake_id`) .set('kbn-xsrf', 'true') - .query() .expect(404); expect(body).to.eql({ @@ -110,7 +106,6 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await supertest .delete(`${DETECTION_ENGINE_RULES_URL}?rule_id=fake_id`) .set('kbn-xsrf', 'true') - .query() .expect(404); expect(body).to.eql({ diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules_bulk.ts index 6b87c94029189..9e9071b82884f 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/delete_rules_bulk.ts @@ -49,7 +49,6 @@ export default ({ getService }: FtrProviderContext): void => { .delete(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) .set('kbn-xsrf', 'true') .send([{ rule_id: 'rule-1' }]) - .query() .expect(200); const bodyToCompare = removeServerGeneratedProperties(body[0]); @@ -69,7 +68,6 @@ export default ({ getService }: FtrProviderContext): void => { .delete(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) .send([{ rule_id: bodyWithCreatedRule.rule_id }]) .set('kbn-xsrf', 'true') - .query() .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body[0]); @@ -89,7 +87,6 @@ export default ({ getService }: FtrProviderContext): void => { .delete(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) .send([{ id: bodyWithCreatedRule.id }]) .set('kbn-xsrf', 'true') - .query() .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body[0]); @@ -101,7 +98,6 @@ export default ({ getService }: FtrProviderContext): void => { .delete(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) .send([{ rule_id: 'fake_id' }]) .set('kbn-xsrf', 'true') - .query() .expect(200); expect(body).to.eql([ @@ -120,7 +116,6 @@ export default ({ getService }: FtrProviderContext): void => { .delete(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) .send([{ id: 'fake_id' }]) .set('kbn-xsrf', 'true') - .query() .expect(200); expect(body).to.eql([ @@ -146,7 +141,6 @@ export default ({ getService }: FtrProviderContext): void => { .delete(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) .send([{ id: bodyWithCreatedRule.id }, { id: 'fake_id' }]) .set('kbn-xsrf', 'true') - .query() .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body[0]); @@ -182,7 +176,6 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) .set('kbn-xsrf', 'true') .send([{ rule_id: 'rule-1' }]) - .query() .expect(200); const bodyToCompare = removeServerGeneratedProperties(body[0]); @@ -202,7 +195,6 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) .send([{ rule_id: bodyWithCreatedRule.rule_id }]) .set('kbn-xsrf', 'true') - .query() .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body[0]); @@ -222,7 +214,6 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) .send([{ id: bodyWithCreatedRule.id }]) .set('kbn-xsrf', 'true') - .query() .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body[0]); @@ -234,7 +225,6 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) .send([{ rule_id: 'fake_id' }]) .set('kbn-xsrf', 'true') - .query() .expect(200); expect(body).to.eql([ @@ -253,7 +243,6 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) .send([{ id: 'fake_id' }]) .set('kbn-xsrf', 'true') - .query() .expect(200); expect(body).to.eql([ @@ -279,7 +268,6 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_bulk_delete`) .send([{ id: bodyWithCreatedRule.id }, { id: 'fake_id' }]) .set('kbn-xsrf', 'true') - .query() .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body[0]); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/export_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/export_rules.ts index 8882448dfcdc2..a8f841db94bbc 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/export_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/export_rules.ts @@ -45,7 +45,6 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_export`) .set('kbn-xsrf', 'true') .send() - .query() .expect(200) .expect('Content-Type', 'application/ndjson') .expect('Content-Disposition', 'attachment; filename="export.ndjson"'); @@ -62,7 +61,6 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_export`) .set('kbn-xsrf', 'true') .send() - .query() .expect(200) .parse(binaryToString); @@ -83,7 +81,6 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_export`) .set('kbn-xsrf', 'true') .send() - .query() .expect(200) .parse(binaryToString); @@ -115,7 +112,6 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_export`) .set('kbn-xsrf', 'true') .send() - .query() .expect(200) .parse(binaryToString); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts index a1cb60483c332..ae4589e32ec11 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts @@ -40,7 +40,6 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') - .query() .expect('Content-Type', 'application/json; charset=utf-8') .expect(200); }); @@ -50,7 +49,6 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.txt') - .query() .expect(400); expect(body).to.eql({ @@ -64,7 +62,6 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') - .query() .expect(200); expect(body).to.eql({ @@ -79,7 +76,6 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') .attach('file', getSimpleRuleAsNdjson(new Array(10001).fill('rule-1')), 'rules.ndjson') - .query() .expect(500); expect(body).to.eql({ message: "Can't import more than 10000 rules", status_code: 500 }); @@ -90,7 +86,6 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') - .query() .expect(200); const { body } = await supertest @@ -107,7 +102,6 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2']), 'rules.ndjson') - .query() .expect(200); expect(body).to.eql({ @@ -122,7 +116,6 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-1']), 'rules.ndjson') - .query() .expect(200); expect(body).to.eql({ @@ -145,7 +138,6 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true`) .set('kbn-xsrf', 'true') .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-1']), 'rules.ndjson') - .query() .expect(200); expect(body).to.eql({ @@ -160,14 +152,12 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') - .query() .expect(200); const { body } = await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') - .query() .expect(200); expect(body).to.eql({ @@ -190,14 +180,12 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true`) .set('kbn-xsrf', 'true') .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') - .query() .expect(200); const { body } = await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true`) .set('kbn-xsrf', 'true') .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') - .query() .expect(200); expect(body).to.eql({ @@ -212,7 +200,6 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') - .query() .expect(200); const simpleRule = getSimpleRule('rule-1'); @@ -223,7 +210,6 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true`) .set('kbn-xsrf', 'true') .attach('file', ndjson, 'rules.ndjson') - .query() .expect(200); const { body } = await supertest @@ -243,14 +229,12 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') - .query() .expect(200); const { body } = await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2', 'rule-3']), 'rules.ndjson') - .query() .expect(200); expect(body).to.eql({ @@ -273,14 +257,12 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2']), 'rules.ndjson') - .query() .expect(200); const { body } = await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2', 'rule-3']), 'rules.ndjson') - .query() .expect(200); expect(body).to.eql({ @@ -310,14 +292,12 @@ export default ({ getService }: FtrProviderContext): void => { .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2']), 'rules.ndjson') - .query() .expect(200); await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2', 'rule-3']), 'rules.ndjson') - .query() .expect(200); const { body: bodyOfRule1 } = await supertest diff --git a/x-pack/test/functional/apps/api_keys/home_page.ts b/x-pack/test/functional/apps/api_keys/home_page.ts index 01548bc5a402d..1c83a17e78ca7 100644 --- a/x-pack/test/functional/apps/api_keys/home_page.ts +++ b/x-pack/test/functional/apps/api_keys/home_page.ts @@ -10,18 +10,31 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['common', 'apiKeys']); const log = getService('log'); + const security = getService('security'); describe('Home page', function() { this.tags('smoke'); before(async () => { + await security.testUser.setRoles(['kibana_admin']); await pageObjects.common.navigateToApp('apiKeys'); }); + after(async () => { + await security.testUser.restoreDefaults(); + }); + + // https://www.elastic.co/guide/en/kibana/7.6/api-keys.html#api-keys-security-privileges + it('Shows required privileges ', async () => { + log.debug('Checking for required privileges method section header'); + const message = await pageObjects.apiKeys.apiKeysPermissionDeniedMessage(); + expect(message).to.be('You need permission to manage API keys'); + }); + it('Loads the app', async () => { + await security.testUser.setRoles(['test_api_keys']); log.debug('Checking for section header'); - const headerText = await (await pageObjects.apiKeys.noAPIKeysHeading()).getVisibleText(); + const headerText = await pageObjects.apiKeys.noAPIKeysHeading(); expect(headerText).to.be('No API keys'); - const goToConsoleButton = await pageObjects.apiKeys.getGoToConsoleButton(); expect(await goToConsoleButton.isDisplayed()).to.be(true); }); diff --git a/x-pack/test/functional/apps/maps/index.js b/x-pack/test/functional/apps/maps/index.js index 58c211724b287..c9e39011fda35 100644 --- a/x-pack/test/functional/apps/maps/index.js +++ b/x-pack/test/functional/apps/maps/index.js @@ -31,6 +31,7 @@ export default function({ loadTestFile, getService }) { this.tags('ciGroup7'); loadTestFile(require.resolve('./documents_source')); loadTestFile(require.resolve('./blended_vector_layer')); + loadTestFile(require.resolve('./vector_styling')); loadTestFile(require.resolve('./saved_object_management')); loadTestFile(require.resolve('./sample_data')); loadTestFile(require.resolve('./feature_controls/maps_security')); diff --git a/x-pack/test/functional/apps/maps/vector_styling.js b/x-pack/test/functional/apps/maps/vector_styling.js new file mode 100644 index 0000000000000..78898f1d1a4d0 --- /dev/null +++ b/x-pack/test/functional/apps/maps/vector_styling.js @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +export default function({ getPageObjects }) { + const PageObjects = getPageObjects(['maps']); + + describe('vector styling', () => { + before(async () => { + await PageObjects.maps.loadSavedMap('document example'); + }); + + describe('categorical styling', () => { + before(async () => { + await PageObjects.maps.openLayerPanel('logstash'); + }); + + it('should provide auto complete suggestions', async () => { + await PageObjects.maps.setStyleByValue('fillColor', 'machine.os.raw'); + await PageObjects.maps.selectCustomColorRamp('fillColor'); + const suggestions = await PageObjects.maps.getCategorySuggestions(); + expect( + suggestions + .trim() + .split('\n') + .join() + ).to.equal('win 8,win xp,win 7,ios,osx'); + }); + }); + }); +} diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index 1586908d8b5ef..cff555feace18 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -233,6 +233,21 @@ export default async function({ readConfigFile }) { }, kibana: [], }, + + //Kibana feature privilege isn't specific to advancedSetting. It can be anything. https://github.com/elastic/kibana/issues/35965 + test_api_keys: { + elasticsearch: { + cluster: ['manage_security', 'manage_api_key'], + }, + kibana: [ + { + feature: { + advancedSettings: ['read'], + }, + spaces: ['default'], + }, + ], + }, }, defaultRoles: ['superuser'], }, diff --git a/x-pack/test/functional/es_archives/endpoint/alerts/api_feature/data.json.gz b/x-pack/test/functional/es_archives/endpoint/alerts/api_feature/data.json.gz index 05fc7d79faf46..c1a3c44cb8d8d 100644 Binary files a/x-pack/test/functional/es_archives/endpoint/alerts/api_feature/data.json.gz and b/x-pack/test/functional/es_archives/endpoint/alerts/api_feature/data.json.gz differ diff --git a/x-pack/test/functional/page_objects/api_keys_page.ts b/x-pack/test/functional/page_objects/api_keys_page.ts index 1ff70a0c1ee02..17f4df74921bc 100644 --- a/x-pack/test/functional/page_objects/api_keys_page.ts +++ b/x-pack/test/functional/page_objects/api_keys_page.ts @@ -11,10 +11,15 @@ export function ApiKeysPageProvider({ getService }: FtrProviderContext) { return { async noAPIKeysHeading() { - return await testSubjects.find('noApiKeysHeader'); + return await testSubjects.getVisibleText('noApiKeysHeader'); }, + async getGoToConsoleButton() { return await testSubjects.find('goToConsoleButton'); }, + + async apiKeysPermissionDeniedMessage() { + return await testSubjects.getVisibleText('apiKeysPermissionDeniedMessage'); + }, }; } diff --git a/x-pack/test/functional/page_objects/gis_page.js b/x-pack/test/functional/page_objects/gis_page.js index ae037c3954a06..f8d1808c1ef8d 100644 --- a/x-pack/test/functional/page_objects/gis_page.js +++ b/x-pack/test/functional/page_objects/gis_page.js @@ -638,6 +638,22 @@ export function GisPageProvider({ getService, getPageObjects }) { } }); } + + async setStyleByValue(styleName, fieldName) { + await testSubjects.selectValue(`staticDynamicSelect_${styleName}`, 'DYNAMIC'); + await comboBox.set(`styleFieldSelect_${styleName}`, fieldName); + } + + async selectCustomColorRamp(styleName) { + // open super select menu + await testSubjects.click(`colorMapSelect_${styleName}`); + // Click option + await testSubjects.click(`colorMapSelectOption_CUSTOM_COLOR_MAP`); + } + + async getCategorySuggestions() { + return await comboBox.getOptionsList(`colorStopInput1`); + } } return new GisPage(); } diff --git a/x-pack/test/functional/services/machine_learning/job_wizard_common.ts b/x-pack/test/functional/services/machine_learning/job_wizard_common.ts index 70d576a3d847b..36181b66786d5 100644 --- a/x-pack/test/functional/services/machine_learning/job_wizard_common.ts +++ b/x-pack/test/functional/services/machine_learning/job_wizard_common.ts @@ -34,23 +34,23 @@ export function MachineLearningJobWizardCommonProvider( }, async assertTimeRangeSectionExists() { - await testSubjects.existOrFail('mlJobWizardStepTitleTimeRange'); + await testSubjects.existOrFail('mlJobWizardStepTitleTimeRange', { timeout: 5000 }); }, async assertPickFieldsSectionExists() { - await testSubjects.existOrFail('mlJobWizardStepTitlePickFields'); + await testSubjects.existOrFail('mlJobWizardStepTitlePickFields', { timeout: 5000 }); }, async assertJobDetailsSectionExists() { - await testSubjects.existOrFail('mlJobWizardStepTitleJobDetails'); + await testSubjects.existOrFail('mlJobWizardStepTitleJobDetails', { timeout: 5000 }); }, async assertValidationSectionExists() { - await testSubjects.existOrFail('mlJobWizardStepTitleValidation'); + await testSubjects.existOrFail('mlJobWizardStepTitleValidation', { timeout: 5000 }); }, async assertSummarySectionExists() { - await testSubjects.existOrFail('mlJobWizardStepTitleSummary'); + await testSubjects.existOrFail('mlJobWizardStepTitleSummary', { timeout: 5000 }); }, async assertConfigureDatafeedSectionExists() { @@ -58,23 +58,31 @@ export function MachineLearningJobWizardCommonProvider( }, async advanceToPickFieldsSection() { - await this.clickNextButton(); - await this.assertPickFieldsSectionExists(); + await retry.tryForTime(15 * 1000, async () => { + await this.clickNextButton(); + await this.assertPickFieldsSectionExists(); + }); }, async advanceToJobDetailsSection() { - await this.clickNextButton(); - await this.assertJobDetailsSectionExists(); + await retry.tryForTime(15 * 1000, async () => { + await this.clickNextButton(); + await this.assertJobDetailsSectionExists(); + }); }, async advanceToValidationSection() { - await this.clickNextButton(); - await this.assertValidationSectionExists(); + await retry.tryForTime(15 * 1000, async () => { + await this.clickNextButton(); + await this.assertValidationSectionExists(); + }); }, async advanceToSummarySection() { - await this.clickNextButton(); - await this.assertSummarySectionExists(); + await retry.tryForTime(15 * 1000, async () => { + await this.clickNextButton(); + await this.assertSummarySectionExists(); + }); }, async assertEventRateChartExists() { diff --git a/x-pack/test/licensing_plugin/legacy/updates.ts b/x-pack/test/licensing_plugin/legacy/updates.ts index efd5df5d14511..5fa1299d1f285 100644 --- a/x-pack/test/licensing_plugin/legacy/updates.ts +++ b/x-pack/test/licensing_plugin/legacy/updates.ts @@ -27,7 +27,7 @@ export default function(ftrContext: FtrProviderContext) { const { body: legacyInitialLicense, - headers: legacyInitialLicenseHeaders, + header: legacyInitialLicenseHeaders, } = await supertest.get('/api/xpack/v1/info').expect(200); expect(legacyInitialLicense.license?.type).to.be('basic'); @@ -37,7 +37,7 @@ export default function(ftrContext: FtrProviderContext) { await scenario.startTrial(); await scenario.waitForPluginToDetectLicenseUpdate(); - const { body: legacyTrialLicense, headers: legacyTrialLicenseHeaders } = await supertest + const { body: legacyTrialLicense, header: legacyTrialLicenseHeaders } = await supertest .get('/api/xpack/v1/info') .expect(200); @@ -50,7 +50,7 @@ export default function(ftrContext: FtrProviderContext) { await scenario.startBasic(); await scenario.waitForPluginToDetectLicenseUpdate(); - const { body: legacyBasicLicense, headers: legacyBasicLicenseHeaders } = await supertest + const { body: legacyBasicLicense, header: legacyBasicLicenseHeaders } = await supertest .get('/api/xpack/v1/info') .expect(200); expect(legacyBasicLicense.license?.type).to.be('basic'); diff --git a/x-pack/test/siem_cypress/es_archives/custom_rules/data.json.gz b/x-pack/test/siem_cypress/es_archives/custom_rules/data.json.gz new file mode 100644 index 0000000000000..8f31a1a469053 Binary files /dev/null and b/x-pack/test/siem_cypress/es_archives/custom_rules/data.json.gz differ diff --git a/x-pack/test/siem_cypress/es_archives/custom_rules/mappings.json b/x-pack/test/siem_cypress/es_archives/custom_rules/mappings.json new file mode 100644 index 0000000000000..ee72812359ae2 --- /dev/null +++ b/x-pack/test/siem_cypress/es_archives/custom_rules/mappings.json @@ -0,0 +1,5701 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "agent_configs": "38abaf89513877745c359e7700c0c66a", + "agent_events": "3231653fafe4ef3196fe3b32ab774bf2", + "agents": "75c0f4a11560dbc38b65e5e1d98fc9da", + "alert": "7b44fba6773e37c806ce290ea9b7024e", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "e8619030e08b671291af04c4603b4944", + "application_usage_totals": "c897e4310c5f24b07caaff3db53ae2c1", + "application_usage_transactional": "965839e75f809fefe04f92dc4d99722a", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "cases": "08b8b110dbca273d37e8aef131ecab61", + "cases-comments": "df3c1aa1b3dd5737c94d9e430b13c48a", + "cases-configure": "42711cbb311976c0687853f4c1354572", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "ae24d22d5986d04124cc6568f771066f", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "datasources": "d4bc0c252b2b5683ff21ea32d00acffc", + "enrollment_api_keys": "28b91e20b105b6f928e2012600085d8f", + "epm-package": "75d12cd13c867fd713d7dfb27366bc20", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "infrastructure-ui-source": "ddc0ecb18383f6b26101a2fadb2dab0c", + "inventory-view": "9ecce5b58867403613d82fe496470b34", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "21c3ea0763beb1ecb0162529706b88c5", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "23d7aa4a720d4938ccde3983f87bd58d", + "maps-telemetry": "268da3a48066123fc5baf35abaa55014", + "metrics-explorer-view": "53c5365793677328df0ccb6138bf3cdd", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "outputs": "aee9782e0d500b867859650a36280165", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "181661168bbadd1eff5902361e2a0d5c", + "server": "ec97f1c5da1a19609a60874e5af1100c", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", + "siem-ui-timeline": "ac8020190f5950dd3250b6499144e7fb", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "a53a20fe086b72c9a86da3cc12dad8a6", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "b6289473c8985c79b6c47eebc19a0ca5", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "52d7a13ad68a150c4525b292d23e12cc" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "agent_configs": { + "properties": { + "datasources": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "text" + }, + "namespace": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "status": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "updated_on": { + "type": "keyword" + } + } + }, + "agent_events": { + "properties": { + "action_id": { + "type": "keyword" + }, + "agent_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "data": { + "type": "text" + }, + "message": { + "type": "text" + }, + "payload": { + "type": "text" + }, + "stream_id": { + "type": "keyword" + }, + "subtype": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "agents": { + "properties": { + "access_api_key_id": { + "type": "keyword" + }, + "actions": { + "properties": { + "created_at": { + "type": "date" + }, + "data": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "active": { + "type": "boolean" + }, + "config_id": { + "type": "keyword" + }, + "config_newest_revision": { + "type": "integer" + }, + "config_revision": { + "type": "integer" + }, + "current_error_events": { + "type": "text" + }, + "default_api_key": { + "type": "keyword" + }, + "enrolled_at": { + "type": "date" + }, + "last_checkin": { + "type": "date" + }, + "last_updated": { + "type": "date" + }, + "local_metadata": { + "type": "text" + }, + "shared_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "user_provided_metadata": { + "type": "text" + }, + "version": { + "type": "keyword" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-telemetry": { + "properties": { + "agents": { + "properties": { + "dotnet": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "ignore_above": 256, + "type": "keyword" + }, + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "name": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "ignore_above": 256, + "type": "keyword" + }, + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + } + } + } + } + }, + "go": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "ignore_above": 256, + "type": "keyword" + }, + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "ignore_above": 256, + "type": "keyword" + }, + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "ignore_above": 256, + "type": "keyword" + }, + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + } + } + } + } + }, + "java": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "ignore_above": 256, + "type": "keyword" + }, + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "ignore_above": 256, + "type": "keyword" + }, + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "ignore_above": 256, + "type": "keyword" + }, + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + } + } + } + } + }, + "js-base": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "ignore_above": 256, + "type": "keyword" + }, + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "ignore_above": 256, + "type": "keyword" + }, + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "ignore_above": 256, + "type": "keyword" + }, + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + } + } + } + } + }, + "nodejs": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "ignore_above": 256, + "type": "keyword" + }, + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "ignore_above": 256, + "type": "keyword" + }, + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "ignore_above": 256, + "type": "keyword" + }, + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + } + } + } + } + }, + "python": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "ignore_above": 256, + "type": "keyword" + }, + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "ignore_above": 256, + "type": "keyword" + }, + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "ignore_above": 256, + "type": "keyword" + }, + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + } + } + } + } + }, + "ruby": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "ignore_above": 256, + "type": "keyword" + }, + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "ignore_above": 256, + "type": "keyword" + }, + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "ignore_above": 256, + "type": "keyword" + }, + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + } + } + } + } + }, + "rum-js": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "ignore_above": 256, + "type": "keyword" + }, + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "ignore_above": 256, + "type": "keyword" + }, + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "ignore_above": 256, + "type": "keyword" + }, + "version": { + "ignore_above": 256, + "type": "keyword" + } + } + } + } + } + } + } + } + }, + "cardinality": { + "properties": { + "transaction": { + "properties": { + "name": { + "properties": { + "all_agents": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "rum": { + "properties": { + "1d": { + "type": "long" + } + } + } + } + } + } + }, + "user_agent": { + "properties": { + "original": { + "properties": { + "all_agents": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "rum": { + "properties": { + "1d": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "counts": { + "properties": { + "agent_configuration": { + "properties": { + "all": { + "type": "long" + } + } + }, + "error": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "max_error_groups_per_service": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "max_transaction_groups_per_service": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "onboarding": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "services": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "sourcemap": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "span": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "traces": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "transaction": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + } + } + }, + "has_any_services": { + "type": "boolean" + }, + "indices": { + "properties": { + "all": { + "properties": { + "total": { + "properties": { + "docs": { + "properties": { + "count": { + "type": "long" + } + } + }, + "store": { + "properties": { + "size_in_bytes": { + "type": "long" + } + } + } + } + } + } + }, + "shards": { + "properties": { + "total": { + "type": "long" + } + } + } + } + }, + "integrations": { + "properties": { + "ml": { + "properties": { + "all_jobs_count": { + "type": "long" + } + } + } + } + }, + "retainment": { + "properties": { + "error": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "onboarding": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "span": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "transaction": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "services_per_agent": { + "properties": { + "dotnet": { + "null_value": 0, + "type": "long" + }, + "go": { + "null_value": 0, + "type": "long" + }, + "java": { + "null_value": 0, + "type": "long" + }, + "js-base": { + "null_value": 0, + "type": "long" + }, + "nodejs": { + "null_value": 0, + "type": "long" + }, + "python": { + "null_value": 0, + "type": "long" + }, + "ruby": { + "null_value": 0, + "type": "long" + }, + "rum-js": { + "null_value": 0, + "type": "long" + } + } + }, + "tasks": { + "properties": { + "agent_configuration": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "agents": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "cardinality": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "groupings": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "indices_stats": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "integrations": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "processor_events": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "services": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "versions": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + }, + "version": { + "properties": { + "apm_server": { + "properties": { + "major": { + "type": "long" + }, + "minor": { + "type": "long" + }, + "patch": { + "type": "long" + } + } + } + } + } + } + }, + "application_usage_totals": { + "properties": { + "appId": { + "type": "keyword" + }, + "minutesOnScreen": { + "type": "float" + }, + "numberOfClicks": { + "type": "long" + } + } + }, + "application_usage_transactional": { + "properties": { + "appId": { + "type": "keyword" + }, + "minutesOnScreen": { + "type": "float" + }, + "numberOfClicks": { + "type": "long" + }, + "timestamp": { + "type": "date" + } + } + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "properties": { + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-user-actions": { + "properties": { + "action": { + "type": "keyword" + }, + "action_at": { + "type": "date" + }, + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { + "type": "text" + } + } + }, + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + }, + "dateFormat:tz": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "datasources": { + "properties": { + "config_id": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "properties": { + "config": { + "type": "flattened" + }, + "enabled": { + "type": "boolean" + }, + "processors": { + "type": "keyword" + }, + "streams": { + "properties": { + "config": { + "type": "flattened" + }, + "dataset": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "processors": { + "type": "keyword" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "revision": { + "type": "integer" + } + } + }, + "enrollment_api_keys": { + "properties": { + "active": { + "type": "boolean" + }, + "api_key": { + "type": "binary" + }, + "api_key_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "expire_at": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "epm-package": { + "properties": { + "installed": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "name": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "properties": { + "description": { + "type": "text" + }, + "fields": { + "properties": { + "container": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "pod": { + "type": "keyword" + }, + "tiebreaker": { + "type": "keyword" + }, + "timestamp": { + "type": "keyword" + } + } + }, + "logAlias": { + "type": "keyword" + }, + "logColumns": { + "properties": { + "fieldColumn": { + "properties": { + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + } + } + }, + "messageColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "timestampColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + } + }, + "type": "nested" + }, + "metricAlias": { + "type": "keyword" + }, + "name": { + "type": "text" + } + } + }, + "inventory-view": { + "properties": { + "autoBounds": { + "type": "boolean" + }, + "autoReload": { + "type": "boolean" + }, + "boundsOverride": { + "properties": { + "max": { + "type": "integer" + }, + "min": { + "type": "integer" + } + } + }, + "customMetrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "customOptions": { + "properties": { + "field": { + "type": "keyword" + }, + "text": { + "type": "keyword" + } + }, + "type": "nested" + }, + "filterQuery": { + "properties": { + "expression": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "groupBy": { + "properties": { + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "metric": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "nodeType": { + "type": "keyword" + }, + "time": { + "type": "integer" + }, + "view": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "expression": { + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "bounds": { + "type": "geo_shape" + }, + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "properties": { + "attributesPerMap": { + "properties": { + "dataSourcesCount": { + "properties": { + "avg": { + "type": "long" + }, + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + }, + "emsVectorLayersCount": { + "dynamic": "true", + "type": "object" + }, + "layerTypesCount": { + "dynamic": "true", + "type": "object" + }, + "layersCount": { + "properties": { + "avg": { + "type": "long" + }, + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + } + } + }, + "indexPatternsWithGeoFieldCount": { + "type": "long" + }, + "mapsTotalCount": { + "type": "long" + }, + "settings": { + "properties": { + "showMapVisualizationTypes": { + "type": "boolean" + } + } + }, + "timeCaptured": { + "type": "date" + } + } + }, + "metrics-explorer-view": { + "properties": { + "chartOptions": { + "properties": { + "stack": { + "type": "boolean" + }, + "type": { + "type": "keyword" + }, + "yAxisMode": { + "type": "keyword" + } + } + }, + "currentTimerange": { + "properties": { + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "to": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "options": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "filterQuery": { + "type": "keyword" + }, + "groupBy": { + "type": "keyword" + }, + "limit": { + "type": "integer" + }, + "metrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "color": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + } + } + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "namespace": { + "type": "keyword" + }, + "outputs": { + "properties": { + "api_key": { + "type": "keyword" + }, + "ca_sha256": { + "type": "keyword" + }, + "config": { + "type": "flattened" + }, + "fleet_enroll_password": { + "type": "binary" + }, + "fleet_enroll_username": { + "type": "binary" + }, + "hosts": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "bulkCreateTimeDurations": { + "type": "float" + }, + "gap": { + "type": "text" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastLookBackDate": { + "type": "date" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "searchAfterTimeDurations": { + "type": "float" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "eventType": { + "type": "keyword" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } + } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" + } + } + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "savedQueryId": { + "type": "keyword" + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "spaceId": { + "type": "keyword" + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "dynamic": "true", + "properties": { + "indexName": { + "type": "keyword" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "uptime-dynamic-settings": { + "properties": { + "heartbeatIndices": { + "type": "keyword" + } + } + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchRefName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + ".siem-signals-default": { + "is_write_index": true + } + }, + "index": ".siem-signals-default-000001", + "mappings": { + "dynamic": "false", + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "client": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cloud": { + "properties": { + "account": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "availability_zone": { + "ignore_above": 1024, + "type": "keyword" + }, + "instance": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "machine": { + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "region": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "container": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "tag": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "runtime": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dns": { + "properties": { + "answers": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "ttl": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "header_flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "op_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "question": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "resolved_ip": { + "type": "ip" + }, + "response_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "norms": false, + "type": "text" + }, + "stack_trace": { + "doc_values": false, + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "doc_values": false, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "file": { + "properties": { + "accessed": { + "type": "date" + }, + "attributes": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "directory": { + "ignore_above": 1024, + "type": "keyword" + }, + "drive_letter": { + "ignore_above": 1, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "target_path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "http": { + "properties": { + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "referrer": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "status_code": { + "type": "long" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "log": { + "properties": { + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "properties": { + "file": { + "properties": { + "line": { + "type": "integer" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "original": { + "doc_values": false, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "syslog": { + "properties": { + "facility": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "priority": { + "type": "long" + }, + "severity": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "network": { + "properties": { + "application": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "community_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "forwarded_ip": { + "type": "ip" + }, + "iana_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "observer": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "organization": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "package": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "build_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "checksum": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "install_scope": { + "ignore_above": 1024, + "type": "keyword" + }, + "installed": { + "type": "date" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "registry": { + "properties": { + "data": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "strings": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hive": { + "ignore_above": 1024, + "type": "keyword" + }, + "key": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "related": { + "properties": { + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "rule": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "ruleset": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "server": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "service": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "signal": { + "properties": { + "ancestors": { + "properties": { + "depth": { + "type": "long" + }, + "id": { + "type": "keyword" + }, + "rule": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "original_event": { + "properties": { + "action": { + "type": "keyword" + }, + "category": { + "type": "keyword" + }, + "code": { + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + }, + "module": { + "type": "keyword" + }, + "original": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "outcome": { + "type": "keyword" + }, + "provider": { + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "original_time": { + "type": "date" + }, + "parent": { + "properties": { + "depth": { + "type": "long" + }, + "id": { + "type": "keyword" + }, + "index": { + "type": "keyword" + }, + "rule": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "rule": { + "properties": { + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "enabled": { + "type": "keyword" + }, + "false_positives": { + "type": "keyword" + }, + "filters": { + "type": "object" + }, + "from": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "immutable": { + "type": "keyword" + }, + "index": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "language": { + "type": "keyword" + }, + "max_signals": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "output_index": { + "type": "keyword" + }, + "query": { + "type": "keyword" + }, + "references": { + "type": "keyword" + }, + "risk_score": { + "type": "keyword" + }, + "rule_id": { + "type": "keyword" + }, + "saved_id": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + }, + "size": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "threat": { + "properties": { + "framework": { + "type": "keyword" + }, + "tactic": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "reference": { + "type": "keyword" + } + } + }, + "technique": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "reference": { + "type": "keyword" + } + } + } + } + }, + "timeline_id": { + "type": "keyword" + }, + "timeline_title": { + "type": "keyword" + }, + "to": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" + } + } + }, + "source": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "threat": { + "properties": { + "framework": { + "ignore_above": 1024, + "type": "keyword" + }, + "tactic": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "technique": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "tls": { + "properties": { + "cipher": { + "ignore_above": 1024, + "type": "keyword" + }, + "client": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "server_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + }, + "supported_ciphers": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "established": { + "type": "boolean" + }, + "next_protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "resumed": { + "type": "boolean" + }, + "server": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3s": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + }, + "version_protocol": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "trace": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "transaction": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "url": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "fragment": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user_agent": { + "properties": { + "device": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vulnerability": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "classification": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "enumeration": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "report_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "scanner": { + "properties": { + "vendor": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "score": { + "properties": { + "base": { + "type": "float" + }, + "environmental": { + "type": "float" + }, + "temporal": { + "type": "float" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "severity": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "lifecycle": { + "name": ".siem-signals-default", + "rollover_alias": ".siem-signals-default" + }, + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index d6ffd77e9a296..b5e72e07f1efe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1197,10 +1197,10 @@ dependencies: "@elastic/apm-rum-core" "^4.7.0" -"@elastic/charts@^18.1.0": - version "18.1.0" - resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-18.1.0.tgz#ee98b3e7680239d79807c09f0ee6efa6c1afa84b" - integrity sha512-cUobkGiKHPKHNytxsO7sSJyH6fOcAsZvcxAfnJPBn143P2d5oEsjSJmJTqu0z/obCs2v476Xlg6POVGainUj1Q== +"@elastic/charts@^18.1.1": + version "18.2.0" + resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-18.2.0.tgz#e141151b4d7ecc71c9f6f235f8ce141665c67195" + integrity sha512-OWsARaHI/4Ict/GkeKIO3a+e2c86esGw3FtSGRLPFVgzpwBXdjvjYyraGntKOIVs/NAGNVWYj5XoRRb5C6cMlQ== dependencies: classnames "^2.2.6" d3-array "^1.2.4" @@ -3930,6 +3930,15 @@ "@types/vinyl-fs" "*" chokidar "^2.1.2" +"@types/h2o2@^8.1.1": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@types/h2o2/-/h2o2-8.1.1.tgz#f990302cd2fdfd7909cff9d6643052002b69998f" + integrity sha512-lwF9WSvo4sfT0TnDZDXKef9Yza4xUXC3561QG4Q3Axhrkr+ZFBVJ7kCwI1mUNzk60jI1aMTYVIIoHKZjwCGuHw== + dependencies: + "@types/boom" "*" + "@types/hapi" "*" + "@types/node" "*" + "@types/hapi-auth-cookie@^9.1.0": version "9.1.0" resolved "https://registry.yarnpkg.com/@types/hapi-auth-cookie/-/hapi-auth-cookie-9.1.0.tgz#cbcd2236b7d429bd0632a8cc45cfd355fdd7e7a2" @@ -3997,6 +4006,13 @@ resolved "https://registry.yarnpkg.com/@types/indent-string/-/indent-string-3.0.0.tgz#9ebb391ceda548926f5819ad16405349641b999f" integrity sha1-nrs5HO2lSJJvWBmtFkBTSWQbmZ8= +"@types/inert@^5.1.2": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@types/inert/-/inert-5.1.2.tgz#2bb8bef3b2462f904c960654c9edfa39285a85c6" + integrity sha512-3IoSFLQWvhLfZ85kHas/F3iD/TyZPfeJbTsDjrwYljK1MgBGCB2OywAsyeA/YiJ62VbNXfXBwpD1/VbJPIZSGA== + dependencies: + "@types/hapi" "*" + "@types/intl-relativeformat@^2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/intl-relativeformat/-/intl-relativeformat-2.1.0.tgz#3a2b0043380388f39c666665ec517e11412f1358" @@ -11552,12 +11568,12 @@ elastic-apm-node@^3.2.0: traceparent "^1.0.0" unicode-byte-truncate "^1.0.0" -elasticsearch-browser@^16.5.0: - version "16.5.0" - resolved "https://registry.yarnpkg.com/elasticsearch-browser/-/elasticsearch-browser-16.5.0.tgz#d2efbbf8751bb563e91b74117a14b9211df5cfe9" - integrity sha512-F7npcrmMi3OgQ4fL+7sYLZp5Y9MS6WobVdCA18d9/Eef06x3+UzVE+pRaM71y4/i0N5rt/QrvmijHt25wz5SLw== +elasticsearch-browser@^16.7.0: + version "16.7.0" + resolved "https://registry.yarnpkg.com/elasticsearch-browser/-/elasticsearch-browser-16.7.0.tgz#1f32a402cd36a9bb14a9ea6cb70f8e126d4cb9b1" + integrity sha512-UES2Fbnzy4Ivq4QvES4sfk/a5UytJczeJdfxRWa4kuHEllKOffKQLTxJ8Ti86OREpACQxppqvYgzctJuEiIr7Q== -elasticsearch@^16.4.0, elasticsearch@^16.5.0: +elasticsearch@^16.4.0: version "16.5.0" resolved "https://registry.yarnpkg.com/elasticsearch/-/elasticsearch-16.5.0.tgz#619a48040be25d345fdddf09fa6042a88c3974d6" integrity sha512-9YbmU2AtM/kQdmp96EI5nu2bjxowdarV6IsKmcS+jQowJ3mhG98J1DCVOtEKuFvsnNaLyKD3aPbCAmb72+WX3w== @@ -11566,6 +11582,15 @@ elasticsearch@^16.4.0, elasticsearch@^16.5.0: chalk "^1.0.0" lodash "^4.17.10" +elasticsearch@^16.7.0: + version "16.7.0" + resolved "https://registry.yarnpkg.com/elasticsearch/-/elasticsearch-16.7.0.tgz#9055e3f586934d8de5fd407b04050e9d54173333" + integrity sha512-du+//TbjCFEkaG0jNcAC95Fp4B6/X5shnCRIXALFL+M4U5iT3YL5ZVUPNf1NgR7dy/sc8Dvw2Ob6IUJKB7FrCw== + dependencies: + agentkeepalive "^3.4.1" + chalk "^1.0.0" + lodash "^4.17.10" + electron-to-chromium@^1.3.191, electron-to-chromium@^1.3.338: version "1.3.340" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.340.tgz#5d4fe78e984d4211194cf5a52e08069543da146f" @@ -30416,9 +30441,9 @@ vega-typings@*, vega-typings@^0.3.17: vega-util "^1.7.0" vega-util@^1.7.0: - version "1.7.1" - resolved "https://registry.yarnpkg.com/vega-util/-/vega-util-1.7.1.tgz#0b95bbe6058895c332596c215247507caa68ab61" - integrity sha512-jdzigLdaXH0rClqkr/qHY//xvmLyxQyZL4Wxb3mew29QpITrMk/USV6v/399h29xVt1+hJuw1vpLoJqAq6WerA== + version "1.13.1" + resolved "https://registry.yarnpkg.com/vega-util/-/vega-util-1.13.1.tgz#3eae51043184c6b873c17b148755c21b01274a0e" + integrity sha512-TmvZSMKqhGlS7eAXphqJUhq+NZVYbvXX2ahargTRkVckGWjEUpWhMC7T13vYihrU2Lf/OevKbrruSXKOBxke2w== vega-view-transforms@^2.0.3: version "2.0.3"