diff --git a/.eslintignore b/.eslintignore index e5b17567b562cf..c4fb806b6d394c 100644 --- a/.eslintignore +++ b/.eslintignore @@ -16,7 +16,7 @@ src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/moc /src/legacy/core_plugins/console/public/webpackShims /src/legacy/core_plugins/console/public/tests/webpackShims /src/legacy/ui/public/utils/decode_geo_hash.js -/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.* +/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.* /src/core/lib/kbn_internal_native_observable /packages/*/target /packages/eslint-config-kibana diff --git a/.github/workflows/pr-project-assigner.yml b/.github/workflows/pr-project-assigner.yml index 59123731dce666..708c9efea404ba 100644 --- a/.github/workflows/pr-project-assigner.yml +++ b/.github/workflows/pr-project-assigner.yml @@ -15,7 +15,6 @@ jobs: [ { "label": "Team:AppArch", "projectName": "kibana-app-arch", "columnId": 6173897 }, { "label": "Feature:Lens", "projectName": "Lens", "columnId": 6219362 }, - { "label": "Team:Platform", "projectName": "kibana-platform", "columnId": 5514360 }, - {"label": "Team:Canvas", "projectName": "canvas", "columnId": 6187580} + { "label": "Team:Canvas", "projectName": "canvas", "columnId": 6187580 } ] ghToken: ${{ secrets.GITHUB_TOKEN }} diff --git a/.i18nrc.json b/.i18nrc.json index 6986d36e8e94f5..73acf92cda1491 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -33,7 +33,7 @@ "statusPage": "src/legacy/core_plugins/status_page", "telemetry": "src/legacy/core_plugins/telemetry", "tileMap": "src/legacy/core_plugins/tile_map", - "timelion": "src/legacy/core_plugins/timelion", + "timelion": ["src/legacy/core_plugins/timelion", "src/legacy/core_plugins/vis_type_timelion"], "uiActions": "src/plugins/ui_actions", "visTypeMarkdown": "src/legacy/core_plugins/vis_type_markdown", "visTypeMetric": "src/legacy/core_plugins/vis_type_metric", diff --git a/docs/canvas/canvas-share-workpad.asciidoc b/docs/canvas/canvas-share-workpad.asciidoc index 412019efc7f353..c46ba8a980ce22 100644 --- a/docs/canvas/canvas-share-workpad.asciidoc +++ b/docs/canvas/canvas-share-workpad.asciidoc @@ -23,9 +23,9 @@ Want to export multiple workpads? Go to the *Canvas workpads* view, select the w [[create-workpad-pdf]] === Create a PDF -Create a PDF copy of your workpad that you can save and share outside of {kib}. +If you have a license that supports the {report-features}, you can create a PDF copy of your workpad that you can save and share outside {kib}. -. If you are using a Gold or Platinum license, enable reporting in your `config/kibana.yml` file. +For more information, refer to <>. . From your workpad, click the *Share workpad* icon in the upper left corner, then select *PDF reports*. @@ -38,12 +38,10 @@ image::images/canvas-generate-pdf.gif[Generate PDF] [[create-workpad-URL]] === Create a POST URL -Create a POST URL that you can use to automatically generate PDF reports using Watcher or a script. +If you have a license that supports the {report-features}, you can create a POST URL that you can use to automatically generate PDF reports using Watcher or a script. For more information, refer to <>. -. If you are using a Gold or Platinum license, enable reporting in your `config/kibana.yml` file. - . From your workpad, click the *Share workpad* icon in the upper left corner, then select *PDF reports*. . Click *Copy POST URL*. @@ -57,8 +55,6 @@ image::images/canvas-create-URL.gif[Create POST URL] beta[] Canvas allows you to create _shareables_, which are workpads that you download and securely share on any website. To customize the behavior of the workpad on your website, you can choose to autoplay the pages or hide the workpad toolbar. -. If you are using a Gold or Platinum license, enable reporting in your `config/kibana.yml` file. - . From your workpad, click the *Share this workpad* icon in the upper left corner, then select *Share on a website*. . On the *Share on a website* pane, follow the instructions. diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlinks.update.md b/docs/development/core/public/kibana-plugin-public.chromenavlinks.update.md index 2deb9f4a9a1512..d1cd2d3b049501 100644 --- a/docs/development/core/public/kibana-plugin-public.chromenavlinks.update.md +++ b/docs/development/core/public/kibana-plugin-public.chromenavlinks.update.md @@ -1,25 +1,30 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLinks](./kibana-plugin-public.chromenavlinks.md) > [update](./kibana-plugin-public.chromenavlinks.update.md) - -## ChromeNavLinks.update() method - -Update the navlink for the given id with the updated attributes. Returns the updated navlink or `undefined` if it does not exist. - -Signature: - -```typescript -update(id: string, values: ChromeNavLinkUpdateableFields): ChromeNavLink | undefined; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| id | string | | -| values | ChromeNavLinkUpdateableFields | | - -Returns: - -`ChromeNavLink | undefined` - + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLinks](./kibana-plugin-public.chromenavlinks.md) > [update](./kibana-plugin-public.chromenavlinks.update.md) + +## ChromeNavLinks.update() method + +> Warning: This API is now obsolete. +> +> Uses the [AppBase.updater$](./kibana-plugin-public.appbase.updater_.md) property when registering your application with [ApplicationSetup.register()](./kibana-plugin-public.applicationsetup.register.md) instead. +> + +Update the navlink for the given id with the updated attributes. Returns the updated navlink or `undefined` if it does not exist. + +Signature: + +```typescript +update(id: string, values: ChromeNavLinkUpdateableFields): ChromeNavLink | undefined; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| id | string | | +| values | ChromeNavLinkUpdateableFields | | + +Returns: + +`ChromeNavLink | undefined` + diff --git a/docs/discover/document-data.asciidoc b/docs/discover/document-data.asciidoc index b45a31065aa9a6..6e9218d66c1154 100644 --- a/docs/discover/document-data.asciidoc +++ b/docs/discover/document-data.asciidoc @@ -15,7 +15,7 @@ tailor the documents table to suit your needs. [horizontal] Add a field column:: -Hover over the list of *Available fields* and then click *add* next to each field you want include as a column in the table. +Hover over the list of *Available fields* and then click *add* next to each field you want to include as a column in the table. The first field you add replaces the `_source` column. Change sort order:: By default, columns are sorted by the values in the field. If a time field is configured for the current index pattern, diff --git a/docs/management/managing-licenses.asciidoc b/docs/management/managing-licenses.asciidoc index 0ad27e68f7fe94..ecb550d3ab267d 100644 --- a/docs/management/managing-licenses.asciidoc +++ b/docs/management/managing-licenses.asciidoc @@ -25,9 +25,9 @@ At the end of the trial period, the platinum features operate in a <>. You can revert to a basic license, extend the trial, or purchase a subscription. -TIP: If {security-features} are enabled, before you revert to a basic license or -install a gold or platinum license, you must configure Transport Layer Security -(TLS) in {es}. See {ref}/encrypting-communications.html[Encrypting communications]. +TIP: If {security-features} are enabled, unless you have a trial license, +you must configure Transport Layer Security (TLS) in {es}. +See {ref}/encrypting-communications.html[Encrypting communications]. {kib} and the {ref}/start-basic.html[start basic API] provide a list of all of the features that will no longer be supported if you revert to a basic license. diff --git a/docs/maps/images/extended_stats_config.png b/docs/maps/images/extended_stats_config.png new file mode 100644 index 00000000000000..018acea96852f1 Binary files /dev/null and b/docs/maps/images/extended_stats_config.png differ diff --git a/docs/maps/images/gear_icon.png b/docs/maps/images/gear_icon.png new file mode 100644 index 00000000000000..355d55dbbc37a5 Binary files /dev/null and b/docs/maps/images/gear_icon.png differ diff --git a/docs/maps/images/gs_link_icon.png b/docs/maps/images/gs_link_icon.png deleted file mode 100644 index 89866484706133..00000000000000 Binary files a/docs/maps/images/gs_link_icon.png and /dev/null differ diff --git a/docs/maps/images/quantitative_data_driven_styling.png b/docs/maps/images/quantitative_data_driven_styling.png new file mode 100644 index 00000000000000..a7852ed2020167 Binary files /dev/null and b/docs/maps/images/quantitative_data_driven_styling.png differ diff --git a/docs/maps/images/vector_style_class.png b/docs/maps/images/vector_style_class.png index 48658bda73ee81..8c685dfcf0ab60 100644 Binary files a/docs/maps/images/vector_style_class.png and b/docs/maps/images/vector_style_class.png differ diff --git a/docs/maps/images/vector_style_dynamic.png b/docs/maps/images/vector_style_dynamic.png index 90d51144c422db..aeaef412b5220d 100644 Binary files a/docs/maps/images/vector_style_dynamic.png and b/docs/maps/images/vector_style_dynamic.png differ diff --git a/docs/maps/images/vector_style_static.png b/docs/maps/images/vector_style_static.png index 6eedb8247e6ba5..47d9c3b21fcb6f 100644 Binary files a/docs/maps/images/vector_style_static.png and b/docs/maps/images/vector_style_static.png differ diff --git a/docs/maps/vector-style.asciidoc b/docs/maps/vector-style.asciidoc index 609cd712e8b137..cd5b086508ae8a 100644 --- a/docs/maps/vector-style.asciidoc +++ b/docs/maps/vector-style.asciidoc @@ -5,6 +5,7 @@ When styling a vector layer, you can customize your data by property, such as size and color. For each property, you can specify whether to use a constant or data driven value for the style. + [float] [[maps-vector-style-static]] ==== Static styling @@ -17,13 +18,41 @@ The *kibana_sample_data_logs* layer uses static styling for all properties. [role="screenshot"] image::maps/images/vector_style_static.png[] + [float] [[maps-vector-style-data-driven]] ==== Data driven styling -Use data driven styling to symbolize features from a range of numeric property values. -To enable data driven styling, click image:maps/images/gs_link_icon.png[] next to the property. -This button is only available when vector features contain numeric properties. +Use data driven styling to symbolize features by property values. +To enable data driven styling for a style property, change the selected value from *Fixed* or *Solid* to *By value*. + +The image below shows an example of data driven styling using the <> data set. +The *kibana_sample_data_logs* layer uses data driven styling for fill color and symbol size style properties. + +* The `hour_of_day` property determines the fill color for each feature based on where the value fits on a linear scale. +Light green circles symbolize documents that occur earlier in the day, and dark green circles symbolize documents that occur later in the day. + +* The `bytes` property determines the size of each symbol based on where the value fits on a linear scale. +Smaller circles symbolize documents with smaller payloads, and larger circles symbolize documents with larger payloads. + +[role="screenshot"] +image::maps/images/vector_style_dynamic.png[] + + +[float] +[[maps-vector-style-quantitative-data-driven]] +==== Quantitative data driven styling + +Quantitative data driven styling symbolizes features from a range of numeric property values. + +To ensure symbols are consistent as you pan, zoom, and filter the map, quantitative data driven styling uses {ref}/search-aggregations-metrics-extendedstats-aggregation.html[extended_stats aggregation] to retrieve statistical metadata. + +Click the gear icon image:maps/images/gear_icon.png[] to configure extended_stats. Set *Sigma* to a smaller value to minimize outliers by moving the range minimum and maximum closer to the average. Clear the *Calculate range from indices* checkbox to turn off the extended_stats aggregation request. + +NOTE: When the *Calculate range from indices* checkbox is cleared, symbols might be inconsistent as users pan, zoom, and filter the map. Without extended_stats, the range is calulated with data from the local layer. The range is recalulcated when layer data changes. + +[role="screenshot"] +image::maps/images/extended_stats_config.png[] When the property value is undefined for a feature: @@ -31,22 +60,32 @@ When the property value is undefined for a feature: * *Border width* and *Symbol size* are set to the minimum size. * *Symbol orientation* is set to 0. -When the minimum and maximum are the same and there is no range: +When the symbol range minimum and maximum are the same and there is no range: * *Fill color* and *Border color* are set to last color in the color ramp. * *Border width* and *Symbol size* are set to the maximum size. -The image below shows an example of data driven styling using the <> data set. -The *kibana_sample_data_logs* layer uses data driven styling for fill color and symbol size style properties. -* The `hour_of_day` property determines the fill color for each feature based on where the value fits on a linear scale. -Light green circles symbolize documents that occur earlier in the day, and dark green circles symbolize documents that occur later in the day. +[float] +[[maps-vector-style-qualitative-data-driven]] +==== Qualitative data driven styling -* The `bytes` property determines the size of each symbol based on where the value fits on a linear scale. -Smaller circles symbolize documents with smaller payloads, and larger circles symbolize documents with larger payloads. +Qualitative data driven styling symbolizes non-numeric properties, such as strings and IP addresses, by category. + +Qualitative data driven styling is available for the following styling properties: + +* *Fill color* +* *Border color* +* *Label color* +* *Label border color* + +Qualitative data driven styling uses a {ref}/search-aggregations-bucket-terms-aggregation.html[terms aggregation] to retrieve the top nine categories for the property. Feature values within the top categories are assigned a unique color. Feature values outside of the top categories are grouped into the *Other* category. A feature is assigned the *Other* category when the property value is undefined. + +The image below shows an example of quantitative data driven styling using the <> data set. +The `machine.os.keyword` property determines the color of each symbol based on category. [role="screenshot"] -image::maps/images/vector_style_dynamic.png[] +image::maps/images/quantitative_data_driven_styling.png[] [float] diff --git a/package.json b/package.json index 9d2068b02bb934..430ab9e1ba77d7 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,7 @@ "@kbn/test-subj-selector": "0.2.1", "@kbn/ui-framework": "1.0.0", "@kbn/ui-shared-deps": "1.0.0", + "@types/flot": "^0.0.31", "@types/json-stable-stringify": "^1.0.32", "@types/lodash.clonedeep": "^4.5.4", "@types/node-forge": "^0.9.0", diff --git a/renovate.json5 b/renovate.json5 index 7f67fae8941104..5af62d0acef854 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -281,6 +281,14 @@ '@types/file-saver', ], }, + { + groupSlug: 'flot', + groupName: 'flot related packages', + packageNames: [ + 'flot', + '@types/flot', + ], + }, { groupSlug: 'getopts', groupName: 'getopts related packages', diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index 173d73ffab664c..f51afd35586bd7 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -1131,6 +1131,7 @@ import { npStart: { core } } from 'ui/new_platform'; | Legacy Platform | New Platform | Notes | | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `chrome.addBasePath` | [`core.http.basePath.prepend`](/docs/development/core/public/kibana-plugin-public.httpservicebase.basepath.md) | | +| `chrome.navLinks.update` | [`core.appbase.updater`](/docs/development/core/public/kibana-plugin-public.appbase.updater_.md) | Use the `updater$` property when registering your application via `core.application.register` | | `chrome.breadcrumbs.set` | [`core.chrome.setBreadcrumbs`](/docs/development/core/public/kibana-plugin-public.chromestart.setbreadcrumbs.md) | | | `chrome.getUiSettingsClient` | [`core.uiSettings`](/docs/development/core/public/kibana-plugin-public.uisettingsclient.md) | | | `chrome.helpExtension.set` | [`core.chrome.setHelpExtension`](/docs/development/core/public/kibana-plugin-public.chromestart.sethelpextension.md) | | diff --git a/src/core/MIGRATION_EXAMPLES.md b/src/core/MIGRATION_EXAMPLES.md index 04535039acbe70..d7964a53358efc 100644 --- a/src/core/MIGRATION_EXAMPLES.md +++ b/src/core/MIGRATION_EXAMPLES.md @@ -15,6 +15,7 @@ APIs to their New Platform equivalents. - [4. New Platform plugin](#4-new-platform-plugin) - [Accessing Services](#accessing-services) - [Chrome](#chrome) + - [Updating an application navlink](#updating-application-navlink) ## Configuration @@ -462,7 +463,59 @@ elsewhere. | `chrome.setVisible` | [`core.chrome.setIsVisible`](/docs/development/core/public/kibana-plugin-public.chromestart.setisvisible.md) | | | `chrome.getInjected` | [`core.injectedMetadata.getInjected`](/docs/development/core/public/kibana-plugin-public.coresetup.injectedmetadata.md) (temporary) | A temporary API is available to read injected vars provided by legacy plugins. This will be removed after [#41990](https://github.com/elastic/kibana/issues/41990) is completed. | | `chrome.setRootTemplate` / `chrome.setRootController` | -- | Use application mounting via `core.application.register` (not currently avaiable to legacy plugins). | +| `chrome.navLinks.update` | [`core.appbase.updater`](/docs/development/core/public/kibana-plugin-public.appbase.updater_.md) | Use the `updater$` property when registering your application via `core.application.register` | In most cases, the most convenient way to access these APIs will be via the [AppMountContext](/docs/development/core/public/kibana-plugin-public.appmountcontext.md) object passed to your application when your app is mounted on the page. + +### Updating an application navlink + +In the legacy platform, the navlink could be updated using `chrome.navLinks.update` + +```ts +uiModules.get('xpack/ml').run(() => { + const showAppLink = xpackInfo.get('features.ml.showLinks', false); + const isAvailable = xpackInfo.get('features.ml.isAvailable', false); + + const navLinkUpdates = { + // hide by default, only show once the xpackInfo is initialized + hidden: !showAppLink, + disabled: !showAppLink || (showAppLink && !isAvailable), + }; + + npStart.core.chrome.navLinks.update('ml', navLinkUpdates); +}); +``` + +In the new platform, navlinks should not be updated directly. Instead, it is now possible to add an `updater` when +registering an application to change the application or the navlink state at runtime. + +```ts +// my_plugin has a required dependencie to the `licensing` plugin +interface MyPluginSetupDeps { + licensing: LicensingPluginSetup; +} + +export class MyPlugin implements Plugin { + setup({ application }, { licensing }: MyPluginSetupDeps) { + const updater$ = licensing.license$.pipe( + map(license => { + const { hidden, disabled } = calcStatusFor(license); + if (hidden) return { navLinkStatus: AppNavLinkStatus.hidden }; + if (disabled) return { navLinkStatus: AppNavLinkStatus.disabled }; + return { navLinkStatus: AppNavLinkStatus.default }; + }) + ); + + application.register({ + id: 'my-app', + title: 'My App', + updater$, + async mount(params) { + const { renderApp } = await import('./application'); + return renderApp(params); + }, + }); + } +``` \ No newline at end of file diff --git a/src/core/public/chrome/nav_links/nav_links_service.ts b/src/core/public/chrome/nav_links/nav_links_service.ts index 650ef77b6fe42e..fec9322b0d77d7 100644 --- a/src/core/public/chrome/nav_links/nav_links_service.ts +++ b/src/core/public/chrome/nav_links/nav_links_service.ts @@ -72,6 +72,10 @@ export interface ChromeNavLinks { /** * Update the navlink for the given id with the updated attributes. * Returns the updated navlink or `undefined` if it does not exist. + * + * @deprecated Uses the {@link AppBase.updater$} property when registering + * your application with {@link ApplicationSetup.register} instead. + * * @param id * @param values */ diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 610b08708c6812..abd39e864bd302 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -279,6 +279,7 @@ export interface ChromeNavLinks { getNavLinks$(): Observable>>; has(id: string): boolean; showOnly(id: string): void; + // @deprecated update(id: string, values: ChromeNavLinkUpdateableFields): ChromeNavLink | undefined; } diff --git a/src/dev/jest/config.js b/src/dev/jest/config.js index 1c95e75396bcc2..807a3fbf4782bf 100644 --- a/src/dev/jest/config.js +++ b/src/dev/jest/config.js @@ -58,6 +58,7 @@ export default { '^ui/(.*)': '/src/legacy/ui/public/$1', '^uiExports/(.*)': '/src/dev/jest/mocks/file_mock.js', '^test_utils/(.*)': '/src/test_utils/public/$1', + '^fixtures/(.*)': '/src/fixtures/$1', '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/src/dev/jest/mocks/file_mock.js', '\\.(css|less|scss)$': '/src/dev/jest/mocks/style_mock.js', diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/input_highlight_rules.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/input_highlight_rules.js index b7b6c5bade9ad7..842736428e8bba 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/input_highlight_rules.js +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/input_highlight_rules.js @@ -89,6 +89,7 @@ export function InputHighlightRules() { addEOL(['url.amp'], /(&)/, 'start') ), 'url-sql': mergeTokens( + addEOL(['url.part'], /([^?\/,\s]+)/, 'start-sql'), addEOL(['url.comma'], /(,)/, 'start-sql'), addEOL(['url.slash'], /(\/)/, 'start-sql'), addEOL(['url.questionmark'], /(\?)/, 'start-sql', 'urlParams-sql') diff --git a/src/legacy/core_plugins/kibana/public/management/landing.html b/src/legacy/core_plugins/kibana/public/management/landing.html index a69033e4131c9e..39459b26f74156 100644 --- a/src/legacy/core_plugins/kibana/public/management/landing.html +++ b/src/legacy/core_plugins/kibana/public/management/landing.html @@ -1,3 +1,3 @@ -
+
diff --git a/src/legacy/core_plugins/timelion/public/app.js b/src/legacy/core_plugins/timelion/public/app.js index 7ef722ee3a277c..084e497761e438 100644 --- a/src/legacy/core_plugins/timelion/public/app.js +++ b/src/legacy/core_plugins/timelion/public/app.js @@ -59,8 +59,6 @@ document.title = 'Timelion - Kibana'; const app = require('ui/modules').get('apps/timelion', []); -require('./vis'); - require('ui/routes').enable(); require('ui/routes').when('/:id?', { diff --git a/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js b/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js index 231330b898edb5..ea2d44bcaefe07 100644 --- a/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js +++ b/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js @@ -19,10 +19,13 @@ import expect from '@kbn/expect'; import PEG from 'pegjs'; -import grammar from 'raw-loader!../../chain.peg'; +import grammar from 'raw-loader!../../../../vis_type_timelion/public/chain.peg'; import { SUGGESTION_TYPE, suggest } from '../timelion_expression_input_helpers'; -import { getArgValueSuggestions } from '../../services/arg_value_suggestions'; -import { setIndexPatterns, setSavedObjectsClient } from '../../services/plugin_services'; +import { getArgValueSuggestions } from '../../../../vis_type_timelion/public/helpers/arg_value_suggestions'; +import { + setIndexPatterns, + setSavedObjectsClient, +} from '../../../../vis_type_timelion/public/helpers/plugin_services'; describe('Timelion expression suggestions', () => { setIndexPatterns({}); diff --git a/src/legacy/core_plugins/timelion/public/directives/_index.scss b/src/legacy/core_plugins/timelion/public/directives/_index.scss index 6ee2f81539032f..cd46a1a0a369e7 100644 --- a/src/legacy/core_plugins/timelion/public/directives/_index.scss +++ b/src/legacy/core_plugins/timelion/public/directives/_index.scss @@ -1,6 +1,5 @@ @import './timelion_expression_input'; @import './cells/index'; -@import './chart/index'; @import './timelion_expression_suggestions/index'; @import './timelion_help/index'; @import './timelion_interval/index'; diff --git a/src/legacy/core_plugins/timelion/public/directives/chart/_index.scss b/src/legacy/core_plugins/timelion/public/directives/chart/_index.scss deleted file mode 100644 index 33c188decd4f17..00000000000000 --- a/src/legacy/core_plugins/timelion/public/directives/chart/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './chart'; diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js index 449c0489fea251..1fec243a277f85 100644 --- a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js +++ b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js @@ -43,7 +43,7 @@ import _ from 'lodash'; import $ from 'jquery'; import PEG from 'pegjs'; -import grammar from 'raw-loader!../chain.peg'; +import grammar from 'raw-loader!../../../vis_type_timelion/public/chain.peg'; import timelionExpressionInputTemplate from './timelion_expression_input.html'; import { SUGGESTION_TYPE, @@ -52,7 +52,7 @@ import { insertAtLocation, } from './timelion_expression_input_helpers'; import { comboBoxKeyCodes } from '@elastic/eui'; -import { getArgValueSuggestions } from '../services/arg_value_suggestions'; +import { getArgValueSuggestions } from '../../../vis_type_timelion/public/helpers/arg_value_suggestions'; const Parser = PEG.generate(grammar); diff --git a/src/legacy/core_plugins/timelion/public/index.scss b/src/legacy/core_plugins/timelion/public/index.scss index 7ccc6c300bc40b..ebf000d160b546 100644 --- a/src/legacy/core_plugins/timelion/public/index.scss +++ b/src/legacy/core_plugins/timelion/public/index.scss @@ -11,6 +11,4 @@ // timChart__legend-isLoading @import './app'; -@import './components/index'; @import './directives/index'; -@import './vis/index'; diff --git a/src/legacy/core_plugins/timelion/public/legacy.ts b/src/legacy/core_plugins/timelion/public/legacy.ts index 1cf6bb65cdc029..63030fcbce3873 100644 --- a/src/legacy/core_plugins/timelion/public/legacy.ts +++ b/src/legacy/core_plugins/timelion/public/legacy.ts @@ -20,15 +20,10 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; import { plugin } from '.'; -import { setup as visualizations } from '../../visualizations/public/np_ready/public/legacy'; import { TimelionPluginSetupDependencies } from './plugin'; import { LegacyDependenciesPlugin } from './shim'; const setupPlugins: Readonly = { - visualizations, - data: npSetup.plugins.data, - expressions: npSetup.plugins.expressions, - // Temporary solution // It will be removed when all dependent services are migrated to the new platform. __LEGACY: new LegacyDependenciesPlugin(), @@ -37,4 +32,4 @@ const setupPlugins: Readonly = { const pluginInstance = plugin({} as PluginInitializerContext); export const setup = pluginInstance.setup(npSetup.core, setupPlugins); -export const start = pluginInstance.start(npStart.core, npStart.plugins); +export const start = pluginInstance.start(npStart.core); diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts index 0bbda4bf3646fc..57ee99f5268b08 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts +++ b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts @@ -17,19 +17,18 @@ * under the License. */ -import './flot'; +import '../../../../vis_type_timelion/public/flot'; import _ from 'lodash'; import $ from 'jquery'; import moment from 'moment-timezone'; import { timefilter } from 'ui/timefilter'; // @ts-ignore import observeResize from '../../lib/observe_resize'; -// @ts-ignore -import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../../common/lib'; +import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../../../vis_type_timelion/common/lib'; +import { tickFormatters } from '../../../../vis_type_timelion/public/helpers/tick_formatters'; import { TimelionVisualizationDependencies } from '../../plugin'; -import { tickFormatters } from '../../services/tick_formatters'; -import { xaxisFormatterProvider } from './xaxis_formatter'; -import { generateTicksProvider } from './tick_generator'; +import { xaxisFormatterProvider } from '../../../../vis_type_timelion/public/helpers/xaxis_formatter'; +import { generateTicksProvider } from '../../../../vis_type_timelion/public/helpers/tick_generator'; const DEBOUNCE_DELAY = 50; diff --git a/src/legacy/core_plugins/timelion/public/plugin.ts b/src/legacy/core_plugins/timelion/public/plugin.ts index 42f0ee3ad47258..636b8bf8e128aa 100644 --- a/src/legacy/core_plugins/timelion/public/plugin.ts +++ b/src/legacy/core_plugins/timelion/public/plugin.ts @@ -22,33 +22,19 @@ import { Plugin, PluginInitializerContext, IUiSettingsClient, - HttpSetup, } from 'kibana/public'; -import { Plugin as ExpressionsPlugin } from 'src/plugins/expressions/public'; -import { DataPublicPluginSetup, TimefilterContract } from 'src/plugins/data/public'; -import { PluginsStart } from 'ui/new_platform/new_platform'; -import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; -import { getTimelionVisualizationConfig } from './timelion_vis_fn'; -import { getTimelionVisualization } from './vis'; import { getTimeChart } from './panels/timechart/timechart'; import { Panel } from './panels/panel'; import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; -import { setIndexPatterns, setSavedObjectsClient } from './services/plugin_services'; /** @internal */ export interface TimelionVisualizationDependencies extends LegacyDependenciesPluginSetup { uiSettings: IUiSettingsClient; - http: HttpSetup; timelionPanels: Map; - timefilter: TimefilterContract; } /** @internal */ export interface TimelionPluginSetupDependencies { - expressions: ReturnType; - visualizations: VisualizationsSetup; - data: DataPublicPluginSetup; - // Temporary solution __LEGACY: LegacyDependenciesPlugin; } @@ -61,24 +47,16 @@ export class TimelionPlugin implements Plugin, void> { this.initializerContext = initializerContext; } - public async setup( - core: CoreSetup, - { __LEGACY, expressions, visualizations, data }: TimelionPluginSetupDependencies - ) { + public async setup(core: CoreSetup, { __LEGACY }: TimelionPluginSetupDependencies) { const timelionPanels: Map = new Map(); const dependencies: TimelionVisualizationDependencies = { uiSettings: core.uiSettings, - http: core.http, timelionPanels, - timefilter: data.query.timefilter.timefilter, ...(await __LEGACY.setup(core, timelionPanels)), }; this.registerPanels(dependencies); - - expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies)); - visualizations.types.createBaseVisualization(getTimelionVisualization(dependencies)); } private registerPanels(dependencies: TimelionVisualizationDependencies) { @@ -87,15 +65,12 @@ export class TimelionPlugin implements Plugin, void> { dependencies.timelionPanels.set(timeChartPanel.name, timeChartPanel); } - public start(core: CoreStart, plugins: PluginsStart) { + public start(core: CoreStart) { const timelionUiEnabled = core.injectedMetadata.getInjectedVar('timelionUiEnabled'); if (timelionUiEnabled === false) { core.chrome.navLinks.update('timelion', { hidden: true }); } - - setIndexPatterns(plugins.data.indexPatterns); - setSavedObjectsClient(core.savedObjects.client); } public stop(): void {} diff --git a/src/legacy/core_plugins/timelion/public/vis/_index.scss b/src/legacy/core_plugins/timelion/public/vis/_index.scss deleted file mode 100644 index 17a2018f7a56a8..00000000000000 --- a/src/legacy/core_plugins/timelion/public/vis/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import './timelion_vis'; -@import './timelion_editor'; diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_vis.html b/src/legacy/core_plugins/timelion/public/vis/timelion_vis.html deleted file mode 100644 index 8bfb9e91a2523e..00000000000000 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_vis.html +++ /dev/null @@ -1,3 +0,0 @@ -
-
-
diff --git a/src/legacy/core_plugins/timelion/server/handlers/chain_runner.js b/src/legacy/core_plugins/timelion/server/handlers/chain_runner.js index 9514e479d36f48..9056362cb723a2 100644 --- a/src/legacy/core_plugins/timelion/server/handlers/chain_runner.js +++ b/src/legacy/core_plugins/timelion/server/handlers/chain_runner.js @@ -26,7 +26,7 @@ import parseSheet from './lib/parse_sheet.js'; import repositionArguments from './lib/reposition_arguments.js'; import indexArguments from './lib/index_arguments.js'; import validateTime from './lib/validate_time.js'; -import { calculateInterval } from '../../common/lib'; +import { calculateInterval } from '../../../vis_type_timelion/common/lib'; export default function chainRunner(tlConfig) { const preprocessChain = require('./lib/preprocess_chain')(tlConfig); diff --git a/src/legacy/core_plugins/timelion/server/handlers/lib/parse_sheet.js b/src/legacy/core_plugins/timelion/server/handlers/lib/parse_sheet.js index 74ef76d1a50cde..4957d3cb78b85e 100644 --- a/src/legacy/core_plugins/timelion/server/handlers/lib/parse_sheet.js +++ b/src/legacy/core_plugins/timelion/server/handlers/lib/parse_sheet.js @@ -21,7 +21,10 @@ import { i18n } from '@kbn/i18n'; import fs from 'fs'; import path from 'path'; import _ from 'lodash'; -const grammar = fs.readFileSync(path.resolve(__dirname, '../../../public/chain.peg'), 'utf8'); +const grammar = fs.readFileSync( + path.resolve(__dirname, '../../../../vis_type_timelion/public/chain.peg'), + 'utf8' +); import PEG from 'pegjs'; const Parser = PEG.generate(grammar); diff --git a/src/legacy/core_plugins/timelion/server/handlers/lib/validate_time.js b/src/legacy/core_plugins/timelion/server/handlers/lib/validate_time.js index 8b1f8998557be5..db924e33be5e9b 100644 --- a/src/legacy/core_plugins/timelion/server/handlers/lib/validate_time.js +++ b/src/legacy/core_plugins/timelion/server/handlers/lib/validate_time.js @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import moment from 'moment'; -import toMS from '../../lib/to_milliseconds.js'; +import { toMS } from '../../../../vis_type_timelion/common/lib'; export default function validateTime(time, tlConfig) { const span = moment.duration(moment(time.to).diff(moment(time.from))).asMilliseconds(); diff --git a/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts b/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts index 798902aa133dee..08358b9d81f781 100644 --- a/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts +++ b/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts @@ -17,7 +17,7 @@ * under the License. */ -import { TimelionFunctionArgs } from '../../../common/types'; +import { TimelionFunctionArgs } from '../../../../vis_type_timelion/common/types'; export interface TimelionFunctionInterface extends TimelionFunctionConfig { chainable: boolean; diff --git a/src/legacy/core_plugins/timelion/server/series_functions/holt/index.js b/src/legacy/core_plugins/timelion/server/series_functions/holt/index.js index 970d146c45b917..0cc41df933e8c8 100644 --- a/src/legacy/core_plugins/timelion/server/series_functions/holt/index.js +++ b/src/legacy/core_plugins/timelion/server/series_functions/holt/index.js @@ -23,7 +23,7 @@ import Chainable from '../../lib/classes/chainable'; import ses from './lib/ses'; import des from './lib/des'; import tes from './lib/tes'; -import toMilliseconds from '../../lib/to_milliseconds'; +import { toMS } from '../../../../vis_type_timelion/common/lib'; export default new Chainable('holt', { args: [ @@ -125,9 +125,7 @@ export default new Chainable('holt', { }) ); } - const season = Math.round( - toMilliseconds(args.byName.season) / toMilliseconds(tlConfig.time.interval) - ); + const season = Math.round(toMS(args.byName.season) / toMS(tlConfig.time.interval)); points = tes(points, alpha, beta, gamma, season, sample); } diff --git a/src/legacy/core_plugins/timelion/server/series_functions/legend.js b/src/legacy/core_plugins/timelion/server/series_functions/legend.js index b4673186867296..fd9ff53a1391f9 100644 --- a/src/legacy/core_plugins/timelion/server/series_functions/legend.js +++ b/src/legacy/core_plugins/timelion/server/series_functions/legend.js @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import alter from '../lib/alter.js'; import Chainable from '../lib/classes/chainable'; -import { DEFAULT_TIME_FORMAT } from '../../common/lib'; +import { DEFAULT_TIME_FORMAT } from '../../../vis_type_timelion/common/lib'; export default new Chainable('legend', { args: [ diff --git a/src/legacy/core_plugins/timelion/server/series_functions/movingaverage.js b/src/legacy/core_plugins/timelion/server/series_functions/movingaverage.js index 361cd1f9dfb670..a4b458991c1bc0 100644 --- a/src/legacy/core_plugins/timelion/server/series_functions/movingaverage.js +++ b/src/legacy/core_plugins/timelion/server/series_functions/movingaverage.js @@ -21,7 +21,7 @@ import { i18n } from '@kbn/i18n'; import alter from '../lib/alter.js'; import _ from 'lodash'; import Chainable from '../lib/classes/chainable'; -import toMS from '../lib/to_milliseconds.js'; +import { toMS } from '../../../vis_type_timelion/common/lib'; const validPositions = ['left', 'right', 'center']; const defaultPosition = 'center'; diff --git a/src/legacy/core_plugins/timelion/server/series_functions/scale_interval.js b/src/legacy/core_plugins/timelion/server/series_functions/scale_interval.js index 778c91d30f2cb2..b604015624dfd6 100644 --- a/src/legacy/core_plugins/timelion/server/series_functions/scale_interval.js +++ b/src/legacy/core_plugins/timelion/server/series_functions/scale_interval.js @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; import alter from '../lib/alter.js'; -import toMS from '../lib/to_milliseconds.js'; +import { toMS } from '../../../vis_type_timelion/common/lib'; import _ from 'lodash'; import Chainable from '../lib/classes/chainable'; diff --git a/src/legacy/core_plugins/vis_type_metric/public/__snapshots__/metric_vis_fn.test.ts.snap b/src/legacy/core_plugins/vis_type_metric/public/__snapshots__/metric_vis_fn.test.ts.snap index d0fba4d164dbf3..44414cedf966ab 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/__snapshots__/metric_vis_fn.test.ts.snap +++ b/src/legacy/core_plugins/vis_type_metric/public/__snapshots__/metric_vis_fn.test.ts.snap @@ -13,7 +13,7 @@ Object { "metrics": undefined, }, "metric": Object { - "colorSchema": "\\"Green to Red\\"", + "colorSchema": "Green to Red", "colorsRange": "{range from=0 to=10000}", "invertColors": false, "labels": Object { diff --git a/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis.js b/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis.js deleted file mode 100644 index f4786123f7b9da..00000000000000 --- a/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis.js +++ /dev/null @@ -1,95 +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 'jquery'; -import ngMock from 'ng_mock'; -import expect from '@kbn/expect'; - -import { Vis } from 'ui/vis'; -import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern'; - -import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; - -describe('metric_vis - createMetricVisTypeDefinition', () => { - let setup = null; - let vis; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(Private => { - setup = () => { - const metricVisType = visualizations.types.get('metric'); - const indexPattern = Private(LogstashIndexPatternStubProvider); - - indexPattern.stubSetFieldFormat('ip', 'url', { - urlTemplate: 'http://ip.info?address={{value}}', - labelTemplate: 'ip[{{value}}]', - }); - - vis = new Vis(indexPattern, { - type: 'metric', - 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 Controller = metricVisType.visualization; - const controller = new Controller(el, vis); - const render = esResponse => { - 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 $link = $(el) - .find('a[href]') - .filter(function() { - return this.href.includes('ip.info'); - }); - - expect($link).to.have.length(1); - expect($link.text()).to.be(`ip[${ip}]`); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis_controller.js b/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis_controller.js deleted file mode 100644 index 367c5f46999376..00000000000000 --- a/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis_controller.js +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { MetricVisComponent } from '../components/metric_vis_controller'; - -describe('metric_vis - controller', function() { - const vis = { - params: { - metric: { - colorSchema: 'Green to Red', - colorsRange: [{ from: 0, to: 1000 }], - style: {}, - }, - dimensions: { - metrics: [{ accessor: 0 }], - bucket: null, - }, - }, - }; - - let metricController; - - beforeEach(() => { - metricController = new MetricVisComponent({ vis: vis, visParams: vis.params }); - }); - - it('should set the metric label and value', function() { - const metrics = metricController._processTableGroups({ - columns: [{ id: 'col-0', name: 'Count' }], - rows: [{ 'col-0': 4301021 }], - }); - - expect(metrics.length).to.be(1); - expect(metrics[0].label).to.be('Count'); - expect(metrics[0].value).to.be('4301021'); - }); - - it('should support multi-value metrics', function() { - vis.params.dimensions.metrics.push({ accessor: 1 }); - const metrics = metricController._processTableGroups({ - columns: [ - { id: 'col-0', name: '1st percentile of bytes' }, - { id: 'col-1', name: '99th percentile of bytes' }, - ], - rows: [{ 'col-0': 182, 'col-1': 445842.4634666484 }], - }); - - expect(metrics.length).to.be(2); - expect(metrics[0].label).to.be('1st percentile of bytes'); - expect(metrics[0].value).to.be('182'); - expect(metrics[1].label).to.be('99th percentile of bytes'); - expect(metrics[1].value).to.be('445842.4634666484'); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap b/src/legacy/core_plugins/vis_type_metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap new file mode 100644 index 00000000000000..d84424cc6179a6 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap @@ -0,0 +1,57 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MetricVisComponent should render correct structure for single metric 1`] = ` +
+ 4301021", + } + } + showLabel={true} + /> +
+`; + +exports[`MetricVisComponent should render correct structure for multi-value metrics 1`] = ` +
+ 182", + } + } + showLabel={true} + /> + 445842.4634666484", + } + } + showLabel={true} + /> +
+`; 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 new file mode 100644 index 00000000000000..901273ccbeb950 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.test.tsx @@ -0,0 +1,93 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { MetricVisComponent } from './metric_vis_component'; +import { Vis } from '../legacy_imports'; + +jest.mock('ui/new_platform'); + +type Props = MetricVisComponent['props']; + +const baseVisData = { + columns: [{ id: 'col-0', name: 'Count' }], + rows: [{ 'col-0': 4301021 }], +} as any; + +describe('MetricVisComponent', function() { + const vis: Vis = { + params: { + metric: { + colorSchema: 'Green to Red', + colorsRange: [{ from: 0, to: 1000 }], + style: {}, + labels: { + show: true, + }, + }, + dimensions: { + metrics: [{ accessor: 0 }], + bucket: null, + }, + }, + } as any; + + const getComponent = (propOverrides: Partial = {} as Partial) => { + const props: Props = { + vis, + visParams: vis.params, + visData: baseVisData, + renderComplete: jest.fn(), + ...propOverrides, + }; + + return shallow(); + }; + + it('should render component', () => { + expect(getComponent().exists()).toBe(true); + }); + + it('should render correct structure for single metric', function() { + expect(getComponent()).toMatchSnapshot(); + }); + + it('should render correct structure for multi-value metrics', function() { + const component = getComponent({ + visData: { + columns: [ + { id: 'col-0', name: '1st percentile of bytes' }, + { id: 'col-1', name: '99th percentile of bytes' }, + ], + rows: [{ 'col-0': 182, 'col-1': 445842.4634666484 }], + }, + visParams: { + ...vis.params, + dimensions: { + ...vis.params.dimensions, + metrics: [{ accessor: 0 }, { accessor: 1 }], + }, + }, + } as any); + + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_controller.js b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx similarity index 64% rename from src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_controller.js rename to src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx index 17dad6a4cd8cb1..f8398f5c83146c 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_controller.js +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx @@ -19,20 +19,33 @@ import { last, findIndex, isNaN } from 'lodash'; import React, { Component } from 'react'; + import { isColorDark } from '@elastic/eui'; -import { getHeatmapColors } from 'ui/color_maps'; -import { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; +import { getHeatmapColors, getFormat, Vis } from '../legacy_imports'; import { MetricVisValue } from './metric_vis_value'; +import { FieldFormat, ContentType } from '../../../../../plugins/data/public'; +import { Context } from '../metric_vis_fn'; +import { KibanaDatatable } from '../../../../../plugins/expressions/public'; +import { VisParams, MetricVisMetric } from '../types'; +import { SchemaConfig } from '../../../visualizations/public'; + +interface MetricVisComponentProps { + visParams: VisParams; + visData: Context; + vis: Vis; + renderComplete: () => void; +} -export class MetricVisComponent extends Component { - _getLabels() { +export class MetricVisComponent extends Component { + private getLabels() { const config = this.props.visParams.metric; const isPercentageMode = config.percentageMode; const colorsRange = config.colorsRange; const max = last(colorsRange).to; - const labels = []; - colorsRange.forEach(range => { + const labels: string[] = []; + + colorsRange.forEach((range: any) => { const from = isPercentageMode ? Math.round((100 * range.from) / max) : range.from; const to = isPercentageMode ? Math.round((100 * range.to) / max) : range.to; labels.push(`${from} - ${to}`); @@ -41,13 +54,13 @@ export class MetricVisComponent extends Component { return labels; } - _getColors() { + private getColors() { const config = this.props.visParams.metric; const invertColors = config.invertColors; const colorSchema = config.colorSchema; const colorsRange = config.colorsRange; - const labels = this._getLabels(); - const colors = {}; + const labels = this.getLabels(); + const colors: any = {}; for (let i = 0; i < labels.length; i += 1) { const divider = Math.max(colorsRange.length - 1, 1); const val = invertColors ? 1 - i / divider : i / divider; @@ -56,9 +69,9 @@ export class MetricVisComponent extends Component { return colors; } - _getBucket(val) { + private getBucket(val: number) { const config = this.props.visParams.metric; - let bucket = findIndex(config.colorsRange, range => { + let bucket = findIndex(config.colorsRange, (range: any) => { return range.from <= val && range.to > val; }); @@ -70,59 +83,65 @@ export class MetricVisComponent extends Component { return bucket; } - _getColor(val, labels, colors) { - const bucket = this._getBucket(val); + private getColor(val: number, labels: string[], colors: { [label: string]: string }) { + const bucket = this.getBucket(val); const label = labels[bucket]; return colors[label]; } - _needsLightText(bgColor) { - const color = /rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/.exec(bgColor); - if (!color) { + private needsLightText(bgColor: string) { + const colors = /rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/.exec(bgColor); + if (!colors) { return false; } - return isColorDark(parseInt(color[1]), parseInt(color[2]), parseInt(color[3])); + + const [red, green, blue] = colors.slice(1).map(parseInt); + return isColorDark(red, green, blue); } - _getFormattedValue = (fieldFormatter, value, format = 'text') => { + private getFormattedValue = ( + fieldFormatter: FieldFormat, + value: any, + format: ContentType = 'text' + ) => { if (isNaN(value)) return '-'; return fieldFormatter.convert(value, format); }; - _processTableGroups(table) { + private processTableGroups(table: KibanaDatatable) { const config = this.props.visParams.metric; const dimensions = this.props.visParams.dimensions; const isPercentageMode = config.percentageMode; const min = config.colorsRange[0].from; const max = last(config.colorsRange).to; - const colors = this._getColors(); - const labels = this._getLabels(); - const metrics = []; + const colors = this.getColors(); + const labels = this.getLabels(); + const metrics: MetricVisMetric[] = []; - let bucketColumnId; - let bucketFormatter; + let bucketColumnId: string; + let bucketFormatter: FieldFormat; if (dimensions.bucket) { bucketColumnId = table.columns[dimensions.bucket.accessor].id; bucketFormatter = getFormat(dimensions.bucket.format); } - dimensions.metrics.forEach(metric => { + dimensions.metrics.forEach((metric: SchemaConfig) => { const columnIndex = metric.accessor; - const column = table.columns[columnIndex]; + const column = table?.columns[columnIndex]; const formatter = getFormat(metric.format); table.rows.forEach((row, rowIndex) => { let title = column.name; - let value = row[column.id]; - const color = this._getColor(value, labels, colors); + let value: any = row[column.id]; + const color = this.getColor(value, labels, colors); if (isPercentageMode) { value = (value - min) / (max - min); } - value = this._getFormattedValue(formatter, value, 'html'); + value = this.getFormattedValue(formatter, value, 'html'); if (bucketColumnId) { - const bucketValue = this._getFormattedValue(bucketFormatter, row[bucketColumnId]); + const bucketValue = this.getFormattedValue(bucketFormatter, row[bucketColumnId]); title = `${bucketValue} - ${title}`; } @@ -130,11 +149,11 @@ export class MetricVisComponent extends Component { metrics.push({ label: title, - value: value, - color: shouldColor && config.style.labelColor ? color : null, - bgColor: shouldColor && config.style.bgColor ? color : null, - lightText: shouldColor && config.style.bgColor && this._needsLightText(color), - rowIndex: rowIndex, + value, + color: shouldColor && config.style.labelColor ? color : undefined, + bgColor: shouldColor && config.style.bgColor ? color : undefined, + lightText: shouldColor && config.style.bgColor && this.needsLightText(color), + rowIndex, }); }); }); @@ -142,7 +161,7 @@ export class MetricVisComponent extends Component { return metrics; } - _filterBucket = metric => { + private filterBucket = (metric: MetricVisMetric) => { const dimensions = this.props.visParams.dimensions; if (!dimensions.bucket) { return; @@ -155,27 +174,18 @@ export class MetricVisComponent extends Component { }); }; - _renderMetric = (metric, index) => { + private renderMetric = (metric: MetricVisMetric, index: number) => { return ( ); }; - render() { - let metricsHtml; - if (this.props.visData) { - const metrics = this._processTableGroups(this.props.visData); - metricsHtml = metrics.map(this._renderMetric); - } - return
{metricsHtml}
; - } - componentDidMount() { this.props.renderComplete(); } @@ -183,4 +193,13 @@ export class MetricVisComponent extends Component { componentDidUpdate() { this.props.renderComplete(); } + + render() { + let metricsHtml; + if (this.props.visData) { + const metrics = this.processTableGroups(this.props.visData); + metricsHtml = metrics.map(this.renderMetric); + } + return
{metricsHtml}
; + } } diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx index 566618c5270197..032f66d92624cc 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx @@ -29,7 +29,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from '../legacy_imports'; import { ColorRanges, ColorSchemaOptions, @@ -39,6 +39,7 @@ import { } from '../../../vis_type_vislib/public/components'; import { ColorModes } from '../../../vis_type_vislib/public/utils/collections'; import { MetricVisParam, VisParams } from '../types'; +import { SetColorRangeValue } from '../../../vis_type_vislib/public/components/common/color_ranges'; function MetricVisOptions({ stateParams, @@ -135,7 +136,7 @@ function MetricVisOptions({ diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.test.js b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.test.tsx similarity index 76% rename from src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.test.js rename to src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.test.tsx index 2fc436cb829bd9..2ee80e92f129b9 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.test.js +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.test.tsx @@ -21,34 +21,32 @@ import { shallow } from 'enzyme'; import { MetricVisValue } from './metric_vis_value'; +const baseMetric = { label: 'Foo', value: 'foo' } as any; + describe('MetricVisValue', () => { it('should be wrapped in EuiKeyboardAccessible if having a click listener', () => { const component = shallow( - {}} /> + {}} /> ); expect(component.find('EuiKeyboardAccessible').exists()).toBe(true); }); it('should not be wrapped in EuiKeyboardAccessible without having a click listener', () => { - const component = shallow( - - ); + const component = shallow(); expect(component.find('EuiKeyboardAccessible').exists()).toBe(false); }); it('should add -isfilterable class if onFilter is provided', () => { const onFilter = jest.fn(); const component = shallow( - + ); component.simulate('click'); expect(component.find('.mtrVis__container-isfilterable')).toHaveLength(1); }); it('should not add -isfilterable class if onFilter is not provided', () => { - const component = shallow( - - ); + const component = shallow(); component.simulate('click'); expect(component.find('.mtrVis__container-isfilterable')).toHaveLength(0); }); @@ -56,9 +54,9 @@ describe('MetricVisValue', () => { it('should call onFilter callback if provided', () => { const onFilter = jest.fn(); const component = shallow( - + ); component.find('.mtrVis__container-isfilterable').simulate('click'); - expect(onFilter).toHaveBeenCalledWith({ label: 'Foo', value: 'foo' }); + expect(onFilter).toHaveBeenCalledWith(baseMetric); }); }); diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.js b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.tsx similarity index 72% rename from src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.js rename to src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.tsx index 0776dd13a98685..79876377c8e448 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.js +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.tsx @@ -17,26 +17,36 @@ * under the License. */ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import React, { Component, KeyboardEvent } from 'react'; import classNames from 'classnames'; import { EuiKeyboardAccessible, keyCodes } from '@elastic/eui'; -class MetricVisValue extends Component { +import { MetricVisMetric } from '../types'; + +interface MetricVisValueProps { + metric: MetricVisMetric; + fontSize: number; + onFilter?: (metric: MetricVisMetric) => void; + showLabel?: boolean; +} + +export class MetricVisValue extends Component { onClick = () => { - this.props.onFilter(this.props.metric); + if (this.props.onFilter) { + this.props.onFilter(this.props.metric); + } }; - onKeyPress = e => { - if (e.keyCode === keyCodes.ENTER) { + onKeyPress = (event: KeyboardEvent) => { + if (event.keyCode === keyCodes.ENTER) { this.onClick(); } }; render() { const { fontSize, metric, onFilter, showLabel } = this.props; - const hasFilter = !!onFilter; + const hasFilter = Boolean(onFilter); const metricValueStyle = { fontSize: `${fontSize}pt`, @@ -52,10 +62,10 @@ class MetricVisValue extends Component {
{showLabel &&
{metric.label}
}
@@ -81,12 +91,3 @@ class MetricVisValue extends Component { return metricComponent; } } - -MetricVisValue.propTypes = { - fontSize: PropTypes.number.isRequired, - metric: PropTypes.object.isRequired, - onFilter: PropTypes.func, - showLabel: PropTypes.bool, -}; - -export { MetricVisValue }; diff --git a/src/legacy/core_plugins/vis_type_metric/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_metric/public/legacy_imports.ts new file mode 100644 index 00000000000000..93dfd76e16b166 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_metric/public/legacy_imports.ts @@ -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. + */ + +export { Vis, VisParams } from 'ui/vis'; +export { vislibColorMaps, colorSchemas, ColorSchemas } from 'ui/color_maps'; +export { getHeatmapColors } from 'ui/color_maps'; +export { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; +export { VisOptionsProps } from 'ui/vis/editors/default'; +// @ts-ignore +export { Schemas } from 'ui/vis/editors/default/schemas'; +export { AggGroupNames } from 'ui/vis/editors/default'; diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts index 04bff5ccb46557..45110ca1130030 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; -import { vislibColorMaps, ColorSchemas } from 'ui/color_maps'; +import { vislibColorMaps, ColorSchemas } from './legacy_imports'; import { ExpressionFunction, KibanaDatatable, @@ -30,13 +30,13 @@ import { import { ColorModes } from '../../vis_type_vislib/public/utils/collections'; import { visType, DimensionsVisParam, VisParams } from './types'; -type Context = KibanaDatatable; +export type Context = KibanaDatatable; const name = 'metricVis'; interface Arguments { - percentage: boolean; - colorScheme: ColorSchemas; + percentageMode: boolean; + colorSchema: ColorSchemas; colorMode: ColorModes; useRanges: boolean; invertColors: boolean; @@ -73,19 +73,19 @@ export const createMetricVisFn = (): ExpressionFunction< defaultMessage: 'Metric visualization', }), args: { - percentage: { + percentageMode: { types: ['boolean'], default: false, - help: i18n.translate('visTypeMetric.function.percentage.help', { + help: i18n.translate('visTypeMetric.function.percentageMode.help', { defaultMessage: 'Shows metric in percentage mode. Requires colorRange to be set.', }), }, - colorScheme: { + colorSchema: { types: ['string'], default: '"Green to Red"', options: Object.values(vislibColorMaps).map((value: any) => value.id), - help: i18n.translate('visTypeMetric.function.colorScheme.help', { - defaultMessage: 'Color scheme to use', + help: i18n.translate('visTypeMetric.function.colorSchema.help', { + defaultMessage: 'Color schema to use', }), }, colorMode: { @@ -174,8 +174,8 @@ export const createMetricVisFn = (): ExpressionFunction< dimensions.bucket = args.bucket; } - if (args.percentage && (!args.colorRange || args.colorRange.length === 0)) { - throw new Error('colorRange must be provided when using percentage'); + if (args.percentageMode && (!args.colorRange || args.colorRange.length === 0)) { + throw new Error('colorRange must be provided when using percentageMode'); } const fontSize = Number.parseInt(args.font.spec.fontSize || '', 10); @@ -188,9 +188,9 @@ export const createMetricVisFn = (): ExpressionFunction< visType, visConfig: { metric: { - percentageMode: args.percentage, + percentageMode: args.percentageMode, useRanges: args.useRanges, - colorSchema: args.colorScheme, + colorSchema: args.colorSchema, metricColorMode: args.colorMode, colorsRange: args.colorRange, labels: { 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 new file mode 100644 index 00000000000000..649959054416c7 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts @@ -0,0 +1,106 @@ +/* + * 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 'jquery'; + +import { npStart } from 'ui/new_platform'; +// @ts-ignore +import getStubIndexPattern from 'fixtures/stubbed_logstash_index_pattern'; + +import { Vis } from '../../visualizations/public'; +import { UrlFormat } from '../../../../plugins/data/public'; +import { + setup as visualizationsSetup, + start as visualizationsStart, +} from '../../visualizations/public/np_ready/public/legacy'; +import { metricVisTypeDefinition } from './metric_vis_type'; + +jest.mock('ui/new_platform'); + +describe('metric_vis - createMetricVisTypeDefinition', () => { + let vis: Vis; + + beforeAll(() => { + visualizationsSetup.types.createReactVisualization(metricVisTypeDefinition); + (npStart.plugins.data.fieldFormats.getType as jest.Mock).mockImplementation(() => { + return UrlFormat; + }); + }); + + const setup = () => { + const stubIndexPattern = getStubIndexPattern(); + + stubIndexPattern.stubSetFieldFormat('ip', 'url', { + urlTemplate: 'http://ip.info?address={{value}}', + labelTemplate: 'ip[{{value}}]', + }); + + // TODO: remove when Vis is converted to typescript. Only importing Vis as type + // @ts-ignore + vis = new Vis(stubIndexPattern, { + type: 'metric', + 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.types.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'); + }); + + expect(links.length).toBe(1); + expect(links.text()).toBe(`ip[${ip}]`); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts index 9b22423012b366..0d9019ee0579c1 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts @@ -19,19 +19,12 @@ import { i18n } from '@kbn/i18n'; -// @ts-ignore -import { Schemas } from 'ui/vis/editors/default/schemas'; - -import { AggGroupNames } from 'ui/vis/editors/default'; -import { colorSchemas, ColorSchemas } from 'ui/color_maps'; - -// @ts-ignore -import { MetricVisComponent } from './components/metric_vis_controller'; - +import { MetricVisComponent } from './components/metric_vis_component'; import { MetricVisOptions } from './components/metric_vis_options'; +import { Schemas, AggGroupNames, colorSchemas, ColorSchemas } from './legacy_imports'; import { ColorModes } from '../../vis_type_vislib/public/utils/collections'; -export const metricVisDefinition = { +export const metricVisTypeDefinition = { name: 'metric', title: i18n.translate('visTypeMetric.metricTitle', { defaultMessage: 'Metric' }), icon: 'visMetric', 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 f5c152ce888c00..413f846d789916 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/plugin.ts @@ -22,7 +22,7 @@ import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressio import { VisualizationsSetup } from '../../visualizations/public'; import { createMetricVisFn } from './metric_vis_fn'; -import { metricVisDefinition } from './metric_vis_type'; +import { metricVisTypeDefinition } from './metric_vis_type'; /** @internal */ export interface MetricVisPluginSetupDependencies { @@ -40,7 +40,7 @@ export class MetricVisPlugin implements Plugin { public setup(core: CoreSetup, { expressions, visualizations }: MetricVisPluginSetupDependencies) { expressions.registerFunction(createMetricVisFn); - visualizations.types.createReactVisualization(metricVisDefinition); + visualizations.types.createReactVisualization(metricVisTypeDefinition); } public start(core: CoreStart) { 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 06ec509f8c4d36..71c1c12b4f8f02 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/types.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/types.ts @@ -17,8 +17,8 @@ * under the License. */ -import { ColorSchemas } from 'ui/color_maps'; -import { RangeValues } from 'ui/vis/editors/default/controls/ranges'; +import { ColorSchemas } from './legacy_imports'; +import { Range } from '../../../../plugins/expressions/public'; import { SchemaConfig } from '../../visualizations/public'; import { ColorModes } from '../../vis_type_vislib/public/utils/collections'; import { Labels, Style } from '../../vis_type_vislib/public/types'; @@ -27,7 +27,7 @@ export const visType = 'metric'; export interface DimensionsVisParam { metrics: SchemaConfig[]; - bucket?: SchemaConfig[]; + bucket?: SchemaConfig; } export interface MetricVisParam { @@ -35,7 +35,7 @@ export interface MetricVisParam { useRanges: boolean; colorSchema: ColorSchemas; metricColorMode: ColorModes; - colorsRange: RangeValues[]; + colorsRange: Range[]; labels: Labels; invertColors: boolean; style: Style; @@ -48,3 +48,12 @@ export interface VisParams { metric: MetricVisParam; type: typeof visType; } + +export interface MetricVisMetric { + value: any; + label: string; + color?: string; + bgColor?: string; + lightText: boolean; + rowIndex: number; +} diff --git a/src/legacy/core_plugins/timelion/common/lib/calculate_interval.js b/src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.ts similarity index 78% rename from src/legacy/core_plugins/timelion/common/lib/calculate_interval.js rename to src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.ts index 7c6b3c2816e67e..328c634ea51537 100644 --- a/src/legacy/core_plugins/timelion/common/lib/calculate_interval.js +++ b/src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.ts @@ -17,11 +17,11 @@ * under the License. */ -import toMS from '../../server/lib/to_milliseconds.js'; +import { toMS } from './to_milliseconds'; // Totally cribbed this from Kibana 3. // I bet there's something similar in the Kibana 4 code. Somewhere. Somehow. -function roundInterval(interval) { +function roundInterval(interval: number) { switch (true) { case interval <= 500: // <= 0.5s return '100ms'; @@ -58,9 +58,24 @@ function roundInterval(interval) { } } -export function calculateInterval(from, to, size, interval, min) { - if (interval !== 'auto') return interval; - const dateMathInterval = roundInterval((to - from) / size); - if (toMS(dateMathInterval) < toMS(min)) return min; +export function calculateInterval( + from: number, + to: number, + size: number, + interval: string, + min: string +) { + if (interval !== 'auto') { + return interval; + } + + const dateMathInterval: string = roundInterval((to - from) / size); + const dateMathIntervalMs = toMS(dateMathInterval); + const minMs = toMS(min); + + if (dateMathIntervalMs !== undefined && minMs !== undefined && dateMathIntervalMs < minMs) { + return min; + } + return dateMathInterval; } diff --git a/src/legacy/core_plugins/timelion/common/lib/index.js b/src/legacy/core_plugins/vis_type_timelion/common/lib/index.ts similarity index 95% rename from src/legacy/core_plugins/timelion/common/lib/index.js rename to src/legacy/core_plugins/vis_type_timelion/common/lib/index.ts index 927331043f0b37..1901b8224f6075 100644 --- a/src/legacy/core_plugins/timelion/common/lib/index.js +++ b/src/legacy/core_plugins/vis_type_timelion/common/lib/index.ts @@ -18,4 +18,6 @@ */ export { calculateInterval } from './calculate_interval'; +export { toMS } from './to_milliseconds'; + export const DEFAULT_TIME_FORMAT = 'MMMM Do YYYY, HH:mm:ss.SSS'; diff --git a/src/legacy/core_plugins/timelion/server/lib/to_milliseconds.js b/src/legacy/core_plugins/vis_type_timelion/common/lib/to_milliseconds.ts similarity index 55% rename from src/legacy/core_plugins/timelion/server/lib/to_milliseconds.js rename to src/legacy/core_plugins/vis_type_timelion/common/lib/to_milliseconds.ts index 0d62d848daba5b..f6fcb08b48b25b 100644 --- a/src/legacy/core_plugins/timelion/server/lib/to_milliseconds.js +++ b/src/legacy/core_plugins/vis_type_timelion/common/lib/to_milliseconds.ts @@ -17,42 +17,45 @@ * under the License. */ -import _ from 'lodash'; -import moment from 'moment'; +import { keys } from 'lodash'; +import moment, { unitOfTime } from 'moment'; + +type Units = unitOfTime.Base | unitOfTime._quarter; +type Values = { [key in Units]: number }; // map of moment's short/long unit ids and elasticsearch's long unit ids // to their value in milliseconds -const vals = _.transform( - [ - ['ms', 'milliseconds', 'millisecond'], - ['s', 'seconds', 'second', 'sec'], - ['m', 'minutes', 'minute', 'min'], - ['h', 'hours', 'hour'], - ['d', 'days', 'day'], - ['w', 'weeks', 'week'], - ['M', 'months', 'month'], - ['quarter'], - ['y', 'years', 'year'], - ], - function(vals, units) { - const normal = moment.normalizeUnits(units[0]); - const val = moment.duration(1, normal).asMilliseconds(); - [].concat(normal, units).forEach(function(unit) { - vals[unit] = val; - }); - }, - {} -); +const unitMappings = [ + ['ms', 'milliseconds', 'millisecond'], + ['s', 'seconds', 'second', 'sec'], + ['m', 'minutes', 'minute', 'min'], + ['h', 'hours', 'hour'], + ['d', 'days', 'day'], + ['w', 'weeks', 'week'], + ['M', 'months', 'month'], + ['quarter'], + ['y', 'years', 'year'], +] as Units[][]; + +const vals = {} as Values; +unitMappings.forEach(units => { + const normal = moment.normalizeUnits(units[0]) as Units; + const val = moment.duration(1, normal).asMilliseconds(); + ([] as Units[]).concat(normal, units).forEach((unit: Units) => { + vals[unit] = val; + }); +}); + // match any key from the vals object preceded by an optional number -const parseRE = new RegExp('^(\\d+(?:\\.\\d*)?)?\\s*(' + _.keys(vals).join('|') + ')$'); +const parseRE = new RegExp('^(\\d+(?:\\.\\d*)?)?\\s*(' + keys(vals).join('|') + ')$'); -export default function(expr) { +export function toMS(expr: string) { const match = expr.match(parseRE); if (match) { if (match[2] === 'M' && match[1] !== '1') { throw new Error('Invalid interval. 1M is only valid monthly interval.'); } - return parseFloat(match[1] || 1) * vals[match[2]]; + return parseFloat(match[1] || '1') * vals[match[2] as Units]; } } diff --git a/src/legacy/core_plugins/timelion/common/types.ts b/src/legacy/core_plugins/vis_type_timelion/common/types.ts similarity index 100% rename from src/legacy/core_plugins/timelion/common/types.ts rename to src/legacy/core_plugins/vis_type_timelion/common/types.ts diff --git a/src/legacy/core_plugins/vis_type_timelion/index.ts b/src/legacy/core_plugins/vis_type_timelion/index.ts new file mode 100644 index 00000000000000..4664bebb4f38a2 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/index.ts @@ -0,0 +1,44 @@ +/* + * 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 { resolve } from 'path'; +import { Legacy } from 'kibana'; + +import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy/types'; + +const timelionVisPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => + new Plugin({ + id: 'timelion_vis', + require: ['kibana', 'elasticsearch', 'visualizations', 'data'], + publicDir: resolve(__dirname, 'public'), + uiExports: { + styleSheetPaths: resolve(__dirname, 'public/index.scss'), + hacks: [resolve(__dirname, 'public/legacy')], + injectDefaultVars: server => ({}), + }, + init: (server: Legacy.Server) => ({}), + config(Joi: any) { + return Joi.object({ + enabled: Joi.boolean().default(true), + }).default(); + }, + }); + +// eslint-disable-next-line import/no-default-export +export default timelionVisPluginInitializer; diff --git a/src/legacy/core_plugins/vis_type_timelion/package.json b/src/legacy/core_plugins/vis_type_timelion/package.json new file mode 100644 index 00000000000000..9b09f98ce6cafa --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/package.json @@ -0,0 +1,4 @@ +{ + "name": "timelion_vis", + "version": "kibana" +} diff --git a/src/legacy/core_plugins/timelion/public/vis/_timelion_editor.scss b/src/legacy/core_plugins/vis_type_timelion/public/_timelion_editor.scss similarity index 100% rename from src/legacy/core_plugins/timelion/public/vis/_timelion_editor.scss rename to src/legacy/core_plugins/vis_type_timelion/public/_timelion_editor.scss diff --git a/src/legacy/core_plugins/timelion/public/vis/_timelion_vis.scss b/src/legacy/core_plugins/vis_type_timelion/public/_timelion_vis.scss similarity index 100% rename from src/legacy/core_plugins/timelion/public/vis/_timelion_vis.scss rename to src/legacy/core_plugins/vis_type_timelion/public/_timelion_vis.scss diff --git a/src/legacy/core_plugins/timelion/public/chain.peg b/src/legacy/core_plugins/vis_type_timelion/public/chain.peg similarity index 100% rename from src/legacy/core_plugins/timelion/public/chain.peg rename to src/legacy/core_plugins/vis_type_timelion/public/chain.peg diff --git a/src/legacy/core_plugins/timelion/public/components/_index.scss b/src/legacy/core_plugins/vis_type_timelion/public/components/_index.scss similarity index 67% rename from src/legacy/core_plugins/timelion/public/components/_index.scss rename to src/legacy/core_plugins/vis_type_timelion/public/components/_index.scss index f2458a367e1768..1d887f43ff9a1c 100644 --- a/src/legacy/core_plugins/timelion/public/components/_index.scss +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/_index.scss @@ -1 +1,2 @@ +@import './panel'; @import './timelion_expression_input'; diff --git a/src/legacy/core_plugins/timelion/public/directives/chart/_chart.scss b/src/legacy/core_plugins/vis_type_timelion/public/components/_panel.scss similarity index 97% rename from src/legacy/core_plugins/timelion/public/directives/chart/_chart.scss rename to src/legacy/core_plugins/vis_type_timelion/public/components/_panel.scss index 39713cd05ab37d..c4d591bc82cad9 100644 --- a/src/legacy/core_plugins/timelion/public/directives/chart/_chart.scss +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/_panel.scss @@ -33,6 +33,7 @@ .ngLegendValue { color: $euiTextColor; + cursor: pointer; &:focus, &:hover { diff --git a/src/legacy/core_plugins/timelion/public/components/_timelion_expression_input.scss b/src/legacy/core_plugins/vis_type_timelion/public/components/_timelion_expression_input.scss similarity index 100% rename from src/legacy/core_plugins/timelion/public/components/_timelion_expression_input.scss rename to src/legacy/core_plugins/vis_type_timelion/public/components/_timelion_expression_input.scss diff --git a/src/legacy/core_plugins/timelion/public/__tests__/index.js b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx similarity index 67% rename from src/legacy/core_plugins/timelion/public/__tests__/index.js rename to src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx index 9cd61c3665a1a6..a8b03bdbc8b7e5 100644 --- a/src/legacy/core_plugins/timelion/public/__tests__/index.js +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx @@ -17,5 +17,23 @@ * under the License. */ -import './_tick_generator.js'; -describe('Timelion', function() {}); +import React from 'react'; + +import { Sheet } from '../helpers/timelion_request_handler'; +import { Panel } from './panel'; + +interface ChartComponentProp { + interval: string; + renderComplete(): void; + seriesList: Sheet; +} + +function ChartComponent(props: ChartComponentProp) { + if (!props.seriesList) { + return null; + } + + return ; +} + +export { ChartComponent }; diff --git a/src/legacy/core_plugins/timelion/public/components/index.ts b/src/legacy/core_plugins/vis_type_timelion/public/components/index.ts similarity index 96% rename from src/legacy/core_plugins/timelion/public/components/index.ts rename to src/legacy/core_plugins/vis_type_timelion/public/components/index.ts index 8d7d32a3ba2627..c70caab8dd70cf 100644 --- a/src/legacy/core_plugins/timelion/public/components/index.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/index.ts @@ -19,3 +19,4 @@ export * from './timelion_expression_input'; export * from './timelion_interval'; +export * from './timelion_vis'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx new file mode 100644 index 00000000000000..6095ba28443b8d --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx @@ -0,0 +1,386 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useState, useEffect, useMemo, useCallback } from 'react'; +import $ from 'jquery'; +import moment from 'moment-timezone'; +import { debounce, compact, get, each, cloneDeep, last, map } from 'lodash'; + +import { useKibana } from '../../../../../plugins/kibana_react/public'; +import '../flot'; +import { DEFAULT_TIME_FORMAT } from '../../common/lib'; + +import { + buildSeriesData, + buildOptions, + SERIES_ID_ATTR, + colors, + Axis, +} from '../helpers/panel_utils'; +import { Series, Sheet } from '../helpers/timelion_request_handler'; +import { tickFormatters } from '../helpers/tick_formatters'; +import { generateTicksProvider } from '../helpers/tick_generator'; +import { TimelionVisDependencies } from '../plugin'; + +interface PanelProps { + interval: string; + seriesList: Sheet; + renderComplete(): void; +} + +interface Position { + x: number; + x1: number; + y: number; + y1: number; + pageX: number; + pageY: number; +} + +interface Range { + to: number; + from: number; +} + +interface Ranges { + xaxis: Range; + yaxis: Range; +} + +const DEBOUNCE_DELAY = 50; +// ensure legend is the same height with or without a caption so legend items do not move around +const emptyCaption = '
'; + +function Panel({ interval, seriesList, renderComplete }: PanelProps) { + const kibana = useKibana(); + const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); + const [canvasElem, setCanvasElem] = useState(); + const [chartElem, setChartElem] = useState(); + + const [originalColorMap, setOriginalColorMap] = useState(() => new Map()); + + const [highlightedSeries, setHighlightedSeries] = useState(null); + const [focusedSeries, setFocusedSeries] = useState(); + const [plot, setPlot] = useState(); + + // Used to toggle the series, and for displaying values on hover + const [legendValueNumbers, setLegendValueNumbers] = useState(); + const [legendCaption, setLegendCaption] = useState(); + + const canvasRef = useCallback(node => { + if (node !== null) { + setCanvasElem(node); + } + }, []); + + const elementRef = useCallback(node => { + if (node !== null) { + setChartElem(node); + } + }, []); + + useEffect( + () => () => { + $(chartElem) + .off('plotselected') + .off('plothover') + .off('mouseleave'); + }, + [chartElem] + ); + + const highlightSeries = useCallback( + debounce(({ currentTarget }: JQuery.TriggeredEvent) => { + const id = Number(currentTarget.getAttribute(SERIES_ID_ATTR)); + if (highlightedSeries === id) { + return; + } + + setHighlightedSeries(id); + setChart(chartState => + chartState.map((series: Series, seriesIndex: number) => { + series.color = + seriesIndex === id + ? originalColorMap.get(series) // color it like it was + : 'rgba(128,128,128,0.1)'; // mark as grey + + return series; + }) + ); + }, DEBOUNCE_DELAY), + [originalColorMap, highlightedSeries] + ); + + const focusSeries = useCallback( + (event: JQuery.TriggeredEvent) => { + const id = Number(event.currentTarget.getAttribute(SERIES_ID_ATTR)); + setFocusedSeries(id); + highlightSeries(event); + }, + [highlightSeries] + ); + + const toggleSeries = useCallback(({ currentTarget }: JQuery.TriggeredEvent) => { + const id = Number(currentTarget.getAttribute(SERIES_ID_ATTR)); + + setChart(chartState => + chartState.map((series: Series, seriesIndex: number) => { + if (seriesIndex === id) { + series._hide = !series._hide; + } + return series; + }) + ); + }, []); + + const updateCaption = useCallback( + (plotData: any) => { + if (get(plotData, '[0]._global.legend.showTime', true)) { + const caption = $(''); + caption.html(emptyCaption); + setLegendCaption(caption); + + const canvasNode = $(canvasElem); + canvasNode.find('div.legend table').append(caption); + setLegendValueNumbers(canvasNode.find('.ngLegendValueNumber')); + + const legend = $(canvasElem).find('.ngLegendValue'); + if (legend) { + legend.click(toggleSeries); + legend.focus(focusSeries); + legend.mouseover(highlightSeries); + } + + // legend has been re-created. Apply focus on legend element when previously set + if (focusedSeries || focusedSeries === 0) { + canvasNode + .find('div.legend table .legendLabel>span') + .get(focusedSeries) + .focus(); + } + } + }, + [focusedSeries, canvasElem, toggleSeries, focusSeries, highlightSeries] + ); + + const updatePlot = useCallback( + (chartValue: Series[], grid?: boolean) => { + if (canvasElem && canvasElem.clientWidth > 0 && canvasElem.clientHeight > 0) { + const options = buildOptions( + interval, + kibana.services.timefilter, + kibana.services.uiSettings, + chartElem && chartElem.clientWidth, + grid + ); + const updatedSeries = buildSeriesData(chartValue, options); + + if (options.yaxes) { + options.yaxes.forEach((yaxis: Axis) => { + if (yaxis && yaxis.units) { + const formatters = tickFormatters(); + yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; + const byteModes = ['bytes', 'bytes/s']; + if (byteModes.includes(yaxis.units.type)) { + yaxis.tickGenerator = generateTicksProvider(); + } + } + }); + } + + const newPlot = $.plot(canvasElem, updatedSeries, options); + setPlot(newPlot); + renderComplete(); + + updateCaption(newPlot.getData()); + } + }, + [canvasElem, chartElem, renderComplete, kibana.services, interval, updateCaption] + ); + + useEffect(() => { + updatePlot(chart, seriesList.render && seriesList.render.grid); + }, [chart, updatePlot, seriesList.render]); + + useEffect(() => { + const colorsSet: Array<[Series, string]> = []; + const newChart = seriesList.list.map((series: Series, seriesIndex: number) => { + const newSeries = { ...series }; + if (!newSeries.color) { + const colorIndex = seriesIndex % colors.length; + newSeries.color = colors[colorIndex]; + } + colorsSet.push([newSeries, newSeries.color]); + return newSeries; + }); + setChart(newChart); + setOriginalColorMap(new Map(colorsSet)); + }, [seriesList.list]); + + const unhighlightSeries = useCallback(() => { + if (highlightedSeries === null) { + return; + } + + setHighlightedSeries(null); + setFocusedSeries(null); + + setChart(chartState => + chartState.map((series: Series) => { + series.color = originalColorMap.get(series); // reset the colors + return series; + }) + ); + }, [originalColorMap, highlightedSeries]); + + // Shamelessly borrowed from the flotCrosshairs example + const setLegendNumbers = useCallback( + (pos: Position) => { + unhighlightSeries(); + + const axes = plot.getAxes(); + if (pos.x < axes.xaxis.min || pos.x > axes.xaxis.max) { + return; + } + + const dataset = plot.getData(); + if (legendCaption) { + legendCaption.text( + moment(pos.x).format(get(dataset, '[0]._global.legend.timeFormat', DEFAULT_TIME_FORMAT)) + ); + } + for (let i = 0; i < dataset.length; ++i) { + const series = dataset[i]; + const useNearestPoint = series.lines.show && !series.lines.steps; + const precision = get(series, '_meta.precision', 2); + + if (series._hide) { + continue; + } + + const currentPoint = series.data.find((point: [number, number], index: number) => { + if (index + 1 === series.data.length) { + return true; + } + if (useNearestPoint) { + return pos.x - point[0] < series.data[index + 1][0] - pos.x; + } else { + return pos.x < series.data[index + 1][0]; + } + }); + + const y = currentPoint[1]; + + if (y != null && legendValueNumbers) { + let label = y.toFixed(precision); + if (series.yaxis.tickFormatter) { + label = series.yaxis.tickFormatter(Number(label), series.yaxis); + } + legendValueNumbers.eq(i).text(`(${label})`); + } else { + legendValueNumbers.eq(i).empty(); + } + } + }, + [plot, legendValueNumbers, unhighlightSeries, legendCaption] + ); + + const debouncedSetLegendNumbers = useCallback( + debounce(setLegendNumbers, DEBOUNCE_DELAY, { + maxWait: DEBOUNCE_DELAY, + leading: true, + trailing: false, + }), + [setLegendNumbers] + ); + + const clearLegendNumbers = useCallback(() => { + if (legendCaption) { + legendCaption.html(emptyCaption); + } + each(legendValueNumbers, (num: Node) => { + $(num).empty(); + }); + }, [legendCaption, legendValueNumbers]); + + const plotHoverHandler = useCallback( + (event: JQuery.TriggeredEvent, pos: Position) => { + if (!plot) { + return; + } + plot.setCrosshair(pos); + debouncedSetLegendNumbers(pos); + }, + [plot, debouncedSetLegendNumbers] + ); + const mouseLeaveHandler = useCallback(() => { + if (!plot) { + return; + } + plot.clearCrosshair(); + clearLegendNumbers(); + }, [plot, clearLegendNumbers]); + + const plotSelectedHandler = useCallback( + (event: JQuery.TriggeredEvent, ranges: Ranges) => { + kibana.services.timefilter.setTime({ + from: moment(ranges.xaxis.from), + to: moment(ranges.xaxis.to), + }); + }, + [kibana.services.timefilter] + ); + + useEffect(() => { + if (chartElem) { + $(chartElem) + .off('plotselected') + .on('plotselected', plotSelectedHandler); + } + }, [chartElem, plotSelectedHandler]); + + useEffect(() => { + if (chartElem) { + $(chartElem) + .off('mouseleave') + .on('mouseleave', mouseLeaveHandler); + } + }, [chartElem, mouseLeaveHandler]); + + useEffect(() => { + if (chartElem) { + $(chartElem) + .off('plothover') + .on('plothover', plotHoverHandler); + } + }, [chartElem, plotHoverHandler]); + + const title: string = useMemo(() => last(compact(map(seriesList.list, '_title'))) || '', [ + seriesList.list, + ]); + + return ( +
+
{title}
+
+
+ ); +} + +export { Panel }; diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input.tsx similarity index 98% rename from src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx rename to src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input.tsx index c695d09ca822bd..fa79e4eb6871a9 100644 --- a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input.tsx @@ -25,7 +25,7 @@ import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; import { CodeEditor, useKibana } from '../../../../../plugins/kibana_react/public'; import { suggest, getSuggestion } from './timelion_expression_input_helpers'; import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types'; -import { getArgValueSuggestions } from '../services/arg_value_suggestions'; +import { getArgValueSuggestions } from '../helpers/arg_value_suggestions'; const LANGUAGE_ID = 'timelion_expression'; monacoEditor.languages.register({ id: LANGUAGE_ID }); diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts similarity index 98% rename from src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts rename to src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts index fc90c276eeca2f..5aa05fb16466bb 100644 --- a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts @@ -26,7 +26,7 @@ import grammar from 'raw-loader!../chain.peg'; import { i18n } from '@kbn/i18n'; import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types'; -import { ArgValueSuggestions, FunctionArg, Location } from '../services/arg_value_suggestions'; +import { ArgValueSuggestions, FunctionArg, Location } from '../helpers/arg_value_suggestions'; const Parser = PEG.generate(grammar); diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_interval.tsx similarity index 98% rename from src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx rename to src/legacy/core_plugins/vis_type_timelion/public/components/timelion_interval.tsx index 6294e51e54788d..4bfa5d424ed852 100644 --- a/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_interval.tsx @@ -21,8 +21,8 @@ import React, { useMemo, useCallback } from 'react'; import { EuiFormRow, EuiComboBox, EuiComboBoxOptionProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useValidation } from 'ui/vis/editors/default/controls/agg_utils'; import { isValidEsInterval } from '../../../../core_plugins/data/common'; +import { useValidation } from '../legacy_imports'; const intervalOptions = [ { 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 new file mode 100644 index 00000000000000..ae55e11380b78e --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; + +import { IUiSettingsClient } from 'kibana/public'; +import { Vis } from '../legacy_imports'; +import { ChartComponent } from './chart'; +import { VisParams } from '../timelion_vis_fn'; +import { TimelionSuccessResponse } from '../helpers/timelion_request_handler'; + +export interface TimelionVisComponentProp { + config: IUiSettingsClient; + renderComplete(): void; + updateStatus: object; + vis: Vis; + visData: TimelionSuccessResponse; + visParams: VisParams; +} + +function TimelionVisComponent(props: TimelionVisComponentProp) { + return ( +
+ +
+ ); +} + +export { TimelionVisComponent }; diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/flot.js b/src/legacy/core_plugins/vis_type_timelion/public/flot.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/panels/timechart/flot.js rename to src/legacy/core_plugins/vis_type_timelion/public/flot.js diff --git a/src/legacy/core_plugins/timelion/public/services/arg_value_suggestions.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts similarity index 100% rename from src/legacy/core_plugins/timelion/public/services/arg_value_suggestions.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts new file mode 100644 index 00000000000000..db29d9112be8e8 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -0,0 +1,187 @@ +/* + * 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 { cloneDeep, defaults, merge, compact } from 'lodash'; +import moment, { Moment } from 'moment-timezone'; + +import { TimefilterContract } from 'src/plugins/data/public'; +import { IUiSettingsClient } from 'kibana/public'; + +import { calculateInterval } from '../../common/lib'; +import { xaxisFormatterProvider } from './xaxis_formatter'; +import { Series } from './timelion_request_handler'; + +export interface Axis { + delta?: number; + max?: number; + min?: number; + mode: string; + options?: { + units: { prefix: string; suffix: string }; + }; + tickSize?: number; + ticks: number; + tickLength: number; + timezone: string; + tickDecimals?: number; + tickFormatter: ((val: number) => string) | ((val: number, axis: Axis) => string); + tickGenerator?(axis: Axis): number[]; + units?: { type: string }; +} + +interface TimeRangeBounds { + min: Moment | undefined; + max: Moment | undefined; +} + +const colors = [ + '#01A4A4', + '#C66', + '#D0D102', + '#616161', + '#00A1CB', + '#32742C', + '#F18D05', + '#113F8C', + '#61AE24', + '#D70060', +]; + +const SERIES_ID_ATTR = 'data-series-id'; + +function buildSeriesData(chart: Series[], options: jquery.flot.plotOptions) { + const seriesData = chart.map((series: Series, seriesIndex: number) => { + const newSeries: Series = cloneDeep( + defaults(series, { + shadowSize: 0, + lines: { + lineWidth: 3, + }, + }) + ); + + newSeries._id = seriesIndex; + + if (series.color) { + const span = document.createElement('span'); + span.style.color = series.color; + newSeries.color = span.style.color; + } + + if (series._hide) { + newSeries.data = []; + newSeries.stack = false; + newSeries.label = `(hidden) ${series.label}`; + } + + if (series._global) { + merge(options, series._global, (objVal, srcVal) => { + // This is kind of gross, it means that you can't replace a global value with a null + // best you can do is an empty string. Deal with it. + if (objVal == null) { + return srcVal; + } + if (srcVal == null) { + return objVal; + } + }); + } + + return newSeries; + }); + + return compact(seriesData); +} + +function buildOptions( + intervalValue: string, + timefilter: TimefilterContract, + uiSettings: IUiSettingsClient, + clientWidth = 0, + showGrid?: boolean +) { + // Get the X-axis tick format + const time: TimeRangeBounds = timefilter.getBounds(); + const interval = calculateInterval( + (time.min && time.min.valueOf()) || 0, + (time.max && time.max.valueOf()) || 0, + uiSettings.get('timelion:target_buckets') || 200, + intervalValue, + uiSettings.get('timelion:min_interval') || '1ms' + ); + const format = xaxisFormatterProvider(uiSettings)(interval); + + const tickLetterWidth = 7; + const tickPadding = 45; + + const options = { + xaxis: { + mode: 'time', + tickLength: 5, + timezone: 'browser', + // Calculate how many ticks can fit on the axis + ticks: Math.floor(clientWidth / (format.length * tickLetterWidth + tickPadding)), + // Use moment to format ticks so we get timezone correction + tickFormatter: (val: number) => moment(val).format(format), + }, + selection: { + mode: 'x', + color: '#ccc', + }, + crosshair: { + mode: 'x', + color: '#C66', + lineWidth: 2, + }, + colors, + grid: { + show: showGrid, + borderWidth: 0, + borderColor: null, + margin: 10, + hoverable: true, + autoHighlight: false, + }, + legend: { + backgroundColor: 'rgb(255,255,255,0)', + position: 'nw', + labelBoxBorderColor: 'rgb(255,255,255,0)', + labelFormatter(label: string, series: { _id: number }) { + const wrapperSpan = document.createElement('span'); + const labelSpan = document.createElement('span'); + const numberSpan = document.createElement('span'); + + wrapperSpan.setAttribute('class', 'ngLegendValue'); + wrapperSpan.setAttribute(SERIES_ID_ATTR, `${series._id}`); + + labelSpan.appendChild(document.createTextNode(label)); + numberSpan.setAttribute('class', 'ngLegendValueNumber'); + + wrapperSpan.appendChild(labelSpan); + wrapperSpan.appendChild(numberSpan); + + return wrapperSpan.outerHTML; + }, + }, + } as jquery.flot.plotOptions & { yaxes?: Axis[] }; + + return options; +} + +export { buildSeriesData, buildOptions, SERIES_ID_ATTR, colors }; diff --git a/src/legacy/core_plugins/timelion/public/services/plugin_services.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/plugin_services.ts similarity index 100% rename from src/legacy/core_plugins/timelion/public/services/plugin_services.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/plugin_services.ts diff --git a/src/legacy/core_plugins/timelion/public/__tests__/services/tick_formatters.js b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts similarity index 56% rename from src/legacy/core_plugins/timelion/public/__tests__/services/tick_formatters.js rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts index 40840c4cd2610e..01734f2f5888a2 100644 --- a/src/legacy/core_plugins/timelion/public/__tests__/services/tick_formatters.js +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts @@ -17,124 +17,123 @@ * under the License. */ -import expect from '@kbn/expect'; -import { tickFormatters } from '../../services/tick_formatters'; +import { tickFormatters } from './tick_formatters'; describe('Tick Formatters', function() { - let formatters; + let formatters: any; beforeEach(function() { formatters = tickFormatters(); }); describe('Bits mode', function() { - let bitFormatter; + let bitFormatter: any; beforeEach(function() { bitFormatter = formatters.bits; }); it('is a function', function() { - expect(bitFormatter).to.be.a('function'); + expect(bitFormatter).toEqual(expect.any(Function)); }); it('formats with b/kb/mb/gb', function() { - expect(bitFormatter(7)).to.equal('7b'); - expect(bitFormatter(4 * 1000)).to.equal('4kb'); - expect(bitFormatter(4.1 * 1000 * 1000)).to.equal('4.1mb'); - expect(bitFormatter(3 * 1000 * 1000 * 1000)).to.equal('3gb'); + expect(bitFormatter(7)).toEqual('7b'); + expect(bitFormatter(4 * 1000)).toEqual('4kb'); + expect(bitFormatter(4.1 * 1000 * 1000)).toEqual('4.1mb'); + expect(bitFormatter(3 * 1000 * 1000 * 1000)).toEqual('3gb'); }); it('formats negative values with b/kb/mb/gb', () => { - expect(bitFormatter(-7)).to.equal('-7b'); - expect(bitFormatter(-4 * 1000)).to.equal('-4kb'); - expect(bitFormatter(-4.1 * 1000 * 1000)).to.equal('-4.1mb'); - expect(bitFormatter(-3 * 1000 * 1000 * 1000)).to.equal('-3gb'); + expect(bitFormatter(-7)).toEqual('-7b'); + expect(bitFormatter(-4 * 1000)).toEqual('-4kb'); + expect(bitFormatter(-4.1 * 1000 * 1000)).toEqual('-4.1mb'); + expect(bitFormatter(-3 * 1000 * 1000 * 1000)).toEqual('-3gb'); }); }); describe('Bits/s mode', function() { - let bitsFormatter; + let bitsFormatter: any; beforeEach(function() { bitsFormatter = formatters['bits/s']; }); it('is a function', function() { - expect(bitsFormatter).to.be.a('function'); + expect(bitsFormatter).toEqual(expect.any(Function)); }); it('formats with b/kb/mb/gb', function() { - expect(bitsFormatter(7)).to.equal('7b/s'); - expect(bitsFormatter(4 * 1000)).to.equal('4kb/s'); - expect(bitsFormatter(4.1 * 1000 * 1000)).to.equal('4.1mb/s'); - expect(bitsFormatter(3 * 1000 * 1000 * 1000)).to.equal('3gb/s'); + expect(bitsFormatter(7)).toEqual('7b/s'); + expect(bitsFormatter(4 * 1000)).toEqual('4kb/s'); + expect(bitsFormatter(4.1 * 1000 * 1000)).toEqual('4.1mb/s'); + expect(bitsFormatter(3 * 1000 * 1000 * 1000)).toEqual('3gb/s'); }); it('formats negative values with b/kb/mb/gb', function() { - expect(bitsFormatter(-7)).to.equal('-7b/s'); - expect(bitsFormatter(-4 * 1000)).to.equal('-4kb/s'); - expect(bitsFormatter(-4.1 * 1000 * 1000)).to.equal('-4.1mb/s'); - expect(bitsFormatter(-3 * 1000 * 1000 * 1000)).to.equal('-3gb/s'); + expect(bitsFormatter(-7)).toEqual('-7b/s'); + expect(bitsFormatter(-4 * 1000)).toEqual('-4kb/s'); + expect(bitsFormatter(-4.1 * 1000 * 1000)).toEqual('-4.1mb/s'); + expect(bitsFormatter(-3 * 1000 * 1000 * 1000)).toEqual('-3gb/s'); }); }); describe('Bytes mode', function() { - let byteFormatter; + let byteFormatter: any; beforeEach(function() { byteFormatter = formatters.bytes; }); it('is a function', function() { - expect(byteFormatter).to.be.a('function'); + expect(byteFormatter).toEqual(expect.any(Function)); }); it('formats with B/KB/MB/GB', function() { - expect(byteFormatter(10)).to.equal('10B'); - expect(byteFormatter(10 * 1024)).to.equal('10KB'); - expect(byteFormatter(10.2 * 1024 * 1024)).to.equal('10.2MB'); - expect(byteFormatter(3 * 1024 * 1024 * 1024)).to.equal('3GB'); + expect(byteFormatter(10)).toEqual('10B'); + expect(byteFormatter(10 * 1024)).toEqual('10KB'); + expect(byteFormatter(10.2 * 1024 * 1024)).toEqual('10.2MB'); + expect(byteFormatter(3 * 1024 * 1024 * 1024)).toEqual('3GB'); }); it('formats negative values with B/KB/MB/GB', function() { - expect(byteFormatter(-10)).to.equal('-10B'); - expect(byteFormatter(-10 * 1024)).to.equal('-10KB'); - expect(byteFormatter(-10.2 * 1024 * 1024)).to.equal('-10.2MB'); - expect(byteFormatter(-3 * 1024 * 1024 * 1024)).to.equal('-3GB'); + expect(byteFormatter(-10)).toEqual('-10B'); + expect(byteFormatter(-10 * 1024)).toEqual('-10KB'); + expect(byteFormatter(-10.2 * 1024 * 1024)).toEqual('-10.2MB'); + expect(byteFormatter(-3 * 1024 * 1024 * 1024)).toEqual('-3GB'); }); }); describe('Bytes/s mode', function() { - let bytesFormatter; + let bytesFormatter: any; beforeEach(function() { bytesFormatter = formatters['bytes/s']; }); it('is a function', function() { - expect(bytesFormatter).to.be.a('function'); + expect(bytesFormatter).toEqual(expect.any(Function)); }); it('formats with B/KB/MB/GB', function() { - expect(bytesFormatter(10)).to.equal('10B/s'); - expect(bytesFormatter(10 * 1024)).to.equal('10KB/s'); - expect(bytesFormatter(10.2 * 1024 * 1024)).to.equal('10.2MB/s'); - expect(bytesFormatter(3 * 1024 * 1024 * 1024)).to.equal('3GB/s'); + expect(bytesFormatter(10)).toEqual('10B/s'); + expect(bytesFormatter(10 * 1024)).toEqual('10KB/s'); + expect(bytesFormatter(10.2 * 1024 * 1024)).toEqual('10.2MB/s'); + expect(bytesFormatter(3 * 1024 * 1024 * 1024)).toEqual('3GB/s'); }); it('formats negative values with B/KB/MB/GB', function() { - expect(bytesFormatter(-10)).to.equal('-10B/s'); - expect(bytesFormatter(-10 * 1024)).to.equal('-10KB/s'); - expect(bytesFormatter(-10.2 * 1024 * 1024)).to.equal('-10.2MB/s'); - expect(bytesFormatter(-3 * 1024 * 1024 * 1024)).to.equal('-3GB/s'); + expect(bytesFormatter(-10)).toEqual('-10B/s'); + expect(bytesFormatter(-10 * 1024)).toEqual('-10KB/s'); + expect(bytesFormatter(-10.2 * 1024 * 1024)).toEqual('-10.2MB/s'); + expect(bytesFormatter(-3 * 1024 * 1024 * 1024)).toEqual('-3GB/s'); }); }); describe('Currency mode', function() { - let currencyFormatter; + let currencyFormatter: any; beforeEach(function() { currencyFormatter = formatters.currency; }); it('is a function', function() { - expect(currencyFormatter).to.be.a('function'); + expect(currencyFormatter).toEqual(expect.any(Function)); }); it('formats with $ by default', function() { @@ -143,7 +142,7 @@ describe('Tick Formatters', function() { units: {}, }, }; - expect(currencyFormatter(10.2, axis)).to.equal('$10.20'); + expect(currencyFormatter(10.2, axis)).toEqual('$10.20'); }); it('accepts currency in ISO 4217', function() { @@ -155,18 +154,18 @@ describe('Tick Formatters', function() { }, }; - expect(currencyFormatter(10.2, axis)).to.equal('CN¥10.20'); + expect(currencyFormatter(10.2, axis)).toEqual('CN¥10.20'); }); }); describe('Percent mode', function() { - let percentFormatter; + let percentFormatter: any; beforeEach(function() { percentFormatter = formatters.percent; }); it('is a function', function() { - expect(percentFormatter).to.be.a('function'); + expect(percentFormatter).toEqual(expect.any(Function)); }); it('formats with %', function() { @@ -175,7 +174,7 @@ describe('Tick Formatters', function() { units: {}, }, }; - expect(percentFormatter(0.1234, axis)).to.equal('12%'); + expect(percentFormatter(0.1234, axis)).toEqual('12%'); }); it('formats with % with decimal precision', function() { @@ -189,18 +188,18 @@ describe('Tick Formatters', function() { }, }, }; - expect(percentFormatter(0.12345, axis)).to.equal('12.345%'); + expect(percentFormatter(0.12345, axis)).toEqual('12.345%'); }); }); describe('Custom mode', function() { - let customFormatter; + let customFormatter: any; beforeEach(function() { customFormatter = formatters.custom; }); it('is a function', function() { - expect(customFormatter).to.be.a('function'); + expect(customFormatter).toEqual(expect.any(Function)); }); it('accepts prefix and suffix', function() { @@ -214,7 +213,7 @@ describe('Tick Formatters', function() { tickDecimals: 1, }; - expect(customFormatter(10.2, axis)).to.equal('prefix10.2suffix'); + expect(customFormatter(10.2, axis)).toEqual('prefix10.2suffix'); }); it('correctly renders small values', function() { @@ -228,7 +227,7 @@ describe('Tick Formatters', function() { tickDecimals: 3, }; - expect(customFormatter(0.00499999999999999, axis)).to.equal('prefix0.005suffix'); + expect(customFormatter(0.00499999999999999, axis)).toEqual('prefix0.005suffix'); }); }); }); diff --git a/src/legacy/core_plugins/timelion/public/services/tick_formatters.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts similarity index 79% rename from src/legacy/core_plugins/timelion/public/services/tick_formatters.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts index 2c78d2423cc06f..c80f9c3ed5f4b1 100644 --- a/src/legacy/core_plugins/timelion/public/services/tick_formatters.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts @@ -17,9 +17,11 @@ * under the License. */ -import _ from 'lodash'; +import { get } from 'lodash'; -function baseTickFormatter(value: any, axis: any) { +import { Axis } from './panel_utils'; + +function baseTickFormatter(value: number, axis: Axis) { const factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; const formatted = '' + Math.round(value * factor) / factor; @@ -40,8 +42,8 @@ function baseTickFormatter(value: any, axis: any) { return formatted; } -function unitFormatter(divisor: any, units: any) { - return (val: any) => { +function unitFormatter(divisor: number, units: string[]) { + return (val: number) => { let index = 0; const isNegative = val < 0; val = Math.abs(val); @@ -55,20 +57,20 @@ function unitFormatter(divisor: any, units: any) { } export function tickFormatters() { - const formatters = { + return { bits: unitFormatter(1000, ['b', 'kb', 'mb', 'gb', 'tb', 'pb']), 'bits/s': unitFormatter(1000, ['b/s', 'kb/s', 'mb/s', 'gb/s', 'tb/s', 'pb/s']), bytes: unitFormatter(1024, ['B', 'KB', 'MB', 'GB', 'TB', 'PB']), 'bytes/s': unitFormatter(1024, ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s']), - currency(val: any, axis: any) { + currency(val: number, axis: Axis) { return val.toLocaleString('en', { style: 'currency', - currency: axis.options.units.prefix || 'USD', + currency: (axis && axis.options && axis.options.units.prefix) || 'USD', }); }, - percent(val: any, axis: any) { + percent(val: number, axis: Axis) { let precision = - _.get(axis, 'tickDecimals', 0) - _.get(axis, 'options.units.tickDecimalsShift', 0); + get(axis, 'tickDecimals', 0) - get(axis, 'options.units.tickDecimalsShift', 0); // toFixed only accepts values between 0 and 20 if (precision < 0) { precision = 0; @@ -78,13 +80,11 @@ export function tickFormatters() { return (val * 100).toFixed(precision) + '%'; }, - custom(val: any, axis: any) { + custom(val: number, axis: Axis) { const formattedVal = baseTickFormatter(val, axis); - const prefix = axis.options.units.prefix; - const suffix = axis.options.units.suffix; + const prefix = axis && axis.options && axis.options.units.prefix; + const suffix = axis && axis.options && axis.options.units.suffix; return prefix + formattedVal + suffix; }, }; - - return formatters; } diff --git a/src/legacy/core_plugins/timelion/public/__tests__/_tick_generator.js b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.test.ts similarity index 72% rename from src/legacy/core_plugins/timelion/public/__tests__/_tick_generator.js rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.test.ts index e42657374af4cc..d1d959dee9501d 100644 --- a/src/legacy/core_plugins/timelion/public/__tests__/_tick_generator.js +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.test.ts @@ -17,11 +17,10 @@ * under the License. */ -import expect from '@kbn/expect'; -import { generateTicksProvider } from '../panels/timechart/tick_generator'; +import { generateTicksProvider } from './tick_generator'; describe('Tick Generator', function() { - let generateTicks; + let generateTicks: any; beforeEach(function() { generateTicks = generateTicksProvider(); @@ -29,7 +28,7 @@ describe('Tick Generator', function() { describe('generateTicksProvider()', function() { it('should return a function', function() { - expect(generateTicks).to.be.a('function'); + expect(generateTicks).toEqual(expect.any(Function)); }); }); @@ -58,14 +57,14 @@ describe('Tick Generator', function() { let n = 1; while (Math.pow(2, n) < axis.delta) n++; const expectedDelta = Math.pow(2, n); - const expectedNr = parseInt((axis.max - axis.min) / expectedDelta) + 2; - expect(ticks instanceof Array).to.be(true); - expect(ticks.length).to.be(expectedNr); - expect(ticks[0]).to.equal(axis.min); - expect(ticks[parseInt(ticks.length / 2)]).to.equal( - axis.min + expectedDelta * parseInt(ticks.length / 2) + const expectedNr = Math.floor((axis.max - axis.min) / expectedDelta) + 2; + expect(ticks instanceof Array).toBeTruthy(); + expect(ticks.length).toBe(expectedNr); + expect(ticks[0]).toEqual(axis.min); + expect(ticks[Math.floor(ticks.length / 2)]).toEqual( + axis.min + expectedDelta * Math.floor(ticks.length / 2) ); - expect(ticks[ticks.length - 1]).to.equal(axis.min + expectedDelta * (ticks.length - 1)); + expect(ticks[ticks.length - 1]).toEqual(axis.min + expectedDelta * (ticks.length - 1)); }); }); }); diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/tick_generator.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.ts similarity index 82% rename from src/legacy/core_plugins/timelion/public/panels/timechart/tick_generator.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.ts index f7d696a0316db1..6321ad01418ac0 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/tick_generator.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.ts @@ -17,15 +17,17 @@ * under the License. */ +import { Axis } from './panel_utils'; + export function generateTicksProvider() { - function floorInBase(n: any, base: any) { + function floorInBase(n: number, base: number) { return base * Math.floor(n / base); } - function generateTicks(axis: any) { + function generateTicks(axis: Axis) { const returnTicks = []; let tickSize = 2; - let delta = axis.delta; + let delta = axis.delta || 0; let steps = 0; let tickVal; let tickCount = 0; @@ -46,16 +48,14 @@ export function generateTicksProvider() { axis.tickSize = tickSize * Math.pow(1024, steps); // Calculate the new ticks - const tickMin = floorInBase(axis.min, axis.tickSize); + const tickMin = floorInBase(axis.min || 0, axis.tickSize); do { tickVal = tickMin + tickCount++ * axis.tickSize; returnTicks.push(tickVal); - } while (tickVal < axis.max); + } while (tickVal < (axis.max || 0)); return returnTicks; } - return function(axis: any) { - return generateTicks(axis); - }; + return (axis: Axis) => generateTicks(axis); } diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts similarity index 83% rename from src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts index 14cd3d0083e6ab..de066b474d987f 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts @@ -17,13 +17,11 @@ * under the License. */ -// @ts-ignore -import { timezoneProvider } from 'ui/vis/lib/timezone'; import { KIBANA_CONTEXT_NAME } from 'src/plugins/expressions/public'; -import { VisParams } from 'ui/vis'; import { i18n } from '@kbn/i18n'; -import { TimelionVisualizationDependencies } from '../plugin'; import { TimeRange, esFilters, esQuery, Query } from '../../../../../plugins/data/public'; +import { timezoneProvider, VisParams } from '../legacy_imports'; +import { TimelionVisDependencies } from '../plugin'; interface Stats { cacheCount: number; @@ -33,9 +31,25 @@ interface Stats { sheetTime: number; } -interface Sheet { - list: Array>; - render: Record; +export interface Series { + _global?: boolean; + _hide?: boolean; + _id?: number; + _title?: string; + color?: string; + data: Array>; + fit: string; + label: string; + split: string; + stack?: boolean; + type: string; +} + +export interface Sheet { + list: Series[]; + render?: { + grid?: boolean; + }; type: string; } @@ -46,8 +60,11 @@ export interface TimelionSuccessResponse { type: KIBANA_CONTEXT_NAME; } -export function getTimelionRequestHandler(dependencies: TimelionVisualizationDependencies) { - const { uiSettings, http, timefilter } = dependencies; +export function getTimelionRequestHandler({ + uiSettings, + http, + timefilter, +}: TimelionVisDependencies) { const timezone = timezoneProvider(uiSettings)(); return async function({ diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/xaxis_formatter.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/xaxis_formatter.ts similarity index 86% rename from src/legacy/core_plugins/timelion/public/panels/timechart/xaxis_formatter.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/xaxis_formatter.ts index db3408dae33db4..5350b1cb26957c 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/xaxis_formatter.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/xaxis_formatter.ts @@ -20,12 +20,13 @@ import moment from 'moment'; import { i18n } from '@kbn/i18n'; +import { IUiSettingsClient } from 'kibana/public'; -export function xaxisFormatterProvider(config: any) { +export function xaxisFormatterProvider(config: IUiSettingsClient) { function getFormat(esInterval: any) { const parts = esInterval.match(/(\d+)(ms|s|m|h|d|w|M|y|)/); - if (parts == null || parts[1] == null || parts[2] == null) { + if (parts === null || parts[1] === null || parts[2] === null) { throw new Error( i18n.translate('timelion.panels.timechart.unknownIntervalErrorMessage', { defaultMessage: 'Unknown interval', @@ -48,7 +49,5 @@ export function xaxisFormatterProvider(config: any) { return config.get('dateFormat'); } - return function(esInterval: any) { - return getFormat(esInterval); - }; + return (esInterval: any) => getFormat(esInterval); } diff --git a/src/legacy/core_plugins/vis_type_timelion/public/index.scss b/src/legacy/core_plugins/vis_type_timelion/public/index.scss new file mode 100644 index 00000000000000..313f14a8acf69b --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/index.scss @@ -0,0 +1,5 @@ +@import 'src/legacy/ui/public/styles/styling_constants'; + +@import './timelion_vis'; +@import './timelion_editor'; +@import './components/index'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/index.ts b/src/legacy/core_plugins/vis_type_timelion/public/index.ts new file mode 100644 index 00000000000000..98cc35877094e3 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/index.ts @@ -0,0 +1,25 @@ +/* + * 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 { TimelionVisPlugin as Plugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new Plugin(initializerContext); +} diff --git a/src/plugins/data/public/autocomplete_provider/index.ts b/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts similarity index 54% rename from src/plugins/data/public/autocomplete_provider/index.ts rename to src/legacy/core_plugins/vis_type_timelion/public/legacy.ts index 6758bd7f379c12..9935f3d92f6bd1 100644 --- a/src/plugins/data/public/autocomplete_provider/index.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts @@ -16,25 +16,22 @@ * specific language governing permissions and limitations * under the License. */ -import { AutocompleteProvider } from './types'; -export class AutocompleteProviderRegister { - private readonly registeredProviders: Map = new Map(); +import { PluginInitializerContext } from 'kibana/public'; - /** @public **/ - public addProvider(language: string, provider: AutocompleteProvider): void { - if (language && provider) { - this.registeredProviders.set(language, provider); - } - } +import { npSetup, npStart } from './legacy_imports'; - /** @public **/ - public getProvider(language: string): AutocompleteProvider | undefined { - return this.registeredProviders.get(language); - } +import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; +import { TimelionVisSetupDependencies } from './plugin'; +import { plugin } from '.'; - /** @internal **/ - public clearProviders(): void { - this.registeredProviders.clear(); - } -} +const setupPlugins: Readonly = { + expressions: npSetup.plugins.expressions, + data: npSetup.plugins.data, + visualizations: visualizationsSetup, +}; + +const pluginInstance = plugin({} as PluginInitializerContext); + +export const setup = pluginInstance.setup(npSetup.core, setupPlugins); +export const start = pluginInstance.start(npStart.core, npStart.plugins); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts new file mode 100644 index 00000000000000..8d1156862d27ee --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts @@ -0,0 +1,29 @@ +/* + * 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 { npSetup, npStart } from 'ui/new_platform'; +export { PluginsStart } from 'ui/new_platform/new_platform'; + +// @ts-ignore +export { DefaultEditorSize } from 'ui/vis/editor_size'; +// @ts-ignore +export { timezoneProvider } from 'ui/vis/lib/timezone'; +export { VisParams, Vis } from 'ui/vis'; +export { VisOptionsProps } from 'ui/vis/editors/default'; +export { useValidation } from 'ui/vis/editors/default/controls/agg_utils'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts new file mode 100644 index 00000000000000..69a2ad3c1351ae --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts @@ -0,0 +1,76 @@ +/* + * 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 { + CoreSetup, + CoreStart, + Plugin, + PluginInitializerContext, + IUiSettingsClient, + HttpSetup, +} from 'kibana/public'; +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 { getTimelionVisualizationConfig } from './timelion_vis_fn'; +import { getTimelionVisDefinition } from './timelion_vis_type'; +import { setIndexPatterns, setSavedObjectsClient } from './helpers/plugin_services'; + +type TimelionVisCoreSetup = CoreSetup; + +/** @internal */ +export interface TimelionVisDependencies extends Partial { + uiSettings: IUiSettingsClient; + http: HttpSetup; + timefilter: TimefilterContract; +} + +/** @internal */ +export interface TimelionVisSetupDependencies { + expressions: ReturnType; + visualizations: VisualizationsSetup; + data: DataPublicPluginSetup; +} + +/** @internal */ +export class TimelionVisPlugin implements Plugin { + constructor(public initializerContext: PluginInitializerContext) {} + + public async setup( + core: TimelionVisCoreSetup, + { expressions, visualizations, data }: TimelionVisSetupDependencies + ) { + const dependencies: TimelionVisDependencies = { + uiSettings: core.uiSettings, + http: core.http, + timefilter: data.query.timefilter.timefilter, + }; + + expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies)); + visualizations.types.createReactVisualization(getTimelionVisDefinition(dependencies)); + } + + public start(core: CoreStart, plugins: PluginsStart) { + setIndexPatterns(plugins.data.indexPatterns); + setSavedObjectsClient(core.savedObjects.client); + } +} diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx b/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx similarity index 89% rename from src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx rename to src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx index 527fcc3bc6ce87..be6829a76ac588 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx @@ -20,9 +20,9 @@ import React, { useCallback } from 'react'; import { EuiPanel } from '@elastic/eui'; -import { VisOptionsProps } from 'ui/vis/editors/default'; -import { VisParams } from '../timelion_vis_fn'; -import { TimelionInterval, TimelionExpressionInput } from '../components'; +import { VisOptionsProps } from './legacy_imports'; +import { VisParams } from './timelion_vis_fn'; +import { TimelionInterval, TimelionExpressionInput } from './components'; function TimelionOptions({ stateParams, setValue, setValidity }: VisOptionsProps) { const setInterval = useCallback((value: VisParams['interval']) => setValue('interval', value), [ diff --git a/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts similarity index 91% rename from src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts rename to src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts index 206f9f5d8368da..8a517b6cecbc73 100644 --- a/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts @@ -20,9 +20,9 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; import { ExpressionFunction, KibanaContext, Render } from 'src/plugins/expressions/public'; -import { getTimelionRequestHandler } from './vis/timelion_request_handler'; -import { TimelionVisualizationDependencies } from './plugin'; -import { TIMELION_VIS_NAME } from './vis'; +import { getTimelionRequestHandler } from './helpers/timelion_request_handler'; +import { TIMELION_VIS_NAME } from './timelion_vis_type'; +import { TimelionVisDependencies } from './plugin'; const name = 'timelion_vis'; @@ -42,7 +42,7 @@ export type VisParams = Arguments; type Return = Promise>; export const getTimelionVisualizationConfig = ( - dependencies: TimelionVisualizationDependencies + dependencies: TimelionVisDependencies ): ExpressionFunction => ({ name, type: 'render', diff --git a/src/legacy/core_plugins/timelion/public/vis/index.tsx b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx similarity index 73% rename from src/legacy/core_plugins/timelion/public/vis/index.tsx rename to src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx index 11d1a7385c408c..51540eea0223c9 100644 --- a/src/legacy/core_plugins/timelion/public/vis/index.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx @@ -20,20 +20,17 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { VisOptionsProps } from 'ui/vis/editors/default'; -import { DefaultEditorSize } from '../../../visualizations/public'; -import { KibanaContextProvider } from '../../../../../plugins/kibana_react/public'; -import { getTimelionRequestHandler } from './timelion_request_handler'; -import visConfigTemplate from './timelion_vis.html'; -import { TimelionVisualizationDependencies } from '../plugin'; -// @ts-ignore -import { AngularVisController } from '../../../../ui/public/vis/vis_types/angular_vis_type'; +import { KibanaContextProvider } from '../../../../plugins/kibana_react/public'; +import { DefaultEditorSize, VisOptionsProps } from './legacy_imports'; +import { getTimelionRequestHandler } from './helpers/timelion_request_handler'; +import { TimelionVisComponent, TimelionVisComponentProp } from './components'; import { TimelionOptions } from './timelion_options'; -import { VisParams } from '../timelion_vis_fn'; +import { VisParams } from './timelion_vis_fn'; +import { TimelionVisDependencies } from './plugin'; export const TIMELION_VIS_NAME = 'timelion'; -export function getTimelionVisualization(dependencies: TimelionVisualizationDependencies) { +export function getTimelionVisDefinition(dependencies: TimelionVisDependencies) { const { http, uiSettings } = dependencies; const timelionRequestHandler = getTimelionRequestHandler(dependencies); @@ -46,13 +43,16 @@ export function getTimelionVisualization(dependencies: TimelionVisualizationDepe description: i18n.translate('timelion.timelionDescription', { defaultMessage: 'Build time-series using functional expressions', }), - visualization: AngularVisController, visConfig: { defaults: { expression: '.es(*)', interval: 'auto', }, - template: visConfigTemplate, + component: (props: TimelionVisComponentProp) => ( + + + + ), }, editorConfig: { optionsTemplate: (props: VisOptionsProps) => ( diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.axislabels.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.axislabels.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.axislabels.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.axislabels.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.crosshair.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.crosshair.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.crosshair.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.crosshair.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.selection.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.selection.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.selection.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.selection.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.stack.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.stack.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.stack.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.stack.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.symbol.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.symbol.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.symbol.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.symbol.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.time.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.time.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.time.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.time.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/common/__tests__/agg_lookup.js b/src/legacy/core_plugins/vis_type_timeseries/common/__tests__/agg_lookup.js deleted file mode 100644 index f78f0a56604190..00000000000000 --- a/src/legacy/core_plugins/vis_type_timeseries/common/__tests__/agg_lookup.js +++ /dev/null @@ -1,61 +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 'chai'; -import { createOptions, isBasicAgg } from '../agg_lookup'; - -describe('aggLookup', () => { - describe('isBasicAgg(metric)', () => { - it('returns true for a basic metric (count)', () => { - expect(isBasicAgg({ type: 'count' })).to.equal(true); - }); - it('returns false for a pipeline metric (derivative)', () => { - expect(isBasicAgg({ type: 'derivative' })).to.equal(false); - }); - }); - - describe('createOptions(type, siblings)', () => { - it('returns options for all aggs', () => { - const options = createOptions(); - expect(options).to.have.length(30); - options.forEach(option => { - expect(option).to.have.property('label'); - expect(option).to.have.property('value'); - expect(option).to.have.property('disabled'); - }); - }); - - it('returns options for basic', () => { - const options = createOptions('basic'); - expect(options).to.have.length(15); - expect(options.every(opt => isBasicAgg({ type: opt.value }))).to.equal(true); - }); - - it('returns options for pipeline', () => { - const options = createOptions('pipeline'); - expect(options).to.have.length(15); - expect(options.every(opt => !isBasicAgg({ type: opt.value }))).to.equal(true); - }); - - it('returns options for all if given unknown key', () => { - const options = createOptions('foo'); - expect(options).to.have.length(30); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timeseries/common/agg_lookup.js b/src/legacy/core_plugins/vis_type_timeseries/common/agg_lookup.js index 0f6eba4add596b..4dfdc83dcfabb8 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/common/agg_lookup.js +++ b/src/legacy/core_plugins/vis_type_timeseries/common/agg_lookup.js @@ -127,25 +127,3 @@ const byType = { export function isBasicAgg(item) { return _.includes(Object.keys(byType.basic), item.type); } - -export function createOptions(type = '_all', siblings = []) { - let aggs = byType[type]; - if (!aggs) aggs = byType._all; - let enablePipelines = siblings.some(isBasicAgg); - if (siblings.length <= 1) enablePipelines = false; - return _(aggs) - .map((label, value) => { - const disabled = _.includes(pipeline, value) ? !enablePipelines : false; - return { - label: disabled - ? i18n.translate('visTypeTimeseries.aggLookup.addPipelineAggDescription', { - defaultMessage: '{label} (use the "+" button to add this pipeline agg)', - values: { label }, - }) - : label, - value, - disabled, - }; - }) - .value(); -} diff --git a/src/legacy/core_plugins/vis_type_timeseries/common/agg_lookup.test.js b/src/legacy/core_plugins/vis_type_timeseries/common/agg_lookup.test.js new file mode 100644 index 00000000000000..a7c5d362e669cd --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timeseries/common/agg_lookup.test.js @@ -0,0 +1,31 @@ +/* + * 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 { isBasicAgg } from './agg_lookup'; + +describe('aggLookup', () => { + describe('isBasicAgg(metric)', () => { + test('returns true for a basic metric (count)', () => { + expect(isBasicAgg({ type: 'count' })).toEqual(true); + }); + test('returns false for a pipeline metric (derivative)', () => { + expect(isBasicAgg({ type: 'derivative' })).toEqual(false); + }); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_timeseries/common/__tests__/calculate_label.js b/src/legacy/core_plugins/vis_type_timeseries/common/calculate_label.test.js similarity index 59% rename from src/legacy/core_plugins/vis_type_timeseries/common/__tests__/calculate_label.js rename to src/legacy/core_plugins/vis_type_timeseries/common/calculate_label.test.js index 955c052a3906e7..a5af6d114c894c 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/common/__tests__/calculate_label.js +++ b/src/legacy/core_plugins/vis_type_timeseries/common/calculate_label.test.js @@ -17,51 +17,50 @@ * under the License. */ -import { expect } from 'chai'; -import { calculateLabel } from '../calculate_label'; +import { calculateLabel } from './calculate_label'; describe('calculateLabel(metric, metrics)', () => { - it('returns "Unknown" for empty metric', () => { - expect(calculateLabel()).to.equal('Unknown'); + test('returns "Unknown" for empty metric', () => { + expect(calculateLabel()).toEqual('Unknown'); }); - it('returns the metric.alias if set', () => { - expect(calculateLabel({ alias: 'Example' })).to.equal('Example'); + test('returns the metric.alias if set', () => { + expect(calculateLabel({ alias: 'Example' })).toEqual('Example'); }); - it('returns "Count" for a count metric', () => { - expect(calculateLabel({ type: 'count' })).to.equal('Count'); + test('returns "Count" for a count metric', () => { + expect(calculateLabel({ type: 'count' })).toEqual('Count'); }); - it('returns "Calculation" for a bucket script metric', () => { - expect(calculateLabel({ type: 'calculation' })).to.equal('Bucket Script'); + test('returns "Calculation" for a bucket script metric', () => { + expect(calculateLabel({ type: 'calculation' })).toEqual('Bucket Script'); }); - it('returns formated label for series_agg', () => { + test('returns formated label for series_agg', () => { const label = calculateLabel({ type: 'series_agg', function: 'max' }); - expect(label).to.equal('Series Agg (max)'); + expect(label).toEqual('Series Agg (max)'); }); - it('returns formated label for basic aggs', () => { + test('returns formated label for basic aggs', () => { const label = calculateLabel({ type: 'avg', field: 'memory' }); - expect(label).to.equal('Average of memory'); + expect(label).toEqual('Average of memory'); }); - it('returns formated label for pipeline aggs', () => { + test('returns formated label for pipeline aggs', () => { const metric = { id: 2, type: 'derivative', field: 1 }; const metrics = [{ id: 1, type: 'max', field: 'network.out.bytes' }, metric]; const label = calculateLabel(metric, metrics); - expect(label).to.equal('Derivative of Max of network.out.bytes'); + expect(label).toEqual('Derivative of Max of network.out.bytes'); }); - it('returns formated label for derivative of percentile', () => { + test('returns formated label for derivative of percentile', () => { const metric = { id: 2, type: 'derivative', field: '1[50.0]' }; const metrics = [{ id: 1, type: 'percentile', field: 'network.out.bytes' }, metric]; const label = calculateLabel(metric, metrics); - expect(label).to.equal('Derivative of Percentile of network.out.bytes (50.0)'); + expect(label).toEqual('Derivative of Percentile of network.out.bytes (50.0)'); }); - it('returns formated label for pipeline aggs (deep)', () => { + test('returns formated label for pipeline aggs (deep)', () => { const metric = { id: 3, type: 'derivative', field: 2 }; const metrics = [ { id: 1, type: 'max', field: 'network.out.bytes' }, @@ -69,16 +68,16 @@ describe('calculateLabel(metric, metrics)', () => { metric, ]; const label = calculateLabel(metric, metrics); - expect(label).to.equal('Derivative of Moving Average of Max of network.out.bytes'); + expect(label).toEqual('Derivative of Moving Average of Max of network.out.bytes'); }); - it('returns formated label for pipeline aggs uses alias for field metric', () => { + test('returns formated label for pipeline aggs uses alias for field metric', () => { const metric = { id: 2, type: 'derivative', field: 1 }; const metrics = [ { id: 1, type: 'max', field: 'network.out.bytes', alias: 'Outbound Traffic' }, metric, ]; const label = calculateLabel(metric, metrics); - expect(label).to.equal('Derivative of Outbound Traffic'); + expect(label).toEqual('Derivative of Outbound Traffic'); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/calculate_siblings.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/calculate_siblings.test.js similarity index 88% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/calculate_siblings.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/calculate_siblings.test.js index 40807399a35a21..4f343022c4b0ff 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/calculate_siblings.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/calculate_siblings.test.js @@ -17,11 +17,10 @@ * under the License. */ -import { calculateSiblings } from '../calculate_siblings'; -import { expect } from 'chai'; +import { calculateSiblings } from './calculate_siblings'; describe('calculateSiblings(metrics, metric)', () => { - it('should return all siblings', () => { + test('should return all siblings', () => { const metrics = [ { id: 1, type: 'max', field: 'network.bytes' }, { id: 2, type: 'derivative', field: 1 }, @@ -30,7 +29,7 @@ describe('calculateSiblings(metrics, metric)', () => { { id: 5, type: 'count' }, ]; const siblings = calculateSiblings(metrics, { id: 2 }); - expect(siblings).to.eql([ + expect(siblings).toEqual([ { id: 1, type: 'max', field: 'network.bytes' }, { id: 5, type: 'count' }, ]); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/collection_actions.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/collection_actions.test.js similarity index 65% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/collection_actions.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/collection_actions.test.js index 5f97f559aa4bde..c76943cc8d6d75 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/collection_actions.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/collection_actions.test.js @@ -17,37 +17,35 @@ * under the License. */ -import sinon from 'sinon'; -import { expect } from 'chai'; -import { handleChange, handleAdd, handleDelete } from '../collection_actions'; +import { handleChange, handleAdd, handleDelete } from './collection_actions'; describe('collection actions', () => { - it('handleChange() calls props.onChange() with updated collection', () => { - const fn = sinon.spy(); + test('handleChange() calls props.onChange() with updated collection', () => { + const fn = jest.fn(); const props = { model: { test: [{ id: 1, title: 'foo' }] }, name: 'test', onChange: fn, }; handleChange.call(null, props, { id: 1, title: 'bar' }); - expect(fn.calledOnce).to.equal(true); - expect(fn.firstCall.args[0]).to.eql({ + expect(fn.mock.calls.length).toEqual(1); + expect(fn.mock.calls[0][0]).toEqual({ test: [{ id: 1, title: 'bar' }], }); }); - it('handleAdd() calls props.onChange() with update collection', () => { - const newItemFn = sinon.stub().returns({ id: 2, title: 'example' }); - const fn = sinon.spy(); + test('handleAdd() calls props.onChange() with update collection', () => { + const newItemFn = jest.fn(() => ({ id: 2, title: 'example' })); + const fn = jest.fn(); const props = { model: { test: [{ id: 1, title: 'foo' }] }, name: 'test', onChange: fn, }; handleAdd.call(null, props, newItemFn); - expect(fn.calledOnce).to.equal(true); - expect(newItemFn.calledOnce).to.equal(true); - expect(fn.firstCall.args[0]).to.eql({ + expect(fn.mock.calls.length).toEqual(1); + expect(newItemFn.mock.calls.length).toEqual(1); + expect(fn.mock.calls[0][0]).toEqual({ test: [ { id: 1, title: 'foo' }, { id: 2, title: 'example' }, @@ -55,16 +53,16 @@ describe('collection actions', () => { }); }); - it('handleDelete() calls props.onChange() with update collection', () => { - const fn = sinon.spy(); + test('handleDelete() calls props.onChange() with update collection', () => { + const fn = jest.fn(); const props = { model: { test: [{ id: 1, title: 'foo' }] }, name: 'test', onChange: fn, }; handleDelete.call(null, props, { id: 1 }); - expect(fn.calledOnce).to.equal(true); - expect(fn.firstCall.args[0]).to.eql({ + expect(fn.mock.calls.length).toEqual(1); + expect(fn.mock.calls[0][0]).toEqual({ test: [], }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/convert_series_to_vars.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/convert_series_to_vars.test.js similarity index 95% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/convert_series_to_vars.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/convert_series_to_vars.test.js index 40e3b302c1cf67..689b62962256af 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/convert_series_to_vars.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/convert_series_to_vars.test.js @@ -18,5 +18,5 @@ */ describe('convertSeriesToVars(series, model)', () => { - it('returns and object', () => {}); + test('returns and object', () => {}); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_number_handler.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_number_handler.test.js similarity index 70% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_number_handler.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_number_handler.test.js index fdbef9eac2d023..e24fe1f1d88d34 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_number_handler.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_number_handler.test.js @@ -17,9 +17,7 @@ * under the License. */ -import sinon from 'sinon'; -import { expect } from 'chai'; -import { createNumberHandler } from '../create_number_handler'; +import { createNumberHandler } from './create_number_handler'; describe('createNumberHandler()', () => { let handleChange; @@ -27,17 +25,17 @@ describe('createNumberHandler()', () => { let event; beforeEach(() => { - handleChange = sinon.spy(); + handleChange = jest.fn(); changeHandler = createNumberHandler(handleChange); - event = { preventDefault: sinon.spy(), target: { value: '1' } }; + event = { preventDefault: jest.fn(), target: { value: '1' } }; const fn = changeHandler('test'); fn(event); }); - it('calls handleChange() function with partial', () => { - expect(event.preventDefault.calledOnce).to.equal(true); - expect(handleChange.calledOnce).to.equal(true); - expect(handleChange.firstCall.args[0]).to.eql({ + test('calls handleChange() function with partial', () => { + expect(event.preventDefault.mock.calls.length).toEqual(1); + expect(handleChange.mock.calls.length).toEqual(1); + expect(handleChange.mock.calls[0][0]).toEqual({ test: 1, }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_select_handler.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_select_handler.test.js similarity index 77% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_select_handler.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_select_handler.test.js index 7bc9ae5de01628..a8d5351341c174 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_select_handler.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_select_handler.test.js @@ -17,24 +17,22 @@ * under the License. */ -import sinon from 'sinon'; -import { expect } from 'chai'; -import { createSelectHandler } from '../create_select_handler'; +import { createSelectHandler } from './create_select_handler'; describe('createSelectHandler()', () => { let handleChange; let changeHandler; beforeEach(() => { - handleChange = sinon.spy(); + handleChange = jest.fn(); changeHandler = createSelectHandler(handleChange); const fn = changeHandler('test'); fn([{ value: 'foo' }]); }); - it('calls handleChange() function with partial', () => { - expect(handleChange.calledOnce).to.equal(true); - expect(handleChange.firstCall.args[0]).to.eql({ + test('calls handleChange() function with partial', () => { + expect(handleChange.mock.calls.length).toEqual(1); + expect(handleChange.mock.calls[0][0]).toEqual({ test: 'foo', }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_text_handler.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_text_handler.test.js similarity index 70% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_text_handler.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_text_handler.test.js index 689fc5c4d7df03..a8a5377b4b0840 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_text_handler.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_text_handler.test.js @@ -17,9 +17,7 @@ * under the License. */ -import sinon from 'sinon'; -import { expect } from 'chai'; -import { createTextHandler } from '../create_text_handler'; +import { createTextHandler } from './create_text_handler'; describe('createTextHandler()', () => { let handleChange; @@ -27,17 +25,17 @@ describe('createTextHandler()', () => { let event; beforeEach(() => { - handleChange = sinon.spy(); + handleChange = jest.fn(); changeHandler = createTextHandler(handleChange); - event = { preventDefault: sinon.spy(), target: { value: 'foo' } }; + event = { preventDefault: jest.fn(), target: { value: 'foo' } }; const fn = changeHandler('test'); fn(event); }); - it('calls handleChange() function with partial', () => { - expect(event.preventDefault.calledOnce).to.equal(true); - expect(handleChange.calledOnce).to.equal(true); - expect(handleChange.firstCall.args[0]).to.eql({ + test('calls handleChange() function with partial', () => { + expect(event.preventDefault.mock.calls.length).toEqual(1); + expect(handleChange.mock.calls.length).toEqual(1); + expect(handleChange.mock.calls[0][0]).toEqual({ test: 'foo', }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/get_axis_label_string.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_axis_label_string.test.js similarity index 65% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/get_axis_label_string.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_axis_label_string.test.js index 455a5b262c9295..cfbd5ecb7bf655 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/get_axis_label_string.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_axis_label_string.test.js @@ -17,17 +17,18 @@ * under the License. */ -import { expect } from 'chai'; -import { getAxisLabelString } from '../get_axis_label_string'; +import { getAxisLabelString } from './get_axis_label_string'; + +jest.mock('ui/new_platform'); describe('getAxisLabelString(interval)', () => { - it('should return a valid label for 10 seconds', () => { - expect(getAxisLabelString(10000)).to.equal('per 10 seconds'); + test('should return a valid label for 10 seconds', () => { + expect(getAxisLabelString(10000)).toEqual('per 10 seconds'); }); - it('should return a valid label for 2 minutes', () => { - expect(getAxisLabelString(120000)).to.equal('per 2 minutes'); + test('should return a valid label for 2 minutes', () => { + expect(getAxisLabelString(120000)).toEqual('per 2 minutes'); }); - it('should return a valid label for 2 hour', () => { - expect(getAxisLabelString(7200000)).to.equal('per 2 hours'); + test('should return a valid label for 2 hour', () => { + expect(getAxisLabelString(7200000)).toEqual('per 2 hours'); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/re_id_series.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/re_id_series.test.js similarity index 64% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/re_id_series.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/re_id_series.test.js index b629e284a0fe43..7c646c7dde2e86 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/re_id_series.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/re_id_series.test.js @@ -18,24 +18,24 @@ */ import uuid from 'uuid'; -import { expect } from 'chai'; -import { reIdSeries } from '../re_id_series'; + +import { reIdSeries } from './re_id_series'; describe('reIdSeries()', () => { - it('reassign ids for series with just basic metrics', () => { + test('reassign ids for series with just basic metrics', () => { const series = { id: uuid.v1(), metrics: [{ id: uuid.v1() }, { id: uuid.v1() }], }; const newSeries = reIdSeries(series); - expect(newSeries).to.not.equal(series); - expect(newSeries.id).to.not.equal(series.id); + expect(newSeries).not.toEqual(series); + expect(newSeries.id).not.toEqual(series.id); newSeries.metrics.forEach((val, key) => { - expect(val.id).to.not.equal(series.metrics[key].id); + expect(val.id).not.toEqual(series.metrics[key].id); }); }); - it('reassign ids for series with just basic metrics and group by', () => { + test('reassign ids for series with just basic metrics and group by', () => { const firstMetricId = uuid.v1(); const series = { id: uuid.v1(), @@ -43,27 +43,27 @@ describe('reIdSeries()', () => { terms_order_by: firstMetricId, }; const newSeries = reIdSeries(series); - expect(newSeries).to.not.equal(series); - expect(newSeries.id).to.not.equal(series.id); + expect(newSeries).not.toEqual(series); + expect(newSeries.id).not.toEqual(series.id); newSeries.metrics.forEach((val, key) => { - expect(val.id).to.not.equal(series.metrics[key].id); + expect(val.id).not.toEqual(series.metrics[key].id); }); - expect(newSeries.terms_order_by).to.equal(newSeries.metrics[0].id); + expect(newSeries.terms_order_by).toEqual(newSeries.metrics[0].id); }); - it('reassign ids for series with pipeline metrics', () => { + test('reassign ids for series with pipeline metrics', () => { const firstMetricId = uuid.v1(); const series = { id: uuid.v1(), metrics: [{ id: firstMetricId }, { id: uuid.v1(), field: firstMetricId }], }; const newSeries = reIdSeries(series); - expect(newSeries).to.not.equal(series); - expect(newSeries.id).to.not.equal(series.id); - expect(newSeries.metrics[0].id).to.equal(newSeries.metrics[1].field); + expect(newSeries).not.toEqual(series); + expect(newSeries.id).not.toEqual(series.id); + expect(newSeries.metrics[0].id).toEqual(newSeries.metrics[1].field); }); - it('reassign ids for series with calculation vars', () => { + test('reassign ids for series with calculation vars', () => { const firstMetricId = uuid.v1(); const series = { id: uuid.v1(), @@ -77,8 +77,8 @@ describe('reIdSeries()', () => { ], }; const newSeries = reIdSeries(series); - expect(newSeries).to.not.equal(series); - expect(newSeries.id).to.not.equal(series.id); - expect(newSeries.metrics[1].variables[0].field).to.equal(newSeries.metrics[0].id); + expect(newSeries).not.toEqual(series); + expect(newSeries.id).not.toEqual(series.id); + expect(newSeries.metrics[1].variables[0].field).toEqual(newSeries.metrics[0].id); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/replace_vars.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/replace_vars.test.js similarity index 74% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/replace_vars.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/replace_vars.test.js index 0ac7427f6faccf..517958692dd2cd 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/replace_vars.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/replace_vars.test.js @@ -17,26 +17,25 @@ * under the License. */ -import { expect } from 'chai'; -import { replaceVars } from '../replace_vars'; +import { replaceVars } from './replace_vars'; describe('replaceVars(str, args, vars)', () => { - it('replaces vars with values', () => { + test('replaces vars with values', () => { const vars = { total: 100 }; const args = { host: 'test-01' }; const template = '# {{args.host}} {{total}}'; - expect(replaceVars(template, args, vars)).to.equal('# test-01 100'); + expect(replaceVars(template, args, vars)).toEqual('# test-01 100'); }); - it('replaces args override vars', () => { + test('replaces args override vars', () => { const vars = { total: 100, args: { test: 'foo-01' } }; const args = { test: 'bar-01' }; const template = '# {{args.test}} {{total}}'; - expect(replaceVars(template, args, vars)).to.equal('# bar-01 100'); + expect(replaceVars(template, args, vars)).toEqual('# bar-01 100'); }); - it('returns original string if error', () => { + test('returns original string if error', () => { const vars = { total: 100 }; const args = { host: 'test-01' }; const template = '# {{args.host}} {{total'; - expect(replaceVars(template, args, vars)).to.equal('# {{args.host}} {{total'); + expect(replaceVars(template, args, vars)).toEqual('# {{args.host}} {{total'); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/tick_formatter.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.test.js similarity index 50% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/tick_formatter.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.test.js index f31af4e8463055..76d3cff17343e1 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/tick_formatter.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.test.js @@ -17,64 +17,93 @@ * under the License. */ -import { expect } from 'chai'; -import { createTickFormatter } from '../tick_formatter'; +import { npStart } from 'ui/new_platform'; +import { createTickFormatter } from './tick_formatter'; +import { getFieldFormatsRegistry } from '../../../../../../test_utils/public/stub_field_formats'; + +const mockUiSettings = { + get: item => { + return mockUiSettings[item]; + }, + getUpdate$: () => ({ + subscribe: jest.fn(), + }), + 'query:allowLeadingWildcards': true, + 'query:queryString:options': {}, + 'courier:ignoreFilterIfFieldNotInIndex': true, + 'dateFormat:tz': 'Browser', + 'format:defaultTypeMap': {}, +}; + +const mockCore = { + chrome: {}, + uiSettings: mockUiSettings, + http: { + basePath: { + get: jest.fn(), + }, + }, +}; describe('createTickFormatter(format, template)', () => { - it('returns a number with two decimal place by default', () => { + npStart.plugins.data = { + fieldFormats: getFieldFormatsRegistry(mockCore), + }; + + test('returns a number with two decimal place by default', () => { const fn = createTickFormatter(); - expect(fn(1.5556)).to.equal('1.56'); + expect(fn(1.5556)).toEqual('1.56'); }); - it('returns a percent with percent formatter', () => { + test('returns a percent with percent formatter', () => { const config = { 'format:percent:defaultPattern': '0.[00]%', }; const fn = createTickFormatter('percent', null, key => config[key]); - expect(fn(0.5556)).to.equal('55.56%'); + expect(fn(0.5556)).toEqual('55.56%'); }); - it('returns a byte formatted string with byte formatter', () => { + test('returns a byte formatted string with byte formatter', () => { const config = { 'format:bytes:defaultPattern': '0.0b', }; const fn = createTickFormatter('bytes', null, key => config[key]); - expect(fn(1500 ^ 10)).to.equal('1.5KB'); + expect(fn(1500 ^ 10)).toEqual('1.5KB'); }); - it('returns a custom formatted string with custom formatter', () => { + test('returns a custom formatted string with custom formatter', () => { const fn = createTickFormatter('0.0a'); - expect(fn(1500)).to.equal('1.5k'); + expect(fn(1500)).toEqual('1.5k'); }); - it('returns a located string with custom locale setting', () => { + test('returns a located string with custom locale setting', () => { const config = { 'format:number:defaultLocale': 'fr', }; const fn = createTickFormatter('0,0.0', null, key => config[key]); - expect(fn(1500)).to.equal('1 500,0'); + expect(fn(1500)).toEqual('1 500,0'); }); - it('returns a custom formatted string with custom formatter and template', () => { + test('returns a custom formatted string with custom formatter and template', () => { const fn = createTickFormatter('0.0a', '{{value}}/s'); - expect(fn(1500)).to.equal('1.5k/s'); + expect(fn(1500)).toEqual('1.5k/s'); }); - it('returns "foo" if passed a string', () => { + test('returns "foo" if passed a string', () => { const fn = createTickFormatter(); - expect(fn('foo')).to.equal('foo'); + expect(fn('foo')).toEqual('foo'); }); - it('returns value if passed a bad formatter', () => { + test('returns value if passed a bad formatter', () => { const fn = createTickFormatter('102'); - expect(fn(100)).to.equal('100'); + expect(fn(100)).toEqual('100'); }); - it('returns formatted value if passed a bad template', () => { + test('returns formatted value if passed a bad template', () => { const config = { 'format:number:defaultPattern': '0,0.[00]', }; const fn = createTickFormatter('number', '{{value', key => config[key]); - expect(fn(1.5556)).to.equal('1.56'); + expect(fn(1.5556)).toEqual('1.56'); }); }); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/common/color_ranges.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/color_ranges.tsx index 2bf58de2f93edf..acbb93c8da1895 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/common/color_ranges.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/common/color_ranges.tsx @@ -24,10 +24,12 @@ import { i18n } from '@kbn/i18n'; import { RangeValues, RangesParamEditor } from '../../legacy_imports'; +export type SetColorRangeValue = (paramName: string, value: RangeValues[]) => void; + interface ColorRangesProps { 'data-test-subj'?: string; colorsRange: RangeValues[]; - setValue(paramName: string, value: RangeValues[]): void; + setValue: SetColorRangeValue; setValidity?(isValid: boolean): void; setTouched?(isTouched: boolean): void; } diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/ranges_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/ranges_panel.tsx index 71ffb22243a0f0..b54d9a5703fcd9 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/ranges_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/ranges_panel.tsx @@ -27,6 +27,7 @@ import { ColorRanges, ColorSchemaOptions, SwitchOption } from '../../common'; import { GaugeOptionsInternalProps } from '.'; import { ColorSchemaVislibParams } from '../../../types'; import { Gauge } from '../../../gauge'; +import { SetColorRangeValue } from '../../common/color_ranges'; function RangesPanel({ setGaugeValue, @@ -68,7 +69,7 @@ function RangesPanel({ diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/index.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/index.tsx index 7ce38fdcda1c8c..d8ca7acda7cbc8 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/index.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/index.tsx @@ -36,6 +36,7 @@ import { SetColorSchemaOptionsValue } from '../../common/color_schema'; import { HeatmapVisParams } from '../../../heatmap'; import { ValueAxis } from '../../../types'; import { LabelsPanel } from './labels_panel'; +import { SetColorRangeValue } from '../../common/color_ranges'; function HeatmapOptions(props: VisOptionsProps) { const { stateParams, vis, uiState, setValue, setValidity, setTouched } = props; @@ -171,7 +172,7 @@ function HeatmapOptions(props: VisOptionsProps) { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/index.ts b/src/legacy/core_plugins/vis_type_vislib/public/index.ts index 5abe79daf11faf..3b4bcb6bc3a7e2 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/index.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/index.ts @@ -23,3 +23,5 @@ import { KbnVislibVisTypesPlugin as Plugin } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { return new Plugin(initializerContext); } + +export { ColorModes } from './utils/collections'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__snapshots__/build_pipeline.test.ts.snap b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__snapshots__/build_pipeline.test.ts.snap index ddd74628e7940a..90c1d4472d5ea9 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__snapshots__/build_pipeline.test.ts.snap +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__snapshots__/build_pipeline.test.ts.snap @@ -8,7 +8,7 @@ exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunct exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metric function with buckets 1`] = `"metricvis metric={visdimension 0 } metric={visdimension 1 } "`; -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metric function with percentage mode should have percentage format 1`] = `"metricvis percentage=true metric={visdimension 0 format='percent' } "`; +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metric function with percentage mode should have percentage format 1`] = `"metricvis percentageMode=true metric={visdimension 0 format='percent' } "`; exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metric function without buckets 1`] = `"metricvis metric={visdimension 0 } metric={visdimension 1 } "`; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts index ab1664d612b35e..a3bf98b5e74a30 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts @@ -305,8 +305,8 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = { } let expr = `metricvis `; - expr += prepareValue('percentage', percentageMode); - expr += prepareValue('colorScheme', colorSchema); + expr += prepareValue('percentageMode', percentageMode); + expr += prepareValue('colorSchema', colorSchema); expr += prepareValue('colorMode', metricColorMode); expr += prepareValue('useRanges', useRanges); expr += prepareValue('invertColors', invertColors); diff --git a/src/plugins/data/public/autocomplete/autocomplete_service.ts b/src/plugins/data/public/autocomplete/autocomplete_service.ts new file mode 100644 index 00000000000000..0527f833b0f8c3 --- /dev/null +++ b/src/plugins/data/public/autocomplete/autocomplete_service.ts @@ -0,0 +1,77 @@ +/* + * 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 { CoreSetup } from 'src/core/public'; +import { QuerySuggestionsGetFn } from './providers/query_suggestion_provider'; +import { + setupValueSuggestionProvider, + ValueSuggestionsGetFn, +} from './providers/value_suggestion_provider'; + +export class AutocompleteService { + private readonly querySuggestionProviders: Map = new Map(); + private getValueSuggestions?: ValueSuggestionsGetFn; + + private addQuerySuggestionProvider = ( + language: string, + provider: QuerySuggestionsGetFn + ): void => { + if (language && provider) { + this.querySuggestionProviders.set(language, provider); + } + }; + + private getQuerySuggestions: QuerySuggestionsGetFn = args => { + const { language } = args; + const provider = this.querySuggestionProviders.get(language); + + if (provider) { + return provider(args); + } + }; + + private hasQuerySuggestions = (language: string) => this.querySuggestionProviders.has(language); + + /** @public **/ + public setup(core: CoreSetup) { + this.getValueSuggestions = setupValueSuggestionProvider(core); + + return { + addQuerySuggestionProvider: this.addQuerySuggestionProvider, + + /** @obsolete **/ + /** please use "getProvider" only from the start contract **/ + getQuerySuggestions: this.getQuerySuggestions, + }; + } + + /** @public **/ + public start() { + return { + getQuerySuggestions: this.getQuerySuggestions, + hasQuerySuggestions: this.hasQuerySuggestions, + getValueSuggestions: this.getValueSuggestions!, + }; + } + + /** @internal **/ + public clearProviders(): void { + this.querySuggestionProviders.clear(); + } +} diff --git a/src/plugins/data/public/suggestions_provider/index.ts b/src/plugins/data/public/autocomplete/index.ts similarity index 84% rename from src/plugins/data/public/suggestions_provider/index.ts rename to src/plugins/data/public/autocomplete/index.ts index c83662043b8224..5b8f3ae510bfd5 100644 --- a/src/plugins/data/public/suggestions_provider/index.ts +++ b/src/plugins/data/public/autocomplete/index.ts @@ -17,4 +17,5 @@ * under the License. */ -export { getSuggestionsProvider } from './value_suggestions'; +export { AutocompleteService } from './autocomplete_service'; +export { QuerySuggestion, QuerySuggestionType, QuerySuggestionsGetFn } from './types'; diff --git a/src/plugins/data/public/autocomplete_provider/types.ts b/src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts similarity index 59% rename from src/plugins/data/public/autocomplete_provider/types.ts rename to src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts index 389057f94144d2..53abdd44c0c3f3 100644 --- a/src/plugins/data/public/autocomplete_provider/types.ts +++ b/src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts @@ -17,56 +17,40 @@ * under the License. */ -import { AutocompleteProviderRegister } from '.'; -import { IIndexPattern, IFieldType } from '../../common'; +import { IFieldType, IIndexPattern } from '../../../common/index_patterns'; -export type AutocompletePublicPluginSetup = Pick< - AutocompleteProviderRegister, - 'addProvider' | 'getProvider' ->; -export type AutocompletePublicPluginStart = Pick; +export type QuerySuggestionType = 'field' | 'value' | 'operator' | 'conjunction' | 'recentSearch'; -/** @public **/ -export type AutocompleteProvider = (args: { - config: { - get(configKey: string): any; - }; - indexPatterns: IIndexPattern[]; - boolFilter?: any; -}) => GetSuggestions; +export type QuerySuggestionsGetFn = ( + args: QuerySuggestionsGetFnArgs +) => Promise | undefined; -/** @public **/ -export type GetSuggestions = (args: { +interface QuerySuggestionsGetFnArgs { + language: string; + indexPatterns: IIndexPattern[]; query: string; selectionStart: number; selectionEnd: number; signal?: AbortSignal; -}) => Promise; - -/** @public **/ -export type AutocompleteSuggestionType = - | 'field' - | 'value' - | 'operator' - | 'conjunction' - | 'recentSearch'; - -// A union type allows us to do easy type guards in the code. For example, if I want to ensure I'm -// working with a FieldAutocompleteSuggestion, I can just do `if ('field' in suggestion)` and the -// TypeScript compiler will narrow the type to the parts of the union that have a field prop. -/** @public **/ -export type AutocompleteSuggestion = BasicAutocompleteSuggestion | FieldAutocompleteSuggestion; + boolFilter?: any; +} -interface BasicAutocompleteSuggestion { +interface BasicQuerySuggestion { + type: QuerySuggestionType; description?: string; end: number; start: number; text: string; - type: AutocompleteSuggestionType; cursorIndex?: number; } -export type FieldAutocompleteSuggestion = BasicAutocompleteSuggestion & { +interface FieldQuerySuggestion extends BasicQuerySuggestion { type: 'field'; field: IFieldType; -}; +} + +// A union type allows us to do easy type guards in the code. For example, if I want to ensure I'm +// working with a FieldAutocompleteSuggestion, I can just do `if ('field' in suggestion)` and the +// TypeScript compiler will narrow the type to the parts of the union that have a field prop. +/** @public **/ +export type QuerySuggestion = BasicQuerySuggestion | FieldQuerySuggestion; diff --git a/src/plugins/data/public/suggestions_provider/value_suggestions.test.ts b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.test.ts similarity index 51% rename from src/plugins/data/public/suggestions_provider/value_suggestions.test.ts rename to src/plugins/data/public/autocomplete/providers/value_suggestion_provider.test.ts index 9089105b4e3a82..6b0c0f07cf6c95 100644 --- a/src/plugins/data/public/suggestions_provider/value_suggestions.test.ts +++ b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.test.ts @@ -17,98 +17,121 @@ * under the License. */ -import { stubIndexPattern, stubFields } from '../stubs'; -import { getSuggestionsProvider } from './value_suggestions'; -import { IUiSettingsClient } from 'kibana/public'; +import { stubIndexPattern, stubFields } from '../../stubs'; +import { setupValueSuggestionProvider, ValueSuggestionsGetFn } from './value_suggestion_provider'; +import { IUiSettingsClient, CoreSetup } from 'kibana/public'; -describe('getSuggestions', () => { - let getSuggestions: any; +describe('FieldSuggestions', () => { + let getValueSuggestions: ValueSuggestionsGetFn; let http: any; + let shouldSuggestValues: boolean; - describe('with value suggestions disabled', () => { - beforeEach(() => { - const config = { get: (key: string) => false } as IUiSettingsClient; - http = { fetch: jest.fn() }; - getSuggestions = getSuggestionsProvider(config, http); - }); + beforeEach(() => { + const uiSettings = { get: (key: string) => shouldSuggestValues } as IUiSettingsClient; + http = { fetch: jest.fn() }; + getValueSuggestions = setupValueSuggestionProvider({ http, uiSettings } as CoreSetup); + }); + + describe('with value suggestions disabled', () => { it('should return an empty array', async () => { - const index = stubIndexPattern.id; - const [field] = stubFields; - const query = ''; - const suggestions = await getSuggestions(index, field, query); + const suggestions = await getValueSuggestions({ + indexPattern: stubIndexPattern, + field: stubFields[0], + query: '', + }); + expect(suggestions).toEqual([]); expect(http.fetch).not.toHaveBeenCalled(); }); }); describe('with value suggestions enabled', () => { - beforeEach(() => { - const config = { get: (key: string) => true } as IUiSettingsClient; - http = { fetch: jest.fn() }; - getSuggestions = getSuggestionsProvider(config, http); - }); + shouldSuggestValues = true; it('should return true/false for boolean fields', async () => { - const index = stubIndexPattern.id; const [field] = stubFields.filter(({ type }) => type === 'boolean'); - const query = ''; - const suggestions = await getSuggestions(index, field, query); + const suggestions = await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + }); + expect(suggestions).toEqual([true, false]); expect(http.fetch).not.toHaveBeenCalled(); }); it('should return an empty array if the field type is not a string or boolean', async () => { - const index = stubIndexPattern.id; const [field] = stubFields.filter(({ type }) => type !== 'string' && type !== 'boolean'); - const query = ''; - const suggestions = await getSuggestions(index, field, query); + const suggestions = await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + }); + expect(suggestions).toEqual([]); expect(http.fetch).not.toHaveBeenCalled(); }); it('should return an empty array if the field is not aggregatable', async () => { - const index = stubIndexPattern.id; const [field] = stubFields.filter(({ aggregatable }) => !aggregatable); - const query = ''; - const suggestions = await getSuggestions(index, field, query); + const suggestions = await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + }); + expect(suggestions).toEqual([]); expect(http.fetch).not.toHaveBeenCalled(); }); it('should otherwise request suggestions', async () => { - const index = stubIndexPattern.id; const [field] = stubFields.filter( ({ type, aggregatable }) => type === 'string' && aggregatable ); - const query = ''; - await getSuggestions(index, field, query); + + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + }); + expect(http.fetch).toHaveBeenCalled(); }); it('should cache results if using the same index/field/query/filter', async () => { - const index = stubIndexPattern.id; const [field] = stubFields.filter( ({ type, aggregatable }) => type === 'string' && aggregatable ); - const query = ''; - await getSuggestions(index, field, query); - await getSuggestions(index, field, query); + const args = { + indexPattern: stubIndexPattern, + field, + query: '', + }; + + await getValueSuggestions(args); + await getValueSuggestions(args); + expect(http.fetch).toHaveBeenCalledTimes(1); }); it('should cache results for only one minute', async () => { - const index = stubIndexPattern.id; const [field] = stubFields.filter( ({ type, aggregatable }) => type === 'string' && aggregatable ); - const query = ''; + const args = { + indexPattern: stubIndexPattern, + field, + query: '', + }; const { now } = Date; Date.now = jest.fn(() => 0); - await getSuggestions(index, field, query); + + await getValueSuggestions(args); + Date.now = jest.fn(() => 60 * 1000); - await getSuggestions(index, field, query); + await getValueSuggestions(args); Date.now = now; expect(http.fetch).toHaveBeenCalledTimes(2); @@ -118,14 +141,54 @@ describe('getSuggestions', () => { const fields = stubFields.filter( ({ type, aggregatable }) => type === 'string' && aggregatable ); - await getSuggestions('index', fields[0], ''); - await getSuggestions('index', fields[0], 'query'); - await getSuggestions('index', fields[1], ''); - await getSuggestions('index', fields[1], 'query'); - await getSuggestions('logstash-*', fields[0], ''); - await getSuggestions('logstash-*', fields[0], 'query'); - await getSuggestions('logstash-*', fields[1], ''); - await getSuggestions('logstash-*', fields[1], 'query'); + + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field: fields[0], + query: '', + }); + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field: fields[0], + query: 'query', + }); + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field: fields[1], + query: '', + }); + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field: fields[1], + query: 'query', + }); + + const customIndexPattern = { + ...stubIndexPattern, + title: 'customIndexPattern', + }; + + await getValueSuggestions({ + indexPattern: customIndexPattern, + field: fields[0], + query: '', + }); + await getValueSuggestions({ + indexPattern: customIndexPattern, + field: fields[0], + query: 'query', + }); + await getValueSuggestions({ + indexPattern: customIndexPattern, + field: fields[1], + query: '', + }); + await getValueSuggestions({ + indexPattern: customIndexPattern, + field: fields[1], + query: 'query', + }); + expect(http.fetch).toHaveBeenCalledTimes(8); }); }); diff --git a/src/plugins/data/public/suggestions_provider/value_suggestions.ts b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts similarity index 55% rename from src/plugins/data/public/suggestions_provider/value_suggestions.ts rename to src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts index e64156c290db10..5df88000edbd5f 100644 --- a/src/plugins/data/public/suggestions_provider/value_suggestions.ts +++ b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts @@ -18,51 +18,53 @@ */ import { memoize } from 'lodash'; +import { CoreSetup } from 'src/core/public'; +import { IIndexPattern, IFieldType } from '../../../common'; -import { IUiSettingsClient, HttpSetup } from 'src/core/public'; -import { IGetSuggestions } from './types'; -import { IFieldType } from '../../common'; +function resolver(title: string, field: IFieldType, query: string, boolFilter: any) { + // Only cache results for a minute + const ttl = Math.floor(Date.now() / 1000 / 60); + + return [ttl, query, title, field.name, JSON.stringify(boolFilter)].join('|'); +} + +export type ValueSuggestionsGetFn = (args: ValueSuggestionsGetFnArgs) => Promise; + +interface ValueSuggestionsGetFnArgs { + indexPattern: IIndexPattern; + field: IFieldType; + query: string; + boolFilter?: any[]; + signal?: AbortSignal; +} -export function getSuggestionsProvider( - uiSettings: IUiSettingsClient, - http: HttpSetup -): IGetSuggestions { +export const setupValueSuggestionProvider = (core: CoreSetup): ValueSuggestionsGetFn => { const requestSuggestions = memoize( - ( - index: string, - field: IFieldType, - query: string, - boolFilter: any = [], - signal?: AbortSignal - ) => { - return http.fetch(`/api/kibana/suggestions/values/${index}`, { + (index: string, field: IFieldType, query: string, boolFilter: any = [], signal?: AbortSignal) => + core.http.fetch(`/api/kibana/suggestions/values/${index}`, { method: 'POST', body: JSON.stringify({ query, field: field.name, boolFilter }), signal, - }); - }, + }), resolver ); - return async ( - index: string, - field: IFieldType, - query: string, - boolFilter?: any, - signal?: AbortSignal - ) => { - const shouldSuggestValues = uiSettings.get('filterEditor:suggestValues'); + return async ({ + indexPattern, + field, + query, + boolFilter, + signal, + }: ValueSuggestionsGetFnArgs): Promise => { + const shouldSuggestValues = core!.uiSettings.get('filterEditor:suggestValues'); + const { title } = indexPattern; + if (field.type === 'boolean') { return [true, false]; } else if (!shouldSuggestValues || !field.aggregatable || field.type !== 'string') { return []; } - return await requestSuggestions(index, field, query, boolFilter, signal); - }; -} -function resolver(index: string, field: IFieldType, query: string, boolFilter: any) { - // Only cache results for a minute - const ttl = Math.floor(Date.now() / 1000 / 60); - return [ttl, query, index, field.name, JSON.stringify(boolFilter)].join('|'); -} + return await requestSuggestions(title, field, query, boolFilter, signal); + }; +}; diff --git a/src/plugins/data/public/suggestions_provider/types.ts b/src/plugins/data/public/autocomplete/types.ts similarity index 67% rename from src/plugins/data/public/suggestions_provider/types.ts rename to src/plugins/data/public/autocomplete/types.ts index a13ecfb10143fe..759e2dd25a5bc1 100644 --- a/src/plugins/data/public/suggestions_provider/types.ts +++ b/src/plugins/data/public/autocomplete/types.ts @@ -16,11 +16,18 @@ * specific language governing permissions and limitations * under the License. */ -import { IFieldType } from '../../common'; -export type IGetSuggestions = ( - index: string, - field: IFieldType, - query: string, - boolFilter?: any -) => any; +import { AutocompleteService } from './autocomplete_service'; + +/** @public **/ +export type AutocompleteSetup = ReturnType; + +/** @public **/ +export type AutocompleteStart = ReturnType; + +/** @public **/ +export { + QuerySuggestion, + QuerySuggestionsGetFn, + QuerySuggestionType, +} from './providers/query_suggestion_provider'; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 4b330600417e7d..19ba246ce02dd2 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -18,6 +18,8 @@ */ import { PluginInitializerContext } from '../../../core/public'; +import * as autocomplete from './autocomplete'; + export function plugin(initializerContext: PluginInitializerContext) { return new DataPublicPlugin(initializerContext); } @@ -49,11 +51,6 @@ export { TimeRange, } from '../common'; -/** - * Static code to be shared externally - * @public - */ -export * from './autocomplete_provider'; export * from './field_formats_provider'; export * from './index_patterns'; export * from './search'; @@ -97,3 +94,5 @@ export { // Export plugin after all other imports import { DataPublicPlugin } from './plugin'; export { DataPublicPlugin as Plugin }; + +export { autocomplete }; diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index 03d3dad61ed052..08a7a3ef11537b 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -30,9 +30,9 @@ export type Setup = jest.Mocked>; export type Start = jest.Mocked>; const autocompleteMock: any = { - addProvider: jest.fn(), - getProvider: jest.fn(), - clearProviders: jest.fn(), + getValueSuggestions: jest.fn(), + getQuerySuggestions: jest.fn(), + hasQuerySuggestions: jest.fn(), }; const fieldFormatsMock: PublicMethodsOf = { diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index cd55048ca527fa..78abcbcfec467f 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -25,8 +25,7 @@ import { DataSetupDependencies, DataStartDependencies, } from './types'; -import { AutocompleteProviderRegister } from './autocomplete_provider'; -import { getSuggestionsProvider } from './suggestions_provider'; +import { AutocompleteService } from './autocomplete'; import { SearchService } from './search/search_service'; import { FieldFormatsService } from './field_formats_provider'; import { QueryService } from './query'; @@ -38,7 +37,7 @@ import { APPLY_FILTER_TRIGGER } from '../../embeddable/public'; import { createSearchBar } from './ui/search_bar/create_search_bar'; export class DataPublicPlugin implements Plugin { - private readonly autocomplete = new AutocompleteProviderRegister(); + private readonly autocomplete = new AutocompleteService(); private readonly searchService: SearchService; private readonly fieldFormatsService: FieldFormatsService; private readonly queryService: QueryService; @@ -62,7 +61,7 @@ export class DataPublicPlugin implements Plugin { appName: string; uiSettings: CoreStart['uiSettings']; diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx b/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx index 61290cc16b8a88..2b2d83c9f5a8ba 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx @@ -63,13 +63,19 @@ export class PhraseSuggestorUI extends Component this.updateSuggestions(`${value}`); }; - protected updateSuggestions = debounce(async (value: string = '') => { + protected updateSuggestions = debounce(async (query: string = '') => { const { indexPattern, field } = this.props as PhraseSuggestorProps; if (!field || !this.isSuggestingValues()) { return; } this.setState({ isLoading: true }); - const suggestions = await this.services.data.getSuggestions(indexPattern.title, field, value); + + const suggestions = await this.services.data.autocomplete.getValueSuggestions({ + indexPattern, + field, + query, + }); + this.setState({ suggestions, isLoading: false }); }, 500); } diff --git a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap index 1fb39710f67548..bc08c87304fcab 100644 --- a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap +++ b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap @@ -151,9 +151,9 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA }, "data": Object { "autocomplete": Object { - "addProvider": [MockFunction], - "clearProviders": [MockFunction], - "getProvider": [MockFunction], + "getQuerySuggestions": [MockFunction], + "getValueSuggestions": [MockFunction], + "hasQuerySuggestions": [MockFunction], }, "fieldFormats": Object { "getByFieldType": [MockFunction], @@ -777,9 +777,9 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA }, "data": Object { "autocomplete": Object { - "addProvider": [MockFunction], - "clearProviders": [MockFunction], - "getProvider": [MockFunction], + "getQuerySuggestions": [MockFunction], + "getValueSuggestions": [MockFunction], + "hasQuerySuggestions": [MockFunction], }, "fieldFormats": Object { "getByFieldType": [MockFunction], @@ -1385,9 +1385,9 @@ exports[`QueryStringInput Should pass the query language to the language switche }, "data": Object { "autocomplete": Object { - "addProvider": [MockFunction], - "clearProviders": [MockFunction], - "getProvider": [MockFunction], + "getQuerySuggestions": [MockFunction], + "getValueSuggestions": [MockFunction], + "hasQuerySuggestions": [MockFunction], }, "fieldFormats": Object { "getByFieldType": [MockFunction], @@ -2008,9 +2008,9 @@ exports[`QueryStringInput Should pass the query language to the language switche }, "data": Object { "autocomplete": Object { - "addProvider": [MockFunction], - "clearProviders": [MockFunction], - "getProvider": [MockFunction], + "getQuerySuggestions": [MockFunction], + "getValueSuggestions": [MockFunction], + "hasQuerySuggestions": [MockFunction], }, "fieldFormats": Object { "getByFieldType": [MockFunction], @@ -2616,9 +2616,9 @@ exports[`QueryStringInput Should render the given query 1`] = ` }, "data": Object { "autocomplete": Object { - "addProvider": [MockFunction], - "clearProviders": [MockFunction], - "getProvider": [MockFunction], + "getQuerySuggestions": [MockFunction], + "getValueSuggestions": [MockFunction], + "hasQuerySuggestions": [MockFunction], }, "fieldFormats": Object { "getByFieldType": [MockFunction], @@ -3239,9 +3239,9 @@ exports[`QueryStringInput Should render the given query 1`] = ` }, "data": Object { "autocomplete": Object { - "addProvider": [MockFunction], - "clearProviders": [MockFunction], - "getProvider": [MockFunction], + "getQuerySuggestions": [MockFunction], + "getValueSuggestions": [MockFunction], + "hasQuerySuggestions": [MockFunction], }, "fieldFormats": Object { "getByFieldType": [MockFunction], diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx index 960a843f98ab90..cf219c35bcced8 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx @@ -35,8 +35,7 @@ import { InjectedIntl, injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { debounce, compact, isEqual } from 'lodash'; import { Toast } from 'src/core/public'; import { - AutocompleteSuggestion, - AutocompleteSuggestionType, + autocomplete, IDataPluginServices, IIndexPattern, PersistedLog, @@ -71,7 +70,7 @@ interface Props { interface State { isSuggestionsVisible: boolean; index: number | null; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; suggestionLimit: number; selectionStart: number | null; selectionEnd: number | null; @@ -90,7 +89,7 @@ const KEY_CODES = { END: 35, }; -const recentSearchType: AutocompleteSuggestionType = 'recentSearch'; +const recentSearchType: autocomplete.QuerySuggestionType = 'recentSearch'; export class QueryStringInputUI extends Component { public state: State = { @@ -138,15 +137,14 @@ export class QueryStringInputUI extends Component { return; } - const uiSettings = this.services.uiSettings; const language = this.props.query.language; const queryString = this.getQueryString(); const recentSearchSuggestions = this.getRecentSearchSuggestions(queryString); - const autocompleteProvider = this.services.data.autocomplete.getProvider(language); + const hasQuerySuggestions = this.services.data.autocomplete.hasQuerySuggestions(language); if ( - !autocompleteProvider || + !hasQuerySuggestions || !Array.isArray(this.state.indexPatterns) || compact(this.state.indexPatterns).length === 0 ) { @@ -154,10 +152,6 @@ export class QueryStringInputUI extends Component { } const indexPatterns = this.state.indexPatterns; - const getAutocompleteSuggestions = autocompleteProvider({ - config: uiSettings, - indexPatterns, - }); const { selectionStart, selectionEnd } = this.inputRef; if (selectionStart === null || selectionEnd === null) { @@ -167,12 +161,16 @@ export class QueryStringInputUI extends Component { try { if (this.abortController) this.abortController.abort(); this.abortController = new AbortController(); - const suggestions: AutocompleteSuggestion[] = await getAutocompleteSuggestions({ - query: queryString, - selectionStart, - selectionEnd, - signal: this.abortController.signal, - }); + const suggestions = + (await this.services.data.autocomplete.getQuerySuggestions({ + language, + indexPatterns, + query: queryString, + selectionStart, + selectionEnd, + signal: this.abortController.signal, + })) || []; + return [...suggestions, ...recentSearchSuggestions]; } catch (e) { // TODO: Waiting on https://github.com/elastic/kibana/issues/51406 for a properly typed error @@ -321,7 +319,7 @@ export class QueryStringInputUI extends Component { } }; - private selectSuggestion = (suggestion: AutocompleteSuggestion) => { + private selectSuggestion = (suggestion: autocomplete.QuerySuggestion) => { if (!this.inputRef) { return; } @@ -351,7 +349,7 @@ export class QueryStringInputUI extends Component { } }; - private handleNestedFieldSyntaxNotification = (suggestion: AutocompleteSuggestion) => { + private handleNestedFieldSyntaxNotification = (suggestion: autocomplete.QuerySuggestion) => { if ( 'field' in suggestion && suggestion.field.subType && @@ -453,7 +451,7 @@ export class QueryStringInputUI extends Component { } }; - private onClickSuggestion = (suggestion: AutocompleteSuggestion) => { + private onClickSuggestion = (suggestion: autocomplete.QuerySuggestion) => { if (!this.inputRef) { return; } diff --git a/src/plugins/data/public/ui/typeahead/suggestion_component.test.tsx b/src/plugins/data/public/ui/typeahead/suggestion_component.test.tsx index 591176bf133faf..0c5c701642757d 100644 --- a/src/plugins/data/public/ui/typeahead/suggestion_component.test.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestion_component.test.tsx @@ -19,14 +19,14 @@ import { mount, shallow } from 'enzyme'; import React from 'react'; -import { AutocompleteSuggestion } from '../..'; +import { autocomplete } from '../..'; import { SuggestionComponent } from './suggestion_component'; const noop = () => { return; }; -const mockSuggestion: AutocompleteSuggestion = { +const mockSuggestion: autocomplete.QuerySuggestion = { description: 'This is not a helpful suggestion', end: 0, start: 42, diff --git a/src/plugins/data/public/ui/typeahead/suggestion_component.tsx b/src/plugins/data/public/ui/typeahead/suggestion_component.tsx index fd29de4573ff07..1d2ac8dee1a8a0 100644 --- a/src/plugins/data/public/ui/typeahead/suggestion_component.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestion_component.tsx @@ -20,7 +20,7 @@ import { EuiIcon } from '@elastic/eui'; import classNames from 'classnames'; import React, { FunctionComponent } from 'react'; -import { AutocompleteSuggestion } from '../..'; +import { autocomplete } from '../..'; function getEuiIconType(type: string) { switch (type) { @@ -40,10 +40,10 @@ function getEuiIconType(type: string) { } interface Props { - onClick: (suggestion: AutocompleteSuggestion) => void; + onClick: (suggestion: autocomplete.QuerySuggestion) => void; onMouseEnter: () => void; selected: boolean; - suggestion: AutocompleteSuggestion; + suggestion: autocomplete.QuerySuggestion; innerRef: (node: HTMLDivElement) => void; ariaId: string; } diff --git a/src/plugins/data/public/ui/typeahead/suggestions_component.test.tsx b/src/plugins/data/public/ui/typeahead/suggestions_component.test.tsx index 7fb2fdf25104ac..b84f612b6d13a9 100644 --- a/src/plugins/data/public/ui/typeahead/suggestions_component.test.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestions_component.test.tsx @@ -19,7 +19,7 @@ import { mount, shallow } from 'enzyme'; import React from 'react'; -import { AutocompleteSuggestion } from '../..'; +import { autocomplete } from '../..'; import { SuggestionComponent } from './suggestion_component'; import { SuggestionsComponent } from './suggestions_component'; @@ -27,7 +27,7 @@ const noop = () => { return; }; -const mockSuggestions: AutocompleteSuggestion[] = [ +const mockSuggestions: autocomplete.QuerySuggestion[] = [ { description: 'This is not a helpful suggestion', end: 0, diff --git a/src/plugins/data/public/ui/typeahead/suggestions_component.tsx b/src/plugins/data/public/ui/typeahead/suggestions_component.tsx index e4cccbcde4fb8b..b37a2e479e874e 100644 --- a/src/plugins/data/public/ui/typeahead/suggestions_component.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestions_component.tsx @@ -19,15 +19,15 @@ import { isEmpty } from 'lodash'; import React, { Component } from 'react'; -import { AutocompleteSuggestion } from '../..'; +import { autocomplete } from '../..'; import { SuggestionComponent } from './suggestion_component'; interface Props { index: number | null; - onClick: (suggestion: AutocompleteSuggestion) => void; + onClick: (suggestion: autocomplete.QuerySuggestion) => void; onMouseEnter: (index: number) => void; show: boolean; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; loadMore: () => void; } diff --git a/src/plugins/management/public/management_app.tsx b/src/plugins/management/public/management_app.tsx index f7e8dba4f82100..705d98eaaf2ffd 100644 --- a/src/plugins/management/public/management_app.tsx +++ b/src/plugins/management/public/management_app.tsx @@ -34,7 +34,7 @@ export class ManagementApp { readonly basePath: string; readonly order: number; readonly mount: ManagementSectionMount; - protected enabledStatus: boolean = true; + private enabledStatus = true; constructor( { id, title, basePath, order = 100, mount }: CreateManagementApp, @@ -54,6 +54,11 @@ export class ManagementApp { title, mount: async ({}, params) => { let appUnmount: Unmount; + if (!this.enabledStatus) { + const [coreStart] = await getStartServices(); + coreStart.application.navigateToApp('kibana#/management'); + return () => {}; + } async function setBreadcrumbs(crumbs: ChromeBreadcrumb[]) { const [coreStart] = await getStartServices(); coreStart.chrome.setBreadcrumbs([ diff --git a/test/interpreter_functional/screenshots/baseline/metric_percentage.png b/test/interpreter_functional/screenshots/baseline/metric_percentage_mode.png similarity index 100% rename from test/interpreter_functional/screenshots/baseline/metric_percentage.png rename to test/interpreter_functional/screenshots/baseline/metric_percentage_mode.png diff --git a/test/interpreter_functional/snapshots/baseline/metric_percentage.json b/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json similarity index 100% rename from test/interpreter_functional/snapshots/baseline/metric_percentage.json rename to test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json diff --git a/test/interpreter_functional/snapshots/session/metric_percentage.json b/test/interpreter_functional/snapshots/session/metric_percentage_mode.json similarity index 100% rename from test/interpreter_functional/snapshots/session/metric_percentage.json rename to test/interpreter_functional/snapshots/session/metric_percentage_mode.json diff --git a/test/interpreter_functional/test_suites/run_pipeline/metric.ts b/test/interpreter_functional/test_suites/run_pipeline/metric.ts index c238bedfa28ce1..5f685037d4fad0 100644 --- a/test/interpreter_functional/test_suites/run_pipeline/metric.ts +++ b/test/interpreter_functional/test_suites/run_pipeline/metric.ts @@ -81,11 +81,15 @@ export default function({ ).toMatchScreenshot(); }); - it('with percentage option', async () => { + it('with percentageMode option', async () => { const expression = - 'metricVis metric={visdimension 0} percentage=true colorRange={range from=0 to=1000}'; + 'metricVis metric={visdimension 0} percentageMode=true colorRange={range from=0 to=1000}'; await ( - await expectExpression('metric_percentage', expression, dataContext).toMatchSnapshot() + await expectExpression( + 'metric_percentage_mode', + expression, + dataContext + ).toMatchSnapshot() ).toMatchScreenshot(); }); }); diff --git a/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx b/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx index 8b7cdd653ed8c3..f3b7a19f70ae3b 100644 --- a/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx +++ b/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx @@ -62,6 +62,22 @@ export class ManagementTestPlugin }; }, }); + + testSection! + .registerApp({ + id: 'test-management-disabled', + title: 'Management Test Disabled', + mount(params) { + params.setBreadcrumbs([{ text: 'Management Test Disabled' }]); + ReactDOM.render(
This is a secret that should never be seen!
, params.element); + + return () => { + ReactDOM.unmountComponentAtNode(params.element); + }; + }, + }) + .disable(); + return {}; } diff --git a/test/plugin_functional/test_suites/management/management_plugin.js b/test/plugin_functional/test_suites/management/management_plugin.js index d65fb1dcd3a7e3..0c185f4b385b51 100644 --- a/test/plugin_functional/test_suites/management/management_plugin.js +++ b/test/plugin_functional/test_suites/management/management_plugin.js @@ -36,5 +36,13 @@ export default function({ getService, getPageObjects }) { await testSubjects.click('test-management-link-basepath'); await testSubjects.existOrFail('test-management-link-one'); }); + + it('should redirect when app is disabled', async () => { + await PageObjects.common.navigateToActualUrl( + 'kibana', + 'management/test-section/test-management-disabled' + ); + await testSubjects.existOrFail('management-landing'); + }); }); } diff --git a/tsconfig.json b/tsconfig.json index a2da9c127e7ba6..811b05abeb648c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,8 @@ ], "test_utils/*": [ "src/test_utils/public/*" - ] + ], + "fixtures/*": ["src/fixtures/*"] }, // Support .tsx files and transform JSX into calls to React.createElement "jsx": "react", @@ -51,7 +52,8 @@ "types": [ "node", "jest", - "react" + "react", + "flot" ] }, "include": [ diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js index f38181ce56a2f3..b746f0ae258cd1 100644 --- a/x-pack/dev-tools/jest/create_jest_config.js +++ b/x-pack/dev-tools/jest/create_jest_config.js @@ -17,6 +17,7 @@ export function createJestConfig({ kibanaDirectory, xPackKibanaDirectory }) { moduleFileExtensions: ['js', 'json', 'ts', 'tsx'], moduleNameMapper: { '^ui/(.*)': `${kibanaDirectory}/src/legacy/ui/public/$1`, + '^fixtures/(.*)': `${kibanaDirectory}/src/fixtures/$1`, 'uiExports/(.*)': fileMockPath, '^src/core/(.*)': `${kibanaDirectory}/src/core/$1`, '^src/legacy/(.*)': `${kibanaDirectory}/src/legacy/$1`, diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts index 2af66059d9fed2..9b8cfe9b69ed04 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts @@ -883,7 +883,6 @@ describe('enable()', () => { schedule: { interval: '10s' }, alertTypeId: '2', enabled: true, - scheduledTaskId: 'task-123', updatedBy: 'elastic', apiKey: null, apiKeyOwner: null, @@ -892,6 +891,9 @@ describe('enable()', () => { version: '123', } ); + expect(savedObjectsClient.update).toHaveBeenCalledWith('alert', '1', { + scheduledTaskId: 'task-123', + }); expect(taskManager.schedule).toHaveBeenCalledWith({ taskType: `alerting:2`, params: { @@ -964,7 +966,6 @@ describe('enable()', () => { schedule: { interval: '10s' }, alertTypeId: '2', enabled: true, - scheduledTaskId: 'task-123', apiKey: Buffer.from('123:abc').toString('base64'), apiKeyOwner: 'elastic', updatedBy: 'elastic', @@ -973,6 +974,9 @@ describe('enable()', () => { version: '123', } ); + expect(savedObjectsClient.update).toHaveBeenCalledWith('alert', '1', { + scheduledTaskId: 'task-123', + }); expect(taskManager.schedule).toHaveBeenCalledWith({ taskType: `alerting:2`, params: { diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.ts index fe96a233b8663f..7801e8f478712e 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.ts @@ -362,7 +362,6 @@ export class AlertsClient { }); if (attributes.enabled === false) { - const scheduledTask = await this.scheduleAlert(id, attributes.alertTypeId); const username = await this.getUserName(); await this.savedObjectsClient.update( 'alert', @@ -372,11 +371,11 @@ export class AlertsClient { enabled: true, ...this.apiKeyAsAlertAttributes(await this.createAPIKey(), username), updatedBy: username, - - scheduledTaskId: scheduledTask.id, }, { version } ); + const scheduledTask = await this.scheduleAlert(id, attributes.alertTypeId); + await this.savedObjectsClient.update('alert', id, { scheduledTaskId: scheduledTask.id }); await this.invalidateApiKey({ apiKey: attributes.apiKey }); } } diff --git a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx index 67bff86c8ccdfc..32432b7b85ef60 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx @@ -18,8 +18,7 @@ import { history } from '../../../utils/history'; import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; import { useDynamicIndexPattern } from '../../../hooks/useDynamicIndexPattern'; import { - AutocompleteProvider, - AutocompleteSuggestion, + autocomplete, esKuery, IIndexPattern } from '../../../../../../../../src/plugins/data/public'; @@ -29,7 +28,7 @@ const Container = styled.div` `; interface State { - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; isLoadingSuggestions: boolean; } @@ -38,32 +37,6 @@ function convertKueryToEsQuery(kuery: string, indexPattern: IIndexPattern) { return esKuery.toElasticsearchQuery(ast, indexPattern); } -function getSuggestions( - query: string, - selectionStart: number, - indexPattern: IIndexPattern, - boolFilter: unknown, - autocompleteProvider?: AutocompleteProvider -) { - if (!autocompleteProvider) { - return []; - } - const config = { - get: () => true - }; - - const getAutocompleteSuggestions = autocompleteProvider({ - config, - indexPatterns: [indexPattern], - boolFilter - }); - return getAutocompleteSuggestions({ - query, - selectionStart, - selectionEnd: selectionStart - }); -} - export function KueryBar() { const [state, setState] = useState({ suggestions: [], @@ -72,7 +45,6 @@ export function KueryBar() { const { urlParams } = useUrlParams(); const location = useLocation(); const { data } = useApmPluginContext().plugins; - const autocompleteProvider = data.autocomplete.getProvider('kuery'); let currentRequestCheck; @@ -100,16 +72,16 @@ export function KueryBar() { const currentRequest = uniqueId(); currentRequestCheck = currentRequest; - const boolFilter = getBoolFilter(urlParams); try { const suggestions = ( - await getSuggestions( - inputValue, + (await data.autocomplete.getQuerySuggestions({ + language: 'kuery', + indexPatterns: [indexPattern], + boolFilter: getBoolFilter(urlParams), + query: inputValue, selectionStart, - indexPattern, - boolFilter, - autocompleteProvider - ) + selectionEnd: selectionStart + })) || [] ) .filter(suggestion => !startsWith(suggestion.text, 'span.')) .slice(0, 15); diff --git a/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/index.tsx b/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/index.tsx index 1f49711ae5e4a1..f3e0f3dfbdae7d 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/index.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/index.tsx @@ -13,7 +13,7 @@ import { import React from 'react'; import styled from 'styled-components'; -import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; import { composeStateUpdaters } from '../../utils/typed_react'; import { SuggestionItem } from './suggestion_item'; @@ -25,7 +25,7 @@ interface AutocompleteFieldProps { onSubmit?: (value: string) => void; onChange?: (value: string) => void; placeholder?: string; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; value: string; } diff --git a/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx b/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx index a753a944a2ecb2..0132667b9e510b 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx @@ -9,13 +9,13 @@ import { tint } from 'polished'; import React from 'react'; import styled from 'styled-components'; -import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; interface SuggestionItemProps { isSelected?: boolean; onClick?: React.MouseEventHandler; onMouseEnter?: React.MouseEventHandler; - suggestion: AutocompleteSuggestion; + suggestion: autocomplete.QuerySuggestion; } export const SuggestionItem: React.FC = props => { diff --git a/x-pack/legacy/plugins/beats_management/public/components/table/table.tsx b/x-pack/legacy/plugins/beats_management/public/components/table/table.tsx index 26ddd682405cbb..d1cbc0888dca8c 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/table/table.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/table/table.tsx @@ -8,7 +8,7 @@ import { EuiBasicTable, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eu import { i18n } from '@kbn/i18n'; import React from 'react'; import styled from 'styled-components'; -import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; import { TABLE_CONFIG } from '../../../common/constants'; import { AutocompleteField } from '../autocomplete_field/index'; import { ControlSchema } from './action_schema'; @@ -31,7 +31,7 @@ export interface KueryBarProps { loadSuggestions: (value: string, cursorPosition: number, maxCount?: number) => void; onChange?: (value: string) => void; onSubmit?: (value: string) => void; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; value: string; } diff --git a/x-pack/legacy/plugins/beats_management/public/containers/with_kuery_autocompletion.tsx b/x-pack/legacy/plugins/beats_management/public/containers/with_kuery_autocompletion.tsx index 2ac20438536c8a..db73a7cb38c110 100644 --- a/x-pack/legacy/plugins/beats_management/public/containers/with_kuery_autocompletion.tsx +++ b/x-pack/legacy/plugins/beats_management/public/containers/with_kuery_autocompletion.tsx @@ -6,7 +6,7 @@ import React from 'react'; -import { AutocompleteSuggestion } from '../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../src/plugins/data/public'; import { FrontendLibs } from '../lib/types'; import { RendererFunction } from '../utils/typed_react'; @@ -17,7 +17,7 @@ interface WithKueryAutocompletionLifecycleProps { children: RendererFunction<{ isLoadingSuggestions: boolean; loadSuggestions: (expression: string, cursorPosition: number, maxSuggestions?: number) => void; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; }>; } @@ -28,7 +28,7 @@ interface WithKueryAutocompletionLifecycleState { expression: string; cursorPosition: number; } | null; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; } export class WithKueryAutocompletion extends React.Component< diff --git a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/adapter_types.ts b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/adapter_types.ts index 4f4ce70e817c6d..12898027d5fb57 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/adapter_types.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/adapter_types.ts @@ -3,10 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { AutocompleteSuggestion } from '../../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../../src/plugins/data/public'; export interface ElasticsearchAdapter { convertKueryToEsQuery: (kuery: string) => Promise; - getSuggestions: (kuery: string, selectionStart: any) => Promise; + getSuggestions: (kuery: string, selectionStart: any) => Promise; isKueryValid(kuery: string): boolean; } diff --git a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/memory.ts b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/memory.ts index e001bf6c6e8442..111255b55c99ba 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/memory.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/memory.ts @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AutocompleteSuggestion } from '../../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../../src/plugins/data/public'; import { ElasticsearchAdapter } from './adapter_types'; export class MemoryElasticsearchAdapter implements ElasticsearchAdapter { constructor( private readonly mockIsKueryValid: (kuery: string) => boolean, private readonly mockKueryToEsQuery: (kuery: string) => string, - private readonly suggestions: AutocompleteSuggestion[] + private readonly suggestions: autocomplete.QuerySuggestion[] ) {} public isKueryValid(kuery: string): boolean { @@ -23,7 +23,7 @@ export class MemoryElasticsearchAdapter implements ElasticsearchAdapter { public async getSuggestions( kuery: string, selectionStart: any - ): Promise { + ): Promise { return this.suggestions; } } diff --git a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts index 8771181639f4d3..fc400c600e5758 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts @@ -7,10 +7,7 @@ import { isEmpty } from 'lodash'; import { npStart } from 'ui/new_platform'; import { ElasticsearchAdapter } from './adapter_types'; -import { AutocompleteSuggestion, esKuery } from '../../../../../../../../src/plugins/data/public'; - -const getAutocompleteProvider = (language: string) => - npStart.plugins.data.autocomplete.getProvider(language); +import { autocomplete, esKuery } from '../../../../../../../../src/plugins/data/public'; export class RestElasticsearchAdapter implements ElasticsearchAdapter { private cachedIndexPattern: any = null; @@ -33,30 +30,23 @@ export class RestElasticsearchAdapter implements ElasticsearchAdapter { const indexPattern = await this.getIndexPattern(); return JSON.stringify(esKuery.toElasticsearchQuery(ast, indexPattern)); } + public async getSuggestions( kuery: string, selectionStart: any - ): Promise { - const autocompleteProvider = getAutocompleteProvider('kuery'); - if (!autocompleteProvider) { - return []; - } - const config = { - get: () => true, - }; + ): Promise { const indexPattern = await this.getIndexPattern(); - const getAutocompleteSuggestions = autocompleteProvider({ - config, - indexPatterns: [indexPattern], - boolFilter: null, - }); - const results = getAutocompleteSuggestions({ - query: kuery || '', - selectionStart, - selectionEnd: selectionStart, - }); - return results; + return ( + (await npStart.plugins.data.autocomplete.getQuerySuggestions({ + language: 'kuery', + indexPatterns: [indexPattern], + boolFilter: [], + query: kuery || '', + selectionStart, + selectionEnd: selectionStart, + })) || [] + ); } private async getIndexPattern() { diff --git a/x-pack/legacy/plugins/beats_management/public/lib/compose/memory.ts b/x-pack/legacy/plugins/beats_management/public/lib/compose/memory.ts index f357e698afc3aa..47df51dea8620b 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/compose/memory.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/compose/memory.ts @@ -24,14 +24,14 @@ import { TagsLib } from '../tags'; import { FrontendLibs } from '../types'; import { MemoryElasticsearchAdapter } from './../adapters/elasticsearch/memory'; import { ElasticsearchLib } from './../elasticsearch'; -import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; const onKibanaReady = uiModules.get('kibana').run; export function compose( mockIsKueryValid: (kuery: string) => boolean, mockKueryToEsQuery: (kuery: string) => string, - suggestions: AutocompleteSuggestion[] + suggestions: autocomplete.QuerySuggestion[] ): FrontendLibs { const esAdapter = new MemoryElasticsearchAdapter( mockIsKueryValid, diff --git a/x-pack/legacy/plugins/beats_management/public/lib/elasticsearch.ts b/x-pack/legacy/plugins/beats_management/public/lib/elasticsearch.ts index 0897dfd9c13928..d71512e80d3d55 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/elasticsearch.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/elasticsearch.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AutocompleteSuggestion } from '../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../src/plugins/data/public'; import { ElasticsearchAdapter } from './adapters/elasticsearch/adapter_types'; interface HiddenFields { @@ -35,7 +35,7 @@ export class ElasticsearchLib { kuery: string, selectionStart: any, fieldPrefix?: string - ): Promise { + ): Promise { const suggestions = await this.adapter.getSuggestions(kuery, selectionStart); const filteredSuggestions = suggestions.filter(suggestion => { diff --git a/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx b/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx index 360561df719571..95b7dd22e9fcf4 100644 --- a/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx +++ b/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx @@ -21,6 +21,7 @@ import { ReactWrapper } from 'enzyme'; import { createMockGraphStore } from '../state_management/mocks'; import { Provider } from 'react-redux'; +jest.mock('ui/new_platform'); jest.mock('../services/source_modal', () => ({ openSourceModal: jest.fn() })); const waitForIndexPatternFetch = () => new Promise(r => setTimeout(r)); @@ -51,7 +52,7 @@ function wrapSearchBarInContext(testProps: OuterSearchBarProps) { savedQueries: {}, }, autocomplete: { - getProvider: () => undefined, + hasQuerySuggestions: () => false, }, }, }; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx index d1b0ee3a3e83e3..f79741d9a1a9f1 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.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, { useState } from 'react'; +import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -14,9 +14,6 @@ import { UseField, FormDataProvider, FormRow, ToggleField } from '../../../share import { ComboBoxOption } from '../../../types'; export const SourceFieldSection = () => { - const [includeComboBoxOptions, setIncludeComboBoxOptions] = useState([]); - const [excludeComboBoxOptions, setExcludeComboBoxOptions] = useState([]); - const renderWarning = () => ( { {({ label, helpText, value, setValue }) => ( { setValue(newValue); @@ -90,7 +87,6 @@ export const SourceFieldSection = () => { }; setValue([...(value as ComboBoxOption[]), newOption]); - setIncludeComboBoxOptions([...includeComboBoxOptions, newOption]); }} fullWidth /> @@ -104,13 +100,13 @@ export const SourceFieldSection = () => { {({ label, helpText, value, setValue }) => ( { setValue(newValue); @@ -121,7 +117,6 @@ export const SourceFieldSection = () => { }; setValue([...(value as ComboBoxOption[]), newOption]); - setExcludeComboBoxOptions([...excludeComboBoxOptions, newOption]); }} fullWidth /> diff --git a/x-pack/legacy/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx b/x-pack/legacy/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx index 4c215835ca2404..dc6eabb325d16f 100644 --- a/x-pack/legacy/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx +++ b/x-pack/legacy/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx @@ -12,7 +12,7 @@ import { } from '@elastic/eui'; import React from 'react'; -import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; import euiStyled from '../../../../../common/eui_styled_components'; import { composeStateUpdaters } from '../../utils/typed_react'; @@ -25,7 +25,7 @@ interface AutocompleteFieldProps { onSubmit?: (value: string) => void; onChange?: (value: string) => void; placeholder?: string; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; value: string; autoFocus?: boolean; 'aria-label'?: string; diff --git a/x-pack/legacy/plugins/infra/public/components/autocomplete_field/suggestion_item.tsx b/x-pack/legacy/plugins/infra/public/components/autocomplete_field/suggestion_item.tsx index 0c29b1f51b07e2..79b18f5888bd57 100644 --- a/x-pack/legacy/plugins/infra/public/components/autocomplete_field/suggestion_item.tsx +++ b/x-pack/legacy/plugins/infra/public/components/autocomplete_field/suggestion_item.tsx @@ -8,14 +8,14 @@ import { EuiIcon } from '@elastic/eui'; import { transparentize } from 'polished'; import React from 'react'; -import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; import euiStyled from '../../../../../common/eui_styled_components'; interface Props { isSelected?: boolean; onClick?: React.MouseEventHandler; onMouseEnter?: React.MouseEventHandler; - suggestion: AutocompleteSuggestion; + suggestion: autocomplete.QuerySuggestion; } export const SuggestionItem: React.FC = props => { diff --git a/x-pack/legacy/plugins/infra/public/containers/with_kuery_autocompletion.tsx b/x-pack/legacy/plugins/infra/public/containers/with_kuery_autocompletion.tsx index ba072b754b9571..c92e2ecec9261f 100644 --- a/x-pack/legacy/plugins/infra/public/containers/with_kuery_autocompletion.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/with_kuery_autocompletion.tsx @@ -6,17 +6,14 @@ import React from 'react'; import { npStart } from 'ui/new_platform'; -import { AutocompleteSuggestion, IIndexPattern } from 'src/plugins/data/public'; +import { autocomplete, IIndexPattern } from 'src/plugins/data/public'; import { RendererFunction } from '../utils/typed_react'; -const getAutocompleteProvider = (language: string) => - npStart.plugins.data.autocomplete.getProvider(language); - interface WithKueryAutocompletionLifecycleProps { children: RendererFunction<{ isLoadingSuggestions: boolean; loadSuggestions: (expression: string, cursorPosition: number, maxSuggestions?: number) => void; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; }>; indexPattern: IIndexPattern; } @@ -28,7 +25,7 @@ interface WithKueryAutocompletionLifecycleState { expression: string; cursorPosition: number; } | null; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; } export class WithKueryAutocompletion extends React.Component< @@ -56,21 +53,13 @@ export class WithKueryAutocompletion extends React.Component< maxSuggestions?: number ) => { const { indexPattern } = this.props; - const autocompletionProvider = getAutocompleteProvider('kuery'); - const config = { - get: () => true, - }; + const language = 'kuery'; + const hasQuerySuggestions = npStart.plugins.data.autocomplete.hasQuerySuggestions(language); - if (!autocompletionProvider) { + if (!hasQuerySuggestions) { return; } - const getSuggestions = autocompletionProvider({ - config, - indexPatterns: [indexPattern], - boolFilter: [], - }); - this.setState({ currentRequest: { expression, @@ -79,11 +68,15 @@ export class WithKueryAutocompletion extends React.Component< suggestions: [], }); - const suggestions = await getSuggestions({ - query: expression, - selectionStart: cursorPosition, - selectionEnd: cursorPosition, - }); + const suggestions = + (await npStart.plugins.data.autocomplete.getQuerySuggestions({ + language, + query: expression, + selectionStart: cursorPosition, + selectionEnd: cursorPosition, + indexPatterns: [indexPattern], + boolFilter: [], + })) || []; this.setState(state => state.currentRequest && diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/index.js b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/index.js deleted file mode 100644 index a5fb4eff388f72..00000000000000 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/index.js +++ /dev/null @@ -1,45 +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 { flatten, mapValues, uniq } from 'lodash'; -import { getSuggestionsProvider as field } from './field'; -import { getSuggestionsProvider as value } from './value'; -import { getSuggestionsProvider as operator } from './operator'; -import { getSuggestionsProvider as conjunction } from './conjunction'; -import { esKuery } from '../../../../../../src/plugins/data/public'; - -const cursorSymbol = '@kuery-cursor@'; - -function dedup(suggestions) { - return uniq(suggestions, ({ type, text, start, end }) => [type, text, start, end].join('|')); -} - -export const kueryProvider = ({ config, indexPatterns, boolFilter }) => { - const getSuggestionsByType = mapValues({ field, value, operator, conjunction }, provider => { - return provider({ config, indexPatterns, boolFilter }); - }); - - return function getSuggestions({ query, selectionStart, selectionEnd, signal }) { - const cursoredQuery = `${query.substr(0, selectionStart)}${cursorSymbol}${query.substr( - selectionEnd - )}`; - - let cursorNode; - try { - cursorNode = esKuery.fromKueryExpression(cursoredQuery, { cursorSymbol, parseCursor: true }); - } catch (e) { - cursorNode = {}; - } - - const { suggestionTypes = [] } = cursorNode; - const suggestionsByType = suggestionTypes.map(type => { - return getSuggestionsByType[type](cursorNode, signal); - }); - return Promise.all(suggestionsByType).then(suggestionsByType => - dedup(flatten(suggestionsByType)) - ); - }; -}; diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__fixtures__/index_pattern_response.json b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/__fixtures__/index_pattern_response.json similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__fixtures__/index_pattern_response.json rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/__fixtures__/index_pattern_response.json diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/conjunction.js b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/__tests__/conjunction.js similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/conjunction.js rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/__tests__/conjunction.js diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/escape_kuery.js b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/__tests__/escape_kuery.js similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/escape_kuery.js rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/__tests__/escape_kuery.js diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/field.js b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/__tests__/field.js similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/field.js rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/__tests__/field.js diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/operator.js b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/__tests__/operator.js similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/operator.js rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/__tests__/operator.js diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/conjunction.js b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/conjunction.js similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/conjunction.js rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/conjunction.js diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/escape_kuery.js b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/escape_kuery.js similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/escape_kuery.js rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/escape_kuery.js diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/field.js b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/field.js similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/field.js rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/field.js diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/index.js b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/index.js new file mode 100644 index 00000000000000..b877f9eb852d5d --- /dev/null +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/index.js @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { flatten, uniq } from 'lodash'; +import { getSuggestionsProvider as field } from './field'; +import { getSuggestionsProvider as value } from './value'; +import { getSuggestionsProvider as operator } from './operator'; +import { getSuggestionsProvider as conjunction } from './conjunction'; +import { esKuery } from '../../../../../../src/plugins/data/public'; + +const cursorSymbol = '@kuery-cursor@'; +const providers = { + field, + value, + operator, + conjunction, +}; + +function dedup(suggestions) { + return uniq(suggestions, ({ type, text, start, end }) => [type, text, start, end].join('|')); +} + +const getProviderByType = (type, args) => providers[type](args); + +export const setupKqlQuerySuggestionProvider = ({ uiSettings }) => ({ + indexPatterns, + boolFilter, + query, + selectionStart, + selectionEnd, + signal, +}) => { + const cursoredQuery = `${query.substr(0, selectionStart)}${cursorSymbol}${query.substr( + selectionEnd + )}`; + + let cursorNode; + try { + cursorNode = esKuery.fromKueryExpression(cursoredQuery, { cursorSymbol, parseCursor: true }); + } catch (e) { + cursorNode = {}; + } + + const { suggestionTypes = [] } = cursorNode; + const suggestionsByType = suggestionTypes.map(type => + getProviderByType(type, { + config: uiSettings, + indexPatterns, + boolFilter, + })(cursorNode, signal) + ); + return Promise.all(suggestionsByType).then(suggestionsByType => + dedup(flatten(suggestionsByType)) + ); +}; diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/operator.js b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/operator.js similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/operator.js rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/operator.js diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/sort_prefix_first.test.ts b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/sort_prefix_first.test.ts similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/sort_prefix_first.test.ts rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/sort_prefix_first.test.ts diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/sort_prefix_first.ts b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/sort_prefix_first.ts similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/sort_prefix_first.ts rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/sort_prefix_first.ts diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.js b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/value.js similarity index 71% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.js rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/value.js index f44a3d9d658f3b..9d0d70fd95747a 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/value.js @@ -15,7 +15,7 @@ export function getSuggestionsProvider({ indexPatterns, boolFilter }) { indexPatterns.map(indexPattern => { return indexPattern.fields.map(field => ({ ...field, - indexPatternTitle: indexPattern.title, + indexPattern, })); }) ); @@ -27,18 +27,22 @@ export function getSuggestionsProvider({ indexPatterns, boolFilter }) { const fullFieldName = nestedPath ? `${nestedPath}.${fieldName}` : fieldName; const fields = allFields.filter(field => field.name === fullFieldName); const query = `${prefix}${suffix}`.trim(); - const { getSuggestions } = npStart.plugins.data; + const { getValueSuggestions } = npStart.plugins.data.autocomplete; - const suggestionsByField = fields.map(field => { - return getSuggestions(field.indexPatternTitle, field, query, boolFilter, signal).then( - data => { - const quotedValues = data.map(value => - typeof value === 'string' ? `"${escapeQuotes(value)}"` : `${value}` - ); - return wrapAsSuggestions(start, end, query, quotedValues); - } - ); - }); + const suggestionsByField = fields.map(field => + getValueSuggestions({ + indexPattern: field.indexPattern, + field, + query, + boolFilter, + signal, + }).then(data => { + const quotedValues = data.map(value => + typeof value === 'string' ? `"${escapeQuotes(value)}"` : `${value}` + ); + return wrapAsSuggestions(start, end, query, quotedValues); + }) + ); return Promise.all(suggestionsByField).then(suggestions => flatten(suggestions)); }; diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.test.js b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/value.test.js similarity index 55% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.test.js rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/value.test.js index d989fd9046a4d5..f5b652d2e21641 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.test.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/value.test.js @@ -6,25 +6,26 @@ import { getSuggestionsProvider } from './value'; import indexPatternResponse from './__fixtures__/index_pattern_response.json'; - import { npStart } from 'ui/new_platform'; jest.mock('ui/new_platform', () => ({ npStart: { plugins: { data: { - getSuggestions: (_, field) => { - let res; - if (field.type === 'boolean') { - res = [true, false]; - } else if (field.name === 'machine.os') { - res = ['Windo"ws', "Mac'", 'Linux']; - } else if (field.name === 'nestedField.child') { - res = ['foo']; - } else { - res = []; - } - return Promise.resolve(res); + autocomplete: { + getValueSuggestions: jest.fn(({ field }) => { + let res; + if (field.type === 'boolean') { + res = [true, false]; + } else if (field.name === 'machine.os') { + res = ['Windo"ws', "Mac'", 'Linux']; + } else if (field.name === 'nestedField.child') { + res = ['foo']; + } else { + res = []; + } + return Promise.resolve(res); + }), }, }, }, @@ -49,19 +50,24 @@ describe('Kuery value suggestions', function() { const fieldName = 'i_dont_exist'; const prefix = ''; const suffix = ''; - const spy = jest.spyOn(npStart.plugins.data, 'getSuggestions'); + const suggestions = await getSuggestions({ fieldName, prefix, suffix }); expect(suggestions.map(({ text }) => text)).toEqual([]); - expect(spy).toHaveBeenCalledTimes(0); + + expect(npStart.plugins.data.autocomplete.getValueSuggestions).toHaveBeenCalledTimes(0); }); test('should format suggestions', async () => { - const fieldName = 'ssl'; // Has results with quotes in mock - const prefix = ''; - const suffix = ''; const start = 1; const end = 5; - const suggestions = await getSuggestions({ fieldName, prefix, suffix, start, end }); + const suggestions = await getSuggestions({ + fieldName: 'ssl', + prefix: '', + suffix: '', + start, + end, + }); + expect(suggestions[0].type).toEqual('value'); expect(suggestions[0].start).toEqual(start); expect(suggestions[0].end).toEqual(end); @@ -80,64 +86,60 @@ describe('Kuery value suggestions', function() { describe('Boolean suggestions', function() { test('should stringify boolean fields', async () => { - const fieldName = 'ssl'; - const prefix = ''; - const suffix = ''; - const spy = jest.spyOn(npStart.plugins.data, 'getSuggestions'); - const suggestions = await getSuggestions({ fieldName, prefix, suffix }); + const suggestions = await getSuggestions({ fieldName: 'ssl', prefix: '', suffix: '' }); + expect(suggestions.map(({ text }) => text)).toEqual(['true ', 'false ']); - expect(spy).toHaveBeenCalledTimes(1); + expect(npStart.plugins.data.autocomplete.getValueSuggestions).toHaveBeenCalledTimes(1); }); test('should filter out boolean suggestions', async () => { - const fieldName = 'ssl'; // Has results with quotes in mock - const prefix = 'fa'; - const suffix = ''; - const suggestions = await getSuggestions({ fieldName, prefix, suffix }); + const suggestions = await getSuggestions({ fieldName: 'ssl', prefix: 'fa', suffix: '' }); + expect(suggestions.length).toEqual(1); }); }); describe('String suggestions', function() { test('should merge prefix and suffix', async () => { - const fieldName = 'machine.os.raw'; const prefix = 'he'; const suffix = 'llo'; - const spy = jest.spyOn(npStart.plugins.data, 'getSuggestions'); - await getSuggestions({ fieldName, prefix, suffix }); - expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toBeCalledWith( - expect.any(String), - expect.any(Object), - prefix + suffix, - undefined, - undefined + + await getSuggestions({ fieldName: 'machine.os.raw', prefix, suffix }); + + expect(npStart.plugins.data.autocomplete.getValueSuggestions).toHaveBeenCalledTimes(1); + expect(npStart.plugins.data.autocomplete.getValueSuggestions).toBeCalledWith( + expect.objectContaining({ + field: expect.any(Object), + query: prefix + suffix, + }) ); }); test('should escape quotes in suggestions', async () => { - const fieldName = 'machine.os'; // Has results with quotes in mock - const prefix = ''; - const suffix = ''; - const suggestions = await getSuggestions({ fieldName, prefix, suffix }); + const suggestions = await getSuggestions({ fieldName: 'machine.os', prefix: '', suffix: '' }); + expect(suggestions[0].text).toEqual('"Windo\\"ws" '); expect(suggestions[1].text).toEqual('"Mac\'" '); expect(suggestions[2].text).toEqual('"Linux" '); }); test('should filter out string suggestions', async () => { - const fieldName = 'machine.os'; // Has results with quotes in mock - const prefix = 'banana'; - const suffix = ''; - const suggestions = await getSuggestions({ fieldName, prefix, suffix }); + const suggestions = await getSuggestions({ + fieldName: 'machine.os', + prefix: 'banana', + suffix: '', + }); + expect(suggestions.length).toEqual(0); }); test('should partially filter out string suggestions - case insensitive', async () => { - const fieldName = 'machine.os'; // Has results with quotes in mock - const prefix = 'ma'; - const suffix = ''; - const suggestions = await getSuggestions({ fieldName, prefix, suffix }); + const suggestions = await getSuggestions({ + fieldName: 'machine.os', + prefix: 'ma', + suffix: '', + }); + expect(suggestions.length).toEqual(1); }); }); diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/plugin.ts b/x-pack/legacy/plugins/kuery_autocomplete/public/plugin.ts index ded66a7c6e8f0b..216e0f49ccd343 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/plugin.ts +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/plugin.ts @@ -8,7 +8,7 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core import { Plugin as DataPublicPlugin } from '../../../../../src/plugins/data/public'; // @ts-ignore -import { kueryProvider } from './autocomplete_providers'; +import { setupKqlQuerySuggestionProvider } from './kql_query_suggestion'; /** @internal */ export interface KueryAutocompletePluginSetupDependencies { @@ -25,8 +25,10 @@ export class KueryAutocompletePlugin implements Plugin, void> { this.initializerContext = initializerContext; } - public async setup(core: CoreSetup, { data }: KueryAutocompletePluginSetupDependencies) { - data.autocomplete.addProvider(KUERY_LANGUAGE_NAME, kueryProvider); + public async setup(core: CoreSetup, plugins: KueryAutocompletePluginSetupDependencies) { + const kueryProvider = setupKqlQuerySuggestionProvider(core, plugins); + + plugins.data.autocomplete.addQuerySuggestionProvider(KUERY_LANGUAGE_NAME, kueryProvider); } public start(core: CoreStart) { diff --git a/x-pack/legacy/plugins/ml/public/application/components/annotations/annotations_table/__snapshots__/annotations_table.test.js.snap b/x-pack/legacy/plugins/ml/public/application/components/annotations/annotations_table/__snapshots__/annotations_table.test.js.snap index 48cf53cf1ac016..3b93213da40335 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/annotations/annotations_table/__snapshots__/annotations_table.test.js.snap +++ b/x-pack/legacy/plugins/ml/public/application/components/annotations/annotations_table/__snapshots__/annotations_table.test.js.snap @@ -9,6 +9,7 @@ exports[`AnnotationsTable Initialization with annotations prop. 1`] = ` Object { "field": "annotation", "name": "Annotation", + "scope": "row", "sortable": true, "width": "50%", }, diff --git a/x-pack/legacy/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js b/x-pack/legacy/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js index 6c4e8925f369f9..3329bf1aab64a7 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js +++ b/x-pack/legacy/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js @@ -323,6 +323,7 @@ const AnnotationsTable = injectI18n( }), sortable: true, width: '50%', + scope: 'row', }, { field: 'timestamp', diff --git a/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js b/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js index 58f1214c11e103..23a40d9ecf295a 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js +++ b/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js @@ -101,6 +101,7 @@ export function getColumns( defaultMessage: 'time', }), dataType: 'date', + scope: 'row', render: date => renderTime(date, interval), textOnly: true, sortable: true, diff --git a/x-pack/legacy/plugins/ml/public/application/components/kql_filter_bar/kql_filter_bar.js b/x-pack/legacy/plugins/ml/public/application/components/kql_filter_bar/kql_filter_bar.js index 3591b0907ebb1f..e604c101a99940 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/kql_filter_bar/kql_filter_bar.js +++ b/x-pack/legacy/plugins/ml/public/application/components/kql_filter_bar/kql_filter_bar.js @@ -46,12 +46,8 @@ export class KqlFilterBar extends Component { const boolFilter = []; try { - const suggestions = await getSuggestions( - inputValue, - selectionStart, - indexPattern, - boolFilter - ); + const suggestions = + (await getSuggestions(inputValue, selectionStart, indexPattern, boolFilter)) || []; if (currentRequest !== this.currentRequest) { return; diff --git a/x-pack/legacy/plugins/ml/public/application/components/kql_filter_bar/kql_filter_bar.test.js b/x-pack/legacy/plugins/ml/public/application/components/kql_filter_bar/kql_filter_bar.test.js index b1e79cbf55925b..4e74a4bd545a34 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/kql_filter_bar/kql_filter_bar.test.js +++ b/x-pack/legacy/plugins/ml/public/application/components/kql_filter_bar/kql_filter_bar.test.js @@ -8,6 +8,8 @@ import React from 'react'; import { shallow } from 'enzyme'; import { KqlFilterBar } from './kql_filter_bar'; +jest.mock('ui/new_platform'); + const defaultProps = { indexPattern: { title: '.ml-anomalies-*', diff --git a/x-pack/legacy/plugins/ml/public/application/components/kql_filter_bar/utils.js b/x-pack/legacy/plugins/ml/public/application/components/kql_filter_bar/utils.js index c007e8bd05c5e5..bb7b143c948d83 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/kql_filter_bar/utils.js +++ b/x-pack/legacy/plugins/ml/public/application/components/kql_filter_bar/utils.js @@ -6,23 +6,11 @@ import { npStart } from 'ui/new_platform'; import { esKuery } from '../../../../../../../../src/plugins/data/public'; -const getAutocompleteProvider = language => npStart.plugins.data.autocomplete.getProvider(language); - -export async function getSuggestions(query, selectionStart, indexPattern, boolFilter) { - const autocompleteProvider = getAutocompleteProvider('kuery'); - if (!autocompleteProvider) { - return []; - } - const config = { - get: () => true, - }; - - const getAutocompleteSuggestions = autocompleteProvider({ - config, +export function getSuggestions(query, selectionStart, indexPattern, boolFilter) { + return npStart.plugins.data.autocomplete.getQuerySuggestions({ + language: 'kuery', indexPatterns: [indexPattern], boolFilter, - }); - return getAutocompleteSuggestions({ query, selectionStart, selectionEnd: selectionStart, diff --git a/x-pack/legacy/plugins/ml/public/application/components/ml_in_memory_table/types.ts b/x-pack/legacy/plugins/ml/public/application/components/ml_in_memory_table/types.ts index ee0540f6d58256..b85fb634891e56 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/ml_in_memory_table/types.ts +++ b/x-pack/legacy/plugins/ml/public/application/components/ml_in_memory_table/types.ts @@ -28,6 +28,7 @@ export interface FieldDataColumnType { render?: RenderFunc; footer?: string | ReactElement | FooterFunc; textOnly?: boolean; + scope?: 'col' | 'row' | 'colgroup' | 'rowgroup'; 'data-test-subj'?: string; } diff --git a/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.tsx b/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.tsx index ca6146f3e23b53..eb068f40716bc7 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.tsx +++ b/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.tsx @@ -23,12 +23,14 @@ interface Duration { function getRecentlyUsedRangesFactory(timeHistory: TimeHistory) { return function(): Duration[] { - return timeHistory.get().map(({ from, to }: TimeRange) => { - return { - start: from, - end: to, - }; - }); + return ( + timeHistory.get()?.map(({ from, to }: TimeRange) => { + return { + start: from, + end: to, + }; + }) ?? [] + ); }; } @@ -54,9 +56,18 @@ export const TopNav: FC = () => { useEffect(() => { const subscriptions = new Subscription(); - subscriptions.add(timefilter.getRefreshIntervalUpdate$().subscribe(timefilterUpdateListener)); - subscriptions.add(timefilter.getTimeUpdate$().subscribe(timefilterUpdateListener)); - subscriptions.add(timefilter.getEnabledUpdated$().subscribe(timefilterUpdateListener)); + const refreshIntervalUpdate$ = timefilter.getRefreshIntervalUpdate$(); + if (refreshIntervalUpdate$ !== undefined) { + subscriptions.add(refreshIntervalUpdate$.subscribe(timefilterUpdateListener)); + } + const timeUpdate$ = timefilter.getTimeUpdate$(); + if (timeUpdate$ !== undefined) { + subscriptions.add(timeUpdate$.subscribe(timefilterUpdateListener)); + } + const enabledUpdated$ = timefilter.getEnabledUpdated$(); + if (enabledUpdated$ !== undefined) { + subscriptions.add(enabledUpdated$.subscribe(timefilterUpdateListener)); + } return function cleanup() { subscriptions.unsubscribe(); diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx index 86f1324cc03772..34f281cec57d37 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx @@ -182,6 +182,7 @@ export const getColumns = ( sortable: true, truncateText: true, 'data-test-subj': 'mlAnalyticsTableColumnId', + scope: 'row', }, { field: DataFrameAnalyticsListColumn.description, diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/job_details/forecasts_table/forecasts_table.js b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/job_details/forecasts_table/forecasts_table.js index 1adb1e311dc682..e70198b36e0df6 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/job_details/forecasts_table/forecasts_table.js +++ b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/job_details/forecasts_table/forecasts_table.js @@ -204,6 +204,7 @@ class ForecastsTableUI extends Component { render: date => formatDate(date, TIME_FORMAT), textOnly: true, sortable: true, + scope: 'row', }, { field: 'forecast_start_timestamp', diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js index 7a6a1c22e39c5a..b691bc34295c58 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js +++ b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js @@ -172,6 +172,7 @@ class JobsListUI extends Component { sortable: true, truncateText: false, width: '20%', + scope: 'row', render: isManagementTable ? id => this.getJobIdLink(id) : undefined, }, { diff --git a/x-pack/legacy/plugins/ml/public/application/routing/routes/index.ts b/x-pack/legacy/plugins/ml/public/application/routing/routes/index.ts index 89ed35d5588f2d..44111ae32cd305 100644 --- a/x-pack/legacy/plugins/ml/public/application/routing/routes/index.ts +++ b/x-pack/legacy/plugins/ml/public/application/routing/routes/index.ts @@ -10,6 +10,6 @@ export * from './new_job'; export * from './datavisualizer'; export * from './settings'; export * from './data_frame_analytics'; -export * from './timeseriesexplorer'; +export { timeSeriesExplorerRoute } from './timeseriesexplorer'; export * from './explorer'; export * from './access_denied'; diff --git a/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.test.tsx b/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.test.tsx new file mode 100644 index 00000000000000..6917ec718d3a8d --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.test.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { render } from '@testing-library/react'; + +import { I18nProvider } from '@kbn/i18n/react'; + +import { TimeSeriesExplorerUrlStateManager } from './timeseriesexplorer'; + +jest.mock('ui/new_platform'); + +describe('TimeSeriesExplorerUrlStateManager', () => { + test('Initial render shows "No single metric jobs found"', () => { + const props = { + config: { get: () => 'Browser' }, + jobsWithTimeRange: [], + }; + + const { container } = render( + + + + + + ); + + expect(container.textContent).toContain('No single metric jobs found'); + }); +}); diff --git a/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx b/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx index cbf54a70ea74f4..c3c644d43fa593 100644 --- a/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx +++ b/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx @@ -74,7 +74,7 @@ interface TimeSeriesExplorerUrlStateManager { jobsWithTimeRange: MlJobWithTimeRange[]; } -const TimeSeriesExplorerUrlStateManager: FC = ({ +export const TimeSeriesExplorerUrlStateManager: FC = ({ config, jobsWithTimeRange, }) => { @@ -102,23 +102,27 @@ const TimeSeriesExplorerUrlStateManager: FC = timefilter.enableAutoRefreshSelector(); }, []); + // We cannot simply infer bounds from the globalState's `time` attribute + // with `moment` since it can contain custom strings such as `now-15m`. + // So when globalState's `time` changes, we update the timefilter and use + // `timefilter.getBounds()` to update `bounds` in this component's state. + const [bounds, setBounds] = useState(undefined); useEffect(() => { if (globalState?.time !== undefined) { timefilter.setTime({ from: globalState.time.from, to: globalState.time.to, }); + + const timefilterBounds = timefilter.getBounds(); + // Only if both min/max bounds are valid moment times set the bounds. + // An invalid string restored from globalState might return `undefined`. + if (timefilterBounds?.min !== undefined && timefilterBounds?.max !== undefined) { + setBounds(timefilter.getBounds()); + } } }, [globalState?.time?.from, globalState?.time?.to]); - let bounds: TimeRangeBounds | undefined; - if (globalState?.time !== undefined) { - bounds = { - min: moment(globalState.time.from), - max: moment(globalState.time.to), - }; - } - const selectedJobIds = globalState?.ml?.jobIds; // Sort selectedJobIds so we can be sure comparison works when stringifying. if (Array.isArray(selectedJobIds)) { @@ -140,14 +144,17 @@ const TimeSeriesExplorerUrlStateManager: FC = }, [JSON.stringify(selectedJobIds)]); // Next we get globalState and appState information to pass it on as props later. - // If a job change is going on, we fall back to defaults (as if appState was already cleard), + // If a job change is going on, we fall back to defaults (as if appState was already cleared), // otherwise the page could break. const selectedDetectorIndex = isJobChange ? 0 : +appState?.mlTimeSeriesExplorer?.detectorIndex || 0; const selectedEntities = isJobChange ? undefined : appState?.mlTimeSeriesExplorer?.entities; const selectedForecastId = isJobChange ? undefined : appState?.mlTimeSeriesExplorer?.forecastId; - const zoom = isJobChange ? undefined : appState?.mlTimeSeriesExplorer?.zoom; + const zoom: { + from: string; + to: string; + } = isJobChange ? undefined : appState?.mlTimeSeriesExplorer?.zoom; const selectedJob = selectedJobIds && mlJobService.getJob(selectedJobIds[0]); diff --git a/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/events_table/__snapshots__/events_table.test.js.snap b/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/events_table/__snapshots__/events_table.test.js.snap index a4ad3777f4be53..7b59fb0ea61dac 100644 --- a/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/events_table/__snapshots__/events_table.test.js.snap +++ b/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/events_table/__snapshots__/events_table.test.js.snap @@ -11,6 +11,7 @@ exports[`EventsTable Renders events table with no search bar 1`] = ` Object { "field": "description", "name": "Description", + "scope": "row", "sortable": true, "truncateText": true, }, @@ -79,6 +80,7 @@ exports[`EventsTable Renders events table with search bar 1`] = ` Object { "field": "description", "name": "Description", + "scope": "row", "sortable": true, "truncateText": true, }, diff --git a/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/events_table/events_table.js b/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/events_table/events_table.js index d5b4207e283ed5..125c75d438af98 100644 --- a/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/events_table/events_table.js +++ b/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/events_table/events_table.js @@ -63,6 +63,7 @@ export const EventsTable = injectI18n(function EventsTable({ }), sortable: true, truncateText: true, + scope: 'row', }, { field: 'start_time', diff --git a/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/__snapshots__/table.test.js.snap b/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/__snapshots__/table.test.js.snap index 8c518f42e0ec77..ff74c592b2b0f6 100644 --- a/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/__snapshots__/table.test.js.snap +++ b/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/__snapshots__/table.test.js.snap @@ -9,6 +9,7 @@ exports[`CalendarsListTable renders the table with all calendars 1`] = ` "field": "calendar_id", "name": "ID", "render": [Function], + "scope": "row", "sortable": true, "truncateText": true, }, diff --git a/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/table.js b/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/table.js index 45c214aede851f..774cc96517cc65 100644 --- a/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/table.js +++ b/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/table.js @@ -43,6 +43,7 @@ export const CalendarsListTable = injectI18n(function CalendarsListTable({ }), sortable: true, truncateText: true, + scope: 'row', render: id => {id}, }, { diff --git a/x-pack/legacy/plugins/ml/public/application/settings/filter_lists/list/__snapshots__/table.test.js.snap b/x-pack/legacy/plugins/ml/public/application/settings/filter_lists/list/__snapshots__/table.test.js.snap index 0ad66c78b9e2b7..8985469a807af9 100644 --- a/x-pack/legacy/plugins/ml/public/application/settings/filter_lists/list/__snapshots__/table.test.js.snap +++ b/x-pack/legacy/plugins/ml/public/application/settings/filter_lists/list/__snapshots__/table.test.js.snap @@ -10,6 +10,7 @@ exports[`Filter Lists Table renders with filter lists and selection supplied 1`] "field": "filter_id", "name": "ID", "render": [Function], + "scope": "row", "sortable": true, }, Object { @@ -118,6 +119,7 @@ exports[`Filter Lists Table renders with filter lists supplied 1`] = ` "field": "filter_id", "name": "ID", "render": [Function], + "scope": "row", "sortable": true, }, Object { diff --git a/x-pack/legacy/plugins/ml/public/application/settings/filter_lists/list/table.js b/x-pack/legacy/plugins/ml/public/application/settings/filter_lists/list/table.js index 79b71e65c3d1a3..0d1ca66de57756 100644 --- a/x-pack/legacy/plugins/ml/public/application/settings/filter_lists/list/table.js +++ b/x-pack/legacy/plugins/ml/public/application/settings/filter_lists/list/table.js @@ -85,6 +85,7 @@ function getColumns() { }), render: id => {id}, sortable: true, + scope: 'row', }, { field: 'description', diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.d.ts b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.d.ts index 3edbbc1af23237..651c6090042365 100644 --- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.d.ts +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.d.ts @@ -4,12 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Timefilter } from 'ui/timefilter'; import { FC } from 'react'; +import { Timefilter } from 'ui/timefilter'; + +import { getDateFormatTz, TimeRangeBounds } from '../explorer/explorer_utils'; + declare const TimeSeriesExplorer: FC<{ appStateHandler: (action: string, payload: any) => void; + autoZoomDuration?: number; + bounds?: TimeRangeBounds; dateFormatTz: string; + jobsWithTimeRange: any[]; + lastRefresh: number; selectedJobIds: string[]; selectedDetectorIndex: number; selectedEntities: any[]; @@ -17,5 +24,5 @@ declare const TimeSeriesExplorer: FC<{ setGlobalState: (arg: any) => void; tableInterval: string; tableSeverity: number; - timefilter: Timefilter; + zoom?: { from: string; to: string }; }>; diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js index 016f054430fa3c..1862ead0457431 100644 --- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js @@ -195,8 +195,10 @@ export class TimeSeriesExplorer extends React.Component { selectedDetectorIndex: PropTypes.number, selectedEntities: PropTypes.object, selectedForecastId: PropTypes.string, + setGlobalState: PropTypes.func.isRequired, tableInterval: PropTypes.string, tableSeverity: PropTypes.number, + zoom: PropTypes.object, }; state = getTimeseriesexplorerDefaultState(); @@ -481,7 +483,7 @@ export class TimeSeriesExplorer extends React.Component { zoom, } = this.props; - if (selectedJobIds === undefined) { + if (selectedJobIds === undefined || bounds === undefined) { return; } diff --git a/x-pack/legacy/plugins/reporting/index.ts b/x-pack/legacy/plugins/reporting/index.ts index 82d94422b70ce0..52e26b3132007d 100644 --- a/x-pack/legacy/plugins/reporting/index.ts +++ b/x-pack/legacy/plugins/reporting/index.ts @@ -7,47 +7,20 @@ import { resolve } from 'path'; import { i18n } from '@kbn/i18n'; import { Legacy } from 'kibana'; -import { IUiSettingsClient } from 'src/core/server'; -import { XPackMainPlugin } from '../xpack_main/server/xpack_main'; import { PLUGIN_ID, UI_SETTINGS_CUSTOM_PDF_LOGO } from './common/constants'; -// @ts-ignore untyped module defintition -import { mirrorPluginStatus } from '../../server/lib/mirror_plugin_status'; -import { registerRoutes } from './server/routes'; -import { - LevelLogger, - checkLicenseFactory, - getExportTypesRegistry, - runValidations, -} from './server/lib'; -import { createBrowserDriverFactory } from './server/browsers'; -import { registerReportingUsageCollector } from './server/usage'; import { ReportingConfigOptions, ReportingPluginSpecOptions } from './types.d'; import { config as reportingConfig } from './config'; -import { logConfiguration } from './log_configuration'; +import { + LegacySetup, + ReportingPlugin, + ReportingSetupDeps, + reportingPluginFactory, +} from './server/plugin'; const kbToBase64Length = (kb: number) => { return Math.floor((kb * 1024 * 8) / 6); }; -type LegacyPlugins = Legacy.Server['plugins']; - -export interface ServerFacade { - config: Legacy.Server['config']; - info: Legacy.Server['info']; - log: Legacy.Server['log']; - plugins: { - elasticsearch: LegacyPlugins['elasticsearch']; - security: LegacyPlugins['security']; - xpack_main: XPackMainPlugin & { - status?: any; - }; - }; - route: Legacy.Server['route']; - savedObjects: Legacy.Server['savedObjects']; - uiSettingsServiceFactory: Legacy.Server['uiSettingsServiceFactory']; - fieldFormatServiceFactory: (uiConfig: IUiSettingsClient) => unknown; -} - export const reporting = (kibana: any) => { return new kibana.Plugin({ id: PLUGIN_ID, @@ -93,7 +66,11 @@ export const reporting = (kibana: any) => { }, async init(server: Legacy.Server) { - const serverFacade: ServerFacade = { + const coreSetup = server.newPlatform.setup.core; + const pluginsSetup: ReportingSetupDeps = { + usageCollection: server.newPlatform.setup.plugins.usageCollection, + }; + const __LEGACY: LegacySetup = { config: server.config, info: server.info, route: server.route.bind(server), @@ -108,38 +85,9 @@ export const reporting = (kibana: any) => { fieldFormatServiceFactory: server.fieldFormatServiceFactory, log: server.log.bind(server), }; - const exportTypesRegistry = getExportTypesRegistry(); - - let isCollectorReady = false; - // Register a function with server to manage the collection of usage stats - const { usageCollection } = server.newPlatform.setup.plugins; - registerReportingUsageCollector( - usageCollection, - serverFacade, - () => isCollectorReady, - exportTypesRegistry - ); - - const logger = LevelLogger.createForServer(serverFacade, [PLUGIN_ID]); - const browserDriverFactory = await createBrowserDriverFactory(serverFacade); - - logConfiguration(serverFacade, logger); - runValidations(serverFacade, logger, browserDriverFactory); - - const { xpack_main: xpackMainPlugin } = serverFacade.plugins; - mirrorPluginStatus(xpackMainPlugin, this); - const checkLicense = checkLicenseFactory(exportTypesRegistry); - (xpackMainPlugin as any).status.once('green', () => { - // Register a function that is called whenever the xpack info changes, - // to re-compute the license check results for this plugin - xpackMainPlugin.info.feature(this.id).registerLicenseCheckResultsGenerator(checkLicense); - }); - - // Post initialization of the above code, the collector is now ready to fetch its data - isCollectorReady = true; - // Reporting routes - registerRoutes(serverFacade, exportTypesRegistry, browserDriverFactory, logger); + const plugin: ReportingPlugin = reportingPluginFactory(__LEGACY, this); + await plugin.setup(coreSetup, pluginsSetup); }, deprecations({ unused }: any) { diff --git a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts index c1777038cc7d46..de8449ff29132f 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { trunc } from 'lodash'; +import { trunc, map } from 'lodash'; import open from 'opn'; import { parse as parseUrl } from 'url'; import { Page, SerializableOrJSHandle, EvaluateFn } from 'puppeteer'; @@ -15,6 +15,7 @@ import { ConditionalHeaders, ConditionalHeadersConditions, ElementPosition, + InterceptedRequest, NetworkPolicy, } from '../../../../types'; @@ -59,35 +60,57 @@ export class HeadlessChromiumDriver { }: { conditionalHeaders: ConditionalHeaders; waitForSelector: string }, logger: LevelLogger ) { - await this.page.setRequestInterception(true); logger.info(`opening url ${url}`); + // @ts-ignore + const client = this.page._client; let interceptedCount = 0; - this.page.on('request', interceptedRequest => { - const interceptedUrl = interceptedRequest.url(); + await this.page.setRequestInterception(true); + + // We have to reach into the Chrome Devtools Protocol to apply headers as using + // puppeteer's API will cause map tile requests to hang indefinitely: + // https://github.com/puppeteer/puppeteer/issues/5003 + // Docs on this client/protocol can be found here: + // https://chromedevtools.github.io/devtools-protocol/tot/Fetch + client.on('Fetch.requestPaused', (interceptedRequest: InterceptedRequest) => { + const { + requestId, + request: { url: interceptedUrl }, + } = interceptedRequest; const allowed = !interceptedUrl.startsWith('file://'); const isData = interceptedUrl.startsWith('data:'); // We should never ever let file protocol requests go through if (!allowed || !this.allowRequest(interceptedUrl)) { logger.error(`Got bad URL: "${interceptedUrl}", closing browser.`); - interceptedRequest.abort('blockedbyclient'); + client.send('Fetch.failRequest', { + errorReason: 'Aborted', + requestId, + }); this.page.browser().close(); throw new Error(`Received disallowed outgoing URL: "${interceptedUrl}", exiting`); } if (this._shouldUseCustomHeaders(conditionalHeaders.conditions, interceptedUrl)) { logger.debug(`Using custom headers for ${interceptedUrl}`); - interceptedRequest.continue({ - headers: { - ...interceptedRequest.headers(), + const headers = map( + { + ...interceptedRequest.request.headers, ...conditionalHeaders.headers, }, + (value, name) => ({ + name, + value, + }) + ); + client.send('Fetch.continueRequest', { + requestId, + headers, }); } else { const loggedUrl = isData ? this.truncateUrl(interceptedUrl) : interceptedUrl; logger.debug(`No custom headers for ${loggedUrl}`); - interceptedRequest.continue(); + client.send('Fetch.continueRequest', { requestId }); } interceptedCount = interceptedCount + (isData ? 0 : 1); }); diff --git a/x-pack/legacy/plugins/reporting/server/plugin.ts b/x-pack/legacy/plugins/reporting/server/plugin.ts new file mode 100644 index 00000000000000..934a3487209c42 --- /dev/null +++ b/x-pack/legacy/plugins/reporting/server/plugin.ts @@ -0,0 +1,107 @@ +/* + * 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 { Legacy } from 'kibana'; +import { CoreSetup, CoreStart, Plugin } from 'src/core/server'; +import { IUiSettingsClient } from 'src/core/server'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { XPackMainPlugin } from '../../xpack_main/server/xpack_main'; +// @ts-ignore +import { mirrorPluginStatus } from '../../../server/lib/mirror_plugin_status'; +import { PLUGIN_ID } from '../common/constants'; +import { ReportingPluginSpecOptions } from '../types.d'; +import { registerRoutes } from './routes'; +import { LevelLogger, checkLicenseFactory, getExportTypesRegistry, runValidations } from './lib'; +import { createBrowserDriverFactory } from './browsers'; +import { registerReportingUsageCollector } from './usage'; +import { logConfiguration } from '../log_configuration'; + +// For now there is no exposed functionality to other plugins +export type ReportingSetup = object; +export type ReportingStart = object; + +export interface ReportingSetupDeps { + usageCollection: UsageCollectionSetup; +} +export type ReportingStartDeps = object; + +type LegacyPlugins = Legacy.Server['plugins']; + +export interface LegacySetup { + config: Legacy.Server['config']; + info: Legacy.Server['info']; + log: Legacy.Server['log']; + plugins: { + elasticsearch: LegacyPlugins['elasticsearch']; + security: LegacyPlugins['security']; + xpack_main: XPackMainPlugin & { + status?: any; + }; + }; + route: Legacy.Server['route']; + savedObjects: Legacy.Server['savedObjects']; + uiSettingsServiceFactory: Legacy.Server['uiSettingsServiceFactory']; + fieldFormatServiceFactory: (uiConfig: IUiSettingsClient) => unknown; +} + +export type ReportingPlugin = Plugin< + ReportingSetup, + ReportingStart, + ReportingSetupDeps, + ReportingStartDeps +>; + +/* We need a factory that returns an instance of the class because the class + * implementation itself restricts against having Legacy dependencies passed + * into `setup`. The factory parameters take the legacy dependencies, and the + * `setup` method gets it from enclosure */ +export function reportingPluginFactory( + __LEGACY: LegacySetup, + legacyPlugin: ReportingPluginSpecOptions +) { + return new (class ReportingPlugin implements ReportingPlugin { + public async setup(core: CoreSetup, plugins: ReportingSetupDeps): Promise { + const exportTypesRegistry = getExportTypesRegistry(); + + let isCollectorReady = false; + // Register a function with server to manage the collection of usage stats + const { usageCollection } = plugins; + registerReportingUsageCollector( + usageCollection, + __LEGACY, + () => isCollectorReady, + exportTypesRegistry + ); + + const logger = LevelLogger.createForServer(__LEGACY, [PLUGIN_ID]); + const browserDriverFactory = await createBrowserDriverFactory(__LEGACY); + + logConfiguration(__LEGACY, logger); + runValidations(__LEGACY, logger, browserDriverFactory); + + const { xpack_main: xpackMainPlugin } = __LEGACY.plugins; + mirrorPluginStatus(xpackMainPlugin, legacyPlugin); + const checkLicense = checkLicenseFactory(exportTypesRegistry); + (xpackMainPlugin as any).status.once('green', () => { + // Register a function that is called whenever the xpack info changes, + // to re-compute the license check results for this plugin + xpackMainPlugin.info.feature(PLUGIN_ID).registerLicenseCheckResultsGenerator(checkLicense); + }); + + // Post initialization of the above code, the collector is now ready to fetch its data + isCollectorReady = true; + + // Reporting routes + registerRoutes(__LEGACY, exportTypesRegistry, browserDriverFactory, logger); + + return {}; + } + + public start(core: CoreStart, plugins: ReportingStartDeps): ReportingStart { + return {}; + } + })(); +} diff --git a/x-pack/legacy/plugins/reporting/types.d.ts b/x-pack/legacy/plugins/reporting/types.d.ts index fd2ca424f4db74..9fae60afee4e82 100644 --- a/x-pack/legacy/plugins/reporting/types.d.ts +++ b/x-pack/legacy/plugins/reporting/types.d.ts @@ -15,7 +15,7 @@ import { CancellationToken } from './common/cancellation_token'; import { LevelLogger } from './server/lib/level_logger'; import { HeadlessChromiumDriverFactory } from './server/browsers/chromium/driver_factory'; import { BrowserType } from './server/browsers/types'; -import { ServerFacade } from './index'; +import { LegacySetup } from './server/plugin'; export type ReportingPlugin = object; // For Plugin contract @@ -69,6 +69,8 @@ interface GenerateExportTypePayload { * Legacy System */ +export type ServerFacade = LegacySetup; + export type ReportingPluginSpecOptions = Legacy.PluginSpecOptions; export type EnqueueJobFn = ( @@ -339,4 +341,17 @@ export interface AbsoluteURLFactoryOptions { port: string | number; } -export { ServerFacade }; +export interface InterceptedRequest { + requestId: string; + request: { + url: string; + method: string; + headers: { + [key: string]: string; + }; + initialPriority: string; + referrerPolicy: string; + }; + frameId: string; + resourceType: string; +} diff --git a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/index.tsx b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/index.tsx index 87d83f7f2972c1..0b99a8b059df79 100644 --- a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/index.tsx @@ -16,11 +16,11 @@ import { MatrixHistogramGqlQuery } from '../../containers/matrix_histogram/index const ID = 'alertsOverTimeQuery'; export const alertsStackByOptions: MatrixHistogramOption[] = [ { - text: i18n.CATEGORY, + text: 'event.category', value: 'event.category', }, { - text: i18n.MODULE, + text: 'event.module', value: 'event.module', }, ]; @@ -54,7 +54,6 @@ export const AlertsView = ({ <> diff --git a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/translations.ts b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/translations.ts index 8c6248e38c057d..408c406a854be4 100644 --- a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/translations.ts @@ -14,10 +14,14 @@ export const TOTAL_COUNT_OF_ALERTS = i18n.translate('xpack.siem.alertsView.total defaultMessage: 'alerts match the search criteria', }); -export const ALERTS_TABLE_TITLE = i18n.translate('xpack.siem.alertsView.alertsDocumentType', { +export const ALERTS_TABLE_TITLE = i18n.translate('xpack.siem.alertsView.alertsTableTitle', { defaultMessage: 'Alerts', }); +export const ALERTS_GRAPH_TITLE = i18n.translate('xpack.siem.alertsView.alertsGraphTitle', { + defaultMessage: 'Alert detection frequency', +}); + export const ALERTS_STACK_BY_MODULE = i18n.translate( 'xpack.siem.alertsView.alertsStackByOptions.module', { diff --git a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/__examples__/index.stories.tsx b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/__examples__/index.stories.tsx index 86d003bf577f3a..4d92e8cb1335d4 100644 --- a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/__examples__/index.stories.tsx +++ b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/__examples__/index.stories.tsx @@ -8,10 +8,10 @@ import * as React from 'react'; import { storiesOf } from '@storybook/react'; import { ThemeProvider } from 'styled-components'; import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; -import { AutocompleteSuggestion } from '../../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../../src/plugins/data/public'; import { SuggestionItem } from '../suggestion_item'; -const suggestion: AutocompleteSuggestion = { +const suggestion: autocomplete.QuerySuggestion = { description: 'Description...', end: 3, start: 1, diff --git a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.test.tsx index 27e87d25e286f5..ef16f79a4b83cf 100644 --- a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.test.tsx @@ -10,13 +10,13 @@ import { mount, shallow } from 'enzyme'; import { noop } from 'lodash/fp'; import React from 'react'; import { ThemeProvider } from 'styled-components'; -import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; import { TestProviders } from '../../mock'; import { AutocompleteField } from '.'; -const mockAutoCompleteData: AutocompleteSuggestion[] = [ +const mockAutoCompleteData: autocomplete.QuerySuggestion[] = [ { type: 'field', text: 'agent.ephemeral_id ', diff --git a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.tsx b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.tsx index 124ef26602f35c..2f76ae21944be7 100644 --- a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.tsx @@ -11,7 +11,7 @@ import { EuiPanel, } from '@elastic/eui'; import React from 'react'; -import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; import euiStyled from '../../../../../common/eui_styled_components'; @@ -25,7 +25,7 @@ interface AutocompleteFieldProps { onSubmit?: (value: string) => void; onChange?: (value: string) => void; placeholder?: string; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; value: string; } diff --git a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/suggestion_item.tsx b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/suggestion_item.tsx index aaf7be2f7f5a6d..44bc65bb0dc15d 100644 --- a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/suggestion_item.tsx +++ b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/suggestion_item.tsx @@ -9,13 +9,13 @@ import { transparentize } from 'polished'; import React from 'react'; import styled from 'styled-components'; import euiStyled from '../../../../../common/eui_styled_components'; -import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; interface SuggestionItemProps { isSelected?: boolean; onClick?: React.MouseEventHandler; onMouseEnter?: React.MouseEventHandler; - suggestion: AutocompleteSuggestion; + suggestion: autocomplete.QuerySuggestion; } export const SuggestionItem = React.memo( diff --git a/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx b/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx index 7c15af3fe642ad..0519f5c7c956b3 100644 --- a/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx +++ b/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx @@ -20,6 +20,7 @@ import { RedirectToHostsPage, RedirectToHostDetailsPage } from './redirect_to_ho import { RedirectToNetworkPage } from './redirect_to_network'; import { RedirectToOverviewPage } from './redirect_to_overview'; import { RedirectToTimelinesPage } from './redirect_to_timelines'; +import { DetectionEngineTab } from '../../pages/detection_engine/types'; interface LinkToPageProps { match: RouteMatch<{}>; @@ -63,6 +64,12 @@ export const LinkToPage = React.memo(({ match }) => ( path={`${match.url}/:pageName(${SiemPageName.detectionEngine})`} strict /> + ; export const DETECTION_ENGINE_PAGE_NAME = 'detection-engine'; export const RedirectToDetectionEnginePage = ({ + match: { + params: { tabName }, + }, location: { search }, -}: DetectionEngineComponentProps) => ( - -); +}: DetectionEngineComponentProps) => { + const defaultSelectedTab = DetectionEngineTab.signals; + const selectedTab = tabName ? tabName : defaultSelectedTab; + const to = `/${DETECTION_ENGINE_PAGE_NAME}/${selectedTab}${search}`; + + return ; +}; export const RedirectToRulesPage = ({ location: { search } }: DetectionEngineComponentProps) => { return ; @@ -28,7 +37,7 @@ export const RedirectToRulesPage = ({ location: { search } }: DetectionEngineCom export const RedirectToCreateRulePage = ({ location: { search }, }: DetectionEngineComponentProps) => { - return ; + return ; }; export const RedirectToRuleDetailsPage = ({ @@ -44,6 +53,8 @@ export const RedirectToEditRulePage = ({ location: { search } }: DetectionEngine }; export const getDetectionEngineUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}`; +export const getDetectionEngineAlertUrl = () => + `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/${DetectionEngineTab.alerts}`; export const getRulesUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules`; export const getCreateRuleUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/create-rule`; export const getRuleDetailsUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/rule-details`; diff --git a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx index 56ebbb06f3eb98..cdd62c430a50cc 100644 --- a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx @@ -46,12 +46,12 @@ export const MatrixHistogramComponent: React.FC { + useEffect(() => { + return () => { + if (deleteQuery) { + deleteQuery({ id: ID }); + } + }; + }, []); + const [, siemJobs] = useSiemJobs(true); const [anomalyScore] = useUiSetting$(DEFAULT_ANOMALY_SCORE); @@ -51,21 +59,12 @@ export const AnomaliesQueryTabBody = ({ ip ); - useEffect(() => { - return () => { - if (deleteQuery) { - deleteQuery({ id: ID }); - } - }; - }, []); - return ( <> => { - const requests = rules.map(rule => - fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}`, { + const response = await fetch( + `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}/_bulk_create`, + { method: 'POST', credentials: 'same-origin', headers: { 'content-type': 'application/json', 'kbn-xsrf': 'true', }, - body: JSON.stringify({ - ...rule, - name: `${rule.name} [${i18n.DUPLICATE}]`, - created_at: undefined, - created_by: undefined, - id: undefined, - rule_id: undefined, - updated_at: undefined, - updated_by: undefined, - enabled: rule.enabled, - immutable: false, - last_success_at: undefined, - last_success_message: undefined, - status: undefined, - status_date: undefined, - }), - }) + body: JSON.stringify( + rules.map(rule => ({ + ...rule, + name: `${rule.name} [${i18n.DUPLICATE}]`, + created_at: undefined, + created_by: undefined, + id: undefined, + rule_id: undefined, + updated_at: undefined, + updated_by: undefined, + enabled: rule.enabled, + immutable: undefined, + last_success_at: undefined, + last_success_message: undefined, + status: undefined, + status_date: undefined, + })) + ), + } ); - const responses = await Promise.all(requests); - await responses.map(response => throwIfNotOk(response)); - return Promise.all( - responses.map>(response => response.json()) - ); + await throwIfNotOk(response); + return response.json(); }; /** 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 feef888c0d47ff..334daa8d1d0286 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 @@ -146,7 +146,7 @@ export interface DeleteRulesProps { } export interface DuplicateRulesProps { - rules: Rules; + rules: Rule[]; } export interface BasicFetchProps { diff --git a/x-pack/legacy/plugins/siem/public/containers/kuery_autocompletion/index.tsx b/x-pack/legacy/plugins/siem/public/containers/kuery_autocompletion/index.tsx index 6361f7abcf9770..4eb51dfe6407c7 100644 --- a/x-pack/legacy/plugins/siem/public/containers/kuery_autocompletion/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/kuery_autocompletion/index.tsx @@ -5,10 +5,7 @@ */ import React, { useState } from 'react'; -import { - AutocompleteSuggestion, - IIndexPattern, -} from '../../../../../../../src/plugins/data/public'; +import { autocomplete, IIndexPattern } from '../../../../../../../src/plugins/data/public'; import { useKibana } from '../../lib/kibana'; type RendererResult = React.ReactElement | null; @@ -18,7 +15,7 @@ interface KueryAutocompletionLifecycleProps { children: RendererFunction<{ isLoadingSuggestions: boolean; loadSuggestions: (expression: string, cursorPosition: number, maxSuggestions?: number) => void; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; }>; indexPattern: IIndexPattern; } @@ -33,26 +30,19 @@ export const KueryAutocompletion = React.memo const [currentRequest, setCurrentRequest] = useState( null ); - const [suggestions, setSuggestions] = useState([]); + const [suggestions, setSuggestions] = useState([]); const kibana = useKibana(); const loadSuggestions = async ( expression: string, cursorPosition: number, maxSuggestions?: number ) => { - const autocompletionProvider = kibana.services.data.autocomplete.getProvider('kuery'); - const config = { - get: () => true, - }; - if (!autocompletionProvider) { + const language = 'kuery'; + + if (!kibana.services.data.autocomplete.hasQuerySuggestions(language)) { return; } - const getSuggestions = autocompletionProvider({ - config, - indexPatterns: [indexPattern], - boolFilter: [], - }); const futureRequest = { expression, cursorPosition, @@ -62,16 +52,22 @@ export const KueryAutocompletion = React.memo cursorPosition, }); setSuggestions([]); - const newSuggestions = await getSuggestions({ - query: expression, - selectionStart: cursorPosition, - selectionEnd: cursorPosition, - }); + if ( futureRequest && futureRequest.expression !== (currentRequest && currentRequest.expression) && futureRequest.cursorPosition !== (currentRequest && currentRequest.cursorPosition) ) { + const newSuggestions = + (await kibana.services.data.autocomplete.getQuerySuggestions({ + language: 'kuery', + indexPatterns: [indexPattern], + boolFilter: [], + query: expression, + selectionStart: cursorPosition, + selectionEnd: cursorPosition, + })) || []; + setCurrentRequest(null); setSuggestions(maxSuggestions ? newSuggestions.slice(0, maxSuggestions) : newSuggestions); } diff --git a/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.tsx b/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.tsx index 5b1be4ca2c1dca..d5fd325bb9a26e 100644 --- a/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.tsx @@ -26,7 +26,6 @@ import { SetQuery } from '../../pages/hosts/navigation/types'; export interface OwnProps extends QueryTemplateProps { dataKey: string | string[]; defaultStackByOption: MatrixHistogramOption; - deleteQuery?: ({ id }: { id: string }) => void; errorMessage: string; headerChildren?: React.ReactNode; hideHistogramIfEmpty?: boolean; diff --git a/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/utils.ts b/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/utils.ts index 9cda9d8f6115f5..1df1aec76627ce 100644 --- a/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/utils.ts +++ b/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/utils.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { getOr } from 'lodash/fp'; -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { MatrixHistogramDataTypes, MatrixHistogramQueryProps, @@ -35,7 +35,7 @@ export const useQuery = ({ }: MatrixHistogramQueryProps) => { const [defaultIndex] = useUiSetting$(DEFAULT_INDEX_KEY); const [, dispatchToaster] = useStateToaster(); - const [refetch, setRefetch] = useState(); + const refetch = useRef(); const [loading, setLoading] = useState(false); const [data, setData] = useState(null); const [inspect, setInspect] = useState(null); @@ -71,7 +71,7 @@ export const useQuery = ({ return apolloClient .query({ query, - fetchPolicy: 'cache-first', + fetchPolicy: 'network-only', variables: matrixHistogramVariables, context: { fetchOptions: { @@ -103,9 +103,7 @@ export const useQuery = ({ } ); } - setRefetch(() => { - fetchData(); - }); + refetch.current = fetchData; fetchData(); return () => { isSubscribed = false; @@ -122,5 +120,5 @@ export const useQuery = ({ endDate, ]); - return { data, loading, inspect, totalCount, refetch }; + return { data, loading, inspect, totalCount, refetch: refetch.current }; }; diff --git a/x-pack/legacy/plugins/siem/public/containers/timeline/index.tsx b/x-pack/legacy/plugins/siem/public/containers/timeline/index.tsx index c585e04d2cfd7b..97f007452854ca 100644 --- a/x-pack/legacy/plugins/siem/public/containers/timeline/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/timeline/index.tsx @@ -81,8 +81,7 @@ class TimelineQueryComponent extends QueryTemplate< sourceId, sortField, } = this.props; - // I needed to do that to avoid test to yell at me since there is no good way yet to mock withKibana - const defaultKibanaIndex = kibana.services.uiSettings.get(DEFAULT_INDEX_KEY) ?? []; + const defaultKibanaIndex = kibana.services.uiSettings.get(DEFAULT_INDEX_KEY); const defaultIndex = isEmpty(indexPattern) ? [...defaultKibanaIndex, ...indexToAdd] : indexPattern?.title.split(',') ?? []; diff --git a/x-pack/legacy/plugins/siem/public/mock/kibana_react.ts b/x-pack/legacy/plugins/siem/public/mock/kibana_react.ts index 7d843977d1f322..968ab6543f4fc5 100644 --- a/x-pack/legacy/plugins/siem/public/mock/kibana_react.ts +++ b/x-pack/legacy/plugins/siem/public/mock/kibana_react.ts @@ -71,7 +71,18 @@ export const createUseUiSetting$Mock = () => { }; export const createUseKibanaMock = () => { - const services = { ...createKibanaCoreStartMock(), ...createKibanaPluginsStartMock() }; + const core = createKibanaCoreStartMock(); + const plugins = createKibanaPluginsStartMock(); + const useUiSetting = createUseUiSettingMock(); + + const services = { + ...core, + ...plugins, + uiSettings: { + ...core.uiSettings, + get: useUiSetting, + }, + }; return () => ({ services }); }; @@ -87,15 +98,11 @@ export const createWithKibanaMock = () => { export const createKibanaContextProviderMock = () => { const kibana = createUseKibanaMock()(); - const uiSettings = { - ...kibana.services.uiSettings, - get: createUseUiSettingMock(), - }; // eslint-disable-next-line @typescript-eslint/no-explicit-any return ({ services, ...rest }: any) => React.createElement(KibanaContextProvider, { ...rest, - services: { ...kibana.services, uiSettings, ...services }, + services: { ...kibana.services, ...services }, }); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/translations.ts index d1ba946be41de1..c262f907c98763 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/translations.ts @@ -11,7 +11,7 @@ export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.pageTitle', }); export const SIGNALS_TABLE_TITLE = i18n.translate('xpack.siem.detectionEngine.signals.tableTitle', { - defaultMessage: 'All signals', + defaultMessage: 'Signals', }); export const SIGNALS_DOCUMENT_TYPE = i18n.translate( diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/config.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/config.ts index f329780b075e3f..d475fd155ea25d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/config.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/config.ts @@ -4,18 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as i18n from './translations'; import { SignalsHistogramOption } from './types'; export const signalsHistogramOptions: SignalsHistogramOption[] = [ - { text: i18n.STACK_BY_RISK_SCORES, value: 'signal.rule.risk_score' }, - { text: i18n.STACK_BY_SEVERITIES, value: 'signal.rule.severity' }, - { text: i18n.STACK_BY_DESTINATION_IPS, value: 'destination.ip' }, - { text: i18n.STACK_BY_ACTIONS, value: 'event.action' }, - { text: i18n.STACK_BY_CATEGORIES, value: 'event.category' }, - { text: i18n.STACK_BY_HOST_NAMES, value: 'host.name' }, - { text: i18n.STACK_BY_RULE_TYPES, value: 'signal.rule.type' }, - { text: i18n.STACK_BY_RULE_NAMES, value: 'signal.rule.name' }, - { text: i18n.STACK_BY_SOURCE_IPS, value: 'source.ip' }, - { text: i18n.STACK_BY_USERS, value: 'user.name' }, + { text: 'signal.rule.risk_score', value: 'signal.rule.risk_score' }, + { text: 'signal.rule.severity', value: 'signal.rule.severity' }, + { text: 'destination.ip', value: 'destination.ip' }, + { text: 'event.action', value: 'event.action' }, + { text: 'event.category', value: 'event.category' }, + { text: 'host.name', value: 'host.name' }, + { text: 'signal.rule.type', value: 'signal.rule.type' }, + { text: 'signal.rule.name', value: 'signal.rule.name' }, + { text: 'source.ip', value: 'source.ip' }, + { text: 'user.name', value: 'user.name' }, ]; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.tsx index fda40f5f9fa5db..64bc7ba24c6895 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.tsx @@ -46,7 +46,7 @@ export const SignalsHistogramPanel = memo( filters, query, from, - legendPosition = 'bottom', + legendPosition = 'right', loadingInitial = false, showLinkToSignals = false, showTotalSignalsCount = false, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram/index.tsx index 218fcc3a70f79f..d4db8cc7c37e87 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram/index.tsx @@ -44,7 +44,7 @@ export const SignalsHistogram = React.memo( from, query, filters, - legendPosition = 'bottom', + legendPosition = 'right', loadingInitial, setTotalSignalsCount, stackByField, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx index 388f667f47fe12..26a9ad128b1dc1 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx @@ -4,27 +4,31 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButton, EuiSpacer } from '@elastic/eui'; -import React, { useCallback } from 'react'; +import { EuiButton, EuiSpacer, EuiTab, EuiTabs } from '@elastic/eui'; +import React, { useCallback, useMemo } from 'react'; +import { useParams } from 'react-router-dom'; import { StickyContainer } from 'react-sticky'; - import { connect } from 'react-redux'; import { ActionCreator } from 'typescript-fsa'; + +import { Query } from '../../../../../../../src/plugins/data/common/query'; +import { esFilters } from '../../../../../../../src/plugins/data/common/es_query'; + +import { GlobalTime } from '../../containers/global_time'; +import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source'; +import { AlertsTable } from '../../components/alerts_viewer/alerts_table'; import { FiltersGlobal } from '../../components/filters_global'; import { HeaderPage } from '../../components/header_page'; +import { DETECTION_ENGINE_PAGE_NAME } from '../../components/link_to/redirect_to_detection_engine'; import { SiemSearchBar } from '../../components/search_bar'; import { WrapperPage } from '../../components/wrapper_page'; -import { GlobalTime } from '../../containers/global_time'; -import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source'; -import { SpyRoute } from '../../utils/route/spy_routes'; - -import { Query } from '../../../../../../../src/plugins/data/common/query'; -import { esFilters } from '../../../../../../../src/plugins/data/common/es_query'; import { State } from '../../store'; import { inputsSelectors } from '../../store/inputs'; import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../store/inputs/actions'; +import { SpyRoute } from '../../utils/route/spy_routes'; import { InputsModelId } from '../../store/inputs/constants'; import { InputsRange } from '../../store/inputs/model'; +import { AlertsByCategory } from '../overview/alerts_by_category'; import { useSignalInfo } from './components/signals_info'; import { SignalsTable } from './components/signals'; import { NoWriteSignalsCallOut } from './components/no_write_signals_callout'; @@ -35,6 +39,7 @@ import { DetectionEngineEmptyPage } from './detection_engine_empty_page'; import { DetectionEngineNoIndex } from './detection_engine_no_signal_index'; import { DetectionEngineUserUnauthenticated } from './detection_engine_user_unauthenticated'; import * as i18n from './translations'; +import { DetectionEngineTab } from './types'; interface ReduxProps { filters: esFilters.Filter[]; @@ -51,8 +56,22 @@ export interface DispatchProps { type DetectionEngineComponentProps = ReduxProps & DispatchProps; +const detectionsTabs = [ + { + id: DetectionEngineTab.signals, + name: i18n.SIGNAL, + disabled: false, + }, + { + id: DetectionEngineTab.alerts, + name: i18n.ALERT, + disabled: false, + }, +]; + const DetectionEngineComponent = React.memo( ({ filters, query, setAbsoluteRangeDatePicker }) => { + const { tabName = DetectionEngineTab.signals } = useParams(); const { loading, isSignalIndexExists, @@ -87,6 +106,25 @@ const DetectionEngineComponent = React.memo( ); } + + const tabs = useMemo( + () => ( + + {detectionsTabs.map(tab => ( + + {tab.name} + + ))} + + ), + [detectionsTabs, tabName] + ); + return ( <> {hasIndexWrite != null && !hasIndexWrite && } @@ -99,7 +137,6 @@ const DetectionEngineComponent = React.memo( @@ -117,26 +154,49 @@ const DetectionEngineComponent = React.memo( - {({ to, from }) => ( + {({ to, from, deleteQuery, setQuery }) => ( <> - + {tabs} - + {tabName === DetectionEngineTab.signals && ( + <> + + + + + )} + {tabName === DetectionEngineTab.alerts && ( + <> + + + + + )} )} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx index c4e83429aebdbf..7a0b8df85416c4 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx @@ -7,12 +7,13 @@ import React from 'react'; import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom'; +import { ManageUserInfo } from './components/user_info'; import { CreateRuleComponent } from './rules/create'; import { DetectionEngine } from './detection_engine'; import { EditRuleComponent } from './rules/edit'; import { RuleDetails } from './rules/details'; import { RulesComponent } from './rules'; -import { ManageUserInfo } from './components/user_info'; +import { DetectionEngineTab } from './types'; const detectionEnginePath = `/:pageName(detection-engine)`; @@ -21,7 +22,11 @@ type Props = Partial> & { url: string }; export const DetectionEngineContainer = React.memo(() => ( - + @@ -30,7 +35,7 @@ export const DetectionEngineContainer = React.memo(() => ( - + @@ -39,7 +44,10 @@ export const DetectionEngineContainer = React.memo(() => ( ( - + )} /> diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx index f83a19445acd6b..435edcab433b6c 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx @@ -29,17 +29,25 @@ export const editRuleAction = (rule: Rule, history: H.History) => { history.push(`/${DETECTION_ENGINE_PAGE_NAME}/rules/id/${rule.id}/edit`); }; -export const duplicateRuleAction = async ( - rule: Rule, +export const duplicateRulesAction = async ( + rules: Rule[], dispatch: React.Dispatch, dispatchToaster: Dispatch ) => { try { - dispatch({ type: 'updateLoading', ids: [rule.id], isLoading: true }); - const duplicatedRule = await duplicateRules({ rules: [rule] }); - dispatch({ type: 'updateLoading', ids: [rule.id], isLoading: false }); - dispatch({ type: 'updateRules', rules: duplicatedRule, appendRuleId: rule.id }); - displaySuccessToast(i18n.SUCCESSFULLY_DUPLICATED_RULES(duplicatedRule.length), dispatchToaster); + const ruleIds = rules.map(r => r.id); + dispatch({ type: 'updateLoading', ids: ruleIds, isLoading: true }); + const duplicatedRules = await duplicateRules({ rules }); + dispatch({ type: 'updateLoading', ids: ruleIds, isLoading: false }); + dispatch({ + type: 'updateRules', + rules: duplicatedRules, + appendRuleId: rules[rules.length - 1].id, + }); + displaySuccessToast( + i18n.SUCCESSFULLY_DUPLICATED_RULES(duplicatedRules.length), + dispatchToaster + ); } catch (e) { displayErrorToast(i18n.DUPLICATE_RULE_ERROR, [e.message], dispatchToaster); } diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx index 06d4c709a32bfd..8a10d4f7100b94 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx @@ -10,9 +10,13 @@ import * as H from 'history'; import * as i18n from '../translations'; import { TableData } from '../types'; import { Action } from './reducer'; -import { deleteRulesAction, enableRulesAction, exportRulesAction } from './actions'; +import { + deleteRulesAction, + duplicateRulesAction, + enableRulesAction, + exportRulesAction, +} from './actions'; import { ActionToaster } from '../../../../components/toasters'; -import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine'; export const getBatchItems = ( selectedState: TableData[], @@ -25,7 +29,6 @@ export const getBatchItems = ( const containsDisabled = selectedState.some(v => !v.activate); const containsLoading = selectedState.some(v => v.isLoading); const containsImmutable = selectedState.some(v => v.immutable); - const containsMultipleRules = Array.from(new Set(selectedState.map(v => v.rule_id))).length > 1; return [ , { closePopover(); - history.push(`/${DETECTION_ENGINE_PAGE_NAME}/rules/id/${selectedState[0].id}/edit`); + await duplicateRulesAction( + selectedState.map(s => s.sourceRule), + dispatch, + dispatchToaster + ); }} > - {i18n.BATCH_ACTION_EDIT_INDEX_PATTERNS} + {i18n.BATCH_ACTION_DUPLICATE_SELECTED} , { closePopover(); await deleteRulesAction( diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx index 91b018eb3078f2..a73e656dddd960 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx @@ -18,7 +18,7 @@ import React, { Dispatch } from 'react'; import { getEmptyTagValue } from '../../../../components/empty_value'; import { deleteRulesAction, - duplicateRuleAction, + duplicateRulesAction, editRuleAction, exportRulesAction, } from './actions'; @@ -48,7 +48,7 @@ const getActions = ( icon: 'copy', name: i18n.DUPLICATE_RULE, onClick: (rowItem: TableData) => - duplicateRuleAction(rowItem.sourceRule, dispatch, dispatchToaster), + duplicateRulesAction([rowItem.sourceRule], dispatch, dispatchToaster), }, { description: i18n.EXPORT_RULE, @@ -62,7 +62,6 @@ const getActions = ( icon: 'trash', name: i18n.DELETE_RULE, onClick: (rowItem: TableData) => deleteRulesAction([rowItem.id], dispatch, dispatchToaster), - enabled: (rowItem: TableData) => !rowItem.immutable, }, ]; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx index 0a823ce545d72e..b996bce8ab500d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx @@ -18,7 +18,7 @@ import { useHistory } from 'react-router-dom'; import { Rule } from '../../../../../containers/detection_engine/rules'; import * as i18n from './translations'; import * as i18nActions from '../../../rules/translations'; -import { deleteRulesAction, duplicateRuleAction } from '../../all/actions'; +import { deleteRulesAction, duplicateRulesAction } from '../../all/actions'; import { displaySuccessToast, useStateToaster } from '../../../../../components/toasters'; import { RuleDownloader } from '../rule_downloader'; import { DETECTION_ENGINE_PAGE_NAME } from '../../../../../components/link_to/redirect_to_detection_engine'; @@ -54,7 +54,7 @@ const RuleActionsOverflowComponent = ({ disabled={userHasNoPermissions} onClick={async () => { setIsPopoverOpen(false); - await duplicateRuleAction(rule, noop, dispatchToaster); + await duplicateRulesAction([rule], noop, dispatchToaster); }} > {i18nActions.DUPLICATE_RULE} @@ -73,7 +73,7 @@ const RuleActionsOverflowComponent = ({ { setIsPopoverOpen(false); await deleteRulesAction([rule.id], noop, dispatchToaster, onRuleDeletedCallback); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx index 099006a34920c0..86d7178e73c608 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx @@ -12,6 +12,7 @@ import { EuiSpacer, EuiHealth, EuiTab, + EuiTabs, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { memo, useCallback, useMemo, useState } from 'react'; @@ -78,14 +79,19 @@ export interface DispatchProps { }>; } +enum RuleDetailTabs { + signals = 'signals', + failures = 'failures', +} + const ruleDetailTabs = [ { - id: 'signal', + id: RuleDetailTabs.signals, name: detectionI18n.SIGNAL, disabled: false, }, { - id: 'failure', + id: RuleDetailTabs.failures, name: i18n.FAILURE_HISTORY_TAB, disabled: false, }, @@ -106,7 +112,7 @@ const RuleDetailsComponent = memo( } = useUserInfo(); const { ruleId } = useParams(); const [isLoading, rule] = useRule(ruleId); - const [ruleDetailTab, setRuleDetailTab] = useState('signal'); + const [ruleDetailTab, setRuleDetailTab] = useState(RuleDetailTabs.signals); const { aboutRuleData, defineRuleData, scheduleRuleData } = getStepsData({ rule, detailsView: true, @@ -187,22 +193,27 @@ const RuleDetailsComponent = memo( : 'subdued'; const tabs = useMemo( - () => - ruleDetailTabs.map(tab => ( - setRuleDetailTab(tab.id)} - isSelected={tab.id === ruleDetailTab} - disabled={tab.disabled} - key={tab.name} - > - {tab.name} - - )), + () => ( + + {ruleDetailTabs.map(tab => ( + setRuleDetailTab(tab.id)} + isSelected={tab.id === ruleDetailTab} + disabled={tab.disabled} + key={tab.id} + > + {tab.name} + + ))} + + ), [ruleDetailTabs, ruleDetailTab, setRuleDetailTab] ); const ruleError = useMemo( () => - rule?.status === 'failed' && ruleDetailTab === 'signal' && rule?.last_failure_at != null ? ( + rule?.status === 'failed' && + ruleDetailTab === RuleDetailTabs.signals && + rule?.last_failure_at != null ? ( ( {ruleError} {tabs} - {ruleDetailTab === 'signal' && ( + {ruleDetailTab === RuleDetailTabs.signals && ( <> @@ -381,7 +392,9 @@ const RuleDetailsComponent = memo( )} )} - {ruleDetailTab === 'failure' && } + {ruleDetailTab === RuleDetailTabs.failures && ( + + )} )} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts index aeeef925d60e56..83479b819f81ef 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts @@ -22,7 +22,7 @@ export const ADD_NEW_RULE = i18n.translate('xpack.siem.detectionEngine.rules.add }); export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.pageTitle', { - defaultMessage: 'Rules', + defaultMessage: 'Signal detection rules', }); export const REFRESH = i18n.translate('xpack.siem.detectionEngine.rules.allRules.refreshTitle', { @@ -75,10 +75,10 @@ export const BATCH_ACTION_EXPORT_SELECTED = i18n.translate( } ); -export const BATCH_ACTION_EDIT_INDEX_PATTERNS = i18n.translate( - 'xpack.siem.detectionEngine.rules.allRules.batchActions.editIndexPatternsTitle', +export const BATCH_ACTION_DUPLICATE_SELECTED = i18n.translate( + 'xpack.siem.detectionEngine.rules.allRules.batchActions.duplicateSelectedTitle', { - defaultMessage: 'Edit selected index patterns…', + defaultMessage: 'Duplicate selected…', } ); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts index e5f830d3a49b0e..d1935b4fd581da 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts @@ -22,8 +22,12 @@ export const SIGNAL = i18n.translate('xpack.siem.detectionEngine.signalTitle', { defaultMessage: 'Signals', }); +export const ALERT = i18n.translate('xpack.siem.detectionEngine.alertTitle', { + defaultMessage: 'Third-party alerts', +}); + export const BUTTON_MANAGE_RULES = i18n.translate('xpack.siem.detectionEngine.buttonManageRules', { - defaultMessage: 'Manage rules', + defaultMessage: 'Manage signal detection rules', }); export const PANEL_SUBTITLE_SHOWING = i18n.translate( diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/types.ts new file mode 100644 index 00000000000000..d529d99ad3ad4b --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/types.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export enum DetectionEngineTab { + signals = 'signals', + alerts = 'alerts', +} diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/authentications_query_tab_body.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/authentications_query_tab_body.tsx index 0bb95632963160..0109eeef914635 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/authentications_query_tab_body.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/authentications_query_tab_body.tsx @@ -25,7 +25,7 @@ const AuthenticationTableManage = manageQuery(AuthenticationTable); const ID = 'authenticationsOverTimeQuery'; const authStackByOptions: MatrixHistogramOption[] = [ { - text: i18n.NAVIGATION_AUTHENTICATIONS_STACK_BY_EVENT_TYPE, + text: 'event.type', value: 'event.type', }, ]; @@ -71,7 +71,6 @@ export const AuthenticationsQueryTabBody = ({ isAuthenticationsHistogram={true} dataKey="AuthenticationsHistogram" defaultStackByOption={authStackByOptions[0]} - deleteQuery={deleteQuery} endDate={endDate} errorMessage={i18n.ERROR_FETCHING_AUTHENTICATIONS_DATA} filterQuery={filterQuery} diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx index a07cbc8484a1b4..85bca90cc8e04a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx @@ -20,11 +20,11 @@ const EVENTS_HISTOGRAM_ID = 'eventsOverTimeQuery'; export const eventsStackByOptions: MatrixHistogramOption[] = [ { - text: i18n.NAVIGATION_EVENTS_STACK_BY_EVENT_ACTION, + text: 'event.action', value: 'event.action', }, { - text: i18n.NAVIGATION_EVENTS_STACK_BY_EVENT_DATASET, + text: 'event.dataset', value: 'event.dataset', }, ]; @@ -50,7 +50,6 @@ export const EventsQueryTabBody = ({ void; filters?: esFilters.Filter[]; from: number; + hideHeaderChildren?: boolean; indexPattern: IIndexPattern; query?: Query; setAbsoluteRangeDatePicker: SetAbsoluteRangeDatePicker; @@ -60,14 +60,24 @@ export const AlertsByCategory = React.memo( deleteQuery, filters = NO_FILTERS, from, + hideHeaderChildren = false, indexPattern, query = DEFAULT_QUERY, setAbsoluteRangeDatePicker, setQuery, to, }) => { + useEffect(() => { + return () => { + if (deleteQuery) { + deleteQuery({ id: ID }); + } + }; + }, []); + const kibana = useKibana(); const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); + const updateDateRangeCallback = useCallback( (min: number, max: number) => { setAbsoluteRangeDatePicker!({ id: 'global', from: min, to: max }); @@ -76,17 +86,11 @@ export const AlertsByCategory = React.memo( ); const alertsCountViewAlertsButton = useMemo( () => ( - - {i18n.VIEW_ALERTS} - + {i18n.VIEW_ALERTS} ), [] ); - const getTitle = useCallback( - (option: MatrixHistogramOption) => i18n.ALERTS_COUNT_BY(option.text), - [] - ); const getSubtitle = useCallback( (totalCount: number) => `${SHOWING}: ${numeral(totalCount).format(defaultNumberFormat)} ${UNIT(totalCount)}`, @@ -96,7 +100,6 @@ export const AlertsByCategory = React.memo( return ( ( queries: [query], filters, })} - headerChildren={alertsCountViewAlertsButton} + headerChildren={hideHeaderChildren ? null : alertsCountViewAlertsButton} id={ID} isAlertsHistogram={true} legendPosition={'right'} @@ -115,7 +118,7 @@ export const AlertsByCategory = React.memo( sourceId="default" stackByOptions={alertsStackByOptions} startDate={from} - title={getTitle} + title={i18n.ALERTS_GRAPH_TITLE} subtitle={getSubtitle} type={HostsType.page} updateDateRange={updateDateRangeCallback} diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx index 52084c4bfc2804..191b4a25926955 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx @@ -6,7 +6,7 @@ import { EuiButton } from '@elastic/eui'; import numeral from '@elastic/numeral'; -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo } from 'react'; import { esFilters, IIndexPattern, Query } from 'src/plugins/data/public'; import styled from 'styled-components'; @@ -66,8 +66,17 @@ export const EventsByDataset = React.memo( setQuery, to, }) => { + useEffect(() => { + return () => { + if (deleteQuery) { + deleteQuery({ id: ID }); + } + }; + }, []); + const kibana = useKibana(); const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); + const updateDateRangeCallback = useCallback( (min: number, max: number) => { setAbsoluteRangeDatePicker!({ id: 'global', from: min, to: max }); @@ -96,7 +105,6 @@ export const EventsByDataset = React.memo( return ( defaultMessage: 'Alerts count by {groupByField}', }); +export const ALERTS_GRAPH_TITLE = i18n.translate('xpack.siem.overview.alertsGraphTitle', { + defaultMessage: 'Alert detection frequency', +}); + export const EVENTS_COUNT_BY = (groupByField: string) => i18n.translate('xpack.siem.overview.eventsCountByTitle', { values: { groupByField }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 30a8d9d9351289..0c6ab1c82bcb8a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -18,9 +18,9 @@ import { DETECTION_ENGINE_PREPACKAGED_URL, } from '../../../../../common/constants'; import { RuleAlertType, IRuleSavedAttributesSavedObjectAttributes } from '../../rules/types'; -import { RuleAlertParamsRest } from '../../types'; +import { RuleAlertParamsRest, PrepackagedRules } from '../../types'; -export const fullRuleAlertParamsRest = (): RuleAlertParamsRest => ({ +export const mockPrepackagedRule = (): PrepackagedRules => ({ rule_id: 'rule-1', description: 'Detecting root and admin users', index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], @@ -51,8 +51,6 @@ export const fullRuleAlertParamsRest = (): RuleAlertParamsRest => ({ false_positives: [], saved_id: 'some-id', max_signals: 100, - created_at: '2019-12-13T16:40:33.400Z', - updated_at: '2019-12-13T16:40:33.400Z', timeline_id: 'timeline-id', timeline_title: 'timeline-title', }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts index 9c18f9040008c7..00a1d2eb980ec3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts @@ -55,7 +55,6 @@ export const createCreateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou enabled, false_positives: falsePositives, from, - immutable, query, language, output_index: outputIndex, @@ -109,7 +108,7 @@ export const createCreateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou enabled, falsePositives, from, - immutable, + immutable: false, query, language, outputIndex: finalIndex, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts index aa535d325f4b96..23acd12d341ed8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts @@ -39,7 +39,6 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = enabled, false_positives: falsePositives, from, - immutable, query, language, output_index: outputIndex, @@ -96,7 +95,7 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = enabled, falsePositives, from, - immutable, + immutable: false, query, language, outputIndex: finalIndex, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts index 180a75bdaaeead..b01108f0de21f9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts @@ -44,7 +44,6 @@ export const createUpdateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou enabled, false_positives: falsePositives, from, - immutable, query, language, output_index: outputIndex, @@ -77,7 +76,6 @@ export const createUpdateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou enabled, falsePositives, from, - immutable, query, language, outputIndex, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts index 147f3f9afa549e..533fe9b7249439 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts @@ -33,7 +33,6 @@ export const createUpdateRulesRoute: Hapi.ServerRoute = { enabled, false_positives: falsePositives, from, - immutable, query, language, output_index: outputIndex, @@ -75,7 +74,6 @@ export const createUpdateRulesRoute: Hapi.ServerRoute = { enabled, falsePositives, from, - immutable, query, language, outputIndex, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts index 1993948808ef49..abdd5a0c7b5084 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts @@ -4,20 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UpdateRuleAlertParamsRest } from '../../rules/types'; -import { ThreatParams, RuleAlertParamsRest } from '../../types'; +import { ThreatParams, PrepackagedRules } from '../../types'; import { addPrepackagedRulesSchema } from './add_prepackaged_rules_schema'; describe('add prepackaged rules schema', () => { test('empty objects do not validate', () => { - expect( - addPrepackagedRulesSchema.validate>({}).error - ).toBeTruthy(); + expect(addPrepackagedRulesSchema.validate>({}).error).toBeTruthy(); }); test('made up values do not validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ madeUp: 'hi', }).error ).toBeTruthy(); @@ -25,7 +22,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id] does not validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', }).error ).toBeTruthy(); @@ -33,7 +30,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description] does not validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', }).error @@ -42,7 +39,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from] does not validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -52,7 +49,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from, to] does not validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -63,7 +60,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from, to, name] does not validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -75,7 +72,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from, to, name, severity] does not validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -88,7 +85,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from, to, name, severity, type] does not validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -102,7 +99,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from, to, name, severity, type, interval] does not validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -117,7 +114,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from, to, name, severity, type, interval, index] does not validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -133,7 +130,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from, to, name, severity, type, query, index, interval, version] does validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -152,7 +149,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -170,7 +167,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, version] does validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -190,7 +187,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index] does not validate because output_index is not allowed', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -211,7 +208,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, version] does validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -229,7 +226,7 @@ describe('add prepackaged rules schema', () => { test('You can send in an empty array to threats', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -251,7 +248,7 @@ describe('add prepackaged rules schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, version, threats] does validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -286,7 +283,7 @@ describe('add prepackaged rules schema', () => { test('allows references to be sent as valid', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -307,7 +304,7 @@ describe('add prepackaged rules schema', () => { test('defaults references to an array', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -327,7 +324,7 @@ describe('add prepackaged rules schema', () => { test('defaults immutable to true', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -347,7 +344,7 @@ describe('add prepackaged rules schema', () => { test('immutable cannot be false', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -368,7 +365,7 @@ describe('add prepackaged rules schema', () => { test('immutable can be true', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -389,7 +386,7 @@ describe('add prepackaged rules schema', () => { test('defaults enabled to false', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -409,7 +406,7 @@ describe('add prepackaged rules schema', () => { test('rule_id is required', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ risk_score: 50, description: 'some description', from: 'now-5m', @@ -429,7 +426,7 @@ describe('add prepackaged rules schema', () => { test('references cannot be numbers', () => { expect( addPrepackagedRulesSchema.validate< - Partial> & { references: number[] } + Partial> & { references: number[] } >({ rule_id: 'rule-1', risk_score: 50, @@ -454,7 +451,7 @@ describe('add prepackaged rules schema', () => { test('indexes cannot be numbers', () => { expect( addPrepackagedRulesSchema.validate< - Partial> & { index: number[] } + Partial> & { index: number[] } >({ rule_id: 'rule-1', risk_score: 50, @@ -477,7 +474,7 @@ describe('add prepackaged rules schema', () => { test('defaults interval to 5 min', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -494,7 +491,7 @@ describe('add prepackaged rules schema', () => { test('defaults max signals to 100', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -512,7 +509,7 @@ describe('add prepackaged rules schema', () => { test('saved_id is required when type is saved_query and will not validate without out', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -530,7 +527,7 @@ describe('add prepackaged rules schema', () => { test('saved_id is required when type is saved_query and validates with it', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -549,7 +546,7 @@ describe('add prepackaged rules schema', () => { test('saved_query type can have filters with it', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -570,7 +567,7 @@ describe('add prepackaged rules schema', () => { test('filters cannot be a string', () => { expect( addPrepackagedRulesSchema.validate< - Partial & { filters: string }> + Partial & { filters: string }> >({ rule_id: 'rule-1', risk_score: 50, @@ -591,7 +588,7 @@ describe('add prepackaged rules schema', () => { test('language validates with kuery', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -612,7 +609,7 @@ describe('add prepackaged rules schema', () => { test('language validates with lucene', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -633,7 +630,7 @@ describe('add prepackaged rules schema', () => { test('language does not validate with something made up', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -654,7 +651,7 @@ describe('add prepackaged rules schema', () => { test('max_signals cannot be negative', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -676,7 +673,7 @@ describe('add prepackaged rules schema', () => { test('max_signals cannot be zero', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -698,7 +695,7 @@ describe('add prepackaged rules schema', () => { test('max_signals can be 1', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -720,7 +717,7 @@ describe('add prepackaged rules schema', () => { test('You can optionally send in an array of tags', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -744,7 +741,7 @@ describe('add prepackaged rules schema', () => { test('You cannot send in an array of tags that are numbers', () => { expect( addPrepackagedRulesSchema.validate< - Partial> & { tags: number[] } + Partial> & { tags: number[] } >({ rule_id: 'rule-1', risk_score: 50, @@ -771,7 +768,7 @@ describe('add prepackaged rules schema', () => { test('You cannot send in an array of threats that are missing "framework"', () => { expect( addPrepackagedRulesSchema.validate< - Partial> & { + Partial> & { threats: Array>>; } >({ @@ -815,7 +812,7 @@ describe('add prepackaged rules schema', () => { test('You cannot send in an array of threats that are missing "tactic"', () => { expect( addPrepackagedRulesSchema.validate< - Partial> & { + Partial> & { threats: Array>>; } >({ @@ -855,7 +852,7 @@ describe('add prepackaged rules schema', () => { test('You cannot send in an array of threats that are missing "techniques"', () => { expect( addPrepackagedRulesSchema.validate< - Partial> & { + Partial> & { threats: Array>>; } >({ @@ -892,7 +889,7 @@ describe('add prepackaged rules schema', () => { test('You can optionally send in an array of false positives', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -916,7 +913,7 @@ describe('add prepackaged rules schema', () => { test('You cannot send in an array of false positives that are numbers', () => { expect( addPrepackagedRulesSchema.validate< - Partial> & { false_positives: number[] } + Partial> & { false_positives: number[] } >({ rule_id: 'rule-1', risk_score: 50, @@ -942,7 +939,7 @@ describe('add prepackaged rules schema', () => { test('You can optionally set the immutable to be true', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -966,7 +963,7 @@ describe('add prepackaged rules schema', () => { test('You cannot set the immutable to be a number', () => { expect( addPrepackagedRulesSchema.validate< - Partial> & { immutable: number } + Partial> & { immutable: number } >({ rule_id: 'rule-1', risk_score: 50, @@ -990,7 +987,7 @@ describe('add prepackaged rules schema', () => { test('You cannot set the risk_score to 101', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 101, description: 'some description', @@ -1013,7 +1010,7 @@ describe('add prepackaged rules schema', () => { test('You cannot set the risk_score to -1', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: -1, description: 'some description', @@ -1036,7 +1033,7 @@ describe('add prepackaged rules schema', () => { test('You can set the risk_score to 0', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 0, description: 'some description', @@ -1059,7 +1056,7 @@ describe('add prepackaged rules schema', () => { test('You can set the risk_score to 100', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 100, description: 'some description', @@ -1082,7 +1079,7 @@ describe('add prepackaged rules schema', () => { test('You can set meta to any object you want', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -1109,7 +1106,7 @@ describe('add prepackaged rules schema', () => { test('You cannot create meta as a string', () => { expect( addPrepackagedRulesSchema.validate< - Partial & { meta: string }> + Partial & { meta: string }> >({ rule_id: 'rule-1', risk_score: 50, @@ -1134,7 +1131,7 @@ describe('add prepackaged rules schema', () => { test('You can omit the query string when filters are present', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -1157,7 +1154,7 @@ describe('add prepackaged rules schema', () => { test('validates with timeline_id and timeline_title', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -1180,7 +1177,7 @@ describe('add prepackaged rules schema', () => { test('You cannot omit timeline_title when timeline_id is present', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -1204,7 +1201,7 @@ describe('add prepackaged rules schema', () => { test('You cannot have a null value for timeline_title when timeline_id is present', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -1229,7 +1226,7 @@ describe('add prepackaged rules schema', () => { test('You cannot have empty string for timeline_title when timeline_id is present', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -1254,7 +1251,7 @@ describe('add prepackaged rules schema', () => { test('You cannot have timeline_title with an empty timeline_id', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -1279,7 +1276,7 @@ describe('add prepackaged rules schema', () => { test('You cannot have timeline_title without timeline_id', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts index 15f4fa7f056484..c76071047434c6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts @@ -884,7 +884,6 @@ describe('create rules schema', () => { description: 'some description', from: 'now-5m', to: 'now', - immutable: true, index: ['index-1'], name: 'some-name', severity: 'severity', @@ -907,7 +906,6 @@ describe('create rules schema', () => { description: 'some description', from: 'now-5m', to: 'now', - immutable: true, index: ['index-1'], name: 'some-name', severity: 'severity', @@ -999,7 +997,6 @@ describe('create rules schema', () => { description: 'some description', from: 'now-5m', to: 'now', - immutable: true, index: ['index-1'], name: 'some-name', severity: 'severity', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts index bed64cc6e7a02a..20f418c57b5dbe 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts @@ -9,18 +9,18 @@ import { importRulesQuerySchema, importRulesPayloadSchema, } from './import_rules_schema'; -import { ThreatParams, RuleAlertParamsRest, ImportRuleAlertRest } from '../../types'; +import { ThreatParams, ImportRuleAlertRest } from '../../types'; import { ImportRulesRequest } from '../../rules/types'; describe('import rules schema', () => { describe('importRulesSchema', () => { test('empty objects do not validate', () => { - expect(importRulesSchema.validate>({}).error).toBeTruthy(); + expect(importRulesSchema.validate>({}).error).toBeTruthy(); }); test('made up values do not validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ madeUp: 'hi', }).error ).toBeTruthy(); @@ -28,7 +28,7 @@ describe('import rules schema', () => { test('[rule_id] does not validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', }).error ).toBeTruthy(); @@ -36,7 +36,7 @@ describe('import rules schema', () => { test('[rule_id, description] does not validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', }).error @@ -45,7 +45,7 @@ describe('import rules schema', () => { test('[rule_id, description, from] does not validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -55,7 +55,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to] does not validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -66,7 +66,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, name] does not validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -78,7 +78,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, name, severity] does not validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -91,7 +91,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, name, severity, type] does not validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -105,7 +105,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, name, severity, type, interval] does not validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -120,7 +120,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, name, severity, type, interval, index] does not validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -136,7 +136,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, name, severity, type, query, index, interval] does validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -154,7 +154,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -172,7 +172,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score] does validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -191,7 +191,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index] does validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -211,7 +211,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score] does validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -228,7 +228,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index] does validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -246,7 +246,7 @@ describe('import rules schema', () => { test('You can send in an empty array to threats', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -269,7 +269,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threats] does validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -304,7 +304,7 @@ describe('import rules schema', () => { test('allows references to be sent as valid', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -325,7 +325,7 @@ describe('import rules schema', () => { test('defaults references to an array', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -346,7 +346,7 @@ describe('import rules schema', () => { test('references cannot be numbers', () => { expect( importRulesSchema.validate< - Partial> & { references: number[] } + Partial> & { references: number[] } >({ rule_id: 'rule-1', output_index: '.siem-signals', @@ -371,7 +371,7 @@ describe('import rules schema', () => { test('indexes cannot be numbers', () => { expect( importRulesSchema.validate< - Partial> & { index: number[] } + Partial> & { index: number[] } >({ rule_id: 'rule-1', output_index: '.siem-signals', @@ -394,7 +394,7 @@ describe('import rules schema', () => { test('defaults interval to 5 min', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -411,7 +411,7 @@ describe('import rules schema', () => { test('defaults max signals to 100', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -429,7 +429,7 @@ describe('import rules schema', () => { test('saved_id is required when type is saved_query and will not validate without out', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -447,7 +447,7 @@ describe('import rules schema', () => { test('saved_id is required when type is saved_query and validates with it', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, output_index: '.siem-signals', @@ -466,7 +466,7 @@ describe('import rules schema', () => { test('saved_query type can have filters with it', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -487,7 +487,7 @@ describe('import rules schema', () => { test('filters cannot be a string', () => { expect( importRulesSchema.validate< - Partial & { filters: string }> + Partial & { filters: string }> >({ rule_id: 'rule-1', output_index: '.siem-signals', @@ -508,7 +508,7 @@ describe('import rules schema', () => { test('language validates with kuery', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -529,7 +529,7 @@ describe('import rules schema', () => { test('language validates with lucene', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, output_index: '.siem-signals', @@ -550,7 +550,7 @@ describe('import rules schema', () => { test('language does not validate with something made up', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -571,7 +571,7 @@ describe('import rules schema', () => { test('max_signals cannot be negative', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -593,7 +593,7 @@ describe('import rules schema', () => { test('max_signals cannot be zero', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -615,7 +615,7 @@ describe('import rules schema', () => { test('max_signals can be 1', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -637,7 +637,7 @@ describe('import rules schema', () => { test('You can optionally send in an array of tags', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -660,7 +660,7 @@ describe('import rules schema', () => { test('You cannot send in an array of tags that are numbers', () => { expect( - importRulesSchema.validate> & { tags: number[] }>( + importRulesSchema.validate> & { tags: number[] }>( { rule_id: 'rule-1', output_index: '.siem-signals', @@ -688,7 +688,7 @@ describe('import rules schema', () => { test('You cannot send in an array of threats that are missing "framework"', () => { expect( importRulesSchema.validate< - Partial> & { + Partial> & { threats: Array>>; } >({ @@ -732,7 +732,7 @@ describe('import rules schema', () => { test('You cannot send in an array of threats that are missing "tactic"', () => { expect( importRulesSchema.validate< - Partial> & { + Partial> & { threats: Array>>; } >({ @@ -772,7 +772,7 @@ describe('import rules schema', () => { test('You cannot send in an array of threats that are missing "techniques"', () => { expect( importRulesSchema.validate< - Partial> & { + Partial> & { threats: Array>>; } >({ @@ -809,7 +809,7 @@ describe('import rules schema', () => { test('You can optionally send in an array of false positives', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -833,7 +833,7 @@ describe('import rules schema', () => { test('You cannot send in an array of false positives that are numbers', () => { expect( importRulesSchema.validate< - Partial> & { false_positives: number[] } + Partial> & { false_positives: number[] } >({ rule_id: 'rule-1', output_index: '.siem-signals', @@ -859,7 +859,7 @@ describe('import rules schema', () => { test('You can optionally set the immutable to be true', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -883,7 +883,7 @@ describe('import rules schema', () => { test('You cannot set the immutable to be a number', () => { expect( importRulesSchema.validate< - Partial> & { immutable: number } + Partial> & { immutable: number } >({ rule_id: 'rule-1', output_index: '.siem-signals', @@ -907,7 +907,7 @@ describe('import rules schema', () => { test('You cannot set the risk_score to 101', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 101, @@ -930,7 +930,7 @@ describe('import rules schema', () => { test('You cannot set the risk_score to -1', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: -1, @@ -953,7 +953,7 @@ describe('import rules schema', () => { test('You can set the risk_score to 0', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 0, @@ -976,7 +976,7 @@ describe('import rules schema', () => { test('You can set the risk_score to 100', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 100, @@ -999,7 +999,7 @@ describe('import rules schema', () => { test('You can set meta to any object you want', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -1025,7 +1025,7 @@ describe('import rules schema', () => { test('You cannot create meta as a string', () => { expect( - importRulesSchema.validate & { meta: string }>>({ + importRulesSchema.validate & { meta: string }>>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -1049,7 +1049,7 @@ describe('import rules schema', () => { test('You can omit the query string when filters are present', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -1072,7 +1072,7 @@ describe('import rules schema', () => { test('validates with timeline_id and timeline_title', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -1095,7 +1095,7 @@ describe('import rules schema', () => { test('You cannot omit timeline_title when timeline_id is present', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -1117,7 +1117,7 @@ describe('import rules schema', () => { test('You cannot have a null value for timeline_title when timeline_id is present', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -1140,7 +1140,7 @@ describe('import rules schema', () => { test('You cannot have empty string for timeline_title when timeline_id is present', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -1165,7 +1165,7 @@ describe('import rules schema', () => { test('You cannot have timeline_title with an empty timeline_id', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -1188,7 +1188,7 @@ describe('import rules schema', () => { test('You cannot have timeline_title without timeline_id', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_prepackaged_rules.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_prepackaged_rules.test.ts index 260147ed0506c8..8ca07caef0c7f0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_prepackaged_rules.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_prepackaged_rules.test.ts @@ -5,7 +5,7 @@ */ import { getPrepackagedRules } from './get_prepackaged_rules'; -import { RuleAlertParamsRest } from '../types'; +import { PrepackagedRules } from '../types'; import { isEmpty } from 'lodash/fp'; describe('get_existing_prepackaged_rules', () => { @@ -15,7 +15,7 @@ describe('get_existing_prepackaged_rules', () => { test('no rule should have the same rule_id as another rule_id', () => { const prePacakgedRules = getPrepackagedRules(); - let existingRuleIds: RuleAlertParamsRest[] = []; + let existingRuleIds: PrepackagedRules[] = []; prePacakgedRules.forEach(rule => { const foundDuplicate = existingRuleIds.reduce((accum, existingRule) => { if (existingRule.rule_id === rule.rule_id) { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_prepackaged_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_prepackaged_rules.ts index 855d0d73f6796a..bcfe6ee203ecd1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_prepackaged_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_prepackaged_rules.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RuleAlertParamsRest } from '../types'; +import { PrepackagedRules } from '../types'; import { addPrepackagedRulesSchema } from '../routes/schemas/add_prepackaged_rules_schema'; import { rawRules } from './prepackaged_rules'; @@ -13,9 +13,7 @@ import { rawRules } from './prepackaged_rules'; * that they are adding incorrect schema rules. Also this will auto-flush in all the default * aspects such as default interval of 5 minutes, default arrays, etc... */ -export const validateAllPrepackagedRules = ( - rules: RuleAlertParamsRest[] -): RuleAlertParamsRest[] => { +export const validateAllPrepackagedRules = (rules: PrepackagedRules[]): PrepackagedRules[] => { return rules.map(rule => { const validatedRule = addPrepackagedRulesSchema.validate(rule); if (validatedRule.error != null) { @@ -35,6 +33,6 @@ export const validateAllPrepackagedRules = ( }); }; -export const getPrepackagedRules = (rules = rawRules): RuleAlertParamsRest[] => { +export const getPrepackagedRules = (rules = rawRules): PrepackagedRules[] => { return validateAllPrepackagedRules(rules); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_install.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_install.test.ts index 1a2bd4a10ac2de..ee76bf2ef15b89 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_install.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_install.test.ts @@ -5,7 +5,7 @@ */ import { getRulesToInstall } from './get_rules_to_install'; -import { getResult, fullRuleAlertParamsRest } from '../routes/__mocks__/request_responses'; +import { getResult, mockPrepackagedRule } from '../routes/__mocks__/request_responses'; describe('get_rules_to_install', () => { test('should return empty array if both rule sets are empty', () => { @@ -14,7 +14,7 @@ describe('get_rules_to_install', () => { }); test('should return empty array if the two rule ids match', () => { - const ruleFromFileSystem = fullRuleAlertParamsRest(); + const ruleFromFileSystem = mockPrepackagedRule(); ruleFromFileSystem.rule_id = 'rule-1'; const installedRule = getResult(); @@ -24,7 +24,7 @@ describe('get_rules_to_install', () => { }); test('should return the rule to install if the id of the two rules do not match', () => { - const ruleFromFileSystem = fullRuleAlertParamsRest(); + const ruleFromFileSystem = mockPrepackagedRule(); ruleFromFileSystem.rule_id = 'rule-1'; const installedRule = getResult(); @@ -34,10 +34,10 @@ describe('get_rules_to_install', () => { }); test('should return two rules to install if both the ids of the two rules do not match', () => { - const ruleFromFileSystem1 = fullRuleAlertParamsRest(); + const ruleFromFileSystem1 = mockPrepackagedRule(); ruleFromFileSystem1.rule_id = 'rule-1'; - const ruleFromFileSystem2 = fullRuleAlertParamsRest(); + const ruleFromFileSystem2 = mockPrepackagedRule(); ruleFromFileSystem2.rule_id = 'rule-2'; const installedRule = getResult(); @@ -47,13 +47,13 @@ describe('get_rules_to_install', () => { }); test('should return two rules of three to install if both the ids of the two rules do not match but the third does', () => { - const ruleFromFileSystem1 = fullRuleAlertParamsRest(); + const ruleFromFileSystem1 = mockPrepackagedRule(); ruleFromFileSystem1.rule_id = 'rule-1'; - const ruleFromFileSystem2 = fullRuleAlertParamsRest(); + const ruleFromFileSystem2 = mockPrepackagedRule(); ruleFromFileSystem2.rule_id = 'rule-2'; - const ruleFromFileSystem3 = fullRuleAlertParamsRest(); + const ruleFromFileSystem3 = mockPrepackagedRule(); ruleFromFileSystem3.rule_id = 'rule-3'; const installedRule = getResult(); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_install.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_install.ts index 1c795941cbb837..c44e4fb812c35c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_install.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_install.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RuleAlertParamsRest } from '../types'; +import { PrepackagedRules } from '../types'; import { RuleAlertType } from './types'; export const getRulesToInstall = ( - rulesFromFileSystem: RuleAlertParamsRest[], + rulesFromFileSystem: PrepackagedRules[], installedRules: RuleAlertType[] -): RuleAlertParamsRest[] => { +): PrepackagedRules[] => { return rulesFromFileSystem.filter( rule => !installedRules.some(installedRule => installedRule.params.ruleId === rule.rule_id) ); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_update.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_update.test.ts index 7f1b64d33cd9bf..40e303bddac1ae 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_update.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_update.test.ts @@ -5,7 +5,7 @@ */ import { getRulesToUpdate } from './get_rules_to_update'; -import { getResult, fullRuleAlertParamsRest } from '../routes/__mocks__/request_responses'; +import { getResult, mockPrepackagedRule } from '../routes/__mocks__/request_responses'; describe('get_rules_to_update', () => { test('should return empty array if both rule sets are empty', () => { @@ -14,7 +14,7 @@ describe('get_rules_to_update', () => { }); test('should return empty array if the id of the two rules do not match', () => { - const ruleFromFileSystem = fullRuleAlertParamsRest(); + const ruleFromFileSystem = mockPrepackagedRule(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; @@ -26,7 +26,7 @@ describe('get_rules_to_update', () => { }); test('should return empty array if the id of file system rule is less than the installed version', () => { - const ruleFromFileSystem = fullRuleAlertParamsRest(); + const ruleFromFileSystem = mockPrepackagedRule(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 1; @@ -38,7 +38,7 @@ describe('get_rules_to_update', () => { }); test('should return empty array if the id of file system rule is the same as the installed version', () => { - const ruleFromFileSystem = fullRuleAlertParamsRest(); + const ruleFromFileSystem = mockPrepackagedRule(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 1; @@ -50,7 +50,7 @@ describe('get_rules_to_update', () => { }); test('should return the rule to update if the id of file system rule is greater than the installed version', () => { - const ruleFromFileSystem = fullRuleAlertParamsRest(); + const ruleFromFileSystem = mockPrepackagedRule(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; @@ -62,7 +62,7 @@ describe('get_rules_to_update', () => { }); test('should return 1 rule out of 2 to update if the id of file system rule is greater than the installed version of just one', () => { - const ruleFromFileSystem = fullRuleAlertParamsRest(); + const ruleFromFileSystem = mockPrepackagedRule(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; @@ -79,11 +79,11 @@ describe('get_rules_to_update', () => { }); test('should return 2 rules out of 2 to update if the id of file system rule is greater than the installed version of both', () => { - const ruleFromFileSystem1 = fullRuleAlertParamsRest(); + const ruleFromFileSystem1 = mockPrepackagedRule(); ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; - const ruleFromFileSystem2 = fullRuleAlertParamsRest(); + const ruleFromFileSystem2 = mockPrepackagedRule(); ruleFromFileSystem2.rule_id = 'rule-2'; ruleFromFileSystem2.version = 2; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_update.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_update.ts index 10b849493858a1..31eff6a4ec87a1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_update.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_update.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RuleAlertParamsRest } from '../types'; +import { PrepackagedRules } from '../types'; import { RuleAlertType } from './types'; export const getRulesToUpdate = ( - rulesFromFileSystem: RuleAlertParamsRest[], + rulesFromFileSystem: PrepackagedRules[], installedRules: RuleAlertType[] -): RuleAlertParamsRest[] => { +): PrepackagedRules[] => { return rulesFromFileSystem.filter(rule => installedRules.some(installedRule => { return ( diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts index 9c3be64f71a0dc..98c04f95387f42 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts @@ -7,12 +7,12 @@ import { ActionsClient } from '../../../../../actions'; import { AlertsClient } from '../../../../../alerting'; import { createRules } from './create_rules'; -import { RuleAlertParamsRest } from '../types'; +import { PrepackagedRules } from '../types'; export const installPrepackagedRules = async ( alertsClient: AlertsClient, actionsClient: ActionsClient, - rules: RuleAlertParamsRest[], + rules: PrepackagedRules[], outputIndex: string ): Promise => { await rules.forEach(async rule => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts index 3d2ca8f91281b8..756634c8fa0425 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts @@ -7,12 +7,12 @@ import { ActionsClient } from '../../../../../actions'; import { AlertsClient } from '../../../../../alerting'; import { updateRules } from './update_rules'; -import { RuleAlertParamsRest } from '../types'; +import { PrepackagedRules } from '../types'; export const updatePrepackagedRules = async ( alertsClient: AlertsClient, actionsClient: ActionsClient, - rules: RuleAlertParamsRest[], + rules: PrepackagedRules[], outputIndex: string ): Promise => { await rules.forEach(async rule => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts index 8a9e050c039b4f..c7bd92322360a8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts @@ -58,6 +58,7 @@ export type RuleAlertParamsRest = Omit< RuleAlertParams, | 'ruleId' | 'falsePositives' + | 'immutable' | 'maxSignals' | 'savedId' | 'riskScore' @@ -99,11 +100,25 @@ export type OutputRuleAlertRest = RuleAlertParamsRest & { id: string; created_by: string | undefined | null; updated_by: string | undefined | null; + immutable: boolean; }; export type ImportRuleAlertRest = Omit & { id: string | undefined | null; rule_id: string; + immutable: boolean; }; +export type PrepackagedRules = Omit< + RuleAlertParamsRest, + | 'status' + | 'status_date' + | 'last_failure_at' + | 'last_success_at' + | 'last_failure_message' + | 'last_success_message' + | 'updated_at' + | 'created_at' +> & { rule_id: string; immutable: boolean }; + export type CallWithRequest = (endpoint: string, params: T, options?: U) => Promise; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.tsx index e2f92e0acd6456..53627d1cf2f6b3 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.tsx @@ -138,6 +138,7 @@ export const getColumns = ( name: 'ID', sortable: true, truncateText: true, + scope: 'row', }, { field: TRANSFORM_LIST_COLUMN.DESCRIPTION, diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/index.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/index.tsx index 731f560d315d63..679106f7e19b4d 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/index.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/index.tsx @@ -13,10 +13,10 @@ import { Typeahead } from './typeahead'; import { useUrlParams } from '../../../hooks'; import { toStaticIndexPattern } from '../../../lib/helper'; import { - AutocompleteProviderRegister, - AutocompleteSuggestion, esKuery, IIndexPattern, + autocomplete, + DataPublicPluginStart, } from '../../../../../../../../src/plugins/data/public'; import { useIndexPattern } from '../../../hooks'; @@ -25,7 +25,7 @@ const Container = styled.div` `; interface State { - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; isLoadingIndexPattern: boolean; } @@ -34,38 +34,11 @@ function convertKueryToEsQuery(kuery: string, indexPattern: IIndexPattern) { return esKuery.toElasticsearchQuery(ast, indexPattern); } -function getSuggestions( - query: string, - selectionStart: number, - apmIndexPattern: IIndexPattern, - autocomplete: Pick -) { - const autocompleteProvider = autocomplete.getProvider('kuery'); - if (!autocompleteProvider) { - return []; - } - const config = { - get: () => true, - }; - - const getAutocompleteSuggestions = autocompleteProvider({ - config, - indexPatterns: [apmIndexPattern], - }); - - const suggestions = getAutocompleteSuggestions({ - query, - selectionStart, - selectionEnd: selectionStart, - }); - return suggestions; -} - interface Props { - autocomplete: Pick; + autocomplete: DataPublicPluginStart['autocomplete']; } -export function KueryBar({ autocomplete }: Props) { +export function KueryBar({ autocomplete: autocompleteService }: Props) { const [state, setState] = useState({ suggestions: [], isLoadingIndexPattern: true, @@ -99,14 +72,16 @@ export function KueryBar({ autocomplete }: Props) { currentRequestCheck = currentRequest; try { - let suggestions = await getSuggestions( - inputValue, - selectionStart, - indexPattern, - autocomplete - ); - suggestions = suggestions - .filter((suggestion: AutocompleteSuggestion) => !startsWith(suggestion.text, 'span.')) + const suggestions = ( + (await autocompleteService.getQuerySuggestions({ + language: 'kuery', + indexPatterns: [indexPattern], + query: inputValue, + selectionStart, + selectionEnd: selectionStart, + })) || [] + ) + .filter(suggestion => !startsWith(suggestion.text, 'span.')) .slice(0, 15); if (currentRequest !== currentRequestCheck) { diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_status_tags.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_status_tags.tsx index 923bf2c68cc56a..e2855f78262e75 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_status_tags.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_status_tags.tsx @@ -66,44 +66,57 @@ export const LocationStatusTags = ({ locations }: Props) => { return a.label > b.label ? 1 : b.label > a.label ? -1 : 0; }); - moment.updateLocale('en', { - relativeTime: { - future: 'in %s', - past: '%s ago', - s: '%ds', - ss: '%ss', - m: '%dm', - mm: '%dm', - h: '%dh', - hh: '%dh', - d: '%dd', - dd: '%dd', - M: '%d Mon', - MM: '%d Mon', - y: '%d Yr', - yy: '%d Yr', - }, - }); + const tagLabel = (item: StatusTag, ind: number, color: string) => { + return ( + + + + {item.label} + + + + {moment(item.timestamp).fromNow()} + + + ); + }; - const tagLabel = (item: StatusTag, ind: number, color: string) => ( - - - - {item.label} - - - - {moment(item.timestamp).fromNow()} - - - ); + const prevLocal: string = moment.locale() ?? 'en'; - return ( - <> + const renderTags = () => { + moment.defineLocale('en-tag', { + relativeTime: { + future: 'in %s', + past: '%s ago', + s: '%ds', + ss: '%ss', + m: '%dm', + mm: '%dm', + h: '%dh', + hh: '%dh', + d: '%dd', + dd: '%dd', + M: '%d Mon', + MM: '%d Mon', + y: '%d Yr', + yy: '%d Yr', + }, + }); + const tags = ( {downLocations.map((item, ind) => tagLabel(item, ind, danger))} {upLocations.map((item, ind) => tagLabel(item, ind, gray))} + ); + + // Need to reset locale so it doesn't effect other parts of the app + moment.locale(prevLocal); + return tags; + }; + + return ( + <> + {renderTags()} {locations.length > 7 && ( diff --git a/x-pack/legacy/plugins/uptime/public/pages/overview.tsx b/x-pack/legacy/plugins/uptime/public/pages/overview.tsx index fbfbfc06e3c52f..1c14d971120be0 100644 --- a/x-pack/legacy/plugins/uptime/public/pages/overview.tsx +++ b/x-pack/legacy/plugins/uptime/public/pages/overview.tsx @@ -20,14 +20,14 @@ import { useIndexPattern, useUrlParams, useUptimeTelemetry, UptimePage } from '. import { stringifyUrlParams } from '../lib/helper/stringify_url_params'; import { useTrackPageview } from '../../../infra/public'; import { combineFiltersAndUserSearch, stringifyKueries, toStaticIndexPattern } from '../lib/helper'; -import { AutocompleteProviderRegister, esKuery } from '../../../../../../src/plugins/data/public'; import { store } from '../state'; import { setEsKueryString } from '../state/actions'; import { PageHeader } from './page_header'; +import { esKuery, DataPublicPluginStart } from '../../../../../../src/plugins/data/public'; import { UptimeThemeContext } from '../contexts/uptime_theme_context'; interface OverviewPageProps { - autocomplete: Pick; + autocomplete: DataPublicPluginStart['autocomplete']; setBreadcrumbs: UMUpdateBreadcrumbs; } diff --git a/x-pack/legacy/plugins/uptime/public/routes.tsx b/x-pack/legacy/plugins/uptime/public/routes.tsx index 08d752f5b32abe..028f2d5958325a 100644 --- a/x-pack/legacy/plugins/uptime/public/routes.tsx +++ b/x-pack/legacy/plugins/uptime/public/routes.tsx @@ -7,14 +7,14 @@ import React, { FC } from 'react'; import { Route, Switch } from 'react-router-dom'; import { MonitorPage, OverviewPage, NotFoundPage } from './pages'; -import { AutocompleteProviderRegister } from '../../../../../src/plugins/data/public'; +import { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; import { UMUpdateBreadcrumbs } from './lib/lib'; export const MONITOR_ROUTE = '/monitor/:monitorId/:location?'; export const OVERVIEW_ROUTE = '/'; interface RouterProps { - autocomplete: Pick; + autocomplete: DataPublicPluginStart['autocomplete']; basePath: string; setBreadcrumbs: UMUpdateBreadcrumbs; } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index c8a58d90595c4e..ce3edbbb598280 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -3141,12 +3141,12 @@ "visTypeMetric.function.bucket.help": "バケットディメンションの構成です。", "visTypeMetric.function.colorMode.help": "色を変更するメトリックの部分", "visTypeMetric.function.colorRange.help": "別の色が適用される値のグループを指定する範囲オブジェクト。", - "visTypeMetric.function.colorScheme.help": "使用する配色", + "visTypeMetric.function.colorSchema.help": "使用する配色", "visTypeMetric.function.font.help": "フォント設定です。", "visTypeMetric.function.help": "メトリックビジュアライゼーション", "visTypeMetric.function.invertColors.help": "色範囲を反転します", "visTypeMetric.function.metric.help": "メトリックディメンションの構成です。", - "visTypeMetric.function.percentage.help": "パーセンテージモードでメトリックを表示します。colorRange を設定する必要があります。", + "visTypeMetric.function.percentageMode.help": "パーセンテージモードでメトリックを表示します。colorRange を設定する必要があります。", "visTypeMetric.function.showLabels.help": "メトリック値の下にラベルを表示します。", "visTypeMetric.function.subText.help": "メトリックの下に表示するカスタムテキスト", "visTypeMetric.function.useRanges.help": "有効な色範囲です。", @@ -3213,7 +3213,6 @@ "visTypeTimeseries.addDeleteButtons.deleteButtonDefaultTooltip": "削除", "visTypeTimeseries.addDeleteButtons.reEnableTooltip": "再度有効にする", "visTypeTimeseries.addDeleteButtons.temporarilyDisableTooltip": "一時的に無効にする", - "visTypeTimeseries.aggLookup.addPipelineAggDescription": "{label} (「+」ボタンでこのパイプライン集約を追加します)", "visTypeTimeseries.aggLookup.averageLabel": "平均", "visTypeTimeseries.aggLookup.calculationLabel": "計算", "visTypeTimeseries.aggLookup.cardinalityLabel": "基数", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 583495972c4c1e..dc72e83ebe3c30 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -3141,12 +3141,12 @@ "visTypeMetric.function.bucket.help": "存储桶维度配置", "visTypeMetric.function.colorMode.help": "指标的哪部分要上色", "visTypeMetric.function.colorRange.help": "指定应将不同颜色应用到的值组的范围对象。", - "visTypeMetric.function.colorScheme.help": "要使用的颜色方案", + "visTypeMetric.function.colorSchema.help": "要使用的颜色方案", "visTypeMetric.function.font.help": "字体设置。", "visTypeMetric.function.help": "指标可视化", "visTypeMetric.function.invertColors.help": "反转颜色范围", "visTypeMetric.function.metric.help": "指标维度配置", - "visTypeMetric.function.percentage.help": "以百分比模式显示指标。需要设置 colorRange。", + "visTypeMetric.function.percentageMode.help": "以百分比模式显示指标。需要设置 colorRange。", "visTypeMetric.function.showLabels.help": "在指标值下显示标签。", "visTypeMetric.function.subText.help": "要在指标下显示的定制文本", "visTypeMetric.function.useRanges.help": "已启用颜色范围。", @@ -3213,7 +3213,6 @@ "visTypeTimeseries.addDeleteButtons.deleteButtonDefaultTooltip": "删除", "visTypeTimeseries.addDeleteButtons.reEnableTooltip": "重新启用", "visTypeTimeseries.addDeleteButtons.temporarilyDisableTooltip": "暂时禁用", - "visTypeTimeseries.aggLookup.addPipelineAggDescription": "{label}(使用“+”按钮添加此管道聚合)", "visTypeTimeseries.aggLookup.averageLabel": "平均值", "visTypeTimeseries.aggLookup.calculationLabel": "计算", "visTypeTimeseries.aggLookup.cardinalityLabel": "基数", diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts index d20450f8ec47e9..08e6c90a1044ca 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts @@ -662,16 +662,7 @@ export default function alertTests({ getService }: FtrProviderContext) { } }); - /** - * Skipping due to an issue we've discovered in the `muteAll` api - * which corrupts the apiKey and causes this test to exhibit flaky behaviour. - * Failed CIs for example: - * 1. https://github.com/elastic/kibana/issues/53690 - * 2. https://github.com/elastic/kibana/issues/53683 - * - * This will be fixed and reverted in PR: https://github.com/elastic/kibana/pull/53333 - */ - it.skip(`shouldn't schedule actions when alert is muted`, async () => { + it(`shouldn't schedule actions when alert is muted`, async () => { const testStart = new Date(); const reference = alertUtils.generateReference(); const response = await alertUtils.createAlwaysFiringAction({ @@ -761,8 +752,7 @@ export default function alertTests({ getService }: FtrProviderContext) { } }); - // Flaky: https://github.com/elastic/kibana/issues/54125 - it.skip(`should unmute all instances when unmuting an alert`, async () => { + it(`should unmute all instances when unmuting an alert`, async () => { const testStart = new Date(); const reference = alertUtils.generateReference(); const response = await alertUtils.createAlwaysFiringAction({ diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json index 7070e3f5aa4939..7d2933f9d92385 100644 --- a/x-pack/tsconfig.json +++ b/x-pack/tsconfig.json @@ -38,7 +38,8 @@ "monitoring/common/*": [ "x-pack/monitoring/common/*" ], - "plugins/*": ["src/legacy/core_plugins/*/public/"] + "plugins/*": ["src/legacy/core_plugins/*/public/"], + "fixtures/*": ["src/fixtures/*"] }, "types": [ "node", diff --git a/yarn.lock b/yarn.lock index 034a2ed08b4e68..b534a7a4fcb799 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4398,6 +4398,13 @@ resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.0.tgz#cbb49815a5e1129d5f23836a98d65d93822409af" integrity sha512-dxdRrUov2HVTbSRFX+7xwUPlbGYVEZK6PrSqClg2QPos3PNe0bCajkDDkDeeC1znjSH03KOEqVbXpnJuWa2wgQ== +"@types/flot@^0.0.31": + version "0.0.31" + resolved "https://registry.yarnpkg.com/@types/flot/-/flot-0.0.31.tgz#0daca37c6c855b69a0a7e2e37dd0f84b3db8c8c1" + integrity sha512-X+RcMQCqPlQo8zPT6cUFTd/PoYBShMQlHUeOXf05jWlfYnvLuRmluB9z+2EsOKFgUzqzZve5brx+gnFxBaHEUw== + dependencies: + "@types/jquery" "*" + "@types/geojson@*": version "7946.0.7" resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.7.tgz#c8fa532b60a0042219cdf173ca21a975ef0666ad" @@ -4601,7 +4608,7 @@ resolved "https://registry.yarnpkg.com/@types/joi/-/joi-13.6.1.tgz#325486a397504f8e22c8c551dc8b0e1d41d5d5ae" integrity sha512-JxZ0NP8NuB0BJOXi1KvAA6rySLTPmhOy4n2gzSFq/IFM3LNFm0h+2Vn/bPPgEYlWqzS2NPeLgKqfm75baX+Hog== -"@types/jquery@^3.3.31": +"@types/jquery@*", "@types/jquery@^3.3.31": version "3.3.31" resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.3.31.tgz#27c706e4bf488474e1cb54a71d8303f37c93451b" integrity sha512-Lz4BAJihoFw5nRzKvg4nawXPzutkv7wmfQ5121avptaSIXlDNJCUuxZxX/G+9EVidZGuO0UBlk+YjKbwRKJigg==