diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 98d6cec161b6ac..274d9a25ef5349 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -42,8 +42,11 @@ /x-pack/legacy/plugins/spaces/ @elastic/kibana-security /x-pack/legacy/plugins/encrypted_saved_objects/ @elastic/kibana-security /src/legacy/server/csp/ @elastic/kibana-security +/x-pack/plugins/security/ @elastic/kibana-security # Kibana Stack Services +/packages/kbn-analytics/ @elastic/kibana-stack-services +/src/legacy/core_plugins/ui_metric/ @elastic/kibana-stack-services /x-pack/legacy/plugins/telemetry @elastic/kibana-stack-services /x-pack/legacy/plugins/alerting @elastic/kibana-stack-services /x-pack/legacy/plugins/actions @elastic/kibana-stack-services diff --git a/.i18nrc.json b/.i18nrc.json index eb9263c04732c4..bcc1fe70ab1e77 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -2,6 +2,7 @@ "paths": { "common.ui": "src/legacy/ui", "data": "src/legacy/core_plugins/data", + "kibana_react": "src/legacy/core_plugins/kibana_react", "server": "src/legacy/server", "console": "src/legacy/core_plugins/console", "core": "src/core", diff --git a/docs/canvas/canvas-function-reference.asciidoc b/docs/canvas/canvas-function-reference.asciidoc index f07268bacc50ef..8cc7f64adc3a51 100644 --- a/docs/canvas/canvas-function-reference.asciidoc +++ b/docs/canvas/canvas-function-reference.asciidoc @@ -1161,7 +1161,7 @@ Default: `""` |`style` |The CSS font properties for the content. For example, `font-family` or `font-weight`. -Default: `{<>}` +Default: `{font}` |=== *Returns:* `render` @@ -1213,13 +1213,13 @@ Default: `""` |`style` |The CSS font properties for the metric. For example, `font-family` or `font-weight`. -Default: `{<> size=48 family="'Open Sans', Helvetica, Arial, sans-serif" color="#000000" align=center lHeight=48}`. +Default: `{font size=48 family="'Open Sans', Helvetica, Arial, sans-serif" color="#000000" align=center lHeight=48}`. |`labelFont` |`style` |The CSS font properties for the label. For example, `font-family` or `font-weight`. -Default: `{<> size=14 family="'Open Sans', Helvetica, Arial, sans-serif" color="#000000" align=center}`. +Default: `{font size=14 family="'Open Sans', Helvetica, Arial, sans-serif" color="#000000" align=center}`. |=== *Returns:* `render` @@ -1305,7 +1305,7 @@ Configures a pie chart element. |`style` |The CSS font properties for the labels. For example, `font-family` or `font-weight`. -Default: `{<>}` +Default: `{font}` |`hole` |`number` @@ -1335,7 +1335,7 @@ Default: `false` |`palette` |A `palette` object for describing the colors to use in this pie chart -Default: `{<>}` +Default: `{palette}` |`radius` |`string`, `number` @@ -1373,13 +1373,13 @@ Configures a plot element. |`seriesStyle` |The default style to use for every series. -Default: `{<> points=5}` +Default: `{seriesStyle points=5}` |`font` |`style` |The CSS font properties for the labels. For example, `font-family` or `font-weight`. -Default: `{<>}` +Default: `{font}` |`legend` |`string`, `boolean` @@ -1391,7 +1391,7 @@ Default: `"ne"` |`palette` |A `palette` object for describing the colors to use in this chart -Default: `{<>}` +Default: `{palette}` |`seriesStyle` † |`seriesStyle` @@ -1512,7 +1512,7 @@ Default: `20` |`style` |The CSS font properties for the label. For example, `font-family` or `font-weight`. -Default: `{<> size=24 family="'Open Sans', Helvetica, Arial, sans-serif" color="#000000" align=center}` +Default: `{font size=24 family="'Open Sans', Helvetica, Arial, sans-serif" color="#000000" align=center}` |`label` |`boolean`, `string` @@ -1572,7 +1572,7 @@ Default: `".canvasRenderEl{\n\n}"` |`containerStyle` |The style for the container, including background, border, and opacity. -Default: `{<>}` +Default: `{containerStyle}` |=== *Returns:* `render` @@ -1726,7 +1726,7 @@ Alias: `format` [[rowCount_fn]] === `rowCount` -Returns the number of rows. Pairs with `<>` to get the count of unique column values, or combinations of unique column values. +Returns the number of rows. Pairs with <> to get the count of unique column values, or combinations of unique column values. *Accepts:* `datatable` @@ -1964,7 +1964,7 @@ Configures a table element. |`style` |The CSS font properties for the contents of the table. For example, `font-family` or `font-weight`. -Default: `{<>}` +Default: `{font}` |`paginate` |`boolean` diff --git a/docs/dev-tools/console/auto-formatting.asciidoc b/docs/dev-tools/console/auto-formatting.asciidoc deleted file mode 100644 index a7e69aa66337ec..00000000000000 --- a/docs/dev-tools/console/auto-formatting.asciidoc +++ /dev/null @@ -1,20 +0,0 @@ -[[auto-formatting]] -=== Auto formatting - -Console can help you format requests. Select one or more requests that you -want to format, click the action icon (image:dev-tools/console/images/wrench.png[]), -and select *Auto indent*. - -For example, you might have a request that is formatted like this: - -[role="screenshot"] -image::dev-tools/console/images/copy-curl.png["Console close-up"] - -Console adjusts the JSON body of the request to apply the indents. - -[role="screenshot"] -image::dev-tools/console/images/request.png["Console close-up"] - -If you select *Auto indent* on a request that is already well formatted, -Console collapses the request body to a single line per document. -This is helpful when working with {es}'s {ref}/docs-bulk.html[bulk APIs]. diff --git a/docs/dev-tools/console/configuring-console.asciidoc b/docs/dev-tools/console/configuring-console.asciidoc deleted file mode 100644 index f750aa7f51ba09..00000000000000 --- a/docs/dev-tools/console/configuring-console.asciidoc +++ /dev/null @@ -1,26 +0,0 @@ -[[configuring-console]] -=== Configuring Console - -You can configure Console to your preferences. - -[float] -==== Configuring settings - -*Settings* allows you to modify the font size and set the fileds for -autocomplete. - -[role="screenshot"] -image::dev-tools/console/images/console-settings.png["Console settings"] - - -[float] -[[console-settings]] -==== Disabling Console - -If you don’t want to use Console, you can disable it by setting `console.enabled` -to false in your `kibana.yml` configuration file. Changing this setting -causes the server to regenerate assets on the next startup, -which might cause a delay before pages start being served. - - - diff --git a/docs/dev-tools/console/console.asciidoc b/docs/dev-tools/console/console.asciidoc index f43b5dad70d96b..a678e7189abec4 100644 --- a/docs/dev-tools/console/console.asciidoc +++ b/docs/dev-tools/console/console.asciidoc @@ -1,22 +1,20 @@ [[console-kibana]] == Console -Console enables you to interact with the REST API of {es}. *Note:* You cannot -interact with {kib} API endpoints via Console. +Console enables you to interact with the REST API of {es}. You can: -Go to *Dev Tools > Console* to get started. +* Send requests to {es} and view the responses +* View API documentation +* Get your request history -Console has two main areas: - -* The *editor*, where you compose requests to send to {es}. -* The *response* pane, which displays the responses to the request. +To get started, go to *Dev Tools > Console*. [role="screenshot"] image::dev-tools/console/images/console.png["Console"] [float] [[console-api]] -=== Writing requests +=== Write requests Console understands commands in a cURL-like syntax. For example, the following is a `GET` request to the {es} `_search` API. @@ -47,51 +45,92 @@ If you paste the above command into Console, {kib} automatically converts it to Console syntax. Alternatively, if you want to want to see Console syntax in cURL, click the action icon (image:dev-tools/console/images/wrench.png[]) and select *Copy as cURL*. -For help with formatting requests, you can use Console's <> -feature. +[float] +[[console-autocomplete]] +==== Autocomplete + +When you're typing a command, Console makes context-sensitive suggestions. +These suggestions show you the parameters for each API and speed up your typing. +To configure your preferences for autocomplete, go to +<>. + +[float] +[[auto-formatting]] +==== Auto-formatting +The auto-formatting +capability can help you format requests. Select one or more requests that you +want to format, click the action icon (image:dev-tools/console/images/wrench.png[]), +and then select *Auto indent*. + +For example, you might have a request formatted like this: + +[role="screenshot"] +image::dev-tools/console/images/copy-curl.png["Console close-up"] + +Console adjusts the JSON body of the request to apply the indents. + +[role="screenshot"] +image::dev-tools/console/images/request.png["Console close-up"] + +If you select *Auto indent* on a request that is already well formatted, +Console collapses the request body to a single line per document. +This is helpful when working with the {es} {ref}/docs-bulk.html[bulk APIs]. + [float] [[console-request]] -=== Submitting requests +=== Submit requests -Once you enter a command in the editor, click the -green triangle to submit the request to {es}. +When you're ready to submit the request to {es}, click the +green triangle. You can select multiple requests and submit them together. Console sends the requests to {es} one by one and shows the output in the response pane. Submitting multiple request is helpful when you're debugging an issue or trying query combinations in multiple scenarios. -[float] -[[console-autocomplete]] -=== Using autocomplete - -When typing a command, Console makes context-sensitive suggestions. -These suggestions can help you explore parameters for each API and speed up typing. -To configure your preferences for autocomplete, go to -<>. [float] [[console-view-api]] -=== Viewing API docs +=== View API docs -You can view the documentation for an API endpoint by clicking -the action icon (image:dev-tools/console/images/wrench.png[]) and selecting +To view the documentation for an API endpoint, click +the action icon (image:dev-tools/console/images/wrench.png[]) and select *Open documentation*. [float] [[console-history]] -=== Getting your request history +=== Get your request history Console maintains a list of the last 500 requests that {es} successfully executed. To view your most recent requests, click *History*. If you select a request and click *Apply*, {kib} adds it to the editor at the current cursor position. +[float] +[[configuring-console]] +=== Configure Console settings + +You can configure the Console font size, JSON syntax, +and autocomplete suggestions in *Settings*. + +[role="screenshot"] +image::dev-tools/console/images/console-settings.png["Console Settings"] +[float] +[[keyboard-shortcuts]] +=== Get keyboard shortcuts + +For a list of available keyboard +shortcuts, click *Help*. + +[float] +[[console-settings]] +=== Disable Console -include::auto-formatting.asciidoc[] +If you don’t want to use Console, you can disable it by setting `console.enabled` +to `false` in your `kibana.yml` configuration file. Changing this setting +causes the server to regenerate assets on the next startup, +which might cause a delay before pages start being served. -include::keyboard-shortcuts.asciidoc[] -include::configuring-console.asciidoc[] diff --git a/docs/dev-tools/console/images/console-settings.png b/docs/dev-tools/console/images/console-settings.png index 0181ed8796e6f3..1edd249cc7ee17 100644 Binary files a/docs/dev-tools/console/images/console-settings.png and b/docs/dev-tools/console/images/console-settings.png differ diff --git a/docs/dev-tools/console/images/console.png b/docs/dev-tools/console/images/console.png index b820f9fb0a20da..090668afc29d19 100644 Binary files a/docs/dev-tools/console/images/console.png and b/docs/dev-tools/console/images/console.png differ diff --git a/docs/dev-tools/console/keyboard-shortcuts.asciidoc b/docs/dev-tools/console/keyboard-shortcuts.asciidoc deleted file mode 100644 index f71e04b0d3cfe7..00000000000000 --- a/docs/dev-tools/console/keyboard-shortcuts.asciidoc +++ /dev/null @@ -1,22 +0,0 @@ -[[keyboard-shortcuts]] -=== Keyboard shortcuts - -The keyboard shortcuts below can help you move quickly through Console. You can -also view these shortcuts by clicking *Help* in Console. - -[float] -==== General editing - -Ctrl/Cmd + I:: Auto indent current request. -Ctrl + Space:: Open Autocomplete (even if not typing). -Ctrl/Cmd + Enter:: Submit request. -Ctrl/Cmd + Up/Down:: Jump to the previous/next request start or end. -Ctrl/Cmd + Alt + L:: Collapse/expand current scope. -Ctrl/Cmd + Option + 0:: Collapse all scopes but the current one. Expand by adding a shift. - -[float] -==== When autocomplete is visible - -Down arrow:: Switch focus to autocomplete menu. Use arrows to further select a term. -Enter/Tab:: Select the currently selected or the top most term in autocomplete menu. -Esc:: Close autocomplete menu. diff --git a/docs/development/core/public/kibana-plugin-public.plugin.md b/docs/development/core/public/kibana-plugin-public.plugin.md index 879897ec18d847..979436e6dab378 100644 --- a/docs/development/core/public/kibana-plugin-public.plugin.md +++ b/docs/development/core/public/kibana-plugin-public.plugin.md @@ -9,7 +9,7 @@ The interface that should be returned by a `PluginInitializer`. Signature: ```typescript -export interface Plugin +export interface Plugin ``` ## Methods diff --git a/docs/development/core/public/kibana-plugin-public.plugininitializer.md b/docs/development/core/public/kibana-plugin-public.plugininitializer.md index 3201e75e8aae3b..0e1124afff3692 100644 --- a/docs/development/core/public/kibana-plugin-public.plugininitializer.md +++ b/docs/development/core/public/kibana-plugin-public.plugininitializer.md @@ -9,5 +9,5 @@ The `plugin` export at the root of a plugin's `public` directory should conform Signature: ```typescript -export declare type PluginInitializer = {}, TPluginsStart extends Record = {}> = (core: PluginInitializerContext) => Plugin; +export declare type PluginInitializer = (core: PluginInitializerContext) => Plugin; ``` diff --git a/docs/development/core/server/kibana-plugin-server.apicaller.md b/docs/development/core/server/kibana-plugin-server.apicaller.md index c4696551f0a49e..10065a4089ce50 100644 --- a/docs/development/core/server/kibana-plugin-server.apicaller.md +++ b/docs/development/core/server/kibana-plugin-server.apicaller.md @@ -8,5 +8,5 @@ Signature: ```typescript -export declare type APICaller = (endpoint: string, clientParams: Record, options?: CallAPIOptions) => Promise; +export declare type APICaller = (endpoint: string, clientParams: Record, options?: CallAPIOptions) => Promise; ``` diff --git a/docs/development/core/server/kibana-plugin-server.authresultdata.md b/docs/development/core/server/kibana-plugin-server.authresultdata.md index 7ba5771b80d675..57908bd7045917 100644 --- a/docs/development/core/server/kibana-plugin-server.authresultdata.md +++ b/docs/development/core/server/kibana-plugin-server.authresultdata.md @@ -17,5 +17,5 @@ export interface AuthResultData | Property | Type | Description | | --- | --- | --- | | [headers](./kibana-plugin-server.authresultdata.headers.md) | AuthHeaders | Auth specific headers to authenticate a user against Elasticsearch. | -| [state](./kibana-plugin-server.authresultdata.state.md) | Record<string, unknown> | Data to associate with an incoming request. Any downstream plugin may get access to the data. | +| [state](./kibana-plugin-server.authresultdata.state.md) | Record<string, any> | Data to associate with an incoming request. Any downstream plugin may get access to the data. | diff --git a/docs/development/core/server/kibana-plugin-server.authresultdata.state.md b/docs/development/core/server/kibana-plugin-server.authresultdata.state.md index 3fb8f8e48bdedd..70054395514b77 100644 --- a/docs/development/core/server/kibana-plugin-server.authresultdata.state.md +++ b/docs/development/core/server/kibana-plugin-server.authresultdata.state.md @@ -9,5 +9,5 @@ Data to associate with an incoming request. Any downstream plugin may get access Signature: ```typescript -state: Record; +state: Record; ``` diff --git a/docs/development/core/server/kibana-plugin-server.clusterclient.callasinternaluser.md b/docs/development/core/server/kibana-plugin-server.clusterclient.callasinternaluser.md index b00e83dd43d52e..0b314c56703656 100644 --- a/docs/development/core/server/kibana-plugin-server.clusterclient.callasinternaluser.md +++ b/docs/development/core/server/kibana-plugin-server.clusterclient.callasinternaluser.md @@ -9,5 +9,5 @@ Calls specified endpoint with provided clientParams on behalf of the Kibana inte Signature: ```typescript -callAsInternalUser: (endpoint: string, clientParams?: Record, options?: CallAPIOptions | undefined) => Promise; +callAsInternalUser: (endpoint: string, clientParams?: Record, options?: CallAPIOptions | undefined) => Promise; ``` diff --git a/docs/development/core/server/kibana-plugin-server.clusterclient.md b/docs/development/core/server/kibana-plugin-server.clusterclient.md index 89ed51762198cb..07c1006dda4999 100644 --- a/docs/development/core/server/kibana-plugin-server.clusterclient.md +++ b/docs/development/core/server/kibana-plugin-server.clusterclient.md @@ -22,7 +22,7 @@ export declare class ClusterClient | Property | Modifiers | Type | Description | | --- | --- | --- | --- | -| [callAsInternalUser](./kibana-plugin-server.clusterclient.callasinternaluser.md) | | (endpoint: string, clientParams?: Record<string, unknown>, options?: CallAPIOptions | undefined) => Promise<any> | Calls specified endpoint with provided clientParams on behalf of the Kibana internal user. | +| [callAsInternalUser](./kibana-plugin-server.clusterclient.callasinternaluser.md) | | (endpoint: string, clientParams?: Record<string, any>, options?: CallAPIOptions | undefined) => Promise<any> | Calls specified endpoint with provided clientParams on behalf of the Kibana internal user. | ## Methods diff --git a/docs/development/core/server/kibana-plugin-server.plugin.md b/docs/development/core/server/kibana-plugin-server.plugin.md index 5cef833ecc30e4..73faf020a4a16c 100644 --- a/docs/development/core/server/kibana-plugin-server.plugin.md +++ b/docs/development/core/server/kibana-plugin-server.plugin.md @@ -9,7 +9,7 @@ The interface that should be returned by a `PluginInitializer`. Signature: ```typescript -export interface Plugin +export interface Plugin ``` ## Methods diff --git a/docs/development/core/server/kibana-plugin-server.plugininitializer.md b/docs/development/core/server/kibana-plugin-server.plugininitializer.md index 402b4001ce6331..1254ed2c88da35 100644 --- a/docs/development/core/server/kibana-plugin-server.plugininitializer.md +++ b/docs/development/core/server/kibana-plugin-server.plugininitializer.md @@ -9,5 +9,5 @@ The `plugin` export at the root of a plugin's `server` directory should conform Signature: ```typescript -export declare type PluginInitializer = {}, TPluginsStart extends Record = {}> = (core: PluginInitializerContext) => Plugin; +export declare type PluginInitializer = (core: PluginInitializerContext) => Plugin; ``` diff --git a/docs/development/core/server/kibana-plugin-server.scopedclusterclient.callascurrentuser.md b/docs/development/core/server/kibana-plugin-server.scopedclusterclient.callascurrentuser.md index 4e462612ba103a..f96554296e653a 100644 --- a/docs/development/core/server/kibana-plugin-server.scopedclusterclient.callascurrentuser.md +++ b/docs/development/core/server/kibana-plugin-server.scopedclusterclient.callascurrentuser.md @@ -9,7 +9,7 @@ Calls specified `endpoint` with provided `clientParams` on behalf of the user in Signature: ```typescript -callAsCurrentUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; +callAsCurrentUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; ``` ## Parameters @@ -17,7 +17,7 @@ callAsCurrentUser(endpoint: string, clientParams?: Record, opti | Parameter | Type | Description | | --- | --- | --- | | endpoint | string | String descriptor of the endpoint e.g. cluster.getSettings or ping. | -| clientParams | Record<string, unknown> | A dictionary of parameters that will be passed directly to the Elasticsearch JS client. | +| clientParams | Record<string, any> | A dictionary of parameters that will be passed directly to the Elasticsearch JS client. | | options | CallAPIOptions | Options that affect the way we call the API and process the result. | Returns: diff --git a/docs/development/core/server/kibana-plugin-server.scopedclusterclient.callasinternaluser.md b/docs/development/core/server/kibana-plugin-server.scopedclusterclient.callasinternaluser.md index e79abfcf81db08..395d65197e2ccd 100644 --- a/docs/development/core/server/kibana-plugin-server.scopedclusterclient.callasinternaluser.md +++ b/docs/development/core/server/kibana-plugin-server.scopedclusterclient.callasinternaluser.md @@ -9,7 +9,7 @@ Calls specified `endpoint` with provided `clientParams` on behalf of the Kibana Signature: ```typescript -callAsInternalUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; +callAsInternalUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; ``` ## Parameters @@ -17,7 +17,7 @@ callAsInternalUser(endpoint: string, clientParams?: Record, opt | Parameter | Type | Description | | --- | --- | --- | | endpoint | string | String descriptor of the endpoint e.g. cluster.getSettings or ping. | -| clientParams | Record<string, unknown> | A dictionary of parameters that will be passed directly to the Elasticsearch JS client. | +| clientParams | Record<string, any> | A dictionary of parameters that will be passed directly to the Elasticsearch JS client. | | options | CallAPIOptions | Options that affect the way we call the API and process the result. | Returns: diff --git a/docs/logs/images/logs-stream-highlight-box.png b/docs/logs/images/logs-stream-highlight-box.png new file mode 100644 index 00000000000000..1a6cb2b59dd593 Binary files /dev/null and b/docs/logs/images/logs-stream-highlight-box.png differ diff --git a/docs/logs/images/logs-stream-highlight-entries.png b/docs/logs/images/logs-stream-highlight-entries.png new file mode 100644 index 00000000000000..d23c7c9853304c Binary files /dev/null and b/docs/logs/images/logs-stream-highlight-entries.png differ diff --git a/docs/logs/using.asciidoc b/docs/logs/using.asciidoc index 3b17c72c6476ff..21902a94c45b89 100644 --- a/docs/logs/using.asciidoc +++ b/docs/logs/using.asciidoc @@ -72,6 +72,19 @@ image::logs/images/logs-usage-streaming-indicator.png[Logs streaming indicator] Historical data offers infinite scrolling. +[float] +[[logs-highlight]] +=== Highlight a phrase in the logs stream +To find instances of a particular word or phrase in the logs stream, click the Highlights button in the toolbar and enter your search terms. + +[role="screenshot"] +image::logs/images/logs-stream-highlight-box.png[Logs highlight dialog box] + +This will highlight any instances of your search terms that appear in the logs stream. + +[role="screenshot"] +image::logs/images/logs-stream-highlight-entries.png[Logs stream with the search term 'safepoint' highlighted] + [float] [[logs-event-inspector]] === Inspect a log event diff --git a/docs/management/rollups/visualize_rollup_data.asciidoc b/docs/management/rollups/visualize_rollup_data.asciidoc index 3c9100246138df..5f64e4bdd0ac54 100644 --- a/docs/management/rollups/visualize_rollup_data.asciidoc +++ b/docs/management/rollups/visualize_rollup_data.asciidoc @@ -6,7 +6,7 @@ beta[] You can visualize your rolled up data in a variety of charts, tables, maps, and more. Most visualizations support rolled up data, with the exception of -Timelion, Visual Builder, and Vega visualizations. +Timelion, TSVB, and Vega visualizations. You create an index pattern for rolled up data the same way you do for any data, in *Management > Kibana > Index patterns*. Clicking *Create index pattern* includes diff --git a/docs/plugins/known-plugins.asciidoc b/docs/plugins/known-plugins.asciidoc index fa26c13fab2db4..d5344a796c7ce4 100644 --- a/docs/plugins/known-plugins.asciidoc +++ b/docs/plugins/known-plugins.asciidoc @@ -39,6 +39,7 @@ This list of plugins is not guaranteed to work on your version of Kibana. Instea * https://github.com/fbaligand/kibana-enhanced-table[Enhanced Table] (fbaligand) * https://github.com/nreese/enhanced_tilemap[Enhanced Tilemap] (nreese) * https://github.com/ommsolutions/kibana_ext_metrics_vis[Extended Metric] (ommsolutions) +* https://github.com/flexmonster/pivot-kibana[Flexmonster Pivot Table & Charts] - a customizable pivot table component for advanced data analysis and reporting. * https://github.com/outbrain/ob-kb-funnel[Funnel Visualization] (roybass) * https://github.com/sbeyn/kibana-plugin-gauge-sg[Gauge] (sbeyn) * https://github.com/clamarque/Kibana_health_metric_vis[Health Metric] (clamarque) @@ -60,7 +61,6 @@ This list of plugins is not guaranteed to work on your version of Kibana. Instea [float] === Other * https://github.com/nreese/kibana-time-plugin[Time picker as a dashboard panel] Widget to view and edit the time range from within dashboards. -* https://github.com/sw-jung/kibana_notification_center[Notification Center] (sw-jung) - for better experience of notifier toasts. * https://github.com/Webiks/kibana-API.git[Kibana-API] (webiks) Exposes an API with Kibana functionality. Use it to create, edit and embed visualizations, and also to search inside an embedded dashboard. diff --git a/docs/security/authorization/index.asciidoc b/docs/security/authorization/index.asciidoc index 6fc0f3f1367da0..d33d6297536fbe 100644 --- a/docs/security/authorization/index.asciidoc +++ b/docs/security/authorization/index.asciidoc @@ -13,6 +13,7 @@ NOTE: When running multiple tenants of Kibana by changing the `kibana.index` in To create a role that grants {kib} privileges, go to **Management -> Security -> Roles** and click **Create role**. +[[adding_kibana_privileges]] ==== Adding {kib} privileges To assign {kib} privileges to the role, click **Add space privilege** in the Kibana section. diff --git a/docs/spaces/images/delete-space.png b/docs/spaces/images/delete-space.png deleted file mode 100644 index ce83670ad2a3ab..00000000000000 Binary files a/docs/spaces/images/delete-space.png and /dev/null differ diff --git a/docs/spaces/images/spaces-roles.png b/docs/spaces/images/spaces-roles.png new file mode 100755 index 00000000000000..2ecbfed6017c4f Binary files /dev/null and b/docs/spaces/images/spaces-roles.png differ diff --git a/docs/spaces/index.asciidoc b/docs/spaces/index.asciidoc index c6bf9a9184a667..fdd564cc69c9fe 100644 --- a/docs/spaces/index.asciidoc +++ b/docs/spaces/index.asciidoc @@ -2,28 +2,119 @@ [[xpack-spaces]] == Spaces -With spaces, you can organize your dashboards and other saved objects into meaningful categories. -After creating your own spaces, you will be asked to choose a space when you enter Kibana. -Once inside a space, you will only see the dashboards and other saved objects that belong to that space. +Spaces enable you to organize your dashboards and other saved +objects into meaningful categories. Once inside a space, you see only +the dashboards and saved objects that belong to that space. -You can change your current space at any time, by clicking on the space avatar in the top left. +{kib} creates a default space for you. +After you create your own +spaces, you're asked to choose a space when you log in to Kibana. You can change your +current space at any time by using the menu in the upper left. [role="screenshot"] image::spaces/images/change-space.png["Change current space"] -With security enabled, you can <>. +Kibana supports spaces in several ways. You can: +[[spaces-getting-started]] + +* <> +* <> +* <> +* <> +* <> [float] -[[spaces-getting-started]] -=== Getting Started +[[spaces-managing]] +=== View, create, and delete spaces + +Go to **Management > Spaces** for an overview of your spaces. This view provides actions +for you to create, edit, and delete spaces. + +[role="screenshot"] +image::spaces/images/space-management.png["Space management"] + +[float] +==== Create or edit a space + +You can create as many spaces as you like. Click *Create a space* and provide a name, +URL identifier, optional description. + +The URL identifier is a short text string that becomes part of the +{kib} URL when you are inside that space. {kib} suggests a URL identifier based +on the name of your space, but you can customize the identifier to your liking. +You cannot change the space identifier once you create the space. + +{kib} also has an <> +if you prefer to create spaces programatically. + +[role="screenshot"] +image::spaces/images/edit-space.png["Space management"] + +[float] +==== Delete a space + +Deleting a space permanently removes the space and all of its contents. +Find the space on the *Spaces* overview page and click the trash icon in the Actions column. +You can't delete the default space, but you can customize it to your liking. + +[float] +[[spaces-control-feature-visibility]] +=== Control feature access based on user needs + +You have control over which features are visible in each space. +For example, you might hide Dev Tools +in your "Executive" space or show Stack Monitoring only in your "Admin" space. +You can define which features to show or hide when you add or edit a space. + +Controlling feature +visibility is not a security feature. To secure access +to specific features on a per-user basis, you must configure +<>. + +[role="screenshot"] +image::spaces/images/edit-space-feature-visibility.png["Controlling features visiblity"] + +[float] +[[spaces-control-user-access]] +=== Control feature access based on user privileges + +When using Kibana with security, you can configure applications and features +based on your users’ privileges. This means different roles can have access +to different features in the same space. +Power users might have privileges to create and edit visualizations and dashboards, +while analysts or executives might have Dashboard and Canvas with read-only privileges. +See <> for details. + +[role="screenshot"] +image::spaces/images/spaces-roles.png["Controlling features visiblity"] + +[float] +[[spaces-moving-objects]] +=== Move saved objects between spaces +Use {kib}'s <> +interface to copy objects from one space to another. + +. Navigate to the space that contains your saved objects. +. Export your saved objects via the import/export interface. +. Navigate to the space where you want to import the objects. +. Import your saved objects via the import/export interface. +. (Optional) Delete objects in the export space that you no longer need. + +{kib} also has experimental <> and +<> dashboard APIs if you want +a dashboard-centric way to automate this process. + + +[float] +[[spaces-delete-started]] +=== Disable and version updates -Spaces are automatically enabled in {kib}. If you don't wish to use this feature, you can disable it -by setting `xpack.spaces.enabled` to `false` in your `kibana.yml` configuration file. +Spaces are automatically enabled in {kib}. If you don't want use this feature, +you can disable it +by setting `xpack.spaces.enabled` to `false` in your +`kibana.yml` configuration file. -{kib} automatically creates a default space for you. If you are upgrading from another -version of {kib}, then the default space will contain all of your existing saved objects. -Although you can't delete the default space, you can customize it to your liking. +If you are upgrading your +version of {kib}, the default space will contain all of your existing saved objects. -include::managing-spaces.asciidoc[] -include::moving-saved-objects.asciidoc[] diff --git a/docs/spaces/managing-spaces.asciidoc b/docs/spaces/managing-spaces.asciidoc deleted file mode 100644 index fbfed15bc6ee9f..00000000000000 --- a/docs/spaces/managing-spaces.asciidoc +++ /dev/null @@ -1,34 +0,0 @@ -[role="xpack"] -[[spaces-managing]] -=== Managing spaces -You can manage spaces from the **Management > Spaces** page. Here you can create, edit, and delete your spaces. - -[NOTE] -{kib} has an <> if you want to create your spaces programatically. - -[role="screenshot"] -image::spaces/images/space-management.png["Space Management"] - -==== Creating and updating spaces -You can create as many spaces as you like, but each space must have a unique space identifier. The space identifier is a short string of text that is part of the {kib} URL when you are inside that space. {kib} automatically suggests a space identifier based on the name of your space, but you are free to customize this to your liking. - -[NOTE] -You cannot change the space identifier once the space is created. - -[role="screenshot"] -image::spaces/images/edit-space.png["Updating a space"] - -==== Controlling feature visibility -You can control which {kib} features are visible in each space. For example, you can hide “Dev Tools” in your “Executive” space, if users of that space don’t need this feature. - -[role="screenshot"] -image::spaces/images/edit-space-feature-visibility.png["Controlling features visiblity"] - - -NOTE: This is not considered a security feature. If you wish to secure access to specific features on a per-user basis, then you need to configure <>. - -==== Deleting spaces -Deleting a space is a destructive operation, which cannot be undone. When you delete a space, all of the saved objects that belong to that space are also deleted. - -[role="screenshot"] -image::spaces/images/delete-space.png["Deleting a space"] diff --git a/docs/spaces/moving-saved-objects.asciidoc b/docs/spaces/moving-saved-objects.asciidoc deleted file mode 100644 index e6a116f54a252f..00000000000000 --- a/docs/spaces/moving-saved-objects.asciidoc +++ /dev/null @@ -1,14 +0,0 @@ -[role="xpack"] -[[spaces-moving-objects]] -=== Moving saved objects between spaces -You can use {kib}'s <> interface to copy objects from one space to another: - -1. Navigate to the space that contains your saved objects. -2. Export your saved objects via the <> interface. -3. Navigate to the space you are importing to. -4. Import your saved objects via the <> interface. -5. (optional) Delete the saved objects from the space you exported from, if you don't want to keep a copy there. - - -[NOTE] -{kib} also has experimental <> and <> dashboard APIs if you are looking for a dashboard-centric way to automate this process. \ No newline at end of file diff --git a/docs/visualize.asciidoc b/docs/visualize.asciidoc index 01ec011d02eb1a..88e1ccdf8ab64c 100644 --- a/docs/visualize.asciidoc +++ b/docs/visualize.asciidoc @@ -42,9 +42,9 @@ To create a visualization: locations. * *Time Series* [horizontal] -<>:: Compute and combine data from multiple time series +<>:: Compute and combine data from multiple time series data sets. -<>:: Visualize time series data using pipeline aggregations. +<>:: Visualize time series data using pipeline aggregations. * *Other* [horizontal] <>:: Controls provide the ability to add interactive inputs to Kibana Dashboards. @@ -160,7 +160,7 @@ include::visualize/regionmap.asciidoc[] include::visualize/timelion.asciidoc[] -include::visualize/time-series-visual-builder.asciidoc[] +include::visualize/tsvb.asciidoc[] include::visualize/tagcloud.asciidoc[] diff --git a/docs/visualize/time-series-visual-builder.asciidoc b/docs/visualize/time-series-visual-builder.asciidoc deleted file mode 100644 index 62e016da5f925a..00000000000000 --- a/docs/visualize/time-series-visual-builder.asciidoc +++ /dev/null @@ -1,219 +0,0 @@ -[[time-series-visual-builder]] -== Time Series Visual Builder - -Time Series Visual Builder is a time series data visualizer with an emphasis -on allowing you to use the full power of Elasticsearch aggregation framework. -Time Series Visual Builder allows you to combine an infinite number of -aggregations and pipeline aggregations to display complex data in a meaningful way. - -image:images/tsvb-screenshot.png["Time Series Visual Builder Interface"] - -[[time-series-visualizations]] -=== Featured Visualizations - -Time Series Visual Build comes with 6 different visualization types. You can -switch between each visualization type using the tabbed picker at the top of the -interface. - - -==== Time Series - -A histogram visualization that supports area, line, bar, and steps along with -multiple y-axis. You can fully customize the colors, points, line thickness -and fill opacity. This visualization also supports time shifting to compare two -time periods. This visualization also supports annotations which can be loaded from -a separate index based on a query. - -image:images/tsvb-screenshot.png["Time Series Visualization"] - - -==== Metric - -A visualization for displaying the latest number in a series. This visualization -supports 2 metrics; a primary metric and a secondary metric. The labels and -backgrounds can be fully customizable based on a set of rules. - -image:images/tsvb-metric.png["Metric Visualization"] - - -==== Top N - -This is a horizontal bar chart where the y-axis is based on a series of metrics -and the x-axis is the latest value in those series; sorted in descending order. -The color of the bars are fully customizable based on set of rules. - -image:images/tsvb-top-n.png["Top N Visualization"] - - -==== Gauge - -This is a single value gauge visualization based on the latest value in a series. -The face of the gauge can either be a half-circle gauge or full-circle. You -can customize the thicknesses of the inner and outer lines to achieve a desired -design aesthetic. The color of the gauge and the text are fully customizable based -on a set of rules. - -image:images/tsvb-gauge.png["Gauge Visualization"] - - -==== Markdown - -This visualization allows you to enter Markdown text and embed Mustache -template syntax to customize the Markdown with data based on a set of series. - -image:images/tsvb-markdown.png["Markdown Visualization"] - - -==== Table - -A table visualization allows you to display data from multiple time series. -You define which field group to show in the rows and what columns of data to display. - -image:images/tsvb-table.png["Table Visualization"] - - -[[time-series-interface]] -=== Interface Overview - -The user interface for each visualization is compose of a "Data" tab and "Panel -Options". The only exception to that is the Time Series and Markdown visualizations; -the Time Series has a third tab for annotations and the Markdown has a third tab for -the editor. - -==== Data Tab - -The data tab is used for configuring the series for each visualization. This tab -allows you to add multiple series, depending on what the visualization -supports, with multiple aggregations composed together to create a single metric. -Here is a breakdown of the significant components of the data tab UI. - -===== Series Label and Color - -Each series supports a label which will be used for legends and titles depending on -which visualization type is selected. For series that are grouped by a term, you -can specify a mustache variable of `{{key}}` to substitute the term. For most -visualizations you can also choose a color by clicking on the swatch, this will display -the color picker. - -image:images/tsvb-data-tab-label.png["Label Example"] - -===== Metrics - -Each series supports multiple metrics (aggregations); the last metric (aggregation) -is the value that will be displayed for the series, this is indicated with the "eye" -icon to the left of the metric. Metrics can be composed using pipeline aggregations. -A common use case is to create a metric with a "max" aggregation then create a "derivative" -metric and choose the previous "max" metric as the source; this will create a rate. - -image:images/tsvb-data-tab-derivative-example.png["Derivative Example"] - -===== Series Options - -Each series also supports a set of options which are dependent on the type of -visualizations you have selected. Universal across each visualization type -you can configure: - -* Data format -* Time range offset -* Index pattern, timestamp, and interval override - - -image:images/tsvb-data-tab-series-options.png["Default Series Options"] - -For the Time Series visualization you can also configure: - -* Chart type -* Options for each chart type -* Legend Visibility -* Y-Axis options -* Split color theme - -image:images/tsvb-data-tab-series-options-time-series.png["Time Series Series Options"] - -===== Group By Controls - -At the bottom of the metrics there is a set of "Group By" controls that allows you -to specify how the series should be grouped or split. There are four choices: - -* Everything -* Filter (single) -* Filters (multiple with configurable colors) -* Terms - -By default the series is grouped by everything. - -==== Panel Options Tab - -The panel options tab is used for configuring the entire panel; the set of options -available is dependent on which visualization you have selected. Below is a list -of the options available per visualization: - -*Time Series* - -* Index pattern, timestamp, and Interval -* Y-Axis min and max -* Y-Axis position -* Background color -* Legend visibility -* Legend position -* Panel filter - -*Metric* - -* Index pattern, timestamp, and interval -* Panel filter -* Color rules for background and primary value - -*Top N* - -* Index pattern, timestamp, and interval -* Panel filter -* Background color -* Item URL -* Color rules for bar colors - -*Gauge* - -* Index pattern, timestamp, and interval -* Panel filter -* Background color -* Gauge max -* Gauge style -* Inner gauge color -* Inner gauge width -* Gauge line width -* Color rules for gauge line - -*Markdown* - -* Index pattern, timestamp, and interval -* Panel filter -* Background color -* Scroll bar visibility -* Vertical alignment of content -* Custom Panel CSS with support for Less syntax - -==== Annotations Tab - -The annotations tab is used for adding annotation data sources to the Time Series -Visualization. You can configure the following options: - -* Index pattern and time field -* Annotation color -* Annotation icon -* Fields to include in message -* Format of message -* Filtering options at the panel and global level - -image:images/tsvb-annotations.png["Annotation Tab"] - -==== Markdown Tab - -The markdown tab is used for editing the source for the Markdown visualization. -The user interface has an editor on the left side and the available variables from -the data tab on the right side. You can click on the variable names to insert -the mustache template variable into the markdown at the cursor position. The mustache -syntax uses the Handlebar.js processor which is an extended version of the Mustache -template language. - -image:images/tsvb-markdown.png["Markdown Tab"] diff --git a/docs/visualize/timelion.asciidoc b/docs/visualize/timelion.asciidoc index 8e21527b2ee8e3..d65ac3f6a2ce17 100644 --- a/docs/visualize/timelion.asciidoc +++ b/docs/visualize/timelion.asciidoc @@ -12,18 +12,18 @@ For example, Timelion enables you to easily get the answers to questions like: * <> * <> -[float] -[[time-series-intro]] -== Create time series visualizations - -To compare the real-time percentage of CPU time spent in user space to the results offset by one hour, create a time series visualization. - [float] [[time-series-before-you-begin]] === Before you begin In this tutorial, you'll use the time series data from https://www.elastic.co/guide/en/beats/metricbeat/current/index.html[Metricbeat]. To ingest the data locally, link:https://www.elastic.co/downloads/beats/metricbeat[download Metricbeat]. +[float] +[[time-series-intro]] +== Create time series visualizations + +To compare the real-time percentage of CPU time spent in user space to the results offset by one hour, create a time series visualization. + [float] [[time-series-define-functions]] === Define the functions @@ -134,31 +134,12 @@ Change the position and style of the legend: image::images/timelion-customize04.png[] {nbsp} -[float] -[[time-series-save-visualization]] -=== Save the visualization - -When you have finished making changes, save the visualization. - -. Click *Save*. - -. In the *Title* field, enter a name for the visualization. - -. Click *Confirm Save*. - -[float] [float] [[mathematical-functions-intro]] == Create visualizations with mathematical functions To create a visualization for inbound and outbound network traffic, use mathematical functions. -[float] -[[mathematical-functions-before-you-begin]] -=== Before you begin - -In this tutorial, you'll use the time series data from https://www.elastic.co/guide/en/beats/metricbeat/current/index.html[Metricbeat]. To ingest the data locally, link:https://www.elastic.co/downloads/beats/metricbeat[download Metricbeat]. - [float] [[mathematical-functions-define-functions]] === Define the functions @@ -237,18 +218,6 @@ Customize and format the visualization using functions: image::images/timelion-math05.png[] {nbsp} -[float] -[[mathematical-functions-save-visualization]] -=== Save the visualization - -When you have finished making changes, save the visualization. - -. Click *Save*. - -. In the *Title* field, enter a name for the visualization. - -. Click *Confirm Save*. - [float] [[timelion-conditional-intro]] == Create visualizations with conditional logic and tracking trends @@ -265,12 +234,6 @@ With Timelion conditional logic, you can use the following operator values to co `gt`:: greater than `gte`:: greater than or equal to -[float] -[[conditional-before-you-begin]] -=== Before you begin - -In this tutorial, you'll use the time series data from https://www.elastic.co/guide/en/beats/metricbeat/current/index.html[Metricbeat]. To ingest the data locally, link:https://www.elastic.co/downloads/beats/metricbeat[download Metricbeat]. - [float] [[conditional-define-functions]] === Define the functions @@ -338,16 +301,4 @@ Customize and format the visualization using functions: image::images/timelion-conditional04.png[] {nbsp} -[float] -[[conditional-save-visualization]] -=== Save the visualization - -When you have finished making changes, save the visualization. - -. Click *Save*. - -. In the *Title* field, enter a name for the visualization. - -. Click *Confirm Save*. - For additional information on Timelion conditional capabilities, go to https://www.elastic.co/blog/timeseries-if-then-else-with-timelion[I have but one .condition()]. diff --git a/docs/visualize/tsvb.asciidoc b/docs/visualize/tsvb.asciidoc new file mode 100644 index 00000000000000..b52066a71837df --- /dev/null +++ b/docs/visualize/tsvb.asciidoc @@ -0,0 +1,120 @@ +[[TSVB]] +== Visualizing your data with TSVB + +TSVB is a time series data visualizer that allows you to use the full power of the +Elasticsearch aggregation framework. With TSVB, you can combine an infinite +number of aggregations to display complex data. + +NOTE: In Elasticsearch version 7.3.0 and later, the time series data visualizer is now referred to as TSVB instead of Time Series Visual Builder. + +TSVB comes with these types of visualizations: + +Time Series:: A histogram visualization that supports area, line, bar, and steps along with multiple y-axis. + +[role="screenshot"] +image:images/tsvb-screenshot.png["Time series visualization"] + +Metric:: A metric that displays the latest number in a data series. + +[role="screenshot"] +image:images/tsvb-metric.png["Metric visualization"] + +Top N:: A horizontal bar chart where the y-axis is based on a series of metrics, and the x-axis is the latest value in the series. + +[role="screenshot"] +image:images/tsvb-top-n.png["Top N visualization"] + +Gauge:: A single value gauge visualization based on the latest value in a series. + +[role="screenshot"] +image:images/tsvb-gauge.png["Gauge visualization"] + +Markdown:: Edit the data using using Markdown text and Mustache template syntax. + +[role="screenshot"] +image:images/tsvb-markdown.png["Markdown visualization"] + +Table:: Display data from multiple time series by defining the field group to show in the rows, and the columns of data to display. + +[role="screenshot"] +image:images/tsvb-table.png["Table visualization"] + +[float] +[[create-tsvb-visualization]] +=== Create TSVB visualizations + +To create a TSVB visualization, choose the data series you want to display, then choose how you want to display the data. The options available are dependent on the visualization. + +[float] +[[tsvb-data-series-options]] +=== Configure the data series + +To create a single metric, add multiple data series with multiple aggregations. + +. Select the visualization type. + +. Specify the data series labels and colors. + +.. Select *Data*. ++ +If you are using the *Table* visualization, select *Columns*. + +.. In the *Label* field, enter a name for the data series, which is used on legends and titles. ++ +For series that are grouped by a term, you can specify a mustache variable of `{{key}}` to substitute the term. + +.. If supported by the visualization, click the swatch and choose a color for the data series. + +.. To add another data series, click *+*, then repeat the steps to specify the labels and colors. + +. Specify the data series metrics. + +.. Select *Metrics*. + +.. From the dropdown lists, choose your options. + +.. To add another metric, click *+*. ++ +When you add more than one metric, the last metric value is displayed, which is indicated by the eye icon. + +. To specify the format and display options, select *Options*. + +. To specify how to group or split the data, choose an option from the *Group by* drop down list. ++ +By default, the data series are grouped by everything. + +[float] +[[tsvb-panel-options]] +=== Configure the panel + +Change the data that you want to display and choose the style options for the panel. + +. Select *Panel options*. + +. Under *Data*, specify how much of the data that you want to display in the visualization. + +. Under *Style*, specify how you want the visualization to look. + +[float] +[[tsvb-add-annotations]] +=== Add annotations + +If you are using the Time Series visualization, add annotation data sources. + +. Select *Annotations*. + +. Click *Add data source*, then specify the options. + +[float] +[[tsvb-enter-markdown]] +=== Enter Markdown text + +Edit the source for the Markdown visualization. + +. Select *Markdown*. + +. In the editor, enter enter your Markdown text, then press Enter. + +. To insert the mustache template variable into the editor, click the variable name. ++ +The http://mustache.github.io/mustache.5.html[mustache syntax] uses the Handlebar.js processor, which is an extended version of the Mustache template language. diff --git a/package.json b/package.json index c4860db4b9ce09..41e08e247b51de 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "@babel/register": "7.4.4", "@elastic/charts": "^7.2.1", "@elastic/datemath": "5.0.2", - "@elastic/eui": "12.3.1", + "@elastic/eui": "12.4.0", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "2.3.3", @@ -347,7 +347,7 @@ "babel-eslint": "10.0.2", "babel-jest": "^24.1.0", "babel-plugin-dynamic-import-node": "^2.2.0", - "backport": "4.6.0", + "backport": "4.6.1", "chai": "3.5.0", "chance": "1.0.18", "cheerio": "0.22.0", diff --git a/packages/kbn-analytics/package.json b/packages/kbn-analytics/package.json new file mode 100644 index 00000000000000..e8fb1aea81a721 --- /dev/null +++ b/packages/kbn-analytics/package.json @@ -0,0 +1,21 @@ +{ + "name": "@kbn/analytics", + "private": true, + "version": "1.0.0", + "description": "Kibana Analytics tool", + "main": "target/index.js", + "types": "target/index.d.ts", + "author": "Ahmad Bamieh ", + "license": "Apache-2.0", + "scripts": { + "build": "tsc", + "kbn:bootstrap": "yarn build", + "kbn:watch": "yarn build --watch" + }, + "devDependencies": { + "typescript": "3.5.1" + }, + "dependencies": { + "@kbn/dev-utils": "1.0.0" + } +} diff --git a/src/legacy/core_plugins/data/public/search/index.tsx b/packages/kbn-analytics/src/index.ts similarity index 80% rename from src/legacy/core_plugins/data/public/search/index.tsx rename to packages/kbn-analytics/src/index.ts index 6a9687ba7e3256..63fd115fa75945 100644 --- a/src/legacy/core_plugins/data/public/search/index.tsx +++ b/packages/kbn-analytics/src/index.ts @@ -17,6 +17,6 @@ * under the License. */ -export { SearchService, SearchSetup } from './search_service'; - -export * from './search_bar'; +export { createReporter, ReportHTTP, Reporter, ReporterConfig } from './reporter'; +export { UiStatsMetricType, METRIC_TYPE } from './metrics'; +export { Report, ReportManager } from './report'; diff --git a/src/legacy/core_plugins/data/public/query/query_bar/directive/index.js b/packages/kbn-analytics/src/metrics/index.ts similarity index 64% rename from src/legacy/core_plugins/data/public/query/query_bar/directive/index.js rename to packages/kbn-analytics/src/metrics/index.ts index 8d388d12ece74c..13b9e5dc59e4e2 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/directive/index.js +++ b/packages/kbn-analytics/src/metrics/index.ts @@ -17,22 +17,21 @@ * under the License. */ -import 'ngreact'; -import { wrapInI18nContext } from 'ui/i18n'; -import { uiModules } from 'ui/modules'; -import { QueryBar } from '../components'; +import { UiStatsMetric, UiStatsMetricType } from './ui_stats'; -const app = uiModules.get('app/data', ['react']); +export { + UiStatsMetric, + createUiStatsMetric, + UiStatsMetricReport, + UiStatsMetricType, +} from './ui_stats'; +export { Stats } from './stats'; -export function setupDirective() { - app.directive('queryBar', (reactDirective, localStorage) => { - return reactDirective( - wrapInI18nContext(QueryBar), - undefined, - {}, - { - store: localStorage, - } - ); - }); +export type Metric = UiStatsMetric; +export type MetricType = keyof typeof METRIC_TYPE; + +export enum METRIC_TYPE { + COUNT = 'count', + LOADED = 'loaded', + CLICK = 'click', } diff --git a/src/legacy/core_plugins/ui_metric/server/usage/index.ts b/packages/kbn-analytics/src/metrics/stats.ts similarity index 90% rename from src/legacy/core_plugins/ui_metric/server/usage/index.ts rename to packages/kbn-analytics/src/metrics/stats.ts index b3b88b94d12691..993290167018c5 100644 --- a/src/legacy/core_plugins/ui_metric/server/usage/index.ts +++ b/packages/kbn-analytics/src/metrics/stats.ts @@ -17,4 +17,9 @@ * under the License. */ -export { registerUiMetricUsageCollector } from './collector'; +export interface Stats { + min: number; + max: number; + sum: number; + avg: number; +} diff --git a/packages/kbn-analytics/src/metrics/ui_stats.ts b/packages/kbn-analytics/src/metrics/ui_stats.ts new file mode 100644 index 00000000000000..7615fd20645e28 --- /dev/null +++ b/packages/kbn-analytics/src/metrics/ui_stats.ts @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Stats } from './stats'; +import { METRIC_TYPE } from './'; + +export type UiStatsMetricType = METRIC_TYPE.CLICK | METRIC_TYPE.LOADED | METRIC_TYPE.COUNT; +export interface UiStatsMetricConfig { + type: T; + appName: string; + eventName: string; + count?: number; +} + +export interface UiStatsMetric { + type: T; + appName: string; + eventName: string; + count: number; +} + +export function createUiStatsMetric({ + type, + appName, + eventName, + count = 1, +}: UiStatsMetricConfig): UiStatsMetric { + return { type, appName, eventName, count }; +} + +export interface UiStatsMetricReport { + key: string; + appName: string; + eventName: string; + type: UiStatsMetricType; + stats: Stats; +} diff --git a/packages/kbn-analytics/src/report.ts b/packages/kbn-analytics/src/report.ts new file mode 100644 index 00000000000000..6187455fa60a55 --- /dev/null +++ b/packages/kbn-analytics/src/report.ts @@ -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 { UnreachableCaseError } from './util'; +import { Metric, Stats, UiStatsMetricReport, METRIC_TYPE } from './metrics'; + +export interface Report { + uiStatsMetrics: { + [key: string]: UiStatsMetricReport; + }; +} + +export class ReportManager { + public report: Report; + constructor(report?: Report) { + this.report = report || ReportManager.createReport(); + } + static createReport() { + return { uiStatsMetrics: {} }; + } + public clearReport() { + this.report = ReportManager.createReport(); + } + public isReportEmpty(): boolean { + return Object.keys(this.report.uiStatsMetrics).length === 0; + } + private incrementStats(count: number, stats?: Stats): Stats { + const { min = 0, max = 0, sum = 0 } = stats || {}; + const newMin = Math.min(min, count); + const newMax = Math.max(max, count); + const newAvg = newMin + newMax / 2; + const newSum = sum + count; + + return { + min: newMin, + max: newMax, + avg: newAvg, + sum: newSum, + }; + } + assignReports(newMetrics: Metric[]) { + newMetrics.forEach(newMetric => this.assignReport(this.report, newMetric)); + } + static createMetricKey(metric: Metric): string { + switch (metric.type) { + case METRIC_TYPE.CLICK: + case METRIC_TYPE.LOADED: + case METRIC_TYPE.COUNT: { + const { appName, type, eventName } = metric; + return `${appName}-${type}-${eventName}`; + } + default: + throw new UnreachableCaseError(metric.type); + } + } + private assignReport(report: Report, metric: Metric) { + switch (metric.type) { + case METRIC_TYPE.CLICK: + case METRIC_TYPE.LOADED: + case METRIC_TYPE.COUNT: { + const { appName, type, eventName, count } = metric; + const key = ReportManager.createMetricKey(metric); + const existingStats = (report.uiStatsMetrics[key] || {}).stats; + this.report.uiStatsMetrics[key] = { + key, + appName, + eventName, + type, + stats: this.incrementStats(count, existingStats), + }; + return; + } + default: + throw new UnreachableCaseError(metric.type); + } + } +} diff --git a/packages/kbn-analytics/src/reporter.ts b/packages/kbn-analytics/src/reporter.ts new file mode 100644 index 00000000000000..37d23aa4430902 --- /dev/null +++ b/packages/kbn-analytics/src/reporter.ts @@ -0,0 +1,114 @@ +/* + * 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 { wrapArray } from './util'; +import { Metric, UiStatsMetric, createUiStatsMetric } from './metrics'; + +import { Storage, ReportStorageManager } from './storage'; +import { Report, ReportManager } from './report'; + +export interface ReporterConfig { + http: ReportHTTP; + storage?: Storage; + checkInterval?: number; + debug?: boolean; + storageKey?: string; +} + +export type ReportHTTP = (report: Report) => Promise; + +export class Reporter { + checkInterval: number; + private interval: any; + private http: ReportHTTP; + private reportManager: ReportManager; + private storageManager: ReportStorageManager; + private debug: boolean; + + constructor(config: ReporterConfig) { + const { http, storage, debug, checkInterval = 10000, storageKey = 'analytics' } = config; + + this.http = http; + this.checkInterval = checkInterval; + this.interval = null; + this.storageManager = new ReportStorageManager(storageKey, storage); + const storedReport = this.storageManager.get(); + this.reportManager = new ReportManager(storedReport); + this.debug = !!debug; + } + + private saveToReport(newMetrics: Metric[]) { + this.reportManager.assignReports(newMetrics); + this.storageManager.store(this.reportManager.report); + } + + private flushReport() { + this.reportManager.clearReport(); + this.storageManager.store(this.reportManager.report); + } + + public start() { + if (!this.interval) { + this.interval = setTimeout(() => { + this.interval = null; + this.sendReports(); + }, this.checkInterval); + } + } + + private log(message: any) { + if (this.debug) { + // eslint-disable-next-line + console.debug(message); + } + } + + public reportUiStats( + appName: string, + type: UiStatsMetric['type'], + eventNames: string | string[], + count?: number + ) { + const metrics = wrapArray(eventNames).map(eventName => { + if (this) this.log(`${type} Metric -> (${appName}:${eventName}):`); + const report = createUiStatsMetric({ type, appName, eventName, count }); + this.log(report); + return report; + }); + this.saveToReport(metrics); + } + + public async sendReports() { + if (!this.reportManager.isReportEmpty()) { + try { + await this.http(this.reportManager.report); + this.flushReport(); + } catch (err) { + this.log(`Error Sending Metrics Report ${err}`); + } + } + this.start(); + } +} + +export function createReporter(reportedConf: ReporterConfig) { + const reporter = new Reporter(reportedConf); + reporter.start(); + return reporter; +} diff --git a/src/legacy/core_plugins/data/public/search/search_service.ts b/packages/kbn-analytics/src/storage.ts similarity index 61% rename from src/legacy/core_plugins/data/public/search/search_service.ts rename to packages/kbn-analytics/src/storage.ts index 2e65280d3bcff1..9abf9fa7dac2ce 100644 --- a/src/legacy/core_plugins/data/public/search/search_service.ts +++ b/packages/kbn-analytics/src/storage.ts @@ -17,27 +17,22 @@ * under the License. */ -import { once } from 'lodash'; -import { SearchBar, setupDirective as setupSearchBarDirective } from './search_bar'; +import { Report } from './report'; -/** - * Search Service - * @internal - */ -export class SearchService { - public setup() { - return { - ui: { - SearchBar, - }, - loadLegacyDirectives: once(setupSearchBarDirective), - }; +export type Storage = Map; +export class ReportStorageManager { + storageKey: string; + private storage?: Storage; + constructor(storageKey: string, storage?: Storage) { + this.storageKey = storageKey; + this.storage = storage; } - - public stop() { - // nothing to do here yet + public get(): Report | undefined { + if (!this.storage) return; + return this.storage.get(this.storageKey); + } + public store(report: Report) { + if (!this.storage) return; + this.storage.set(this.storageKey, report); } } - -/** @public */ -export type SearchSetup = ReturnType; diff --git a/packages/kbn-analytics/src/util.ts b/packages/kbn-analytics/src/util.ts new file mode 100644 index 00000000000000..fe3e8b8f9f7ba0 --- /dev/null +++ b/packages/kbn-analytics/src/util.ts @@ -0,0 +1,28 @@ +/* + * 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 function wrapArray(subj: T | T[]): T[] { + return Array.isArray(subj) ? subj : [subj]; +} + +export class UnreachableCaseError extends Error { + constructor(val: never) { + super(`Unreachable case: ${val}`); + } +} diff --git a/packages/kbn-analytics/tsconfig.json b/packages/kbn-analytics/tsconfig.json new file mode 100644 index 00000000000000..fcb8ddbbde68e2 --- /dev/null +++ b/packages/kbn-analytics/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": true, + "declarationDir": "./target", + "outDir": "./target", + "stripInternal": true, + "declarationMap": true, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "./src/**/*.ts" + ], + "exclude": [ + "target" + ] +} diff --git a/packages/kbn-plugin-generator/sao_template/template/.i18nrc.json b/packages/kbn-plugin-generator/sao_template/template/.i18nrc.json index edcca37a248f0a..3fb2834877898c 100644 --- a/packages/kbn-plugin-generator/sao_template/template/.i18nrc.json +++ b/packages/kbn-plugin-generator/sao_template/template/.i18nrc.json @@ -1,5 +1,8 @@ { "paths": { "<%= camelCase(name) %>": "./" - } + }, + "translations": [ + "translations/zh-CN.json" + ] } diff --git a/src/core/public/plugins/plugin.ts b/src/core/public/plugins/plugin.ts index 5d40086336cdfa..a24c19e3219f3d 100644 --- a/src/core/public/plugins/plugin.ts +++ b/src/core/public/plugins/plugin.ts @@ -17,7 +17,7 @@ * under the License. */ -import { DiscoveredPlugin, PluginName } from '../../server'; +import { DiscoveredPlugin } from '../../server'; import { PluginInitializerContext } from './plugin_context'; import { loadPluginBundle } from './plugin_loader'; import { CoreStart, CoreSetup } from '..'; @@ -30,8 +30,8 @@ import { CoreStart, CoreSetup } from '..'; export interface Plugin< TSetup = void, TStart = void, - TPluginsSetup extends {} = {}, - TPluginsStart extends {} = {} + TPluginsSetup extends object = object, + TPluginsStart extends object = object > { setup(core: CoreSetup, plugins: TPluginsSetup): TSetup | Promise; start(core: CoreStart, plugins: TPluginsStart): TStart | Promise; @@ -47,8 +47,8 @@ export interface Plugin< export type PluginInitializer< TSetup, TStart, - TPluginsSetup extends Record = {}, - TPluginsStart extends Record = {} + TPluginsSetup extends object = object, + TPluginsStart extends object = object > = (core: PluginInitializerContext) => Plugin; /** @@ -60,8 +60,8 @@ export type PluginInitializer< export class PluginWrapper< TSetup = unknown, TStart = unknown, - TPluginsSetup extends Record = Record, - TPluginsStart extends Record = Record + TPluginsSetup extends object = object, + TPluginsStart extends object = object > { public readonly name: DiscoveredPlugin['id']; public readonly configPath: DiscoveredPlugin['configPath']; diff --git a/src/core/public/plugins/plugin_context.ts b/src/core/public/plugins/plugin_context.ts index 022c71492f383f..bc77b139a86dc5 100644 --- a/src/core/public/plugins/plugin_context.ts +++ b/src/core/public/plugins/plugin_context.ts @@ -19,7 +19,7 @@ import { omit } from 'lodash'; -import { DiscoveredPlugin, PluginName } from '../../server'; +import { DiscoveredPlugin } from '../../server'; import { CoreContext } from '../core_system'; import { PluginWrapper } from './plugin'; import { PluginsServiceSetupDeps, PluginsServiceStartDeps } from './plugins_service'; @@ -61,8 +61,8 @@ export function createPluginInitializerContext( export function createPluginSetupContext< TSetup, TStart, - TPluginsSetup extends Record, - TPluginsStart extends Record + TPluginsSetup extends object, + TPluginsStart extends object >( coreContext: CoreContext, deps: PluginsServiceSetupDeps, @@ -89,8 +89,8 @@ export function createPluginSetupContext< export function createPluginStartContext< TSetup, TStart, - TPluginsSetup extends Record, - TPluginsStart extends Record + TPluginsSetup extends object, + TPluginsStart extends object >( coreContext: CoreContext, deps: PluginsServiceStartDeps, diff --git a/src/core/public/plugins/plugin_loader.ts b/src/core/public/plugins/plugin_loader.ts index 9ec24adaabbe7c..871091324a6b95 100644 --- a/src/core/public/plugins/plugin_loader.ts +++ b/src/core/public/plugins/plugin_loader.ts @@ -62,8 +62,8 @@ export const LOAD_TIMEOUT = 120 * 1000; // 2 minutes export const loadPluginBundle: LoadPluginBundle = < TSetup, TStart, - TPluginsSetup extends Record, - TPluginsStart extends Record + TPluginsSetup extends object, + TPluginsStart extends object >( addBasePath: (path: string) => string, pluginName: PluginName, @@ -125,8 +125,8 @@ export const loadPluginBundle: LoadPluginBundle = < export type LoadPluginBundle = < TSetup, TStart, - TPluginsSetup extends Record, - TPluginsStart extends Record + TPluginsSetup extends object, + TPluginsStart extends object >( addBasePath: (path: string) => string, pluginName: PluginName, diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 55f2a252103211..36c5ed84cd248d 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -475,7 +475,7 @@ export interface OverlayStart { } // @public -export interface Plugin { +export interface Plugin { // (undocumented) setup(core: CoreSetup, plugins: TPluginsSetup): TSetup | Promise; // (undocumented) @@ -485,7 +485,7 @@ export interface Plugin = {}, TPluginsStart extends Record = {}> = (core: PluginInitializerContext) => Plugin; +export type PluginInitializer = (core: PluginInitializerContext) => Plugin; // @public export interface PluginInitializerContext { diff --git a/src/core/server/elasticsearch/cluster_client.ts b/src/core/server/elasticsearch/cluster_client.ts index a451bd549f956e..c8500499d97029 100644 --- a/src/core/server/elasticsearch/cluster_client.ts +++ b/src/core/server/elasticsearch/cluster_client.ts @@ -70,7 +70,7 @@ export interface CallAPIOptions { async function callAPI( client: Client, endpoint: string, - clientParams: Record = {}, + clientParams: Record = {}, options: CallAPIOptions = { wrap401Errors: true } ): Promise { const clientPath = endpoint.split('.'); @@ -150,7 +150,7 @@ export class ClusterClient { */ public callAsInternalUser = async ( endpoint: string, - clientParams: Record = {}, + clientParams: Record = {}, options?: CallAPIOptions ) => { this.assertIsNotClosed(); @@ -216,7 +216,7 @@ export class ClusterClient { */ private callAsCurrentUser = async ( endpoint: string, - clientParams: Record = {}, + clientParams: Record = {}, options?: CallAPIOptions ) => { this.assertIsNotClosed(); diff --git a/src/core/server/elasticsearch/scoped_cluster_client.ts b/src/core/server/elasticsearch/scoped_cluster_client.ts index 86b3df9460a16b..d89a15e336b337 100644 --- a/src/core/server/elasticsearch/scoped_cluster_client.ts +++ b/src/core/server/elasticsearch/scoped_cluster_client.ts @@ -27,7 +27,7 @@ export { Headers }; /** @public */ export type APICaller = ( endpoint: string, - clientParams: Record, + clientParams: Record, options?: CallAPIOptions ) => Promise; @@ -58,7 +58,7 @@ export class ScopedClusterClient { */ public callAsInternalUser( endpoint: string, - clientParams: Record = {}, + clientParams: Record = {}, options?: CallAPIOptions ) { return this.internalAPICaller(endpoint, clientParams, options); @@ -73,7 +73,7 @@ export class ScopedClusterClient { */ public callAsCurrentUser( endpoint: string, - clientParams: Record = {}, + clientParams: Record = {}, options?: CallAPIOptions ) { const defaultHeaders = this.headers; diff --git a/src/core/server/http/http_server.mocks.ts b/src/core/server/http/http_server.mocks.ts index fbf98aa4f9c993..bb52fa1fa38df9 100644 --- a/src/core/server/http/http_server.mocks.ts +++ b/src/core/server/http/http_server.mocks.ts @@ -27,9 +27,9 @@ import { KibanaRequest, RouteMethod } from './router'; interface RequestFixtureOptions { headers?: Record; - params?: Record; - body?: Record; - query?: Record; + params?: Record; + body?: Record; + query?: Record; path?: string; method?: RouteMethod; } diff --git a/src/core/server/http/lifecycle/auth.ts b/src/core/server/http/lifecycle/auth.ts index b866c00a756cc1..8319d52c2e8847 100644 --- a/src/core/server/http/lifecycle/auth.ts +++ b/src/core/server/http/lifecycle/auth.ts @@ -92,7 +92,7 @@ export interface AuthResultData { /** * Data to associate with an incoming request. Any downstream plugin may get access to the data. */ - state: Record; + state: Record; /** * Auth specific headers to authenticate a user against Elasticsearch. */ diff --git a/src/core/server/plugins/plugin.ts b/src/core/server/plugins/plugin.ts index 3f24d44992b37d..289f6f7cda7acd 100644 --- a/src/core/server/plugins/plugin.ts +++ b/src/core/server/plugins/plugin.ts @@ -138,8 +138,8 @@ export interface DiscoveredPluginInternal extends DiscoveredPlugin { export interface Plugin< TSetup = void, TStart = void, - TPluginsSetup extends {} = {}, - TPluginsStart extends {} = {} + TPluginsSetup extends object = object, + TPluginsStart extends object = object > { setup(core: CoreSetup, plugins: TPluginsSetup): TSetup | Promise; start(core: CoreStart, plugins: TPluginsStart): TStart | Promise; @@ -155,8 +155,8 @@ export interface Plugin< export type PluginInitializer< TSetup, TStart, - TPluginsSetup extends Record = {}, - TPluginsStart extends Record = {} + TPluginsSetup extends object = object, + TPluginsStart extends object = object > = (core: PluginInitializerContext) => Plugin; /** @@ -168,8 +168,8 @@ export type PluginInitializer< export class PluginWrapper< TSetup = unknown, TStart = unknown, - TPluginsSetup extends Record = Record, - TPluginsStart extends Record = Record + TPluginsSetup extends object = object, + TPluginsStart extends object = object > { public readonly name: PluginManifest['id']; public readonly configPath: PluginManifest['configPath']; diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index a6fbfebf9d9470..0fefb2d80892e3 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -21,7 +21,7 @@ import { TypeOf } from '@kbn/config-schema'; import { Url } from 'url'; // @public (undocumented) -export type APICaller = (endpoint: string, clientParams: Record, options?: CallAPIOptions) => Promise; +export type APICaller = (endpoint: string, clientParams: Record, options?: CallAPIOptions) => Promise; // Warning: (ae-forgotten-export) The symbol "AuthResult" needs to be exported by the entry point index.d.ts // @@ -34,7 +34,7 @@ export type AuthHeaders = Record; // @public export interface AuthResultData { headers: AuthHeaders; - state: Record; + state: Record; } // @public @@ -61,7 +61,7 @@ export interface CallAPIOptions { export class ClusterClient { constructor(config: ElasticsearchClientConfig, log: Logger, getAuthHeaders?: GetAuthHeaders); asScoped(request?: KibanaRequest | LegacyRequest | FakeRequest): ScopedClusterClient; - callAsInternalUser: (endpoint: string, clientParams?: Record, options?: CallAPIOptions | undefined) => Promise; + callAsInternalUser: (endpoint: string, clientParams?: Record, options?: CallAPIOptions | undefined) => Promise; close(): void; } @@ -331,7 +331,7 @@ export interface OnPreAuthToolkit { } // @public -export interface Plugin { +export interface Plugin { // (undocumented) setup(core: CoreSetup, plugins: TPluginsSetup): TSetup | Promise; // (undocumented) @@ -341,7 +341,7 @@ export interface Plugin = {}, TPluginsStart extends Record = {}> = (core: PluginInitializerContext) => Plugin; +export type PluginInitializer = (core: PluginInitializerContext) => Plugin; // @public export interface PluginInitializerContext { @@ -670,8 +670,8 @@ export interface SavedObjectsUpdateResponse | undefined); - callAsCurrentUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; - callAsInternalUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; + callAsCurrentUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; + callAsInternalUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; } // @public diff --git a/src/dev/ci_setup/setup.sh b/src/dev/ci_setup/setup.sh index db785e2524525f..951a83c091ec80 100755 --- a/src/dev/ci_setup/setup.sh +++ b/src/dev/ci_setup/setup.sh @@ -8,9 +8,6 @@ cacheDir="${CACHE_DIR:-"$HOME/.kibana"}" RED='\033[0;31m' C_RESET='\033[0m' # Reset color -### Force ES Snapshot -export TEST_ES_SNAPSHOT_VERSION=8.0.0-5480a616 - ### ### Since the Jenkins logging output collector doesn't look like a TTY ### Node/Chalk and other color libs disable their color output. But Jenkins diff --git a/src/legacy/core_plugins/data/public/filter/index.tsx b/src/legacy/core_plugins/data/public/filter/index.tsx index 997dae3854b4cc..d4dba0d834ffeb 100644 --- a/src/legacy/core_plugins/data/public/filter/index.tsx +++ b/src/legacy/core_plugins/data/public/filter/index.tsx @@ -18,3 +18,5 @@ */ export { FilterService, FilterSetup } from './filter_service'; + +export { FilterBar } from './filter_bar'; diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index adfcc2f03c25a4..9cce64b0c5741e 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -24,7 +24,6 @@ // @ts-ignore import { renderersRegistry } from 'plugins/interpreter/registries'; import { ExpressionsService, ExpressionsSetup } from './expressions'; -import { SearchService, SearchSetup } from './search'; import { QueryService, QuerySetup } from './query'; import { FilterService, FilterSetup } from './filter'; import { IndexPatternsService, IndexPatternsSetup } from './index_patterns'; @@ -34,14 +33,12 @@ export class DataPlugin { private readonly expressions: ExpressionsService; private readonly filter: FilterService; private readonly indexPatterns: IndexPatternsService; - private readonly search: SearchService; private readonly query: QueryService; constructor() { this.indexPatterns = new IndexPatternsService(); this.filter = new FilterService(); this.query = new QueryService(); - this.search = new SearchService(); this.expressions = new ExpressionsService(); } @@ -61,7 +58,6 @@ export class DataPlugin { filter: this.filter.setup({ indexPatterns: indexPatternsService.indexPatterns, }), - search: this.search.setup(), query: this.query.setup(), }; } @@ -70,7 +66,6 @@ export class DataPlugin { this.expressions.stop(); this.indexPatterns.stop(); this.filter.stop(); - this.search.stop(); this.query.stop(); } } @@ -80,7 +75,6 @@ export interface DataSetup { expressions: ExpressionsSetup; indexPatterns: IndexPatternsSetup; filter: FilterSetup; - search: SearchSetup; query: QuerySetup; } @@ -89,8 +83,8 @@ export { ExpressionRenderer, ExpressionRendererProps, ExpressionRunner } from '. /** @public types */ export { IndexPattern, StaticIndexPattern, StaticIndexPatternField, Field } from './index_patterns'; -export { Query } from './query'; -export { SearchBar, SearchBarProps } from './search'; +export { Query, QueryBar } from './query'; +export { FilterBar } from './filter'; export { FilterManager, FilterStateManager, uniqFilters } from './filter/filter_manager'; /** @public static code */ diff --git a/src/legacy/core_plugins/data/public/query/index.ts b/src/legacy/core_plugins/data/public/query/index.ts index 9a4d1b4f50c100..976d868241ddd4 100644 --- a/src/legacy/core_plugins/data/public/query/index.ts +++ b/src/legacy/core_plugins/data/public/query/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { QueryService, QuerySetup, Query } from './query_service'; +export * from './query_service'; diff --git a/src/legacy/core_plugins/data/public/query/query_bar/index.ts b/src/legacy/core_plugins/data/public/query/query_bar/index.ts index c761681847c8e8..d182123d401d9d 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/index.ts +++ b/src/legacy/core_plugins/data/public/query/query_bar/index.ts @@ -22,7 +22,4 @@ export { fromUser } from './lib/from_user'; export { toUser } from './lib/to_user'; export { getQueryLog } from './lib/get_query_log'; -// @ts-ignore -export { setupDirective } from './directive'; - export { Query } from '../../../../../../plugins/data/common/query/types'; diff --git a/src/legacy/core_plugins/data/public/query/query_service.ts b/src/legacy/core_plugins/data/public/query/query_service.ts index be678776f75263..745fb1bac686bf 100644 --- a/src/legacy/core_plugins/data/public/query/query_service.ts +++ b/src/legacy/core_plugins/data/public/query/query_service.ts @@ -17,15 +17,7 @@ * under the License. */ -import { once } from 'lodash'; -import { - QueryBar, - QueryBarInput, - fromUser, - toUser, - getQueryLog, - setupDirective as setupQueryBarDirective, -} from './query_bar'; +import { QueryBar, QueryBarInput, fromUser, toUser, getQueryLog } from './query_bar'; /** * Query Service @@ -35,7 +27,6 @@ import { export class QueryService { public setup() { return { - loadLegacyDirectives: once(setupQueryBarDirective), helpers: { fromUser, toUser, @@ -56,4 +47,4 @@ export class QueryService { /** @public */ export type QuerySetup = ReturnType; -export { Query } from './query_bar'; +export { Query, QueryBar } from './query_bar'; diff --git a/src/legacy/core_plugins/data/public/search/search_bar/directive/index.js b/src/legacy/core_plugins/data/public/search/search_bar/directive/index.js deleted file mode 100644 index 4b3546fc7d2269..00000000000000 --- a/src/legacy/core_plugins/data/public/search/search_bar/directive/index.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 'ngreact'; -import { wrapInI18nContext } from 'ui/i18n'; -import { uiModules } from 'ui/modules'; -import { SearchBar } from '../components'; - -const app = uiModules.get('app/data', ['react']); - -export function setupDirective() { - app.directive('searchBar', (reactDirective, localStorage) => { - return reactDirective( - wrapInI18nContext(SearchBar), - [ - ['query', { watchDepth: 'reference' }], - ['store', { watchDepth: 'reference' }], - ['intl', { watchDepth: 'reference' }], - - ['onQuerySubmit', { watchDepth: 'reference' }], - ['onFiltersUpdated', { watchDepth: 'reference' }], - ['onRefreshChange', { watchDepth: 'reference' }], - - ['indexPatterns', { watchDepth: 'collection' }], - ['filters', { watchDepth: 'collection' }], - - 'appName', - 'screenTitle', - 'showFilterBar', - 'showQueryBar', - 'showDatePicker', - 'dateRangeFrom', - 'dateRangeTo', - 'isRefreshPaused', - 'refreshInterval', - 'disableAutoFocus', - 'showAutoRefreshOnly', - ], - {}, - { - store: localStorage, - }, - ); - }); -} diff --git a/src/legacy/core_plugins/kibana/public/context/app.js b/src/legacy/core_plugins/kibana/public/context/app.js index 59184c8f7f8e5f..243a58a63b4579 100644 --- a/src/legacy/core_plugins/kibana/public/context/app.js +++ b/src/legacy/core_plugins/kibana/public/context/app.js @@ -45,7 +45,6 @@ const module = uiModules.get('apps/context', [ 'elasticsearch', 'kibana', 'kibana/config', - 'kibana/notify', 'ngRoute', ]); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html index 4b77a7e3733ca9..bf9d97b3867f61 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html @@ -4,6 +4,7 @@ > { +jest.mock('../../../../data/public', () => { return { FilterBar: () =>
, - }; -}); - -jest.mock('../../../query/query_bar', () => { - return { QueryBar: () =>
, }; }); diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx b/src/legacy/core_plugins/kibana_react/public/search_bar/components/search_bar.tsx similarity index 94% rename from src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx rename to src/legacy/core_plugins/kibana_react/public/search_bar/components/search_bar.tsx index 84cd1f26b42561..cf6ebc947c1e9e 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx +++ b/src/legacy/core_plugins/kibana_react/public/search_bar/components/search_bar.tsx @@ -26,9 +26,7 @@ import React, { Component } from 'react'; import ResizeObserver from 'resize-observer-polyfill'; import { Storage } from 'ui/storage'; -import { IndexPattern } from '../../../index_patterns'; -import { Query, QueryBar } from '../../../query/query_bar'; -import { FilterBar } from '../../../filter/filter_bar'; +import { IndexPattern, Query, QueryBar, FilterBar } from '../../../../data/public'; interface DateRange { from: string; @@ -110,16 +108,16 @@ class SearchBarUI extends Component { private getFilterTriggerButton() { const filtersAppliedText = this.props.intl.formatMessage({ - id: 'data.search.searchBar.filtersButtonFiltersAppliedTitle', + id: 'kibana_react.search.searchBar.filtersButtonFiltersAppliedTitle', defaultMessage: 'filters applied.', }); const clickToShowOrHideText = this.state.isFiltersVisible ? this.props.intl.formatMessage({ - id: 'data.search.searchBar.filtersButtonClickToShowTitle', + id: 'kibana_react.search.searchBar.filtersButtonClickToShowTitle', defaultMessage: 'Select to hide', }) : this.props.intl.formatMessage({ - id: 'data.search.searchBar.filtersButtonClickToHideTitle', + id: 'kibana_react.search.searchBar.filtersButtonClickToHideTitle', defaultMessage: 'Select to show', }); diff --git a/src/legacy/core_plugins/data/public/search/search_bar/index.tsx b/src/legacy/core_plugins/kibana_react/public/search_bar/index.tsx similarity index 93% rename from src/legacy/core_plugins/data/public/search/search_bar/index.tsx rename to src/legacy/core_plugins/kibana_react/public/search_bar/index.tsx index 357e28d181ae34..faf6e24aa6ed5d 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/index.tsx +++ b/src/legacy/core_plugins/kibana_react/public/search_bar/index.tsx @@ -18,6 +18,3 @@ */ export * from './components'; - -// @ts-ignore -export { setupDirective } from './directive'; diff --git a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu.test.tsx b/src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu.test.tsx index 764e93a8685ea0..03213afa8a5d38 100644 --- a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu.test.tsx +++ b/src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu.test.tsx @@ -22,7 +22,7 @@ import { TopNavMenu } from './top_nav_menu'; import { TopNavMenuData } from './top_nav_menu_data'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -jest.mock('../../../../core_plugins/data/public', () => { +jest.mock('../search_bar', () => { return { SearchBar: () =>
, SearchBarProps: {}, diff --git a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu.tsx b/src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu.tsx index e0c705ece7b4b1..31ad676bb4cb71 100644 --- a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu.tsx +++ b/src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu.tsx @@ -23,7 +23,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { I18nProvider } from '@kbn/i18n/react'; import { TopNavMenuData } from './top_nav_menu_data'; import { TopNavMenuItem } from './top_nav_menu_item'; -import { SearchBar, SearchBarProps } from '../../../../core_plugins/data/public'; +import { SearchBar, SearchBarProps } from '../search_bar'; type Props = Partial & { name: string; diff --git a/src/legacy/core_plugins/metrics/public/components/lib/get_interval.js b/src/legacy/core_plugins/metrics/public/components/lib/get_interval.js index 44112cc0e75cba..41caf3aa2b55fb 100644 --- a/src/legacy/core_plugins/metrics/public/components/lib/get_interval.js +++ b/src/legacy/core_plugins/metrics/public/components/lib/get_interval.js @@ -18,8 +18,7 @@ */ import moment from 'moment'; import { i18n } from '@kbn/i18n'; -import { pluck, get, clone } from 'lodash'; -import { relativeOptions } from '../../../../../ui/public/timepicker/relative_options'; +import { get } from 'lodash'; import { GTE_INTERVAL_RE } from '../../../common/interval_regexp'; import { parseEsInterval } from '../../../../data/common/parse_es_interval'; @@ -37,7 +36,8 @@ export const unitLookup = { }; export const convertIntervalIntoUnit = (interval, hasTranslateUnitString = true) => { - const units = pluck(clone(relativeOptions).reverse(), 'value').filter(s => /^[smhdwMy]$/.test(s)); + // Iterate units from biggest to smallest + const units = Object.keys(unitLookup).reverse(); const duration = moment.duration(interval, 'ms'); for (let i = 0; i < units.length; i++) { diff --git a/src/legacy/core_plugins/ui_metric/README.md b/src/legacy/core_plugins/ui_metric/README.md index 9b78cf600dc4e8..90855faff61a69 100644 --- a/src/legacy/core_plugins/ui_metric/README.md +++ b/src/legacy/core_plugins/ui_metric/README.md @@ -16,29 +16,32 @@ the name of a dashboard they've viewed, or the timestamp of the interaction. ## How to use it -To track a user interaction, import the `trackUiMetric` helper function from UI Metric app: +To track a user interaction, import the `createUiStatsReporter` helper function from UI Metric app: ```js -import { trackUiMetric } from 'relative/path/to/src/legacy/core_plugins/ui_metric/public'; +import { createUiStatsReporter, METRIC_TYPE } from 'relative/path/to/src/legacy/core_plugins/ui_metric/public'; +const trackMetric = createUiStatsReporter(``); +trackMetric(METRIC_TYPE.CLICK, ``); +trackMetric('click', ``); ``` +Metric Types: + - `METRIC_TYPE.CLICK` for tracking clicks `trackMetric(METRIC_TYPE.CLICK, 'my_button_clicked');` + - `METRIC_TYPE.LOADED` for a component load or page load `trackMetric(METRIC_TYPE.LOADED', 'my_component_loaded');` + - `METRIC_TYPE.COUNT` for a tracking a misc count `trackMetric(METRIC_TYPE.COUNT', 'my_counter', });` + Call this function whenever you would like to track a user interaction within your app. The function -accepts two arguments, `appName` and `metricType`. These should be underscore-delimited strings. -For example, to track the `my_metric` metric in the app `my_app` call `trackUiMetric('my_app', 'my_metric)`. +accepts two arguments, `metricType` and `eventNames`. These should be underscore-delimited strings. +For example, to track the `my_event` metric in the app `my_app` call `trackUiMetric(METRIC_TYPE.*, 'my_event)`. That's all you need to do! -To track multiple metrics within a single request, provide an array of metric types, e.g. `trackUiMetric('my_app', ['my_metric1', 'my_metric2', 'my_metric3'])`. - -**NOTE:** When called, this function sends a `POST` request to `/api/ui_metric/{appName}/{metricType}`. -It's important that this request is sent via the `trackUiMetric` function, because it contains special -logic for blocking the request if the user hasn't opted in to telemetry. +To track multiple metrics within a single request, provide an array of events, e.g. `trackMetric(METRIC_TYPE.*, ['my_event1', 'my_event2', 'my_event3'])`. ### Disallowed characters -The colon and comma characters (`,`, `:`) should not be used in app name or metric types. Colons play -a sepcial role in how metrics are stored as saved objects, and the API endpoint uses commas to delimit -multiple metric types in a single API request. +The colon character (`:`) should not be used in app name or event names. Colons play +a special role in how metrics are stored as saved objects. ### Tracking timed interactions @@ -47,7 +50,7 @@ logic yourself. You'll also need to predefine some buckets into which the UI met For example, if you're timing how long it takes to create a visualization, you may decide to measure interactions that take less than 1 minute, 1-5 minutes, 5-20 minutes, and longer than 20 minutes. To track these interactions, you'd use the timed length of the interaction to determine whether to -use a `metricType` of `create_vis_1m`, `create_vis_5m`, `create_vis_20m`, or `create_vis_infinity`. +use a `eventName` of `create_vis_1m`, `create_vis_5m`, `create_vis_20m`, or `create_vis_infinity`. ## How it works diff --git a/src/legacy/core_plugins/ui_metric/index.ts b/src/legacy/core_plugins/ui_metric/index.ts index 34ca346b8756e9..6c957f23b5c40d 100644 --- a/src/legacy/core_plugins/ui_metric/index.ts +++ b/src/legacy/core_plugins/ui_metric/index.ts @@ -18,9 +18,10 @@ */ import { resolve } from 'path'; +import JoiNamespace from 'joi'; +import { Server } from 'hapi'; import { Legacy } from '../../../../kibana'; -import { registerUserActionRoute } from './server/routes/api/ui_metric'; -import { registerUiMetricUsageCollector } from './server/usage/index'; +import { registerUiMetricRoute } from './server/routes/api/ui_metric'; // eslint-disable-next-line import/no-default-export export default function(kibana: any) { @@ -28,15 +29,25 @@ export default function(kibana: any) { id: 'ui_metric', require: ['kibana', 'elasticsearch'], publicDir: resolve(__dirname, 'public'), - + config(Joi: typeof JoiNamespace) { + return Joi.object({ + enabled: Joi.boolean().default(true), + debug: Joi.boolean().default(Joi.ref('$dev')), + }).default(); + }, uiExports: { + injectDefaultVars(server: Server) { + const config = server.config(); + return { + debugUiMetric: config.get('ui_metric.debug'), + }; + }, mappings: require('./mappings.json'), - hacks: ['plugins/ui_metric'], + hacks: ['plugins/ui_metric/hacks/ui_metric_init'], }, init(server: Legacy.Server) { - registerUserActionRoute(server); - registerUiMetricUsageCollector(server); + registerUiMetricRoute(server); }, }); } diff --git a/src/legacy/server/http/secure_options.js b/src/legacy/core_plugins/ui_metric/public/hacks/ui_metric_init.ts similarity index 58% rename from src/legacy/server/http/secure_options.js rename to src/legacy/core_plugins/ui_metric/public/hacks/ui_metric_init.ts index e8440a86280b39..7aafc82cfe4c63 100644 --- a/src/legacy/server/http/secure_options.js +++ b/src/legacy/core_plugins/ui_metric/public/hacks/ui_metric_init.ts @@ -17,25 +17,18 @@ * under the License. */ -import crypto from 'crypto'; -import { chain } from 'lodash'; +// @ts-ignore +import { uiModules } from 'ui/modules'; +import chrome from 'ui/chrome'; +import { createAnalyticsReporter, setTelemetryReporter } from '../services/telemetry_analytics'; -const protocolMap = { - TLSv1: crypto.constants.SSL_OP_NO_TLSv1, - 'TLSv1.1': crypto.constants.SSL_OP_NO_TLSv1_1, - 'TLSv1.2': crypto.constants.SSL_OP_NO_TLSv1_2 -}; - -export default function (supportedProtocols) { - if (!supportedProtocols || !supportedProtocols.length) { - return null; - } - - return chain(protocolMap) - .omit(supportedProtocols) - .values() - .reduce(function (value, sum) { - return value | sum; - }, 0) - .value(); +function telemetryInit($injector: any) { + const localStorage = $injector.get('localStorage'); + const debug = chrome.getInjected('debugUiMetric'); + const $http = $injector.get('$http'); + const basePath = chrome.getBasePath(); + const uiReporter = createAnalyticsReporter({ localStorage, $http, basePath, debug }); + setTelemetryReporter(uiReporter); } + +uiModules.get('kibana').run(telemetryInit); diff --git a/src/legacy/core_plugins/ui_metric/public/index.ts b/src/legacy/core_plugins/ui_metric/public/index.ts index 6bf54943a3ac40..b1e78b56d05d0a 100644 --- a/src/legacy/core_plugins/ui_metric/public/index.ts +++ b/src/legacy/core_plugins/ui_metric/public/index.ts @@ -17,39 +17,5 @@ * under the License. */ -import chrome from 'ui/chrome'; -// @ts-ignore -import { uiModules } from 'ui/modules'; -import { getCanTrackUiMetrics } from 'ui/ui_metric'; -import { API_BASE_PATH } from '../common'; - -let _http: any; - -uiModules.get('kibana').run(($http: any) => { - _http = $http; -}); - -function createErrorMessage(subject: string): any { - const message = - `trackUiMetric was called with ${subject}, which is not allowed to contain a colon. ` + - `Colons play a special role in how metrics are saved as stored objects`; - return new Error(message); -} - -export function trackUiMetric(appName: string, metricType: string | string[]) { - if (!getCanTrackUiMetrics()) { - return; - } - - if (appName.includes(':')) { - throw createErrorMessage(`app name '${appName}'`); - } - - if (metricType.includes(':')) { - throw createErrorMessage(`metric type ${metricType}`); - } - - const metricTypes = Array.isArray(metricType) ? metricType.join(',') : metricType; - const uri = chrome.addBasePath(`${API_BASE_PATH}/${appName}/${metricTypes}`); - _http.post(uri); -} +export { createUiStatsReporter } from './services/telemetry_analytics'; +export { METRIC_TYPE } from '@kbn/analytics'; diff --git a/src/legacy/core_plugins/ui_metric/public/services/telemetry_analytics.ts b/src/legacy/core_plugins/ui_metric/public/services/telemetry_analytics.ts new file mode 100644 index 00000000000000..63adccb3e02b04 --- /dev/null +++ b/src/legacy/core_plugins/ui_metric/public/services/telemetry_analytics.ts @@ -0,0 +1,60 @@ +/* + * 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 { createReporter, Reporter, UiStatsMetricType } from '@kbn/analytics'; + +let telemetryReporter: Reporter; + +export const setTelemetryReporter = (aTelemetryReporter: Reporter): void => { + telemetryReporter = aTelemetryReporter; +}; + +export const getTelemetryReporter = () => { + return telemetryReporter; +}; + +export const createUiStatsReporter = (appName: string) => ( + type: UiStatsMetricType, + eventNames: string | string[], + count?: number +): void => { + if (telemetryReporter) { + return telemetryReporter.reportUiStats(appName, type, eventNames, count); + } +}; + +interface AnalyicsReporterConfig { + localStorage: any; + basePath: string; + debug: boolean; + $http: ng.IHttpService; +} + +export function createAnalyticsReporter(config: AnalyicsReporterConfig) { + const { localStorage, basePath, $http, debug } = config; + + return createReporter({ + debug, + storage: localStorage, + async http(report) { + const url = `${basePath}/api/telemetry/report`; + await $http.post(url, { report }); + }, + }); +} diff --git a/src/legacy/core_plugins/ui_metric/server/routes/api/ui_metric.ts b/src/legacy/core_plugins/ui_metric/server/routes/api/ui_metric.ts index 4bbafbb18f5509..8a7950c46fa317 100644 --- a/src/legacy/core_plugins/ui_metric/server/routes/api/ui_metric.ts +++ b/src/legacy/core_plugins/ui_metric/server/routes/api/ui_metric.ts @@ -17,36 +17,65 @@ * under the License. */ +import Joi from 'joi'; import Boom from 'boom'; +import { Report } from '@kbn/analytics'; import { Server } from 'hapi'; -import { API_BASE_PATH } from '../../../common'; -export const registerUserActionRoute = (server: Server) => { - /* - * Increment a count on an object representing a specific interaction with the UI. - */ +export async function storeReport(server: any, report: Report) { + const { getSavedObjectsRepository } = server.savedObjects; + const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); + const internalRepository = getSavedObjectsRepository(callWithInternalUser); + + const metricKeys = Object.keys(report.uiStatsMetrics); + return Promise.all( + metricKeys.map(async key => { + const metric = report.uiStatsMetrics[key]; + const { appName, eventName } = metric; + const savedObjectId = `${appName}:${eventName}`; + return internalRepository.incrementCounter('ui-metric', savedObjectId, 'count'); + }) + ); +} + +export function registerUiMetricRoute(server: Server) { server.route({ - path: `${API_BASE_PATH}/{appName}/{metricTypes}`, method: 'POST', - handler: async (request: any) => { - const { appName, metricTypes } = request.params; + path: '/api/telemetry/report', + options: { + validate: { + payload: Joi.object({ + report: Joi.object({ + uiStatsMetrics: Joi.object() + .pattern( + /.*/, + Joi.object({ + key: Joi.string().required(), + type: Joi.string().required(), + appName: Joi.string().required(), + eventName: Joi.string().required(), + stats: Joi.object({ + min: Joi.number(), + sum: Joi.number(), + max: Joi.number(), + avg: Joi.number(), + }).allow(null), + }) + ) + .allow(null), + }), + }), + }, + }, + handler: async (req: any, h: any) => { + const { report } = req.payload; try { - const { getSavedObjectsRepository } = server.savedObjects; - const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); - const internalRepository = getSavedObjectsRepository(callWithInternalUser); - - const incrementRequests = metricTypes.split(',').map((metricType: string) => { - const savedObjectId = `${appName}:${metricType}`; - // This object is created if it doesn't already exist. - return internalRepository.incrementCounter('ui-metric', savedObjectId, 'count'); - }); - - await Promise.all(incrementRequests); + await storeReport(server, report); return {}; } catch (error) { return new Boom('Something went wrong', { statusCode: error.status }); } }, }); -}; +} diff --git a/src/legacy/core_plugins/ui_metric/server/usage/collector.ts b/src/legacy/core_plugins/ui_metric/server/usage/collector.ts deleted file mode 100644 index bbb7b1af8e7c75..00000000000000 --- a/src/legacy/core_plugins/ui_metric/server/usage/collector.ts +++ /dev/null @@ -1,59 +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. - */ - -const UI_METRIC_USAGE_TYPE = 'ui_metric'; - -export function registerUiMetricUsageCollector(server: any) { - const collector = server.usage.collectorSet.makeUsageCollector({ - type: UI_METRIC_USAGE_TYPE, - fetch: async (callCluster: any) => { - const { SavedObjectsClient, getSavedObjectsRepository } = server.savedObjects; - const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); - const internalRepository = getSavedObjectsRepository(callWithInternalUser); - const savedObjectsClient = new SavedObjectsClient(internalRepository); - - const { saved_objects: rawUiMetrics } = await savedObjectsClient.find({ - type: 'ui-metric', - fields: ['count'], - }); - - const uiMetricsByAppName = rawUiMetrics.reduce((accum: any, rawUiMetric: any) => { - const { - id, - attributes: { count }, - } = rawUiMetric; - - const [appName, metricType] = id.split(':'); - - if (!accum[appName]) { - accum[appName] = []; - } - - const pair = { key: metricType, value: count }; - accum[appName].push(pair); - return accum; - }, {}); - - return uiMetricsByAppName; - }, - isReady: () => true, - }); - - server.usage.collectorSet.register(collector); -} diff --git a/src/legacy/plugin_discovery/types.ts b/src/legacy/plugin_discovery/types.ts index eb772e9970ed46..76b62b7eb693c5 100644 --- a/src/legacy/plugin_discovery/types.ts +++ b/src/legacy/plugin_discovery/types.ts @@ -22,6 +22,8 @@ import { Capabilities } from '../../core/public'; // Disable lint errors for imports from src/core/* until SavedObjects migration is complete // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { SavedObjectsSchemaDefinition } from '../../core/server/saved_objects/schema'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { SavedObjectsManagementDefinition } from '../../core/server/saved_objects/management'; /** * Usage @@ -66,6 +68,8 @@ export interface LegacyPluginOptions { home: string[]; mappings: any; savedObjectSchemas: SavedObjectsSchemaDefinition; + savedObjectsManagement: SavedObjectsManagementDefinition; + visTypes: string[]; embeddableActions?: string[]; embeddableFactories?: string[]; }>; diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index 1b237f2c8557c6..671d0165abe9b9 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -29,7 +29,11 @@ import { import { getData } from '../path'; -import { DEFAULT_CSP_RULES } from '../csp'; +import { + DEFAULT_CSP_RULES, + DEFAULT_CSP_STRICT, + DEFAULT_CSP_WARN_LEGACY_BROWSERS, +} from '../csp'; export default () => Joi.object({ pkg: Joi.object({ @@ -56,8 +60,8 @@ export default () => Joi.object({ csp: Joi.object({ rules: Joi.array().items(Joi.string()).default(DEFAULT_CSP_RULES), - strict: Joi.boolean().default(false), - warnLegacyBrowsers: Joi.boolean().default(true), + strict: Joi.boolean().default(DEFAULT_CSP_STRICT), + warnLegacyBrowsers: Joi.boolean().default(DEFAULT_CSP_WARN_LEGACY_BROWSERS), }).default(), cpu: Joi.object({ diff --git a/src/legacy/server/csp/index.test.ts b/src/legacy/server/csp/index.test.ts index 823501a6433fe3..9586166b65641a 100644 --- a/src/legacy/server/csp/index.test.ts +++ b/src/legacy/server/csp/index.test.ts @@ -17,7 +17,13 @@ * under the License. */ -import { createCSPRuleString, DEFAULT_CSP_RULES, generateCSPNonce } from './'; +import { + createCSPRuleString, + generateCSPNonce, + DEFAULT_CSP_RULES, + DEFAULT_CSP_STRICT, + DEFAULT_CSP_WARN_LEGACY_BROWSERS, +} from './'; // CSP rules aren't strictly additive, so any change can potentially expand or // restrict the policy in a way we consider a breaking change. For that reason, @@ -41,6 +47,14 @@ Array [ `); }); +test('CSP strict mode defaults to disabled', () => { + expect(DEFAULT_CSP_STRICT).toBe(false); +}); + +test('CSP legacy browser warning defaults to enabled', () => { + expect(DEFAULT_CSP_WARN_LEGACY_BROWSERS).toBe(true); +}); + test('generateCSPNonce() creates a 16 character string', async () => { const nonce = await generateCSPNonce(); diff --git a/src/legacy/server/csp/index.ts b/src/legacy/server/csp/index.ts index 78e943ebf772c5..96c359e61e25b2 100644 --- a/src/legacy/server/csp/index.ts +++ b/src/legacy/server/csp/index.ts @@ -28,6 +28,10 @@ export const DEFAULT_CSP_RULES = Object.freeze([ 'child-src blob:', ]); +export const DEFAULT_CSP_STRICT = false; + +export const DEFAULT_CSP_WARN_LEGACY_BROWSERS = true; + export async function generateCSPNonce() { return (await randomBytesAsync(12)).toString('base64'); } diff --git a/src/legacy/server/http/secure_options.test.js b/src/legacy/server/http/secure_options.test.js deleted file mode 100644 index 1374b9dd8667b0..00000000000000 --- a/src/legacy/server/http/secure_options.test.js +++ /dev/null @@ -1,46 +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 secureOptions from './secure_options'; -import crypto from 'crypto'; - -const constants = crypto.constants; - -describe('secure_options', function () { - it('allows null', function () { - expect(secureOptions(null)).toBe(null); - }); - - it ('allows an empty array', function () { - expect(secureOptions([])).toBe(null); - }); - - it ('removes TLSv1 if we only support TLSv1.1 and TLSv1.2', function () { - expect(secureOptions(['TLSv1.1', 'TLSv1.2'])).toBe(constants.SSL_OP_NO_TLSv1); - }); - - it ('removes TLSv1.1 and TLSv1.2 if we only support TLSv1', function () { - expect(secureOptions(['TLSv1'])).toBe(constants.SSL_OP_NO_TLSv1_1 | constants.SSL_OP_NO_TLSv1_2); - }); - - it ('removes TLSv1 and TLSv1.1 if we only support TLSv1.2', function () { - expect(secureOptions(['TLSv1.2'])).toBe(constants.SSL_OP_NO_TLSv1 | constants.SSL_OP_NO_TLSv1_1); - }); - -}); diff --git a/src/legacy/server/i18n/get_translations_path.js b/src/legacy/server/i18n/get_translations_path.js index 95de6c75606605..6ac3e75e1d4a85 100644 --- a/src/legacy/server/i18n/get_translations_path.js +++ b/src/legacy/server/i18n/get_translations_path.js @@ -34,10 +34,12 @@ export async function getTranslationPaths({ cwd, glob }) { try { const content = await readFileAsync(entryFullPath, 'utf8'); const { translations } = JSON.parse(content); - translations.forEach(translation => { - const translationFullPath = resolve(pluginBasePath, translation); - translationPaths.push(translationFullPath); - }); + if (translations && translations.length) { + translations.forEach(translation => { + const translationFullPath = resolve(pluginBasePath, translation); + translationPaths.push(translationFullPath); + }); + } } catch (err) { throw new Error(`Failed to parse .i18nrc.json file at ${entryFullPath}`); } diff --git a/src/legacy/ui/public/chrome/directives/kbn_chrome.html b/src/legacy/ui/public/chrome/directives/kbn_chrome.html index 15053a3b1e242b..541082e68de58d 100644 --- a/src/legacy/ui/public/chrome/directives/kbn_chrome.html +++ b/src/legacy/ui/public/chrome/directives/kbn_chrome.html @@ -1,8 +1,4 @@
- -
{ return $location.path().split('/')[1]; }; diff --git a/src/legacy/ui/public/directives/__tests__/truncate.js b/src/legacy/ui/public/directives/__tests__/truncate.js deleted file mode 100644 index 7cf0d2e3799b1b..00000000000000 --- a/src/legacy/ui/public/directives/__tests__/truncate.js +++ /dev/null @@ -1,114 +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 angular from 'angular'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import 'plugins/kibana/discover/index'; - - -let $parentScope; - -let $scope; - -let $elem; - -const init = function (text) { - // Load the application - ngMock.module('kibana'); - - // Create the scope - ngMock.inject(function ($rootScope, $compile) { - - // Give us a scope - $parentScope = $rootScope; - - // Create the element - $elem = angular.element( - '' - ); - - // And compile it - $compile($elem)($parentScope); - - // Fire a digest cycle - $elem.scope().$digest(); - - // Grab the isolate scope so we can test it - $scope = $elem.isolateScope(); - }); -}; - -function trimmed(text) { - return text.trim().replace(/\s+/g, ' '); -} - -describe('kbnTruncate directive', function () { - - describe('long strings', function () { - - beforeEach(function () { - init('some string of text over 10 characters'); - }); - - it('should trim long strings', function (done) { - expect(trimmed($elem.text())).to.be('some … more'); - done(); - }); - - it('should have a link to see more text', function (done) { - expect($elem.find('[ng-click="toggle()"]').text()).to.be('more'); - done(); - }); - - it('should show more text if the link is clicked and less text if clicked again', function (done) { - $scope.toggle(); - $scope.$digest(); - expect(trimmed($elem.text())).to.be('some string of text over 10 characters less'); - expect($elem.find('[ng-click="toggle()"]').text()).to.be('less'); - - $scope.toggle(); - $scope.$digest(); - expect(trimmed($elem.text())).to.be('some … more'); - expect($elem.find('[ng-click="toggle()"]').text()).to.be('more'); - - done(); - }); - - }); - - describe('short strings', function () { - - beforeEach(function () { - init('short'); - }); - - it('should not trim short strings', function (done) { - expect(trimmed($elem.text())).to.be('short'); - done(); - }); - - it('should not have a link', function (done) { - expect($elem.find('[ng-click="toggle()"]').length).to.be(0); - done(); - }); - - }); - -}); diff --git a/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js b/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js index 24d0f612dc5f7c..c1843017356849 100644 --- a/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js +++ b/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js @@ -55,7 +55,7 @@ import _ from 'lodash'; import angular from 'angular'; -import '../timepicker'; +import './timepicker'; import '../watch_multi'; import '../directives/input_focus'; import { uiModules } from '../modules'; diff --git a/src/legacy/ui/public/timepicker/index.js b/src/legacy/ui/public/kbn_top_nav/timepicker/index.js similarity index 100% rename from src/legacy/ui/public/timepicker/index.js rename to src/legacy/ui/public/kbn_top_nav/timepicker/index.js diff --git a/src/legacy/ui/public/timepicker/kbn_global_timepicker.html b/src/legacy/ui/public/kbn_top_nav/timepicker/kbn_global_timepicker.html similarity index 100% rename from src/legacy/ui/public/timepicker/kbn_global_timepicker.html rename to src/legacy/ui/public/kbn_top_nav/timepicker/kbn_global_timepicker.html diff --git a/src/legacy/ui/public/timepicker/kbn_global_timepicker.js b/src/legacy/ui/public/kbn_top_nav/timepicker/kbn_global_timepicker.js similarity index 88% rename from src/legacy/ui/public/timepicker/kbn_global_timepicker.js rename to src/legacy/ui/public/kbn_top_nav/timepicker/kbn_global_timepicker.js index 2ff0e8b1c8f549..7c60cd16b0bf33 100644 --- a/src/legacy/ui/public/timepicker/kbn_global_timepicker.js +++ b/src/legacy/ui/public/kbn_top_nav/timepicker/kbn_global_timepicker.js @@ -17,12 +17,35 @@ * under the License. */ -import { uiModules } from '../modules'; +import { uiModules } from '../../modules'; import toggleHtml from './kbn_global_timepicker.html'; import { timefilter } from 'ui/timefilter'; import { timeHistory } from 'ui/timefilter/time_history'; +import { + EuiSuperDatePicker, +} from '@elastic/eui'; + +uiModules + .get('kibana') + .directive('superDatePicker', reactDirective => reactDirective(EuiSuperDatePicker, [ + 'start', + 'end', + 'isPaused', + 'refreshInterval', + 'commonlyUsedRanges', + 'dateFormat', + 'recentlyUsedRanges', + 'onTimeChange', + 'onRefreshChange', + 'isAutoRefreshOnly', + 'commonlyUsedRanges', + 'dateFormat', + 'recentlyUsedRanges', + ])); + + uiModules .get('kibana') .directive('kbnGlobalTimepicker', (globalState, config) => { diff --git a/src/legacy/ui/public/notify/__tests__/notifier.js b/src/legacy/ui/public/notify/__tests__/notifier.js deleted file mode 100644 index f12a1e917cc0c3..00000000000000 --- a/src/legacy/ui/public/notify/__tests__/notifier.js +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import ngMock from 'ng_mock'; -import expect from '@kbn/expect'; -import sinon from 'sinon'; -import { Notifier } from '..'; -import { metadata } from 'ui/metadata'; - -describe('Notifier', function () { - let $interval; - let notifier; - let params; - const message = 'Oh, the humanity!'; - - beforeEach(function () { - ngMock.module('kibana'); - - ngMock.inject(function (_$interval_) { - $interval = _$interval_; - }); - }); - - beforeEach(function () { - params = { location: 'foo' }; - notifier = new Notifier(params); - }); - - afterEach(function () { - Notifier.prototype._notifs.length = 0; - }); - - describe('#constructor()', function () { - it('sets #from from given location', function () { - expect(notifier.from).to.equal(params.location); - }); - }); - - describe('#error', function () { - testVersionInfo('error'); - - it('prepends location to message for content', function () { - expect(notify('error').content).to.equal(params.location + ': ' + message); - }); - - it('sets type to "danger"', function () { - expect(notify('error').type).to.equal('danger'); - }); - - it('sets icon to "warning"', function () { - expect(notify('error').icon).to.equal('warning'); - }); - - it('sets title to "Error"', function () { - expect(notify('error').title).to.equal('Error'); - }); - - it('sets lifetime to 5 minutes', function () { - expect(notify('error').lifetime).to.equal(300000); - }); - - it('sets timeRemaining and decrements', function () { - const notif = notify('error'); - - expect(notif.timeRemaining).to.equal(300); - $interval.flush(1000); - expect(notif.timeRemaining).to.equal(299); - }); - - it('closes notification on lifetime expiry', function () { - const expectation = sinon.mock(); - const notif = notifier.error(message, expectation); - - expectation.once(); - expectation.withExactArgs('ignore'); - - $interval.flush(300000); - - expect(notif.timerId).to.be(undefined); - }); - - it('allows canceling of timer', function () { - const notif = notify('error'); - - expect(notif.timerId).to.not.be(undefined); - notif.cancelTimer(); - - expect(notif.timerId).to.be(undefined); - }); - - it('resets timer on addition to stack', function () { - const notif = notify('error'); - - $interval.flush(100000); - expect(notif.timeRemaining).to.equal(200); - - notify('error'); - expect(notif.timeRemaining).to.equal(300); - }); - - it('allows reporting', function () { - const includesReport = _.includes(notify('error').actions, 'report'); - expect(includesReport).to.true; - }); - - it('allows accepting', function () { - const includesAccept = _.includes(notify('error').actions, 'accept'); - expect(includesAccept).to.true; - }); - - it('includes stack', function () { - expect(notify('error').stack).to.be.defined; - }); - - it('has css class helper functions', function () { - expect(notify('error').getIconClass()).to.equal('fa fa-warning'); - expect(notify('error').getButtonClass()).to.equal('kuiButton--danger'); - expect(notify('error').getAlertClassStack()).to.equal('kbnToast kbnToast-isStack kbnToast--danger'); - expect(notify('error').getAlertClass()).to.equal('kbnToast kbnToast--danger'); - expect(notify('error').getButtonGroupClass()).to.equal('kbnToast__controls'); - expect(notify('error').getToastMessageClass()).to.equal('kbnToast__message'); - }); - }); - - function notify(fnName, opts) { - notifier[fnName](message, opts); - return latestNotification(); - } - - function latestNotification() { - return _.last(notifier._notifs); - } - - function testVersionInfo(fnName) { - describe('when version is configured', function () { - it('adds version to notification', function () { - const notification = notify(fnName); - expect(notification.info.version).to.equal(metadata.version); - }); - }); - describe('when build number is configured', function () { - it('adds buildNum to notification', function () { - const notification = notify(fnName); - expect(notification.info.buildNum).to.equal(metadata.buildNum); - }); - }); - } -}); diff --git a/src/legacy/ui/public/notify/_index.scss b/src/legacy/ui/public/notify/_index.scss index 3a02853b79d8ad..fe86883aedcf90 100644 --- a/src/legacy/ui/public/notify/_index.scss +++ b/src/legacy/ui/public/notify/_index.scss @@ -1,2 +1 @@ @import './banners/index'; -@import './partials/index'; diff --git a/src/legacy/ui/public/notify/directives/truncated.js b/src/legacy/ui/public/notify/directives/truncated.js deleted file mode 100644 index 6d72e8780d4a19..00000000000000 --- a/src/legacy/ui/public/notify/directives/truncated.js +++ /dev/null @@ -1,66 +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 truncText from 'trunc-text'; -import { i18n } from '@kbn/i18n'; -import truncHTML from 'trunc-html'; -import { uiModules } from '../../modules'; -import truncatedTemplate from '../partials/truncated.html'; -import 'angular-sanitize'; - -const module = uiModules.get('kibana', ['ngSanitize']); - -module.directive('kbnTruncated', function () { - return { - restrict: 'E', - scope: { - source: '@', - length: '@', - isHtml: '@' - }, - template: truncatedTemplate, - link: function ($scope) { - const source = $scope.source; - const max = $scope.length; - const truncated = $scope.isHtml - ? truncHTML(source, max).html - : truncText(source, max); - - $scope.content = truncated; - - if (source === truncated) { - return; - } - $scope.truncated = true; - $scope.expanded = false; - const showLessText = i18n.translate('common.ui.directives.truncated.showLessLinkText', { - defaultMessage: 'less' - }); - const showMoreText = i18n.translate('common.ui.directives.truncated.showMoreLinkText', { - defaultMessage: 'more' - }); - $scope.action = showMoreText; - $scope.toggle = () => { - $scope.expanded = !$scope.expanded; - $scope.content = $scope.expanded ? source : truncated; - $scope.action = $scope.expanded ? showLessText : showMoreText; - }; - } - }; -}); diff --git a/src/legacy/ui/public/notify/index.js b/src/legacy/ui/public/notify/index.js index 078b4807430dde..da4a5af6b20ad9 100644 --- a/src/legacy/ui/public/notify/index.js +++ b/src/legacy/ui/public/notify/index.js @@ -17,8 +17,6 @@ * under the License. */ -export { notify } from './notify'; -export { Notifier } from './notifier'; export { fatalError, addFatalErrorCallback } from './fatal_error'; export { toastNotifications } from './toasts'; export { GlobalBannerList, banners } from './banners'; diff --git a/src/legacy/ui/public/notify/notifier.js b/src/legacy/ui/public/notify/notifier.js deleted file mode 100644 index 735681c8a48d03..00000000000000 --- a/src/legacy/ui/public/notify/notifier.js +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { metadata } from '../metadata'; -import { formatMsg, formatStack } from './lib'; -import '../render_directive'; -import { i18n } from '@kbn/i18n'; - -const notifs = []; - -const { version, buildNum } = metadata; - -function closeNotif(notif, cb = _.noop, key) { - return function () { - // this === notif - const i = notifs.indexOf(notif); - if (i !== -1) notifs.splice(i, 1); - - cancelTimer(notif); - cb(key); - }; -} - -function cancelTimer(notif) { - if (notif.timerId) { - Notifier.config.clearInterval(notif.timerId); - notif.timerId = undefined; - } -} - -function timerCanceler(notif, cb = _.noop, key) { - return function cancelNotifTimer() { - cancelTimer(notif); - cb(key); - }; -} - -/** - * Initiates a timer to update _timeRemaining_ on the notif at second - * intervals and clears the notif once the notif _lifetime_ has been reached. - */ -function startNotifTimer(notif, cb) { - const interval = 1000; - - if (notif.lifetime === Infinity || notif.lifetime === 0) { - return; - } - - notif.timeRemaining = Math.floor(notif.lifetime / interval); - - notif.timerId = Notifier.config.setInterval( - function () { - notif.timeRemaining -= 1; - - if (notif.timeRemaining <= 0) { - closeNotif(notif, cb, 'ignore')(); - } - }, - interval, - notif.timeRemaining - ); - - notif.cancelTimer = timerCanceler(notif, cb); -} - -function restartNotifTimer(notif, cb) { - cancelTimer(notif); - startNotifTimer(notif, cb); -} - -const typeToButtonClassMap = { - danger: 'kuiButton--danger', // NOTE: `error` type is internally named as `danger` - info: 'kuiButton--secondary', -}; -const typeToAlertClassMap = { - danger: `kbnToast--danger`, - info: `kbnToast--info`, -}; - -function add(notif, cb) { - _.set(notif, 'info.version', version); - _.set(notif, 'info.buildNum', buildNum); - - notif.clear = closeNotif(notif); - - if (notif.actions) { - notif.actions.forEach(function (action) { - notif[action] = closeNotif(notif, cb, action); - }); - } else if (notif.customActions) { - // wrap all of the custom functions in a close - notif.customActions = notif.customActions.map((action) => { - return { - key: action.text, - dataTestSubj: action.dataTestSubj, - callback: closeNotif(notif, action.callback, action.text), - getButtonClass() { - const buttonTypeClass = typeToButtonClassMap[notif.type]; - return `${buttonTypeClass}`; - }, - }; - }); - } - - notif.count = (notif.count || 0) + 1; - - notif.isTimed = function isTimed() { - return notif.timerId ? true : false; - }; - - // decorate the notification with helper functions for the template - notif.getButtonClass = () => `${typeToButtonClassMap[notif.type]}`; - notif.getAlertClassStack = () => `kbnToast kbnToast-isStack ${typeToAlertClassMap[notif.type]}`; - notif.getIconClass = () => `fa fa-${notif.icon}`; - notif.getToastMessageClass = () => 'kbnToast__message'; - notif.getAlertClass = () => `kbnToast ${typeToAlertClassMap[notif.type]}`; - notif.getButtonGroupClass = () => 'kbnToast__controls'; - - let dup = null; - if (notif.content) { - dup = _.find(notifs, function (item) { - return item.content === notif.content && item.lifetime === notif.lifetime; - }); - } - - if (dup) { - dup.count += 1; - dup.stacks = _.union(dup.stacks, [notif.stack]); - - restartNotifTimer(dup, cb); - - return dup; - } - - startNotifTimer(notif, cb); - - notif.stacks = [notif.stack]; - notifs.push(notif); - return notif; -} - -Notifier.prototype.add = add; - -/** - * Functionality to check that - */ -export function Notifier(opts) { - const self = this; - opts = opts || {}; - - // label type thing to say where notifications came from - self.from = opts.location; - - const notificationLevels = ['error']; - - notificationLevels.forEach(function (m) { - self[m] = _.bind(self[m], self); - }); -} - -Notifier.config = { - bannerLifetime: 3000000, - errorLifetime: 300000, - infoLifetime: 5000, - setInterval: window.setInterval, - clearInterval: window.clearInterval, -}; - -Notifier.applyConfig = function (config) { - _.merge(Notifier.config, config); -}; - -// simply a pointer to the global notif list -Notifier.prototype._notifs = notifs; - -const overridableOptions = ['lifetime', 'icon']; - -/** - * Alert the user of an error that occured - * @param {Error|String} err - * @param {Function} cb - */ -Notifier.prototype.error = function (err, opts, cb) { - if (_.isFunction(opts)) { - cb = opts; - opts = {}; - } - - const config = _.assign({ - type: 'danger', - content: formatMsg(err, this.from), - icon: 'warning', - title: i18n.translate('common.ui.notify.toaster.errorTitle', { - defaultMessage: 'Error', - }), - lifetime: Notifier.config.errorLifetime, - actions: ['report', 'accept'], - stack: formatStack(err) - }, _.pick(opts, overridableOptions)); - - return add(config, cb); -}; diff --git a/src/legacy/ui/public/notify/notify.js b/src/legacy/ui/public/notify/notify.js index 80859e94610546..5e6c12461dcc37 100644 --- a/src/legacy/ui/public/notify/notify.js +++ b/src/legacy/ui/public/notify/notify.js @@ -19,14 +19,10 @@ import React from 'react'; import { MarkdownSimple } from 'ui/markdown'; -import { uiModules } from '../modules'; -import { metadata } from '../metadata'; +import chrome from '../chrome'; import { fatalError } from './fatal_error'; import { banners } from './banners'; -import { Notifier } from './notifier'; -import template from './partials/toaster.html'; import './filters/markdown'; -import './directives/truncated'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -34,60 +30,16 @@ import { EuiButton, } from '@elastic/eui'; -const module = uiModules.get('kibana/notify'); +const config = chrome.getUiSettingsClient(); -module.directive('kbnNotifications', function () { - return { - restrict: 'E', - scope: { - list: '=list' - }, - replace: true, - template - }; -}); - -export const notify = new Notifier(); - -module.factory('createNotifier', function () { - return function (opts) { - return new Notifier(opts); - }; -}); - -module.factory('Notifier', function () { - return Notifier; -}); - -// teach Notifier how to use angular interval services -module.run(function (config, $interval, $compile) { - Notifier.applyConfig({ - setInterval: $interval, - clearInterval: $interval.cancel - }); +config.getUpdate$().subscribe(() => { applyConfig(config); - Notifier.$compile = $compile; }); -// if kibana is not included then the notify service can't -// expect access to config (since it's dependent on kibana) -if (!!metadata.kbnIndex) { - require('ui/config'); - module.run(function (config) { - config.watchAll(() => applyConfig(config)); - }); -} - let bannerId; let bannerTimeoutId; function applyConfig(config) { - Notifier.applyConfig({ - errorLifetime: config.get('notifications:lifetime:error'), - warningLifetime: config.get('notifications:lifetime:warning'), - infoLifetime: config.get('notifications:lifetime:info') - }); - // Show user-defined banner. const bannerContent = config.get('notifications:banner'); const bannerLifetime = config.get('notifications:lifetime:banner'); diff --git a/src/legacy/ui/public/notify/partials/_index.scss b/src/legacy/ui/public/notify/partials/_index.scss deleted file mode 100644 index 32421b33128c4f..00000000000000 --- a/src/legacy/ui/public/notify/partials/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './toaster'; diff --git a/src/legacy/ui/public/notify/partials/_toaster.scss b/src/legacy/ui/public/notify/partials/_toaster.scss deleted file mode 100644 index 92dab77a3ae9d1..00000000000000 --- a/src/legacy/ui/public/notify/partials/_toaster.scss +++ /dev/null @@ -1,96 +0,0 @@ -// REDO Bootstrap alternatives to match EUI -@import '@elastic/eui/src/components/call_out/variables'; -@import '@elastic/eui/src/components/call_out/mixins'; - -.kbnToaster__container { - visibility: visible; - width: 100%; - - .kbnToaster { - margin: 0; - padding: 0; - list-style: none; - } - - /** - * 1. Shouldn't look like a button. - */ - .kbnToaster__countdown { - appearance: none; - background: none; - border: none; - } - - .kbnToast { - display: flex; - align-items: center; - padding: $euiSizeXS $euiSize; - - > * { - flex: 0 0 auto; - - &:not(:last-child) { - margin-right: $euiSizeXS; - } - } - } - - .kbnToast__message { - @include euiFontSizeS; - text-overflow: ellipsis; - flex: 1 1 auto; - white-space: normal; - } - - .kbnToast-isStack { - @include euiFontSizeS; - padding-bottom: $euiSizeS; - - pre { - display: inline-block; - width: 100%; - margin: $euiSizeS 0; - word-break: normal; - word-wrap: normal; - white-space: pre-wrap; - } - } - - .kbnToast__controls { - display: flex; - } -} - -$kbnToastTypes: ( - info: 'primary', - warning: 'warning', - danger: 'danger', - success: 'success', -); - -@each $name, $color in $kbnToastTypes { - $foreground: euiCallOutColor($color, 'foreground'); - - .kbnToast--#{$name} { - background-color: euiCallOutColor($color, 'background'); - color: $foreground; - - // Fix dark mode contrast by forcing button colors to use the same foreground color - // Override hover/focus text color changes by using !important - @if ($name = 'danger') { - .kuiButton--danger, - .kuiButton--danger:hover { - color: $foreground !important; - border-color: $foreground; - } - } - - @if ($name = 'info') { - .kuiButton--secondary, - .kuiButton--secondary:hover { - color: $foreground !important; - border-color: $foreground; - } - } - } -} diff --git a/src/legacy/ui/public/notify/partials/toaster.html b/src/legacy/ui/public/notify/partials/toaster.html deleted file mode 100644 index 24ceb8ec22a26d..00000000000000 --- a/src/legacy/ui/public/notify/partials/toaster.html +++ /dev/null @@ -1,105 +0,0 @@ -
-
    -
  • -
    - - - {{ notif.count }} - - - - - - - - - -
    - - - - - -
    - - - -
    - -
    -
    
    -      
    -
  • -
-
diff --git a/src/legacy/ui/public/notify/partials/truncated.html b/src/legacy/ui/public/notify/partials/truncated.html deleted file mode 100644 index 60e20567b7409a..00000000000000 --- a/src/legacy/ui/public/notify/partials/truncated.html +++ /dev/null @@ -1,5 +0,0 @@ -{{content}} - - - {{action}} - diff --git a/src/legacy/ui/public/react_components.js b/src/legacy/ui/public/react_components.js index 6a0c52a59f640c..91044142e942c0 100644 --- a/src/legacy/ui/public/react_components.js +++ b/src/legacy/ui/public/react_components.js @@ -19,44 +19,19 @@ import 'ngreact'; -import { - KuiToolBarSearchBox, -} from '@kbn/ui-framework/components'; - import { EuiConfirmModal, EuiIcon, EuiIconTip, - EuiCallOut, - EuiSuperDatePicker, } from '@elastic/eui'; import { uiModules } from './modules'; const app = uiModules.get('app/kibana', ['react']); -app.directive('toolBarSearchBox', reactDirective => reactDirective(KuiToolBarSearchBox)); - app.directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal)); app.directive('icon', reactDirective => reactDirective(EuiIcon)); app.directive('iconTip', reactDirective => reactDirective(EuiIconTip, ['content', 'type', 'position', 'title', 'color'])); -app.directive('callOut', reactDirective => reactDirective(EuiCallOut, ['title', 'color', 'size', 'iconType', 'children'])); - -app.directive('superDatePicker', reactDirective => reactDirective(EuiSuperDatePicker, [ - 'start', - 'end', - 'isPaused', - 'refreshInterval', - 'commonlyUsedRanges', - 'dateFormat', - 'recentlyUsedRanges', - 'onTimeChange', - 'onRefreshChange', - 'isAutoRefreshOnly', - 'commonlyUsedRanges', - 'dateFormat', - 'recentlyUsedRanges', -])); diff --git a/src/legacy/ui/public/routes/__tests__/_wrap_route_with_prep.js b/src/legacy/ui/public/routes/__tests__/_wrap_route_with_prep.js index 63c24c336b2e30..78afd0f8d8a994 100644 --- a/src/legacy/ui/public/routes/__tests__/_wrap_route_with_prep.js +++ b/src/legacy/ui/public/routes/__tests__/_wrap_route_with_prep.js @@ -41,7 +41,7 @@ describe('wrapRouteWithPrep fn', function () { const delayUserWork = opts.delayUserWork ? 50 : 0; return function () { - ngMock.module('kibana', 'kibana/notify'); + ngMock.module('kibana'); let setupComplete = false; let userWorkComplete = false; let route; diff --git a/src/legacy/ui/public/test_harness/test_harness.js b/src/legacy/ui/public/test_harness/test_harness.js index e742121688df3d..8c58ca4e0ad036 100644 --- a/src/legacy/ui/public/test_harness/test_harness.js +++ b/src/legacy/ui/public/test_harness/test_harness.js @@ -22,7 +22,6 @@ import chrome from '../chrome'; import { parse as parseUrl } from 'url'; import sinon from 'sinon'; -import { Notifier } from '../notify'; import { metadata } from '../metadata'; import { UiSettingsClient } from '../../../../core/public'; @@ -67,15 +66,6 @@ function createStubUiSettings() { createStubUiSettings(); sinon.stub(chrome, 'getUiSettingsClient').callsFake(() => stubUiSettings); -beforeEach(function () { - // ensure that notifications are not left in the notifiers - if (Notifier.prototype._notifs.length) { - const notifs = JSON.stringify(Notifier.prototype._notifs); - Notifier.prototype._notifs.length = 0; - throw new Error('notifications were left in the notifier: ' + notifs); - } -}); - afterEach(function () { createStubUiSettings(); }); diff --git a/src/legacy/ui/public/timepicker/relative_options.js b/src/legacy/ui/public/timepicker/relative_options.js deleted file mode 100644 index 097eb13b8327d5..00000000000000 --- a/src/legacy/ui/public/timepicker/relative_options.js +++ /dev/null @@ -1,39 +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 { i18n } from '@kbn/i18n'; - -export const relativeOptions = [ - { text: i18n.translate('common.ui.timepicker.relOpts.secondsAgo', { defaultMessage: 'Seconds ago' }), value: 's' }, - { text: i18n.translate('common.ui.timepicker.relOpts.minutesAgo', { defaultMessage: 'Minutes ago' }), value: 'm' }, - { text: i18n.translate('common.ui.timepicker.relOpts.hoursAgo', { defaultMessage: 'Hours ago' }), value: 'h' }, - { text: i18n.translate('common.ui.timepicker.relOpts.daysAgo', { defaultMessage: 'Days ago' }), value: 'd' }, - { text: i18n.translate('common.ui.timepicker.relOpts.weeksAgo', { defaultMessage: 'Weeks ago' }), value: 'w' }, - { text: i18n.translate('common.ui.timepicker.relOpts.monthsAgo', { defaultMessage: 'Months ago' }), value: 'M' }, - { text: i18n.translate('common.ui.timepicker.relOpts.yearsAgo', { defaultMessage: 'Years ago' }), value: 'y' }, - - { text: i18n.translate('common.ui.timepicker.relOpts.secondsFromNow', { defaultMessage: 'Seconds from now' }), value: 's+' }, - { text: i18n.translate('common.ui.timepicker.relOpts.minutesFromNow', { defaultMessage: 'Minutes from now' }), value: 'm+' }, - { text: i18n.translate('common.ui.timepicker.relOpts.hoursFromNow', { defaultMessage: 'Hours from now' }), value: 'h+' }, - { text: i18n.translate('common.ui.timepicker.relOpts.daysFromNow', { defaultMessage: 'Days from now' }), value: 'd+' }, - { text: i18n.translate('common.ui.timepicker.relOpts.weeksFromNow', { defaultMessage: 'Weeks from now' }), value: 'w+' }, - { text: i18n.translate('common.ui.timepicker.relOpts.monthsFromNow', { defaultMessage: 'Months from now' }), value: 'M+' }, - { text: i18n.translate('common.ui.timepicker.relOpts.yearsFromNow', { defaultMessage: 'Years from now' }), value: 'y+' }, - -]; diff --git a/src/legacy/ui/public/vis/map/service_settings.js b/src/legacy/ui/public/vis/map/service_settings.js index 7ea45665b09d30..d290aacbeb6ad7 100644 --- a/src/legacy/ui/public/vis/map/service_settings.js +++ b/src/legacy/ui/public/vis/map/service_settings.js @@ -23,6 +23,7 @@ import MarkdownIt from 'markdown-it'; import { ORIGIN } from '../../../../core_plugins/tile_map/common/origin'; import { EMSClient } from '../../../../core_plugins/tile_map/common/ems_client'; import { i18n } from '@kbn/i18n'; +import 'angular-sanitize'; const markdownIt = new MarkdownIt({ html: false, @@ -31,7 +32,7 @@ const markdownIt = new MarkdownIt({ const TMS_IN_YML_ID = 'TMS in config/kibana.yml'; -uiModules.get('kibana') +uiModules.get('kibana', ['ngSanitize']) .service('serviceSettings', function ($sanitize, mapConfig, tilemapsConfig, kbnVersion) { const attributionFromConfig = $sanitize(markdownIt.render(tilemapsConfig.deprecated.config.options.attribution || '')); diff --git a/test/api_integration/apis/ui_metric/ui_metric.js b/test/api_integration/apis/ui_metric/ui_metric.js index e841176025bb5c..efa6be47b50c9b 100644 --- a/test/api_integration/apis/ui_metric/ui_metric.js +++ b/test/api_integration/apis/ui_metric/ui_metric.js @@ -18,16 +18,33 @@ */ import expect from '@kbn/expect'; +import { ReportManager } from '@kbn/analytics'; export default function ({ getService }) { const supertest = getService('supertest'); const es = getService('es'); + const createMetric = (eventName) => ({ + key: ReportManager.createMetricKey({ appName: 'myApp', type: 'click', eventName }), + eventName, + appName: 'myApp', + type: 'click', + stats: { sum: 1, avg: 1, min: 1, max: 1 }, + }); + describe('ui_metric API', () => { + const uiStatsMetric = createMetric('myEvent'); + const report = { + uiStatsMetrics: { + [uiStatsMetric.key]: uiStatsMetric, + } + }; it('increments the count field in the document defined by the {app}/{action_type} path', async () => { await supertest - .post('/api/ui_metric/myApp/myAction') + .post('/api/telemetry/report') .set('kbn-xsrf', 'kibana') + .set('content-type', 'application/json') + .send({ report }) .expect(200); return es.search({ @@ -35,14 +52,24 @@ export default function ({ getService }) { q: 'type:user-action', }).then(response => { const ids = response.hits.hits.map(({ _id }) => _id); - expect(ids.includes('user-action:myApp:myAction')); + expect(ids.includes('user-action:myApp:myEvent')); }); }); - it('supports comma-delimited action types', async () => { + it('supports multiple events', async () => { + const uiStatsMetric1 = createMetric('myEvent1'); + const uiStatsMetric2 = createMetric('myEvent2'); + const report = { + uiStatsMetrics: { + [uiStatsMetric1.key]: uiStatsMetric1, + [uiStatsMetric2.key]: uiStatsMetric2, + } + }; await supertest - .post('/api/ui_metric/myApp/myAction1,myAction2') + .post('/api/telemetry/report') .set('kbn-xsrf', 'kibana') + .set('content-type', 'application/json') + .send({ report }) .expect(200); return es.search({ @@ -50,8 +77,8 @@ export default function ({ getService }) { q: 'type:user-action', }).then(response => { const ids = response.hits.hits.map(({ _id }) => _id); - expect(ids.includes('user-action:myApp:myAction1')); - expect(ids.includes('user-action:myApp:myAction2')); + expect(ids.includes('user-action:myApp:myEvent1')); + expect(ids.includes('user-action:myApp:myEvent2')); }); }); }); diff --git a/test/functional/apps/dashboard/dashboard_filtering.js b/test/functional/apps/dashboard/dashboard_filtering.js index af865226465681..fb32de7832b4d3 100644 --- a/test/functional/apps/dashboard/dashboard_filtering.js +++ b/test/functional/apps/dashboard/dashboard_filtering.js @@ -217,8 +217,7 @@ export default function ({ getService, getPageObjects }) { await dashboardExpect.tsvbMarkdownWithValuesExists(['7,209.286']); }); - // FLAKY: https://github.com/elastic/kibana/issues/41087 - it.skip('saved searches', async () => { + it('saved searches', async () => { await dashboardExpect.savedSearchRowCount(1); }); diff --git a/test/functional/apps/dashboard/embed_mode.js b/test/functional/apps/dashboard/embed_mode.js index 4263abc3acdd88..24aa70523c8adc 100644 --- a/test/functional/apps/dashboard/embed_mode.js +++ b/test/functional/apps/dashboard/embed_mode.js @@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }) { }); it('hides the chrome', async () => { - let isChromeVisible = await PageObjects.common.isChromeVisible(); + const isChromeVisible = await PageObjects.common.isChromeVisible(); expect(isChromeVisible).to.be(true); const currentUrl = await browser.getCurrentUrl(); @@ -40,8 +40,8 @@ export default function ({ getService, getPageObjects }) { await browser.get(newUrl.toString(), useTimeStamp); await retry.try(async () => { - isChromeVisible = await PageObjects.common.isChromeVisible(); - expect(isChromeVisible).to.be(false); + const isChromeHidden = await PageObjects.common.isChromeHidden(); + expect(isChromeHidden).to.be(true); }); }); diff --git a/test/functional/apps/dashboard/full_screen_mode.js b/test/functional/apps/dashboard/full_screen_mode.js index 3c5ced7339df38..1f935e9b9aa1b0 100644 --- a/test/functional/apps/dashboard/full_screen_mode.js +++ b/test/functional/apps/dashboard/full_screen_mode.js @@ -43,14 +43,14 @@ export default function ({ getService, getPageObjects }) { }); it('hides the chrome', async () => { - let isChromeVisible = await PageObjects.common.isChromeVisible(); + const isChromeVisible = await PageObjects.common.isChromeVisible(); expect(isChromeVisible).to.be(true); await PageObjects.dashboard.clickFullScreenMode(); await retry.try(async () => { - isChromeVisible = await PageObjects.common.isChromeVisible(); - expect(isChromeVisible).to.be(false); + const isChromeHidden = await PageObjects.common.isChromeHidden(); + expect(isChromeHidden).to.be(true); }); }); diff --git a/test/functional/apps/xpack/index.js b/test/functional/apps/xpack/index.js deleted file mode 100644 index 39765914e79ff2..00000000000000 --- a/test/functional/apps/xpack/index.js +++ /dev/null @@ -1,49 +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. - */ - -export default function ({ getService, getPageObjects }) { - const log = getService('log'); - const PageObjects = getPageObjects(['monitoring', 'settings']); - - describe('dismiss x-pack', function () { - // Putting everything here in 'before' so it doesn't count as a test - // since x-pack may or may not be installed. We just want the banner closed. - before(function () { - log.debug('check for X-Pack welcome, opt-out, and dismiss it'); - - // find class kbnToaster and see if there's any list items in it? - return PageObjects.settings - .navigateTo() - .then(() => { - return PageObjects.monitoring.getToasterContents(); - }) - .then(contents => { - // Welcome to X-Pack! - // Sharing your cluster statistics with us helps us improve. Your data is never shared with anyone. Not interested? Opt out here. - // Dismiss - log.debug('Toast banner contents = ' + contents); - if (contents.includes('X-Pack')) { - return PageObjects.monitoring.clickOptOut().then(() => { - return PageObjects.monitoring.dismissWelcome(); - }); - } - }); - }); - }); -} diff --git a/test/functional/config.js b/test/functional/config.js index 1835808763ae18..e78cee72d50a00 100644 --- a/test/functional/config.js +++ b/test/functional/config.js @@ -35,7 +35,6 @@ export default async function ({ readConfigFile }) { require.resolve('./apps/status_page'), require.resolve('./apps/timelion'), require.resolve('./apps/visualize'), - require.resolve('./apps/xpack'), ], pageObjects, services, diff --git a/test/functional/page_objects/common_page.js b/test/functional/page_objects/common_page.js index 11f44ae6442cb3..651d82608961a4 100644 --- a/test/functional/page_objects/common_page.js +++ b/test/functional/page_objects/common_page.js @@ -336,6 +336,12 @@ export function CommonPageProvider({ getService, getPageObjects }) { return globalNavShown && topNavShown; } + async isChromeHidden() { + const globalNavShown = await globalNav.exists(); + const topNavShown = await testSubjects.exists('top-nav'); + return !globalNavShown && !topNavShown; + } + async waitForTopNavToBeVisible() { await retry.try(async () => { const isNavVisible = await testSubjects.exists('top-nav'); diff --git a/test/functional/page_objects/header_page.js b/test/functional/page_objects/header_page.js index b3dd6f17b45c34..b92e058a2b7475 100644 --- a/test/functional/page_objects/header_page.js +++ b/test/functional/page_objects/header_page.js @@ -21,7 +21,6 @@ export function HeaderPageProvider({ getService, getPageObjects }) { const config = getService('config'); const log = getService('log'); const retry = getService('retry'); - const find = getService('find'); const testSubjects = getService('testSubjects'); const appsMenu = getService('appsMenu'); const globalNav = getService('globalNav'); @@ -65,21 +64,6 @@ export function HeaderPageProvider({ getService, getPageObjects }) { await this.awaitGlobalLoadingIndicatorHidden(); } - async getToastMessage(findTimeout = defaultFindTimeout) { - const toastMessage = await find.displayedByCssSelector( - 'kbn-truncated.kbnToast__message', - findTimeout - ); - const messageText = await toastMessage.getVisibleText(); - log.debug(`getToastMessage: ${messageText}`); - return messageText; - } - - async clickToastOK() { - log.debug('clickToastOK'); - await find.clickByCssSelector('button[ng-if="notif.accept"]'); - } - async waitUntilLoadingHasFinished() { try { await this.isGlobalLoadingIndicatorVisible(); diff --git a/test/functional/page_objects/monitoring_page.js b/test/functional/page_objects/monitoring_page.js index 8ae8dc88388ff8..7dab9dc3e52b1e 100644 --- a/test/functional/page_objects/monitoring_page.js +++ b/test/functional/page_objects/monitoring_page.js @@ -18,7 +18,6 @@ */ export function MonitoringPageProvider({ getService }) { - const testSubjects = getService('testSubjects'); const find = getService('find'); class MonitoringPage { @@ -27,15 +26,6 @@ export function MonitoringPageProvider({ getService }) { return await el.getVisibleText(); } - dismissWelcome() { - return testSubjects.click('notifierDismissButton'); - } - - async getToasterContents() { - const el = await find.byCssSelector('div.kbnToaster__container'); - return await el.getVisibleText(); - } - async clickOptOut() { return find.clickByLinkText('Opt out here'); } diff --git a/test/functional/services/combo_box.ts b/test/functional/services/combo_box.ts index 8bee9ee2863f82..b8267f0b4cbe36 100644 --- a/test/functional/services/combo_box.ts +++ b/test/functional/services/combo_box.ts @@ -18,6 +18,8 @@ */ import { FtrProviderContext } from '../ftr_provider_context'; import { WebElementWrapper } from './lib/web_element_wrapper'; +// @ts-ignore not supported yet +import { scrollIntoViewIfNecessary } from './lib/web_element_wrapper/scroll_into_view_if_necessary'; export function ComboBoxProvider({ getService, getPageObjects }: FtrProviderContext) { const config = getService('config'); @@ -58,6 +60,7 @@ export function ComboBoxProvider({ getService, getPageObjects }: FtrProviderCont return; } + comboBoxElement.scrollIntoViewIfNecessary(); await this._filterOptionsList(comboBoxElement, value); await this.openOptionsList(comboBoxElement); diff --git a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts index 33aa36a38293d6..2ae4616283205c 100644 --- a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts +++ b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts @@ -205,6 +205,18 @@ export class WebElementWrapper { }); } + /** + * Focuses this element. + * + * @return {Promise} + */ + public async focus() { + await this.retryCall(async function focus(wrapper) { + await wrapper.scrollIntoViewIfNecessary(); + await wrapper.driver.executeScript(`arguments[0].focus()`, wrapper._webElement); + }); + } + /** * Clear the value of this element. This command has no effect if the underlying DOM element * is neither a text INPUT element nor a TEXTAREA element. diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json index 98ea93d5b984ee..090d1d6adc60fa 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "12.3.1", + "@elastic/eui": "12.4.0", "react": "^16.8.0", "react-dom": "^16.8.0" } diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json index 5f394962dc478a..62a3c4f1df77dc 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "12.3.1", + "@elastic/eui": "12.4.0", "react": "^16.8.0" } } diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json index 093d9cd3df803b..68882f5cc0889d 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "12.3.1", + "@elastic/eui": "12.4.0", "react": "^16.8.0" }, "scripts": { diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json index a52d76a4069329..8d603914b2734f 100644 --- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json +++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "12.3.1", + "@elastic/eui": "12.4.0", "react": "^16.8.0" }, "scripts": { diff --git a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json b/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json index 2478bbb90609d7..aea4359ef0fc5a 100644 --- a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json +++ b/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "12.3.1", + "@elastic/eui": "12.4.0", "react": "^16.8.0", "react-dom": "^16.8.0" } diff --git a/x-pack/index.js b/x-pack/index.js index c035d13ceaebcc..9b62fc18616f78 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -26,7 +26,6 @@ import { indexManagement } from './legacy/plugins/index_management'; import { indexLifecycleManagement } from './legacy/plugins/index_lifecycle_management'; import { consoleExtensions } from './legacy/plugins/console_extensions'; import { spaces } from './legacy/plugins/spaces'; -import { notifications } from './legacy/plugins/notifications'; import { kueryAutocomplete } from './legacy/plugins/kuery_autocomplete'; import { canvas } from './legacy/plugins/canvas'; import { infra } from './legacy/plugins/infra'; @@ -70,7 +69,6 @@ module.exports = function (kibana) { cloud(kibana), indexManagement(kibana), consoleExtensions(kibana), - notifications(kibana), indexLifecycleManagement(kibana), kueryAutocomplete(kibana), infra(kibana), diff --git a/x-pack/legacy/common/eui_draggable/index.d.ts b/x-pack/legacy/common/eui_draggable/index.d.ts new file mode 100644 index 00000000000000..a85da7a69534cb --- /dev/null +++ b/x-pack/legacy/common/eui_draggable/index.d.ts @@ -0,0 +1,17 @@ +/* + * 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 { EuiDraggable, EuiDragDropContext } from '@elastic/eui'; + +type PropsOf = T extends React.ComponentType ? ComponentProps : never; +type FirstArgumentOf = Func extends ((arg1: infer FirstArgument, ...rest: any[]) => any) + ? FirstArgument + : never; +export type DragHandleProps = FirstArgumentOf< + Exclude['children'], React.ReactElement> +>['dragHandleProps']; +export type DropResult = FirstArgumentOf['onDragEnd']>; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx index f18d45198f8493..e0b8b40e3dcf42 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx @@ -53,6 +53,14 @@ interface Props { readonly transaction: Transaction; } +interface InfraConfigItem { + icon: string; + label: string; + condition?: boolean; + path: string; + query: Record; +} + export const TransactionActionMenu: FunctionComponent = ( props: Props ) => { @@ -69,14 +77,14 @@ export const TransactionActionMenu: FunctionComponent = ( const time = Math.round(transaction.timestamp.us / 1000); const infraMetricsQuery = getInfraMetricsQuery(transaction); - const infraItems = [ + const infraConfigItems: InfraConfigItem[] = [ { icon: 'loggingApp', label: i18n.translate( 'xpack.apm.transactionActionMenu.showPodLogsLinkLabel', { defaultMessage: 'Show pod logs' } ), - condition: podId, + condition: !!podId, path: `/link-to/pod-logs/${podId}`, query: { time } }, @@ -86,7 +94,7 @@ export const TransactionActionMenu: FunctionComponent = ( 'xpack.apm.transactionActionMenu.showContainerLogsLinkLabel', { defaultMessage: 'Show container logs' } ), - condition: containerId, + condition: !!containerId, path: `/link-to/container-logs/${containerId}`, query: { time } }, @@ -96,7 +104,7 @@ export const TransactionActionMenu: FunctionComponent = ( 'xpack.apm.transactionActionMenu.showHostLogsLinkLabel', { defaultMessage: 'Show host logs' } ), - condition: hostName, + condition: !!hostName, path: `/link-to/host-logs/${hostName}`, query: { time } }, @@ -107,7 +115,7 @@ export const TransactionActionMenu: FunctionComponent = ( { defaultMessage: 'Show trace logs' } ), condition: true, - hash: `/link-to/logs`, + path: `/link-to/logs`, query: { time, filter: `trace.id:${transaction.trace.id}` } }, { @@ -116,7 +124,7 @@ export const TransactionActionMenu: FunctionComponent = ( 'xpack.apm.transactionActionMenu.showPodMetricsLinkLabel', { defaultMessage: 'Show pod metrics' } ), - condition: podId, + condition: !!podId, path: `/link-to/pod-detail/${podId}`, query: infraMetricsQuery }, @@ -126,7 +134,7 @@ export const TransactionActionMenu: FunctionComponent = ( 'xpack.apm.transactionActionMenu.showContainerMetricsLinkLabel', { defaultMessage: 'Show container metrics' } ), - condition: containerId, + condition: !!containerId, path: `/link-to/container-detail/${containerId}`, query: infraMetricsQuery }, @@ -136,20 +144,24 @@ export const TransactionActionMenu: FunctionComponent = ( 'xpack.apm.transactionActionMenu.showHostMetricsLinkLabel', { defaultMessage: 'Show host metrics' } ), - condition: hostName, + condition: !!hostName, path: `/link-to/host-detail/${hostName}`, query: infraMetricsQuery } - ].map(({ icon, label, condition, path, query }, index) => ({ - icon, - key: `infra-link-${index}`, - child: ( - - {label} - - ), - condition - })); + ]; + + const infraItems = infraConfigItems.map( + ({ icon, label, condition, path, query }, index) => ({ + icon, + key: `infra-link-${index}`, + child: ( + + {label} + + ), + condition + }) + ); const uptimeLink = url.format({ pathname: chrome.addBasePath('/app/uptime'), @@ -158,7 +170,7 @@ export const TransactionActionMenu: FunctionComponent = ( { dateRangeStart: urlParams.rangeFrom, dateRangeEnd: urlParams.rangeTo, - search: `url.domain:${idx(transaction, t => t.url.domain)}` + search: `url.domain:"${idx(transaction, t => t.url.domain)}"` }, (val: string) => !!val ) diff --git a/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/__tests__/workpad_telemetry.test.tsx b/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/__tests__/workpad_telemetry.test.tsx index db1427d6a7201b..b8779e7d44fcfb 100644 --- a/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/__tests__/workpad_telemetry.test.tsx +++ b/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/__tests__/workpad_telemetry.test.tsx @@ -11,6 +11,7 @@ import { WorkpadLoadedMetric, WorkpadLoadedWithErrorsMetric, } from '../workpad_telemetry'; +import { METRIC_TYPE } from '../../../../lib/ui_metric'; const trackMetric = jest.fn(); const Component = withUnconnectedElementsLoadedTelemetry(() =>
, trackMetric); @@ -83,7 +84,7 @@ describe('Elements Loaded Telemetry', () => { /> ); - expect(trackMetric).toBeCalledWith(WorkpadLoadedMetric); + expect(trackMetric).toBeCalledWith(METRIC_TYPE.LOADED, WorkpadLoadedMetric); }); it('only tracks loaded once', () => { @@ -154,7 +155,10 @@ describe('Elements Loaded Telemetry', () => { /> ); - expect(trackMetric).toBeCalledWith([WorkpadLoadedMetric, WorkpadLoadedWithErrorsMetric]); + expect(trackMetric).toBeCalledWith(METRIC_TYPE.LOADED, [ + WorkpadLoadedMetric, + WorkpadLoadedWithErrorsMetric, + ]); }); it('tracks when the workpad changes and is loaded', () => { @@ -198,7 +202,7 @@ describe('Elements Loaded Telemetry', () => { /> ); - expect(trackMetric).toBeCalledWith(WorkpadLoadedMetric); + expect(trackMetric).toBeCalledWith(METRIC_TYPE.LOADED, WorkpadLoadedMetric); }); it('does not track if workpad has no elements', () => { diff --git a/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.tsx b/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.tsx index 59375d3d0ef009..720f1726c1e3c1 100644 --- a/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.tsx +++ b/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.tsx @@ -7,7 +7,7 @@ import React, { useState, useEffect } from 'react'; import { connect } from 'react-redux'; // @ts-ignore: Local Untyped -import { trackCanvasUiMetric } from '../../../lib/ui_metric'; +import { trackCanvasUiMetric, METRIC_TYPE } from '../../../lib/ui_metric'; // @ts-ignore: Local Untyped import { getElementCounts } from '../../../state/selectors/workpad'; // @ts-ignore: Local Untyped @@ -79,7 +79,7 @@ function areAllElementsInResolvedArgs(workpad: Workpad, resolvedArgs: ResolvedAr export const withUnconnectedElementsLoadedTelemetry = function

( Component: React.ComponentType

, - trackMetric: (metric: string | string[]) => void = trackCanvasUiMetric + trackMetric = trackCanvasUiMetric ): React.SFC

{ return function ElementsLoadedTelemetry( props: P & ElementsLoadedTelemetryProps @@ -117,11 +117,10 @@ export const withUnconnectedElementsLoadedTelemetry = function

resolvedArgsAreForWorkpad ) { if (telemetryElementCounts.error > 0) { - trackMetric([WorkpadLoadedMetric, WorkpadLoadedWithErrorsMetric]); + trackMetric(METRIC_TYPE.LOADED, [WorkpadLoadedMetric, WorkpadLoadedWithErrorsMetric]); } else { - trackMetric(WorkpadLoadedMetric); + trackMetric(METRIC_TYPE.LOADED, WorkpadLoadedMetric); } - setHasReported(true); } }); diff --git a/x-pack/legacy/plugins/canvas/public/components/element_types/index.js b/x-pack/legacy/plugins/canvas/public/components/element_types/index.js index 557e1e3883c542..20d19d2fcbd828 100644 --- a/x-pack/legacy/plugins/canvas/public/components/element_types/index.js +++ b/x-pack/legacy/plugins/canvas/public/components/element_types/index.js @@ -15,7 +15,7 @@ import { notify } from '../../lib/notify'; import { selectToplevelNodes } from '../../state/actions/transient'; import { insertNodes, addElement } from '../../state/actions/elements'; import { getSelectedPage } from '../../state/selectors/workpad'; -import { trackCanvasUiMetric } from '../../lib/ui_metric'; +import { trackCanvasUiMetric, METRIC_TYPE } from '../../lib/ui_metric'; import { ElementTypes as Component } from './element_types'; const customElementAdded = 'elements-custom-added'; @@ -51,7 +51,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { selectToplevelNodes(clonedNodes); // then select the cloned node(s) } onClose(); - trackCanvasUiMetric(customElementAdded); + trackCanvasUiMetric(METRIC_TYPE.LOADED, customElementAdded); }, // custom element search findCustomElements: async text => { diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/fullscreen_control/index.js b/x-pack/legacy/plugins/canvas/public/components/workpad_header/fullscreen_control/index.js index aba89051277292..cf2e80cee03bf5 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/fullscreen_control/index.js +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/fullscreen_control/index.js @@ -16,7 +16,7 @@ import { getPages, getWorkpad, } from '../../../state/selectors/workpad'; -import { trackCanvasUiMetric } from '../../../lib/ui_metric'; +import { trackCanvasUiMetric, METRIC_TYPE } from '../../../lib/ui_metric'; import { LAUNCHED_FULLSCREEN, LAUNCHED_FULLSCREEN_AUTOPLAY, @@ -54,6 +54,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { if (value === true) { trackCanvasUiMetric( + METRIC_TYPE.COUNT, stateProps.autoplayEnabled ? [LAUNCHED_FULLSCREEN, LAUNCHED_FULLSCREEN_AUTOPLAY] : LAUNCHED_FULLSCREEN diff --git a/x-pack/legacy/plugins/canvas/public/lib/ui_metric.js b/x-pack/legacy/plugins/canvas/public/lib/ui_metric.ts similarity index 53% rename from x-pack/legacy/plugins/canvas/public/lib/ui_metric.js rename to x-pack/legacy/plugins/canvas/public/lib/ui_metric.ts index 57ec5b03356c92..33976a147df46c 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/ui_metric.js +++ b/x-pack/legacy/plugins/canvas/public/lib/ui_metric.ts @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { trackUiMetric } from '../../../../../../src/legacy/core_plugins/ui_metric/public'; +import { + createUiStatsReporter, + METRIC_TYPE, +} from '../../../../../../src/legacy/core_plugins/ui_metric/public'; -const APP = 'canvas'; -export const trackCanvasUiMetric = uiMetrics => { - trackUiMetric(APP, uiMetrics); -}; +export const trackCanvasUiMetric = createUiStatsReporter('canvas'); +export { METRIC_TYPE }; diff --git a/x-pack/legacy/plugins/code/model/commit.ts b/x-pack/legacy/plugins/code/model/commit.ts index 595d50e85da2af..f4d613d5096240 100644 --- a/x-pack/legacy/plugins/code/model/commit.ts +++ b/x-pack/legacy/plugins/code/model/commit.ts @@ -11,6 +11,7 @@ export interface CommitInfo { author: string; id: string; parents: string[]; + treeId: string; } export interface ReferenceInfo { diff --git a/x-pack/legacy/plugins/code/public/components/diff_page/diff.tsx b/x-pack/legacy/plugins/code/public/components/diff_page/diff.tsx index ee85a57becd533..5aa840fdbc7826 100644 --- a/x-pack/legacy/plugins/code/public/components/diff_page/diff.tsx +++ b/x-pack/legacy/plugins/code/public/components/diff_page/diff.tsx @@ -58,11 +58,18 @@ const TopBarContainer = styled.div` justify-content: space-between; `; -const Accordion = styled(EuiAccordion)` - border: ${theme.euiBorderThick}; - border-radius: ${theme.euiSizeS}; - margin-bottom: ${theme.euiSize}; -`; +// @types/styled-components@3.0.1 does not yet support `defaultProps`, which EuiAccordion uses +// Ref: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/31903 +// const Accordion = styled(EuiAccordion)` +// border: ${theme.euiBorderThick}; +// border-radius: ${theme.euiSizeS}; +// margin-bottom: ${theme.euiSize}; +// `; +const accordionStyles = { + border: theme.euiBorderThick, + borderRadius: theme.euiSizeS, + marginBottom: theme.euiSize, +}; const Icon = styled(EuiIcon)` margin-right: ${theme.euiSizeS}; @@ -103,7 +110,8 @@ const onClick = (e: MouseEvent) => { }; const Difference = (props: { fileDiff: FileDiff; repoUri: string; revision: string }) => ( - - + ); export class DiffPage extends React.Component { diff --git a/x-pack/legacy/plugins/code/public/components/editor/editor.tsx b/x-pack/legacy/plugins/code/public/components/editor/editor.tsx index 10b3f1954d3547..b186f3b7896dd8 100644 --- a/x-pack/legacy/plugins/code/public/components/editor/editor.tsx +++ b/x-pack/legacy/plugins/code/public/components/editor/editor.tsx @@ -199,6 +199,12 @@ export class EditorComponent extends React.Component { private async loadText(text: string, repo: string, file: string, lang: string, revision: string) { if (this.monaco) { + try { + await monaco.editor.colorize(text, lang, {}); + } catch (e) { + // workaround a upstream issue: https://github.com/microsoft/monaco-editor/issues/134 + lang = 'text'; + } this.editor = await this.monaco.loadFile(repo, file, text, lang, revision); this.registerGutterClickHandler(); } diff --git a/x-pack/legacy/plugins/code/public/components/status_indicator/status_indicator.tsx b/x-pack/legacy/plugins/code/public/components/status_indicator/status_indicator.tsx index 3ebcff578e2977..5c8ab208eb5850 100644 --- a/x-pack/legacy/plugins/code/public/components/status_indicator/status_indicator.tsx +++ b/x-pack/legacy/plugins/code/public/components/status_indicator/status_indicator.tsx @@ -129,7 +129,11 @@ export class StatusIndicatorComponent extends React.Component { return install it here; case CTA.SWITCH_TO_HEAD: const { uri, path } = this.props.currentStatusPath!; - return switch to HEAD; + const headUrl = path + ? `/${uri}/${this.props.pathType}/HEAD/${path}` + : `/${uri}/${this.props.pathType}/HEAD/`; + + return switch to HEAD; } } } diff --git a/x-pack/legacy/plugins/code/public/sagas/status.ts b/x-pack/legacy/plugins/code/public/sagas/status.ts index b6cd1e00864ca0..84f7840a0ca092 100644 --- a/x-pack/legacy/plugins/code/public/sagas/status.ts +++ b/x-pack/legacy/plugins/code/public/sagas/status.ts @@ -98,13 +98,15 @@ function* startPollingStatus(location: FetchFilePayload) { while (isEqual(location, currentStatusPath)) { const previousStatus: StatusReport = yield select(statusSelector); const newStatus: StatusReport = yield call(fetchStatus, location); - yield call(compareStatus, previousStatus, newStatus); - const delayMs = - newStatus.langServerStatus === RepoFileStatus.LANG_SERVER_IS_INITIALIZING - ? STATUS_POLLING_FREQ_HIGH_MS - : STATUS_POLLING_FREQ_LOW_MS; + let delayMs = STATUS_POLLING_FREQ_LOW_MS; + if (newStatus) { + yield call(compareStatus, previousStatus, newStatus); + if (newStatus.langServerStatus === RepoFileStatus.LANG_SERVER_IS_INITIALIZING) { + delayMs = STATUS_POLLING_FREQ_HIGH_MS; + } + currentStatusPath = yield select((state: RootState) => state.status.currentStatusPath); + } yield call(delay, delayMs); - currentStatusPath = yield select((state: RootState) => state.status.currentStatusPath); } } diff --git a/x-pack/legacy/plugins/code/server/git_operations.ts b/x-pack/legacy/plugins/code/server/git_operations.ts index 49d6aabd422a35..d2adffbccaab93 100644 --- a/x-pack/legacy/plugins/code/server/git_operations.ts +++ b/x-pack/legacy/plugins/code/server/git_operations.ts @@ -13,8 +13,6 @@ import { Error as NodeGitError, Oid, Repository, - Tree, - TreeEntry, } from '@elastic/nodegit'; import Boom from 'boom'; import LruCache from 'lru-cache'; @@ -23,10 +21,9 @@ import * as fs from 'fs'; import * as isogit from 'isomorphic-git'; import { CommitDescription, TreeDescription } from 'isomorphic-git'; import { isBinaryFileSync } from 'isbinaryfile'; - import { GitBlame } from '../common/git_blame'; import { CommitDiff, Diff, DiffKind } from '../common/git_diff'; -import { FileTree, FileTreeItemType, RepositoryUri, sortFileTree } from '../model'; +import { FileTree, FileTreeItemType, RepositoryUri } from '../model'; import { CommitInfo, ReferenceInfo, ReferenceType } from '../model/commit'; import { detectLanguage } from './utils/detect_language'; @@ -59,34 +56,22 @@ async function checkExists(func: () => Promise, message: string): Promise< return result; } -function entry2Tree(entry: TreeEntry): FileTree { - let type: FileTreeItemType; - switch (entry.filemode()) { - case TreeEntry.FILEMODE.LINK: - type = FileTreeItemType.Link; - break; - case TreeEntry.FILEMODE.COMMIT: - type = FileTreeItemType.Submodule; - break; - case TreeEntry.FILEMODE.TREE: - type = FileTreeItemType.Directory; - break; - case TreeEntry.FILEMODE.BLOB: - case TreeEntry.FILEMODE.EXECUTABLE: - type = FileTreeItemType.File; - break; - default: - // @ts-ignore - throw new Error('unreadable file'); - } +function entry2Tree(entry: isogit.TreeEntry, prefixPath: string = ''): FileTree { + const type: FileTreeItemType = GitOperations.mode2type(entry.mode); + const { path, oid } = entry; return { - name: entry.name(), - path: entry.path(), - sha1: entry.sha(), + name: path, + path: prefixPath ? prefixPath + '/' + path : path, + sha1: oid, type, }; } +interface Tree { + entries: isogit.TreeEntry[]; + gitdir: string; + oid: string; +} export class GitOperations { private REPO_LRU_CACHE_SIZE = 16; private REPO_MAX_AGE_MS = 60 * 60 * 1000; // 1 hour; @@ -221,6 +206,18 @@ export class GitOperations { throw new Error('invalid path'); } } + + private static async isTextFile(gitdir: string, entry: isogit.TreeEntry) { + if (entry.type === 'blob') { + const type = GitOperations.mode2type(entry.mode); + if (type === FileTreeItemType.File) { + const blob = await isogit.readObject({ gitdir, oid: entry.oid, format: 'content' }); + return !isBinaryFileSync(blob.object as Buffer); + } + } + return false; + } + public async countRepoFiles(uri: RepositoryUri, revision: string): Promise { let count = 0; const commit = await this.getCommitOr404(uri, revision); @@ -229,19 +226,12 @@ export class GitOperations { const treeId = (commitObject.object as CommitDescription).tree; async function walk(oid: string) { - const { object } = await isogit.readObject({ gitdir, oid }); - const tree = object as TreeDescription; + const tree = await GitOperations.readTree(gitdir, oid); for (const entry of tree.entries) { if (entry.type === 'tree') { await walk(entry.oid); - } else if (entry.type === 'blob') { - const type = GitOperations.mode2type(entry.mode); - if (type === FileTreeItemType.File) { - const blob = await isogit.readObject({ gitdir, oid: entry.oid, format: 'content' }); - if (!isBinaryFileSync(blob.object as Buffer)) { - count++; - } - } + } else if (await GitOperations.isTextFile(gitdir, entry)) { + count++; } } } @@ -254,38 +244,48 @@ export class GitOperations { uri: RepositoryUri, revision: string ): Promise> { + const commit = await this.getCommitOr404(uri, revision); + const gitdir = this.repoDir(uri); + const commitObject = await isogit.readObject({ gitdir, oid: commit.id }); + const treeId = (commitObject.object as CommitDescription).tree; async function* walk(oid: string, prefix: string = ''): AsyncIterableIterator { - const { object } = await isogit.readObject({ gitdir, oid }); - const tree = object as TreeDescription; + const tree = await GitOperations.readTree(gitdir, oid); for (const entry of tree.entries) { const path = prefix ? `${prefix}/${entry.path}` : entry.path; if (entry.type === 'tree') { yield* walk(entry.oid, path); - } else if (entry.type === 'blob') { + } else if (await GitOperations.isTextFile(gitdir, entry)) { const type = GitOperations.mode2type(entry.mode); - if (type === FileTreeItemType.File) { - const blob = await isogit.readObject({ gitdir, oid: entry.oid, format: 'content' }); - if (!isBinaryFileSync(blob.object as Buffer)) { - yield { - name: entry.path, - type, - path, - repoUri: uri, - sha1: entry.oid, - } as FileTree; - } - } + yield { + name: entry.path, + type, + path, + repoUri: uri, + sha1: entry.oid, + } as FileTree; } } } - const commit = await this.getCommitOr404(uri, revision); - const gitdir = this.repoDir(uri); - const commitObject = await isogit.readObject({ gitdir, oid: commit.id }); - const treeId = (commitObject.object as CommitDescription).tree; return await walk(treeId); } + private static async readTree(gitdir: string, oid: string): Promise { + const { type, object } = await isogit.readObject({ gitdir, oid }); + if (type === 'commit') { + return await this.readTree(gitdir, (object as CommitDescription).tree); + } else if (type === 'tree') { + const tree = object as TreeDescription; + return { + entries: tree.entries, + gitdir, + oid, + } as Tree; + } else { + throw new Error(`${oid} is not a tree`); + } + } + static mode2type(mode: string): FileTreeItemType { switch (mode) { case '100755': @@ -311,7 +311,6 @@ export class GitOperations { * @param skip pagination parameter, skip how many nodes in each children. * @param limit pagination parameter, limit the number of node's children. * @param resolveParents whether the return value should always start from root - * @param childrenDepth how depth should the children walk. * @param flatten */ public async fileTree( @@ -321,54 +320,111 @@ export class GitOperations { skip: number = 0, limit: number = DEFAULT_TREE_CHILDREN_LIMIT, resolveParents: boolean = false, - childrenDepth: number = 1, flatten: boolean = false ): Promise { - const commit = await this.getCommit(uri, revision); - const tree = await commit.getTree(); - if (path === '/') { - path = ''; + const commit = await this.getCommitOr404(uri, revision); + const gitdir = this.repoDir(uri); + if (path.startsWith('/')) { + path = path.slice(1); } - const getRoot = async () => { - return await this.walkTree( - { - name: '', - path: '', - type: FileTreeItemType.Directory, - }, - tree, - [], - skip, - limit, - childrenDepth, - flatten - ); - }; - if (path) { - if (resolveParents) { - return this.walkTree( - await getRoot(), - tree, - path.split('/'), - skip, - limit, - childrenDepth, - flatten - ); - } else { - const entry = await checkExists( - () => Promise.resolve(tree.getEntry(path)), - `path ${path} does not exists.` + if (path.endsWith('/')) { + path = path.slice(0, -1); + } + + function type2item(type: string) { + switch (type) { + case 'blob': + return FileTreeItemType.File; + case 'tree': + return FileTreeItemType.Directory; + case 'commit': + return FileTreeItemType.Submodule; + default: + throw new Error(`unsupported file tree item type ${type}`); + } + } + + if (resolveParents) { + const root: FileTree = { + name: '', + path: '', + type: FileTreeItemType.Directory, + }; + const tree = await GitOperations.readTree(gitdir, commit.treeId); + await this.fillChildren(root, tree, { skip, limit, flatten }); + if (path) { + await this.resolvePath(root, tree, path.split('/'), { skip, limit, flatten }); + } + return root; + } else { + const obj = await isogit.readObject({ gitdir, oid: commit.id, filepath: path }); + const result: FileTree = { + name: path.split('/').pop() || '', + path, + type: type2item(obj.type!), + sha1: obj.oid, + }; + if (result.type === FileTreeItemType.Directory) { + await this.fillChildren( + result, + { + gitdir, + entries: (obj.object as TreeDescription).entries, + oid: obj.oid, + }, + { skip, limit, flatten } ); - if (entry.isDirectory()) { - const tree1 = await entry.getTree(); - return this.walkTree(entry2Tree(entry), tree1, [], skip, limit, childrenDepth, flatten); + } + return result; + } + } + + private async fillChildren( + result: FileTree, + { entries, gitdir }: Tree, + { skip, limit, flatten }: { skip: number; limit: number; flatten: boolean } + ) { + result.childrenCount = entries.length; + result.children = []; + for (const e of entries.slice(skip, Math.min(entries.length, skip + limit))) { + const child = entry2Tree(e, result.path); + result.children.push(child); + if (flatten && child.type === FileTreeItemType.Directory) { + const tree = await GitOperations.readTree(gitdir, e.oid); + if (tree.entries.length === 1) { + await this.fillChildren(child, tree, { skip, limit, flatten }); + } + } + } + } + + private async resolvePath( + result: FileTree, + tree: Tree, + paths: string[], + opt: { skip: number; limit: number; flatten: boolean } + ) { + const [path, ...rest] = paths; + for (const entry of tree.entries) { + if (entry.path === path) { + if (!result.children) { + result.children = []; + } + const child = entry2Tree(entry, result.path); + const idx = result.children.findIndex(i => i.sha1 === entry.oid); + if (idx < 0) { + result.children.push(child); } else { - return entry2Tree(entry); + result.children[idx] = child; + } + if (entry.type === 'tree') { + const subTree = await GitOperations.readTree(tree.gitdir, entry.oid); + await this.fillChildren(child, subTree, opt); + if (rest.length > 0) { + await this.resolvePath(child, subTree, rest, opt); + } } } - } else { - return getRoot(); } } @@ -521,68 +577,6 @@ export class GitOperations { return (await entry.getBlob()).content().toString('utf8'); } - private async walkTree( - fileTree: FileTree, - tree: Tree, - paths: string[], - skip: number, - limit: number, - childrenDepth: number = 1, - flatten: boolean = false - ): Promise { - const [path, ...rest] = paths; - fileTree.childrenCount = tree.entryCount(); - if (!fileTree.children) { - fileTree.children = []; - for (const e of tree.entries().slice(skip, limit)) { - const child = entry2Tree(e); - fileTree.children.push(child); - if (e.isDirectory()) { - const childChildrenCount = (await e.getTree()).entryCount(); - if ((childChildrenCount === 1 && flatten) || childrenDepth > 1) { - await this.walkTree( - child, - await e.getTree(), - [], - skip, - limit, - childrenDepth - 1, - flatten - ); - } - } - } - fileTree.children.sort(sortFileTree); - } - if (path) { - const entry = await checkExists( - () => Promise.resolve(tree.getEntry(path)), - `path ${fileTree.path}/${path} does not exists.` - ); - let child = entry2Tree(entry); - if (entry.isDirectory()) { - child = await this.walkTree( - child, - await entry.getTree(), - rest, - skip, - limit, - childrenDepth, - flatten - ); - } - const idx = fileTree.children.findIndex(c => c.name === entry.name()); - if (idx >= 0) { - // replace the entry in children if found - fileTree.children[idx] = child; - } else { - fileTree.children.push(child); - } - } - - return fileTree; - } - private async findCommit(repo: Repository, oid: string): Promise { try { return repo.getCommit(Oid.fromString(oid)); @@ -675,6 +669,7 @@ export class GitOperations { message: commit.message, updated: new Date(commit.committer.timestamp * 1000), parents: commit.parent, + treeId: commit.tree, } as CommitInfo; } else if (obj.type === 'tag') { const tag = obj.object as isogit.TagDescription; @@ -695,5 +690,6 @@ export function commitInfo(commit: Commit): CommitInfo { committer: commit.committer().name(), id: commit.sha().substr(0, 7), parents: commit.parents().map(oid => oid.toString().substring(0, 7)), + treeId: commit.treeId().tostrS(), }; } diff --git a/x-pack/legacy/plugins/code/server/lsp/lsp_test_runner.ts b/x-pack/legacy/plugins/code/server/lsp/lsp_test_runner.ts index 5190d485bd8811..943fc7bd321343 100644 --- a/x-pack/legacy/plugins/code/server/lsp/lsp_test_runner.ts +++ b/x-pack/legacy/plugins/code/server/lsp/lsp_test_runner.ts @@ -20,7 +20,6 @@ import { TypescriptServerLauncher } from './ts_launcher'; import { GitOperations } from '../git_operations'; import { createTestServerOption } from '../test_utils'; import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; -import { RepositoryUtils } from '../../common/repository_utils'; import { LspRequest } from '../../model'; import { Repo, RequestType } from '../../model/test_config'; @@ -191,18 +190,15 @@ export class LspTestRunner { private async getAllFile() { const gitOperator: GitOperations = new GitOperations(this.repo.path); try { - const fileTree = await gitOperator.fileTree( - '', - '', - 'HEAD', - 0, - Number.MAX_SAFE_INTEGER, - false, - Number.MAX_SAFE_INTEGER - ); - return RepositoryUtils.getAllFiles(fileTree).filter((filePath: string) => { - return filePath.endsWith(this.repo.language); - }); + const result: string[] = []; + const fileIterator = await gitOperator.iterateRepo('', 'HEAD'); + for await (const file of fileIterator) { + const filePath = file.path!; + if (filePath.endsWith(this.repo.language)) { + result.push(filePath); + } + } + return result; } catch (e) { console.error(`get files error: ${e}`); throw e; diff --git a/x-pack/legacy/plugins/code/server/routes/file.ts b/x-pack/legacy/plugins/code/server/routes/file.ts index 75c9fc7cc02aec..e1458a5c288445 100644 --- a/x-pack/legacy/plugins/code/server/routes/file.ts +++ b/x-pack/legacy/plugins/code/server/routes/file.ts @@ -41,7 +41,6 @@ export function fileRoute(server: CodeServerRouter, gitOps: GitOperations) { ? parseInt(queries.limit as string, 10) : DEFAULT_TREE_CHILDREN_LIMIT; const skip = queries.skip ? parseInt(queries.skip as string, 10) : 0; - const depth = queries.depth ? parseInt(queries.depth as string, 10) : 0; const withParents = 'parents' in queries; const flatten = 'flatten' in queries; const repoExist = await repoExists(req, uri); @@ -50,7 +49,7 @@ export function fileRoute(server: CodeServerRouter, gitOps: GitOperations) { } try { - return await gitOps.fileTree(uri, path, revision, skip, limit, withParents, depth, flatten); + return await gitOps.fileTree(uri, path, revision, skip, limit, withParents, flatten); } catch (e) { if (e.isBoom) { return e; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_list.test.js b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_list.test.js index f116e5657280ab..167d0799d8c485 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_list.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_list.test.js @@ -19,10 +19,6 @@ jest.mock('ui/index_patterns', () => { return { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE }; }); -jest.mock('../../../../../../src/legacy/core_plugins/ui_metric/public', () => ({ - trackUiMetric: jest.fn(), -})); - const { setup } = pageHelpers.autoFollowPatternList; describe('', () => { diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_indices_list.test.js b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_indices_list.test.js index 8df920aa064236..f3047206c77ba3 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_indices_list.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_indices_list.test.js @@ -19,10 +19,6 @@ jest.mock('ui/index_patterns', () => { return { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE }; }); -jest.mock('../../../../../../src/legacy/core_plugins/ui_metric/public', () => ({ - trackUiMetric: jest.fn(), -})); - const { setup } = pageHelpers.followerIndexList; describe('', () => { diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/home.test.js b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/home.test.js index a4b559dc24d2f4..cf6ccd80f461f1 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/home.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/home.test.js @@ -28,10 +28,6 @@ jest.mock('ui/index_patterns', () => { return { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE }; }); -jest.mock('../../../../../../src/legacy/core_plugins/ui_metric/public', () => ({ - trackUiMetric: jest.fn(), -})); - const { setup } = pageHelpers.home; describe('', () => { diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js b/x-pack/legacy/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js index 12311f5f6d2dcd..d73fca1d76dcf0 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js @@ -19,7 +19,7 @@ import { import routing from '../../../services/routing'; import { extractQueryParams } from '../../../services/query_params'; -import { trackUiMetric } from '../../../services/track_ui_metric'; +import { trackUiMetric, METRIC_TYPE } from '../../../services/track_ui_metric'; import { API_STATUS, UIM_AUTO_FOLLOW_PATTERN_LIST_LOAD } from '../../../constants'; import { SectionLoading, SectionError, SectionUnauthorized } from '../../../components'; import { AutoFollowPatternTable, DetailPanel } from './components'; @@ -60,7 +60,7 @@ export class AutoFollowPatternList extends PureComponent { componentDidMount() { const { loadAutoFollowPatterns, loadAutoFollowStats, selectAutoFollowPattern, history } = this.props; - trackUiMetric(UIM_AUTO_FOLLOW_PATTERN_LIST_LOAD); + trackUiMetric(METRIC_TYPE.LOADED, UIM_AUTO_FOLLOW_PATTERN_LIST_LOAD); loadAutoFollowPatterns(); loadAutoFollowStats(); diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js b/x-pack/legacy/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js index 8d38d7efec4f6b..dee85c54653b1a 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js @@ -20,7 +20,7 @@ import { import { API_STATUS, UIM_AUTO_FOLLOW_PATTERN_SHOW_DETAILS_CLICK } from '../../../../../constants'; import { AutoFollowPatternDeleteProvider } from '../../../../../components'; import routing from '../../../../../services/routing'; -import { trackUiMetric } from '../../../../../services/track_ui_metric'; +import { trackUiMetric, METRIC_TYPE } from '../../../../../services/track_ui_metric'; export class AutoFollowPatternTable extends PureComponent { static propTypes = { @@ -77,7 +77,7 @@ export class AutoFollowPatternTable extends PureComponent { return ( { - trackUiMetric(UIM_AUTO_FOLLOW_PATTERN_SHOW_DETAILS_CLICK); + trackUiMetric(METRIC_TYPE.CLICK, UIM_AUTO_FOLLOW_PATTERN_SHOW_DETAILS_CLICK); selectAutoFollowPattern(name); }} data-test-subj="autoFollowPatternLink" diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js b/x-pack/legacy/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js index 19fe01c4174231..620dd4e79c7edc 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js @@ -23,7 +23,7 @@ import { FollowerIndexUnfollowProvider } from '../../../../../components'; import routing from '../../../../../services/routing'; -import { trackUiMetric } from '../../../../../services/track_ui_metric'; +import { trackUiMetric, METRIC_TYPE } from '../../../../../services/track_ui_metric'; import { ContextMenu } from '../context_menu'; export class FollowerIndicesTable extends PureComponent { @@ -191,7 +191,7 @@ export class FollowerIndicesTable extends PureComponent { return ( { - trackUiMetric(UIM_FOLLOWER_INDEX_SHOW_DETAILS_CLICK); + trackUiMetric(METRIC_TYPE.CLICK, UIM_FOLLOWER_INDEX_SHOW_DETAILS_CLICK); selectFollowerIndex(name); }} data-test-subj="followerIndexLink" diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js b/x-pack/legacy/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js index 5c593458dd50ba..b78f6399168a2b 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js @@ -19,7 +19,7 @@ import { import routing from '../../../services/routing'; import { extractQueryParams } from '../../../services/query_params'; -import { trackUiMetric } from '../../../services/track_ui_metric'; +import { trackUiMetric, METRIC_TYPE } from '../../../services/track_ui_metric'; import { API_STATUS, UIM_FOLLOWER_INDEX_LIST_LOAD } from '../../../constants'; import { SectionLoading, SectionError, SectionUnauthorized } from '../../../components'; import { FollowerIndicesTable, DetailPanel } from './components'; @@ -58,7 +58,7 @@ export class FollowerIndicesList extends PureComponent { componentDidMount() { const { loadFollowerIndices, selectFollowerIndex, history } = this.props; - trackUiMetric(UIM_FOLLOWER_INDEX_LIST_LOAD); + trackUiMetric(METRIC_TYPE.LOADED, UIM_FOLLOWER_INDEX_LIST_LOAD); loadFollowerIndices(); // Select the pattern in the URL query params diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/app/services/track_ui_metric.js b/x-pack/legacy/plugins/cross_cluster_replication/public/app/services/track_ui_metric.js index 7b2f0d04078621..23314fdd0ef83f 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/app/services/track_ui_metric.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/public/app/services/track_ui_metric.js @@ -4,13 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { trackUiMetric as track } from '../../../../../../../src/legacy/core_plugins/ui_metric/public'; +import { createUiStatsReporter, METRIC_TYPE } from '../../../../../../../src/legacy/core_plugins/ui_metric/public'; import { UIM_APP_NAME } from '../constants'; -export function trackUiMetric(actionType) { - track(UIM_APP_NAME, actionType); -} - +export const trackUiMetric = createUiStatsReporter(UIM_APP_NAME); +export { METRIC_TYPE }; /** * Transparently return provided request Promise, while allowing us to track * a successful completion of the request. @@ -18,7 +16,7 @@ export function trackUiMetric(actionType) { export function trackUserRequest(request, actionType) { // Only track successful actions. return request.then(response => { - trackUiMetric(actionType); + trackUiMetric(METRIC_TYPE.LOADED, actionType); // We return the response immediately without waiting for the tracking request to resolve, // to avoid adding additional latency. return response; diff --git a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js index 89a265d81ed4bb..ca29b5ae5c6040 100644 --- a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js +++ b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js @@ -36,7 +36,6 @@ import 'plugins/kibana/dashboard'; import 'ui/vislib'; import 'ui/agg_response'; import 'ui/agg_types'; -import 'ui/timepicker'; import 'leaflet'; import { npStart } from 'ui/new_platform'; diff --git a/x-pack/legacy/plugins/graph/public/app.js b/x-pack/legacy/plugins/graph/public/app.js index 77f92693b3561f..3f01c73bf62653 100644 --- a/x-pack/legacy/plugins/graph/public/app.js +++ b/x-pack/legacy/plugins/graph/public/app.js @@ -553,7 +553,7 @@ app.controller('graphuiPlugin', function ( //== Drill-down functionality == const defaultKibanaQuery = ',query:(query_string:(analyze_wildcard:!t,query:\'*\'))'; - const drillDownRegex = /\{\{gquery\}\}/; + const drillDownRegex = /\{\{gquery\}\}/g; $scope.checkForKibanaUrl = function () { $scope.suggestTemplateFix = $scope.newUrlTemplate.url === $scope.lastPastedURL && diff --git a/x-pack/legacy/plugins/index_lifecycle_management/public/app.js b/x-pack/legacy/plugins/index_lifecycle_management/public/app.js index d9381d98f17ad8..ddd3ac2f68f8ca 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/public/app.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/public/app.js @@ -12,7 +12,7 @@ import { PolicyTable } from './sections/policy_table'; import { trackUiMetric } from './services'; export const App = () => { - useEffect(() => trackUiMetric(UIM_APP_LOAD), []); + useEffect(() => trackUiMetric('loaded', UIM_APP_LOAD), []); return ( diff --git a/x-pack/legacy/plugins/index_lifecycle_management/public/sections/policy_table/components/policy_table/policy_table.js b/x-pack/legacy/plugins/index_lifecycle_management/public/sections/policy_table/components/policy_table/policy_table.js index 8d8d9700cbc271..25141e5a2fe580 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/public/sections/policy_table/components/policy_table/policy_table.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/public/sections/policy_table/components/policy_table/policy_table.js @@ -178,7 +178,7 @@ export class PolicyTable extends Component { className="policyTable__link" data-test-subj="policyTablePolicyNameLink" href={getPolicyPath(value)} - onClick={() => trackUiMetric(UIM_EDIT_CLICK)} + onClick={() => trackUiMetric('click', UIM_EDIT_CLICK)} > {value} diff --git a/x-pack/legacy/plugins/index_lifecycle_management/public/services/api.js b/x-pack/legacy/plugins/index_lifecycle_management/public/services/api.js index f76a30761d34b6..3819822088e97c 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/public/services/api.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/public/services/api.js @@ -58,34 +58,34 @@ export async function savePolicy(policy, httpClient = getHttpClient()) { export async function deletePolicy(policyName, httpClient = getHttpClient()) { const response = await httpClient.delete(`${apiPrefix}/policies/${encodeURIComponent(policyName)}`); // Only track successful actions. - trackUiMetric(UIM_POLICY_DELETE); + trackUiMetric('count', UIM_POLICY_DELETE); return response.data; } export const retryLifecycleForIndex = async (indexNames, httpClient = getHttpClient()) => { const response = await httpClient.post(`${apiPrefix}/index/retry`, { indexNames }); // Only track successful actions. - trackUiMetric(UIM_INDEX_RETRY_STEP); + trackUiMetric('count', UIM_INDEX_RETRY_STEP); return response.data; }; export const removeLifecycleForIndex = async (indexNames, httpClient = getHttpClient()) => { const response = await httpClient.post(`${apiPrefix}/index/remove`, { indexNames }); // Only track successful actions. - trackUiMetric(UIM_POLICY_DETACH_INDEX); + trackUiMetric('count', UIM_POLICY_DETACH_INDEX); return response.data; }; export const addLifecyclePolicyToIndex = async (body, httpClient = getHttpClient()) => { const response = await httpClient.post(`${apiPrefix}/index/add`, body); // Only track successful actions. - trackUiMetric(UIM_POLICY_ATTACH_INDEX); + trackUiMetric('count', UIM_POLICY_ATTACH_INDEX); return response.data; }; export const addLifecyclePolicyToTemplate = async (body, httpClient = getHttpClient()) => { const response = await httpClient.post(`${apiPrefix}/template`, body); // Only track successful actions. - trackUiMetric(UIM_POLICY_ATTACH_INDEX_TEMPLATE); + trackUiMetric('count', UIM_POLICY_ATTACH_INDEX_TEMPLATE); return response.data; }; diff --git a/x-pack/legacy/plugins/index_lifecycle_management/public/services/ui_metric.js b/x-pack/legacy/plugins/index_lifecycle_management/public/services/ui_metric.js index 8382d16596d8fd..d33d6688f49222 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/public/services/ui_metric.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/public/services/ui_metric.js @@ -5,8 +5,7 @@ */ import { get } from 'lodash'; - -import { trackUiMetric as track } from '../../../../../../src/legacy/core_plugins/ui_metric/public'; +import { createUiStatsReporter } from '../../../../../../src/legacy/core_plugins/ui_metric/public'; import { UIM_APP_NAME, @@ -29,9 +28,7 @@ import { defaultHotPhase, } from '../store/defaults'; -export function trackUiMetric(metricType) { - track(UIM_APP_NAME, metricType); -} +export const trackUiMetric = createUiStatsReporter(UIM_APP_NAME); export function getUiMetricsForPhases(phases) { const phaseUiMetrics = [{ diff --git a/x-pack/legacy/plugins/index_lifecycle_management/public/store/actions/lifecycle.js b/x-pack/legacy/plugins/index_lifecycle_management/public/store/actions/lifecycle.js index 9acb76d0936e12..64d73e4aaeff5e 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/public/store/actions/lifecycle.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/public/store/actions/lifecycle.js @@ -32,7 +32,7 @@ export const saveLifecyclePolicy = (lifecycle, isNew) => async () => { const uiMetrics = getUiMetricsForPhases(lifecycle.phases); uiMetrics.push(isNew ? UIM_POLICY_CREATE : UIM_POLICY_UPDATE); - trackUiMetric(uiMetrics); + trackUiMetric('count', uiMetrics); const message = i18n.translate('xpack.indexLifecycleMgmt.editPolicy.successfulSaveMessage', { diff --git a/x-pack/legacy/plugins/index_management/public/app.js b/x-pack/legacy/plugins/index_management/public/app.js index c911759932e495..8f25183a21bc1a 100644 --- a/x-pack/legacy/plugins/index_management/public/app.js +++ b/x-pack/legacy/plugins/index_management/public/app.js @@ -11,7 +11,7 @@ import { IndexManagementHome } from './sections/home'; import { trackUiMetric } from './services'; export const App = () => { - useEffect(() => trackUiMetric(UIM_APP_LOAD), []); + useEffect(() => trackUiMetric('loaded', UIM_APP_LOAD), []); return ( diff --git a/x-pack/legacy/plugins/index_management/public/sections/home/index_list/index_table/index_table.js b/x-pack/legacy/plugins/index_management/public/sections/home/index_list/index_table/index_table.js index 708cd1eb8867a8..ddd3c14dba6964 100644 --- a/x-pack/legacy/plugins/index_management/public/sections/home/index_list/index_table/index_table.js +++ b/x-pack/legacy/plugins/index_management/public/sections/home/index_list/index_table/index_table.js @@ -220,7 +220,7 @@ export class IndexTable extends Component { className="indTable__link" data-test-subj="indexTableIndexNameLink" onClick={() => { - trackUiMetric(UIM_SHOW_DETAILS_CLICK); + trackUiMetric('click', UIM_SHOW_DETAILS_CLICK); openDetailPanel(value); }} > diff --git a/x-pack/legacy/plugins/index_management/public/sections/home/templates_list/templates_list.tsx b/x-pack/legacy/plugins/index_management/public/sections/home/templates_list/templates_list.tsx index e5b5e9ad281e83..a17bfaf1ce27aa 100644 --- a/x-pack/legacy/plugins/index_management/public/sections/home/templates_list/templates_list.tsx +++ b/x-pack/legacy/plugins/index_management/public/sections/home/templates_list/templates_list.tsx @@ -19,7 +19,7 @@ import { SectionError, SectionLoading } from '../../../components'; import { TemplatesTable } from './templates_table'; import { loadIndexTemplates } from '../../../services/api'; import { Template } from '../../../../common/types'; -import { trackUiMetric } from '../../../services/track_ui_metric'; +import { trackUiMetric, METRIC_TYPE } from '../../../services/track_ui_metric'; import { UIM_TEMPLATE_LIST_LOAD } from '../../../../common/constants'; export const TemplatesList: React.FunctionComponent = () => { @@ -38,7 +38,7 @@ export const TemplatesList: React.FunctionComponent = () => { // Track component loaded useEffect(() => { - trackUiMetric(UIM_TEMPLATE_LIST_LOAD); + trackUiMetric(METRIC_TYPE.LOADED, UIM_TEMPLATE_LIST_LOAD); }, []); if (isLoading) { diff --git a/x-pack/legacy/plugins/index_management/public/services/api.ts b/x-pack/legacy/plugins/index_management/public/services/api.ts index 0b16d02a733b07..3a6c9fdf7b238b 100644 --- a/x-pack/legacy/plugins/index_management/public/services/api.ts +++ b/x-pack/legacy/plugins/index_management/public/services/api.ts @@ -32,7 +32,7 @@ import { import { TAB_SETTINGS, TAB_MAPPING, TAB_STATS } from '../constants'; -import { trackUiMetric } from './track_ui_metric'; +import { trackUiMetric, METRIC_TYPE } from './track_ui_metric'; import { useRequest, sendRequest } from './use_request'; import { Template } from '../../common/types'; @@ -67,8 +67,8 @@ export async function closeIndices(indices: string[]) { }; const response = await httpClient.post(`${apiPrefix}/indices/close`, body); // Only track successful requests. - const actionType = indices.length > 1 ? UIM_INDEX_CLOSE_MANY : UIM_INDEX_CLOSE; - trackUiMetric(actionType); + const eventName = indices.length > 1 ? UIM_INDEX_CLOSE_MANY : UIM_INDEX_CLOSE; + trackUiMetric(METRIC_TYPE.COUNT, eventName); return response.data; } @@ -78,8 +78,8 @@ export async function deleteIndices(indices: string[]) { }; const response = await httpClient.post(`${apiPrefix}/indices/delete`, body); // Only track successful requests. - const actionType = indices.length > 1 ? UIM_INDEX_DELETE_MANY : UIM_INDEX_DELETE; - trackUiMetric(actionType); + const eventName = indices.length > 1 ? UIM_INDEX_DELETE_MANY : UIM_INDEX_DELETE; + trackUiMetric(METRIC_TYPE.COUNT, eventName); return response.data; } @@ -89,8 +89,8 @@ export async function openIndices(indices: string[]) { }; const response = await httpClient.post(`${apiPrefix}/indices/open`, body); // Only track successful requests. - const actionType = indices.length > 1 ? UIM_INDEX_OPEN_MANY : UIM_INDEX_OPEN; - trackUiMetric(actionType); + const eventName = indices.length > 1 ? UIM_INDEX_OPEN_MANY : UIM_INDEX_OPEN; + trackUiMetric(METRIC_TYPE.COUNT, eventName); return response.data; } @@ -100,8 +100,8 @@ export async function refreshIndices(indices: string[]) { }; const response = await httpClient.post(`${apiPrefix}/indices/refresh`, body); // Only track successful requests. - const actionType = indices.length > 1 ? UIM_INDEX_REFRESH_MANY : UIM_INDEX_REFRESH; - trackUiMetric(actionType); + const eventName = indices.length > 1 ? UIM_INDEX_REFRESH_MANY : UIM_INDEX_REFRESH; + trackUiMetric(METRIC_TYPE.COUNT, eventName); return response.data; } @@ -111,8 +111,8 @@ export async function flushIndices(indices: string[]) { }; const response = await httpClient.post(`${apiPrefix}/indices/flush`, body); // Only track successful requests. - const actionType = indices.length > 1 ? UIM_INDEX_FLUSH_MANY : UIM_INDEX_FLUSH; - trackUiMetric(actionType); + const eventName = indices.length > 1 ? UIM_INDEX_FLUSH_MANY : UIM_INDEX_FLUSH; + trackUiMetric(METRIC_TYPE.COUNT, eventName); return response.data; } @@ -123,8 +123,8 @@ export async function forcemergeIndices(indices: string[], maxNumSegments: strin }; const response = await httpClient.post(`${apiPrefix}/indices/forcemerge`, body); // Only track successful requests. - const actionType = indices.length > 1 ? UIM_INDEX_FORCE_MERGE_MANY : UIM_INDEX_FORCE_MERGE; - trackUiMetric(actionType); + const eventName = indices.length > 1 ? UIM_INDEX_FORCE_MERGE_MANY : UIM_INDEX_FORCE_MERGE; + trackUiMetric(METRIC_TYPE.COUNT, eventName); return response.data; } @@ -134,8 +134,8 @@ export async function clearCacheIndices(indices: string[]) { }; const response = await httpClient.post(`${apiPrefix}/indices/clear_cache`, body); // Only track successful requests. - const actionType = indices.length > 1 ? UIM_INDEX_CLEAR_CACHE_MANY : UIM_INDEX_CLEAR_CACHE; - trackUiMetric(actionType); + const eventName = indices.length > 1 ? UIM_INDEX_CLEAR_CACHE_MANY : UIM_INDEX_CLEAR_CACHE; + trackUiMetric(METRIC_TYPE.COUNT, eventName); return response.data; } export async function freezeIndices(indices: string[]) { @@ -144,8 +144,8 @@ export async function freezeIndices(indices: string[]) { }; const response = await httpClient.post(`${apiPrefix}/indices/freeze`, body); // Only track successful requests. - const actionType = indices.length > 1 ? UIM_INDEX_FREEZE_MANY : UIM_INDEX_FREEZE; - trackUiMetric(actionType); + const eventName = indices.length > 1 ? UIM_INDEX_FREEZE_MANY : UIM_INDEX_FREEZE; + trackUiMetric(METRIC_TYPE.COUNT, eventName); return response.data; } export async function unfreezeIndices(indices: string[]) { @@ -154,8 +154,8 @@ export async function unfreezeIndices(indices: string[]) { }; const response = await httpClient.post(`${apiPrefix}/indices/unfreeze`, body); // Only track successful requests. - const actionType = indices.length > 1 ? UIM_INDEX_UNFREEZE_MANY : UIM_INDEX_UNFREEZE; - trackUiMetric(actionType); + const eventName = indices.length > 1 ? UIM_INDEX_UNFREEZE_MANY : UIM_INDEX_UNFREEZE; + trackUiMetric(METRIC_TYPE.COUNT, eventName); return response.data; } @@ -167,7 +167,7 @@ export async function loadIndexSettings(indexName: string) { export async function updateIndexSettings(indexName: string, settings: object) { const response = await httpClient.put(`${apiPrefix}/settings/${indexName}`, settings); // Only track successful requests. - trackUiMetric(UIM_UPDATE_SETTINGS); + trackUiMetric(METRIC_TYPE.COUNT, UIM_UPDATE_SETTINGS); return response; } diff --git a/x-pack/legacy/plugins/index_management/public/services/track_ui_metric.ts b/x-pack/legacy/plugins/index_management/public/services/track_ui_metric.ts index 6378fa0aa413bf..cf5b04d513bc4b 100644 --- a/x-pack/legacy/plugins/index_management/public/services/track_ui_metric.ts +++ b/x-pack/legacy/plugins/index_management/public/services/track_ui_metric.ts @@ -4,9 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { trackUiMetric as track } from '../../../../../../src/legacy/core_plugins/ui_metric/public'; +import { + createUiStatsReporter, + METRIC_TYPE, +} from '../../../../../../src/legacy/core_plugins/ui_metric/public'; import { UIM_APP_NAME } from '../../common/constants'; -export function trackUiMetric(metricType: string) { - track(UIM_APP_NAME, metricType); -} +export { METRIC_TYPE }; +export const trackUiMetric = createUiStatsReporter(UIM_APP_NAME); diff --git a/x-pack/legacy/plugins/index_management/public/services/use_request.ts b/x-pack/legacy/plugins/index_management/public/services/use_request.ts index 4d275ec124c430..2168ec4f655df0 100644 --- a/x-pack/legacy/plugins/index_management/public/services/use_request.ts +++ b/x-pack/legacy/plugins/index_management/public/services/use_request.ts @@ -6,7 +6,7 @@ import { useEffect, useState } from 'react'; import { getHttpClient } from './api'; -import { trackUiMetric } from './track_ui_metric'; +import { trackUiMetric, METRIC_TYPE } from './track_ui_metric'; interface SendRequest { path?: string; @@ -35,7 +35,7 @@ export const sendRequest = async ({ // Track successful request if (uimActionType) { - trackUiMetric(uimActionType); + trackUiMetric(METRIC_TYPE.COUNT, uimActionType); } return { diff --git a/x-pack/legacy/plugins/index_management/public/store/reducers/detail_panel.js b/x-pack/legacy/plugins/index_management/public/store/reducers/detail_panel.js index 47949f17518541..6a3df9d38ee600 100644 --- a/x-pack/legacy/plugins/index_management/public/store/reducers/detail_panel.js +++ b/x-pack/legacy/plugins/index_management/public/store/reducers/detail_panel.js @@ -54,7 +54,7 @@ export const detailPanel = handleActions( }; if (panelTypeToUiMetricMap[panelType]) { - trackUiMetric(panelTypeToUiMetricMap[panelType]); + trackUiMetric('count', panelTypeToUiMetricMap[panelType]); } return { diff --git a/x-pack/legacy/plugins/infra/common/graphql/types.ts b/x-pack/legacy/plugins/infra/common/graphql/types.ts index 5f085f67e73faa..c93d51e1efc833 100644 --- a/x-pack/legacy/plugins/infra/common/graphql/types.ts +++ b/x-pack/legacy/plugins/infra/common/graphql/types.ts @@ -560,6 +560,7 @@ export enum InfraMetric { nginxRequestRate = 'nginxRequestRate', nginxActiveConnections = 'nginxActiveConnections', nginxRequestsPerConnection = 'nginxRequestsPerConnection', + custom = 'custom', } // ==================================================== diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart.tsx b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart.tsx index 222e00b57fe702..d3fcd9671acf76 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart.tsx +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart.tsx @@ -16,15 +16,18 @@ import { MetricsExplorerSeries } from '../../../server/routes/metrics_explorer/t import { MetricsExplorerOptions, MetricsExplorerTimeOptions, + MetricsExplorerYAxisMode, + MetricsExplorerChartOptions, } from '../../containers/metrics_explorer/use_metrics_explorer_options'; import euiStyled from '../../../../../common/eui_styled_components'; import { createFormatterForMetric } from './helpers/create_formatter_for_metric'; -import { MetricLineSeries } from './line_series'; +import { MetricExplorerSeriesChart } from './series_chart'; import { MetricsExplorerChartContextMenu } from './chart_context_menu'; import { SourceQuery } from '../../graphql/types'; import { MetricsExplorerEmptyChart } from './empty_chart'; import { MetricsExplorerNoMetrics } from './no_metrics'; import { getChartTheme } from './helpers/get_chart_theme'; +import { calculateDomain } from './helpers/calculate_domain'; interface Props { intl: InjectedIntl; @@ -33,6 +36,7 @@ interface Props { width?: number | string; height?: number | string; options: MetricsExplorerOptions; + chartOptions: MetricsExplorerChartOptions; series: MetricsExplorerSeries; source: SourceQuery.Query['source']['configuration'] | undefined; timeRange: MetricsExplorerTimeOptions; @@ -45,6 +49,7 @@ export const MetricsExplorerChart = injectUICapabilities( ({ source, options, + chartOptions, series, title, onFilter, @@ -66,6 +71,11 @@ export const MetricsExplorerChart = injectUICapabilities( [series.rows] ); const yAxisFormater = useCallback(createFormatterForMetric(first(metrics)), [options]); + const dataDomain = calculateDomain(series, metrics, chartOptions.stack); + const domain = + chartOptions.yAxisMode === MetricsExplorerYAxisMode.fromZero + ? { ...dataDomain, min: 0 } + : dataDomain; return (

{options.groupBy ? ( @@ -80,6 +90,7 @@ export const MetricsExplorerChart = injectUICapabilities( 0 ? ( {metrics.map((metric, id) => ( - + ))} diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.test.tsx b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.test.tsx index 0f5547cb0e7932..c5c5a26d8241b2 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.test.tsx +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { MetricsExplorerChartContextMenu, createNodeDetailLink } from './chart_context_menu'; import { mountWithIntl } from '../../utils/enzyme_helpers'; -import { options, source, timeRange } from '../../utils/fixtures/metrics_explorer'; +import { options, source, timeRange, chartOptions } from '../../utils/fixtures/metrics_explorer'; import { UICapabilities } from 'ui/capabilities'; import { InfraNodeType } from '../../graphql/types'; import DateMath from '@elastic/datemath'; @@ -37,6 +37,7 @@ describe('MetricsExplorerChartContextMenu', () => { options={options} onFilter={onFilter} uiCapabilities={uiCapabilities} + chartOptions={chartOptions} /> ); @@ -57,6 +58,7 @@ describe('MetricsExplorerChartContextMenu', () => { options={customOptions} onFilter={onFilter} uiCapabilities={uiCapabilities} + chartOptions={chartOptions} /> ); component.find('button').simulate('click'); @@ -71,6 +73,7 @@ describe('MetricsExplorerChartContextMenu', () => { series={series} options={options} uiCapabilities={uiCapabilities} + chartOptions={chartOptions} /> ); @@ -89,6 +92,7 @@ describe('MetricsExplorerChartContextMenu', () => { options={customOptions} onFilter={onFilter} uiCapabilities={uiCapabilities} + chartOptions={chartOptions} /> ); @@ -105,6 +109,7 @@ describe('MetricsExplorerChartContextMenu', () => { series={series} options={customOptions} uiCapabilities={uiCapabilities} + chartOptions={chartOptions} /> ); @@ -125,6 +130,7 @@ describe('MetricsExplorerChartContextMenu', () => { options={options} onFilter={onFilter} uiCapabilities={customUICapabilities} + chartOptions={chartOptions} /> ); @@ -144,6 +150,7 @@ describe('MetricsExplorerChartContextMenu', () => { options={customOptions} onFilter={onFilter} uiCapabilities={customUICapabilities} + chartOptions={chartOptions} /> ); expect(component.find('button').length).toBe(0); diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx index 01d44d446392d7..a0e696a20ff9d7 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx @@ -17,6 +17,7 @@ import { MetricsExplorerSeries } from '../../../server/routes/metrics_explorer/t import { MetricsExplorerOptions, MetricsExplorerTimeOptions, + MetricsExplorerChartOptions, } from '../../containers/metrics_explorer/use_metrics_explorer_options'; import { createTSVBLink } from './helpers/create_tsvb_link'; import { InfraNodeType } from '../../graphql/types'; @@ -31,6 +32,7 @@ interface Props { source?: SourceConfiguration; timeRange: MetricsExplorerTimeOptions; uiCapabilities: UICapabilities; + chartOptions: MetricsExplorerChartOptions; } const fieldToNodeType = (source: SourceConfiguration, field: string): InfraNodeType | undefined => { @@ -66,7 +68,7 @@ export const createNodeDetailLink = ( }; export const MetricsExplorerChartContextMenu = injectI18n( - ({ intl, onFilter, options, series, source, timeRange, uiCapabilities }: Props) => { + ({ intl, onFilter, options, series, source, timeRange, uiCapabilities, chartOptions }: Props) => { const [isPopoverOpen, setPopoverState] = useState(false); const supportFiltering = options.groupBy != null && onFilter != null; const handleFilter = useCallback(() => { @@ -78,7 +80,7 @@ export const MetricsExplorerChartContextMenu = injectI18n( setPopoverState(false); }, [supportFiltering, options.groupBy, series.id, onFilter]); - const tsvbUrl = createTSVBLink(source, options, series, timeRange); + const tsvbUrl = createTSVBLink(source, options, series, timeRange, chartOptions); // Only display the "Add Filter" option if it's supported const filterByItem = supportFiltering diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_options.tsx b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_options.tsx new file mode 100644 index 00000000000000..b44ba147784482 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_options.tsx @@ -0,0 +1,166 @@ +/* + * 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, { useState, useCallback } from 'react'; +import { InjectedIntl, injectI18n, FormattedMessage } from '@kbn/i18n/react'; +import { + EuiRadioGroup, + EuiButtonEmpty, + EuiPopover, + EuiForm, + EuiFormRow, + EuiSwitch, +} from '@elastic/eui'; +import { + MetricsExplorerChartOptions as ChartOptions, + MetricsExplorerYAxisMode, + MetricsExplorerChartType, +} from '../../containers/metrics_explorer/use_metrics_explorer_options'; + +interface Props { + chartOptions: ChartOptions; + onChange: (options: ChartOptions) => void; + intl: InjectedIntl; +} + +export const MetricsExplorerChartOptions = injectI18n(({ chartOptions, onChange, intl }: Props) => { + const [isPopoverOpen, setPopoverState] = useState(false); + + const handleClosePopover = useCallback(() => { + setPopoverState(false); + }, []); + + const handleOpenPopover = useCallback(() => { + setPopoverState(true); + }, []); + + const button = ( + + + + ); + + const yAxisRadios = [ + { + id: MetricsExplorerYAxisMode.auto, + label: intl.formatMessage({ + id: 'xpack.infra.metricsExplorer.chartOptions.autoLabel', + defaultMessage: 'Automatic (Min to Max)', + }), + }, + { + id: MetricsExplorerYAxisMode.fromZero, + label: intl.formatMessage({ + id: 'xpack.infra.metricsExplorer.chartOptions.fromZeroLabel', + defaultMessage: 'From Zero (0 to Max)', + }), + }, + ]; + + const typeRadios = [ + { + id: MetricsExplorerChartType.line, + label: intl.formatMessage({ + id: 'xpack.infra.metricsExplorer.chartOptions.lineLabel', + defaultMessage: 'Line', + }), + }, + { + id: MetricsExplorerChartType.area, + label: intl.formatMessage({ + id: 'xpack.infra.metricsExplorer.chartOptions.areaLabel', + defaultMessage: 'Area', + }), + }, + ]; + + const handleYAxisChange = useCallback( + (id: string) => { + onChange({ + ...chartOptions, + yAxisMode: id as MetricsExplorerYAxisMode, + }); + }, + [chartOptions, onChange] + ); + + const handleTypeChange = useCallback( + (id: string) => { + onChange({ + ...chartOptions, + type: id as MetricsExplorerChartType, + }); + }, + [chartOptions, onChange] + ); + + const handleStackChange = useCallback( + e => { + onChange({ + ...chartOptions, + stack: e.target.checked, + }); + }, + [chartOptions, onChange] + ); + + return ( + + + + + + + + + + + + + + ); +}); diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/charts.tsx b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/charts.tsx index 6ab77fd0d50178..69134383f08f93 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/charts.tsx +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/charts.tsx @@ -11,6 +11,7 @@ import { MetricsExplorerResponse } from '../../../server/routes/metrics_explorer import { MetricsExplorerOptions, MetricsExplorerTimeOptions, + MetricsExplorerChartOptions, } from '../../containers/metrics_explorer/use_metrics_explorer_options'; import { InfraLoadingPanel } from '../loading'; import { NoData } from '../empty_states/no_data'; @@ -20,6 +21,7 @@ import { SourceQuery } from '../../graphql/types'; interface Props { loading: boolean; options: MetricsExplorerOptions; + chartOptions: MetricsExplorerChartOptions; onLoadMore: (afterKey: string | null) => void; onRefetch: () => void; onFilter: (filter: string) => void; @@ -35,6 +37,7 @@ export const MetricsExplorerCharts = injectI18n( data, onLoadMore, options, + chartOptions, onRefetch, intl, onFilter, @@ -77,7 +80,7 @@ export const MetricsExplorerCharts = injectI18n( } return ( - +
{data.series.map(series => ( @@ -85,6 +88,7 @@ export const MetricsExplorerCharts = injectI18n( key={`chart-${series.id}`} onFilter={onFilter} options={options} + chartOptions={chartOptions} title={options.groupBy ? series.id : null} height={data.series.length > 1 ? 200 : 400} series={series} @@ -127,7 +131,7 @@ export const MetricsExplorerCharts = injectI18n( ) : null}
) : null} -
+
); } ); diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/helpers/calculate_domain.ts b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/helpers/calculate_domain.ts new file mode 100644 index 00000000000000..2f6097a1514fa0 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/helpers/calculate_domain.ts @@ -0,0 +1,33 @@ +/* + * 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 { min, max, sum } from 'lodash'; +import { MetricsExplorerSeries } from '../../../../server/routes/metrics_explorer/types'; +import { MetricsExplorerOptionsMetric } from '../../../containers/metrics_explorer/use_metrics_explorer_options'; + +export const calculateDomain = ( + series: MetricsExplorerSeries, + metrics: MetricsExplorerOptionsMetric[], + stacked = false +): { min: number; max: number } => { + const values = series.rows + .reduce( + (acc, row) => { + const rowValues = metrics + .map((m, index) => { + return (row[`metric_${index}`] as number) || null; + }) + .filter(v => v); + const minValue = min(rowValues); + // For stacked domains we want to add 10% head room so the charts have + // enough room to draw the 2 pixel line as well. + const maxValue = stacked ? sum(rowValues) * 1.1 : max(rowValues); + return acc.concat([minValue || null, maxValue || null]); + }, + [] as Array + ) + .filter(v => v); + return { min: min(values) || 0, max: max(values) || 0 }; +}; diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/helpers/calculate_domian.test.ts b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/helpers/calculate_domian.test.ts new file mode 100644 index 00000000000000..c335b8b3c31ac1 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/helpers/calculate_domian.test.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { calculateDomain } from './calculate_domain'; +import { + MetricsExplorerSeries, + MetricsExplorerAggregation, + MetricsExplorerColumnType, +} from '../../../../server/routes/metrics_explorer/types'; +import { MetricsExplorerOptionsMetric } from '../../../containers/metrics_explorer/use_metrics_explorer_options'; +import { MetricsExplorerColor } from '../../../../common/color_palette'; +describe('calculateDomain()', () => { + const series: MetricsExplorerSeries = { + id: 'test-01', + columns: [ + { type: MetricsExplorerColumnType.date, name: 'timestamp' }, + { type: MetricsExplorerColumnType.number, name: 'metric_0' }, + { type: MetricsExplorerColumnType.number, name: 'metric_1' }, + { type: MetricsExplorerColumnType.string, name: 'groupBy' }, + ], + rows: [ + { timestamp: 1562860500000, metric_0: null, metric_1: null }, + { timestamp: 1562860600000, metric_0: 0.1, metric_1: 0.3 }, + { timestamp: 1562860700000, metric_0: 0.5, metric_1: 0.7 }, + { timestamp: 1562860700000, metric_0: 0.4, metric_1: 0.9 }, + { timestamp: 1562860900000, metric_0: 0.01, metric_1: 0.5 }, + ], + }; + const metrics: MetricsExplorerOptionsMetric[] = [ + { + aggregation: MetricsExplorerAggregation.avg, + field: 'system.memory.free', + color: MetricsExplorerColor.color0, + }, + { + aggregation: MetricsExplorerAggregation.avg, + field: 'system.memory.used.bytes', + color: MetricsExplorerColor.color1, + }, + ]; + it('should return the min and max across 2 metrics', () => { + expect(calculateDomain(series, metrics)).toEqual({ min: 0.01, max: 0.9 }); + }); + it('should return the min and combined max across 2 metrics with 10% head room when stacked', () => { + expect(calculateDomain(series, metrics, true)).toEqual({ min: 0.01, max: 1.4300000000000002 }); + }); +}); diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/helpers/create_tsvb_link.test.ts b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/helpers/create_tsvb_link.test.ts index 92d91a20439272..d190d09da992ea 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/helpers/create_tsvb_link.test.ts +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/helpers/create_tsvb_link.test.ts @@ -5,10 +5,14 @@ */ import { createTSVBLink, createFilterFromOptions } from './create_tsvb_link'; -import { source, options, timeRange } from '../../../utils/fixtures/metrics_explorer'; +import { source, options, timeRange, chartOptions } from '../../../utils/fixtures/metrics_explorer'; import uuid from 'uuid'; import { OutputBuffer } from 'uuid/interfaces'; import { MetricsExplorerAggregation } from '../../../../server/routes/metrics_explorer/types'; +import { + MetricsExplorerYAxisMode, + MetricsExplorerChartType, +} from '../../../containers/metrics_explorer/use_metrics_explorer_options'; jest.mock('uuid'); const mockedUuid = uuid as jest.Mocked; @@ -17,11 +21,12 @@ const series = { id: 'example-01', rows: [], columns: [] }; describe('createTSVBLink()', () => { it('should just work', () => { - const link = createTSVBLink(source, options, series, timeRange); + const link = createTSVBLink(source, options, series, timeRange, chartOptions); expect(link).toBe( - "../app/kibana#/visualize/create?type=metrics&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))&_a=(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_position:left,axis_scale:normal,default_index_pattern:'metricbeat-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metricbeat-*',interval:auto,series:!((axis_position:right,chart_type:line,color:%233185FC,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))" + "../app/kibana#/visualize/create?type=metrics&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))&_a=(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'metricbeat-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metricbeat-*',interval:auto,series:!((axis_position:right,chart_type:line,color:%233185FC,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))" ); }); + it('should work with rates', () => { const customOptions = { ...options, @@ -29,16 +34,16 @@ describe('createTSVBLink()', () => { { aggregation: MetricsExplorerAggregation.rate, field: 'system.network.out.bytes' }, ], }; - const link = createTSVBLink(source, customOptions, series, timeRange); + const link = createTSVBLink(source, customOptions, series, timeRange, chartOptions); expect(link).toBe( - "../app/kibana#/visualize/create?type=metrics&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))&_a=(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_position:left,axis_scale:normal,default_index_pattern:'metricbeat-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metricbeat-*',interval:auto,series:!((axis_position:right,chart_type:line,color:%233185FC,fill:0,formatter:bytes,id:test-id,label:'rate(system.network.out.bytes)',line_width:2,metrics:!((field:system.network.out.bytes,id:test-id,type:max),(field:test-id,id:test-id,type:derivative,unit:'1s'),(field:test-id,id:test-id,type:positive_only)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}}/s)),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))" + "../app/kibana#/visualize/create?type=metrics&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))&_a=(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'metricbeat-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metricbeat-*',interval:auto,series:!((axis_position:right,chart_type:line,color:%233185FC,fill:0,formatter:bytes,id:test-id,label:'rate(system.network.out.bytes)',line_width:2,metrics:!((field:system.network.out.bytes,id:test-id,type:max),(field:test-id,id:test-id,type:derivative,unit:'1s'),(field:test-id,id:test-id,type:positive_only)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}}/s)),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))" ); }); it('should work with time range', () => { const customTimeRange = { ...timeRange, from: 'now-10m', to: 'now' }; - const link = createTSVBLink(source, options, series, customTimeRange); + const link = createTSVBLink(source, options, series, customTimeRange, chartOptions); expect(link).toBe( - "../app/kibana#/visualize/create?type=metrics&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-10m,to:now))&_a=(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_position:left,axis_scale:normal,default_index_pattern:'metricbeat-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metricbeat-*',interval:auto,series:!((axis_position:right,chart_type:line,color:%233185FC,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))" + "../app/kibana#/visualize/create?type=metrics&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-10m,to:now))&_a=(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'metricbeat-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metricbeat-*',interval:auto,series:!((axis_position:right,chart_type:line,color:%233185FC,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))" ); }); it('should work with source', () => { @@ -47,9 +52,9 @@ describe('createTSVBLink()', () => { metricAlias: 'my-beats-*', fields: { ...source.fields, timestamp: 'time' }, }; - const link = createTSVBLink(customSource, options, series, timeRange); + const link = createTSVBLink(customSource, options, series, timeRange, chartOptions); expect(link).toBe( - "../app/kibana#/visualize/create?type=metrics&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))&_a=(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_position:left,axis_scale:normal,default_index_pattern:'my-beats-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'my-beats-*',interval:auto,series:!((axis_position:right,chart_type:line,color:%233185FC,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:time,type:timeseries),title:example-01,type:metrics))" + "../app/kibana#/visualize/create?type=metrics&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))&_a=(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'my-beats-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'my-beats-*',interval:auto,series:!((axis_position:right,chart_type:line,color:%233185FC,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:time,type:timeseries),title:example-01,type:metrics))" ); }); it('should work with filterQuery', () => { @@ -59,9 +64,37 @@ describe('createTSVBLink()', () => { fields: { ...source.fields, timestamp: 'time' }, }; const customOptions = { ...options, filterQuery: 'system.network.name:lo*' }; - const link = createTSVBLink(customSource, customOptions, series, timeRange); + const link = createTSVBLink(customSource, customOptions, series, timeRange, chartOptions); + expect(link).toBe( + "../app/kibana#/visualize/create?type=metrics&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))&_a=(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'my-beats-*',filter:(language:kuery,query:'system.network.name:lo* and host.name : \"example-01\"'),id:test-id,index_pattern:'my-beats-*',interval:auto,series:!((axis_position:right,chart_type:line,color:%233185FC,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:time,type:timeseries),title:example-01,type:metrics))" + ); + }); + + it('should remove axis_min from link', () => { + const customChartOptions = { ...chartOptions, yAxisMode: MetricsExplorerYAxisMode.auto }; + const link = createTSVBLink(source, options, series, timeRange, customChartOptions); + expect(link).toBe( + "../app/kibana#/visualize/create?type=metrics&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))&_a=(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_position:left,axis_scale:normal,default_index_pattern:'metricbeat-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metricbeat-*',interval:auto,series:!((axis_position:right,chart_type:line,color:%233185FC,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))" + ); + }); + + it('should change series to area', () => { + const customChartOptions = { ...chartOptions, type: MetricsExplorerChartType.area }; + const link = createTSVBLink(source, options, series, timeRange, customChartOptions); + expect(link).toBe( + "../app/kibana#/visualize/create?type=metrics&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))&_a=(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'metricbeat-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metricbeat-*',interval:auto,series:!((axis_position:right,chart_type:line,color:%233185FC,fill:0.5,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))" + ); + }); + + it('should change series to area and stacked', () => { + const customChartOptions = { + ...chartOptions, + type: MetricsExplorerChartType.area, + stack: true, + }; + const link = createTSVBLink(source, options, series, timeRange, customChartOptions); expect(link).toBe( - "../app/kibana#/visualize/create?type=metrics&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))&_a=(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_position:left,axis_scale:normal,default_index_pattern:'my-beats-*',filter:(language:kuery,query:'system.network.name:lo* and host.name : \"example-01\"'),id:test-id,index_pattern:'my-beats-*',interval:auto,series:!((axis_position:right,chart_type:line,color:%233185FC,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:time,type:timeseries),title:example-01,type:metrics))" + "../app/kibana#/visualize/create?type=metrics&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))&_a=(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'metricbeat-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metricbeat-*',interval:auto,series:!((axis_position:right,chart_type:line,color:%233185FC,fill:0.5,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:stacked,value_template:{{value}})),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))" ); }); diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/helpers/create_tsvb_link.ts b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/helpers/create_tsvb_link.ts index 9053002795d45e..788de6a129aa9c 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/helpers/create_tsvb_link.ts +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/helpers/create_tsvb_link.ts @@ -6,6 +6,7 @@ import { encode } from 'rison-node'; import uuid from 'uuid'; +import { set } from 'lodash'; import { colorTransformer, MetricsExplorerColor } from '../../../../common/color_palette'; import { MetricsExplorerSeries, @@ -15,6 +16,9 @@ import { MetricsExplorerOptions, MetricsExplorerOptionsMetric, MetricsExplorerTimeOptions, + MetricsExplorerChartOptions, + MetricsExplorerYAxisMode, + MetricsExplorerChartType, } from '../../../containers/metrics_explorer/use_metrics_explorer_options'; import { metricToFormat } from './metric_to_format'; import { InfraFormatterType } from '../../../lib/lib'; @@ -55,7 +59,9 @@ export const metricsExplorerMetricToTSVBMetric = (metric: MetricsExplorerOptions } }; -const mapMetricToSeries = (metric: MetricsExplorerOptionsMetric) => { +const mapMetricToSeries = (chartOptions: MetricsExplorerChartOptions) => ( + metric: MetricsExplorerOptionsMetric +) => { const format = metricToFormat(metric); return { label: createMetricLabel(metric), @@ -65,7 +71,7 @@ const mapMetricToSeries = (metric: MetricsExplorerOptionsMetric) => { (metric.color && colorTransformer(metric.color)) || colorTransformer(MetricsExplorerColor.color0) ), - fill: 0, + fill: chartOptions.type === MetricsExplorerChartType.area ? 0.5 : 0, formatter: format === InfraFormatterType.bits ? InfraFormatterType.bytes : format, value_template: MetricsExplorerAggregation.rate === metric.aggregation ? '{{value}}/s' : '{{value}}', @@ -75,7 +81,7 @@ const mapMetricToSeries = (metric: MetricsExplorerOptionsMetric) => { point_size: 0, separate_axis: 0, split_mode: 'everything', - stacked: 'none', + stacked: chartOptions.stack ? 'stacked' : 'none', }; }; @@ -98,7 +104,8 @@ export const createTSVBLink = ( source: SourceQuery.Query['source']['configuration'] | undefined, options: MetricsExplorerOptions, series: MetricsExplorerSeries, - timeRange: MetricsExplorerTimeOptions + timeRange: MetricsExplorerTimeOptions, + chartOptions: MetricsExplorerChartOptions ) => { const appState = { filters: [], @@ -115,7 +122,7 @@ export const createTSVBLink = ( default_index_pattern: (source && source.metricAlias) || 'metricbeat-*', index_pattern: (source && source.metricAlias) || 'metricbeat-*', interval: 'auto', - series: options.metrics.map(mapMetricToSeries), + series: options.metrics.map(mapMetricToSeries(chartOptions)), show_grid: 1, show_legend: 1, time_field: (source && source.fields.timestamp) || '@timestamp', @@ -127,6 +134,10 @@ export const createTSVBLink = ( }, }; + if (chartOptions.yAxisMode === MetricsExplorerYAxisMode.fromZero) { + set(appState, 'vis.params.axis_min', 0); + } + const globalState = { refreshInterval: { pause: true, value: 0 }, time: { from: timeRange.from, to: timeRange.to }, diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/line_series.tsx b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/series_chart.tsx similarity index 73% rename from x-pack/legacy/plugins/infra/public/components/metrics_explorer/line_series.tsx rename to x-pack/legacy/plugins/infra/public/components/metrics_explorer/series_chart.tsx index 6e29d943627988..b077d7c17a0f99 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/line_series.tsx +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/series_chart.tsx @@ -6,33 +6,54 @@ import React from 'react'; import { - LineSeries, ScaleType, getSpecId, DataSeriesColorsValues, CustomSeriesColorsMap, + AreaSeries, } from '@elastic/charts'; import { MetricsExplorerSeries } from '../../../server/routes/metrics_explorer/types'; import { colorTransformer, MetricsExplorerColor } from '../../../common/color_palette'; import { createMetricLabel } from './helpers/create_metric_label'; -import { MetricsExplorerOptionsMetric } from '../../containers/metrics_explorer/use_metrics_explorer_options'; +import { + MetricsExplorerOptionsMetric, + MetricsExplorerChartType, +} from '../../containers/metrics_explorer/use_metrics_explorer_options'; interface Props { metric: MetricsExplorerOptionsMetric; id: string | number; series: MetricsExplorerSeries; + type: MetricsExplorerChartType; + stack: boolean; } -export const MetricLineSeries = ({ metric, id, series }: Props) => { +export const MetricExplorerSeriesChart = ({ metric, id, series, type, stack }: Props) => { const color = (metric.color && colorTransformer(metric.color)) || colorTransformer(MetricsExplorerColor.color0); - const seriesLineStyle = { + + const yAccessor = `metric_${id}`; + const specId = getSpecId(yAccessor); + const colors: DataSeriesColorsValues = { + colorValues: [], + specId, + }; + const customColors: CustomSeriesColorsMap = new Map(); + customColors.set(colors, color); + const chartId = `series-${series.id}-${yAccessor}`; + + const seriesAreaStyle = { line: { stroke: color, strokeWidth: 2, visible: true, }, + area: { + fill: color, + opacity: 0.5, + visible: type === MetricsExplorerChartType.area, + }, border: { visible: false, strokeWidth: 2, @@ -46,19 +67,9 @@ export const MetricLineSeries = ({ metric, id, series }: Props) => { opacity: 1, }, }; - - const yAccessor = `metric_${id}`; - const specId = getSpecId(yAccessor); - const colors: DataSeriesColorsValues = { - colorValues: [], - specId, - }; - const customColors: CustomSeriesColorsMap = new Map(); - customColors.set(colors, color); - return ( - { xAccessor="timestamp" yAccessors={[yAccessor]} data={series.rows} - lineSeriesStyle={seriesLineStyle} + stackAccessors={stack ? ['timestamp'] : void 0} + areaSeriesStyle={seriesAreaStyle} customSeriesColors={customColors} /> ); diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/toolbar.tsx b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/toolbar.tsx index 8b686bb1de62d7..80d91587811d3d 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/toolbar.tsx +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/toolbar.tsx @@ -15,24 +15,28 @@ import { import { MetricsExplorerOptions, MetricsExplorerTimeOptions, + MetricsExplorerChartOptions, } from '../../containers/metrics_explorer/use_metrics_explorer_options'; import { Toolbar } from '../eui/toolbar'; import { MetricsExplorerKueryBar } from './kuery_bar'; import { MetricsExplorerMetrics } from './metrics'; import { MetricsExplorerGroupBy } from './group_by'; import { MetricsExplorerAggregationPicker } from './aggregation'; +import { MetricsExplorerChartOptions as MetricsExplorerChartOptionsComponent } from './chart_options'; interface Props { intl: InjectedIntl; derivedIndexPattern: StaticIndexPattern; timeRange: MetricsExplorerTimeOptions; options: MetricsExplorerOptions; + chartOptions: MetricsExplorerChartOptions; onRefresh: () => void; onTimeChange: (start: string, end: string) => void; onGroupByChange: (groupBy: string | null) => void; onFilterQuerySubmit: (query: string) => void; onMetricsChange: (metrics: MetricsExplorerMetric[]) => void; onAggregationChange: (aggregation: MetricsExplorerAggregation) => void; + onChartOptionsChange: (chartOptions: MetricsExplorerChartOptions) => void; } export const MetricsExplorerToolbar = injectI18n( @@ -46,6 +50,8 @@ export const MetricsExplorerToolbar = injectI18n( onFilterQuerySubmit, onMetricsChange, onAggregationChange, + chartOptions, + onChartOptionsChange, }: Props) => { const isDefaultOptions = options.aggregation === MetricsExplorerAggregation.avg && options.metrics.length === 0; @@ -99,6 +105,12 @@ export const MetricsExplorerToolbar = injectI18n( value={options.filterQuery} /> + + + { + if (destinationIndex >= 0 && sourceIndex < formState.logColumns.length - 1) { + const newLogColumns = [...formState.logColumns]; + newLogColumns.splice(destinationIndex, 0, newLogColumns.splice(sourceIndex, 1)[0]); + setFormStateChanges(changes => ({ + ...changes, + logColumns: newLogColumns, + })); + } + }, + [formState.logColumns] + ); + const errors = useMemo( () => logColumnConfigurationProps.length <= 0 @@ -125,6 +139,7 @@ export const useLogColumnsConfigurationFormState = ({ return { addLogColumn, + moveLogColumn, errors, logColumnConfigurationProps, formState, diff --git a/x-pack/legacy/plugins/infra/public/components/source_configuration/log_columns_configuration_panel.tsx b/x-pack/legacy/plugins/infra/public/components/source_configuration/log_columns_configuration_panel.tsx index 9dfd95a7733513..bbc3cde41794c3 100644 --- a/x-pack/legacy/plugins/infra/public/components/source_configuration/log_columns_configuration_panel.tsx +++ b/x-pack/legacy/plugins/infra/public/components/source_configuration/log_columns_configuration_panel.tsx @@ -14,9 +14,14 @@ import { EuiTitle, EuiFlexGroup, EuiFlexItem, + EuiDragDropContext, + EuiDraggable, + EuiDroppable, + EuiIcon, } from '@elastic/eui'; import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; -import React from 'react'; +import React, { useCallback } from 'react'; +import { DragHandleProps, DropResult } from '../../../../../common/eui_draggable'; import { AddLogColumnButtonAndPopover } from './add_log_column_popover'; import { @@ -30,70 +35,95 @@ interface LogColumnsConfigurationPanelProps { isLoading: boolean; logColumnConfiguration: LogColumnConfigurationProps[]; addLogColumn: (logColumn: LogColumnConfiguration) => void; + moveLogColumn: (sourceIndex: number, destinationIndex: number) => void; } export const LogColumnsConfigurationPanel: React.FunctionComponent< LogColumnsConfigurationPanelProps -> = ({ addLogColumn, availableFields, isLoading, logColumnConfiguration }) => ( - - - - -

- -

-
-
- - - -
- {logColumnConfiguration.length > 0 ? ( - logColumnConfiguration.map((column, index) => ( - - )) - ) : ( - - )} -
-); +> = ({ addLogColumn, moveLogColumn, availableFields, isLoading, logColumnConfiguration }) => { + const onDragEnd = useCallback( + ({ source, destination }: DropResult) => + destination && moveLogColumn(source.index, destination.index), + [moveLogColumn] + ); + + return ( + + + + +

+ +

+
+
+ + + +
+ {logColumnConfiguration.length > 0 ? ( + + + <> + {/* Fragment here necessary for typechecking */} + {logColumnConfiguration.map((column, index) => ( + + {provided => ( + + )} + + ))} + + + + ) : ( + + )} +
+ ); +}; interface LogColumnConfigurationPanelProps { logColumnConfigurationProps: LogColumnConfigurationProps; + dragHandleProps: DragHandleProps; } -const LogColumnConfigurationPanel: React.FunctionComponent = ({ - logColumnConfigurationProps, -}) => ( +const LogColumnConfigurationPanel: React.FunctionComponent< + LogColumnConfigurationPanelProps +> = props => ( <> - {logColumnConfigurationProps.type === 'timestamp' ? ( - - ) : logColumnConfigurationProps.type === 'message' ? ( - + {props.logColumnConfigurationProps.type === 'timestamp' ? ( + + ) : props.logColumnConfigurationProps.type === 'message' ? ( + ) : ( - + )} ); const TimestampLogColumnConfigurationPanel: React.FunctionComponent< LogColumnConfigurationPanelProps -> = ({ logColumnConfigurationProps }) => ( +> = ({ logColumnConfigurationProps, dragHandleProps }) => ( } removeColumn={logColumnConfigurationProps.remove} + dragHandleProps={dragHandleProps} /> ); const MessageLogColumnConfigurationPanel: React.FunctionComponent< LogColumnConfigurationPanelProps -> = ({ logColumnConfigurationProps }) => ( +> = ({ logColumnConfigurationProps, dragHandleProps }) => ( } removeColumn={logColumnConfigurationProps.remove} + dragHandleProps={dragHandleProps} /> ); const FieldLogColumnConfigurationPanel: React.FunctionComponent<{ logColumnConfigurationProps: FieldLogColumnConfigurationProps; + dragHandleProps: DragHandleProps; }> = ({ logColumnConfigurationProps: { logColumnConfiguration: { field }, remove, }, + dragHandleProps, }) => ( + +
+ +
+
void; -}> = ({ fieldName, helpText, removeColumn }) => ( + dragHandleProps: DragHandleProps; +}> = ({ fieldName, helpText, removeColumn, dragHandleProps }) => ( + +
+ +
+
{fieldName} diff --git a/x-pack/legacy/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx b/x-pack/legacy/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx index 799c207b94bee7..4fea7b76e4f1f2 100644 --- a/x-pack/legacy/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx +++ b/x-pack/legacy/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx @@ -57,6 +57,7 @@ export const SourceConfigurationFlyout = injectI18n( const { addLogColumn, + moveLogColumn, indicesConfigurationProps, logColumnConfigurationProps, errors, @@ -137,6 +138,7 @@ export const SourceConfigurationFlyout = injectI18n( =10s', }; +export const DEFAULT_CHART_OPTIONS: MetricsExplorerChartOptions = { + type: MetricsExplorerChartType.line, + yAxisMode: MetricsExplorerYAxisMode.fromZero, + stack: false, +}; + export const DEFAULT_METRICS: MetricsExplorerOptionsMetric[] = [ { aggregation: MetricsExplorerAggregation.avg, @@ -92,9 +114,15 @@ export const useMetricsExplorerOptions = () => { 'MetricsExplorerTimeRange', DEFAULT_TIMERANGE ); + const [chartOptions, setChartOptions] = useStateWithLocalStorage( + 'MetricsExplorerChartOptions', + DEFAULT_CHART_OPTIONS + ); const [isAutoReloading, setAutoReloading] = useState(false); return { options, + chartOptions, + setChartOptions, currentTimerange, isAutoReloading, setOptions, diff --git a/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/with_metrics_explorer_options_url_state.tsx b/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/with_metrics_explorer_options_url_state.tsx index d9f3a5d65911dd..027d79d8cb0721 100644 --- a/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/with_metrics_explorer_options_url_state.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/with_metrics_explorer_options_url_state.tsx @@ -14,17 +14,26 @@ import { MetricsExplorerOptions, MetricsExplorerOptionsContainer, MetricsExplorerTimeOptions, + MetricsExplorerYAxisMode, + MetricsExplorerChartType, + MetricsExplorerChartOptions, } from './use_metrics_explorer_options'; interface MetricsExplorerUrlState { timerange?: MetricsExplorerTimeOptions; options?: MetricsExplorerOptions; + chartOptions?: MetricsExplorerChartOptions; } export const WithMetricsExplorerOptionsUrlState = () => { - const { options, currentTimerange, setOptions: setRawOptions, setTimeRange } = useContext( - MetricsExplorerOptionsContainer.Context - ); + const { + options, + chartOptions, + setChartOptions, + currentTimerange, + setOptions: setRawOptions, + setTimeRange, + } = useContext(MetricsExplorerOptionsContainer.Context); const setOptions = (value: MetricsExplorerOptions) => { setRawOptions(value); @@ -33,32 +42,31 @@ export const WithMetricsExplorerOptionsUrlState = () => { const urlState = useMemo( () => ({ options, + chartOptions, timerange: currentTimerange, }), - [options, currentTimerange] + [options, chartOptions, currentTimerange] ); + const handleChange = (newUrlState: MetricsExplorerUrlState | undefined) => { + if (newUrlState && newUrlState.options) { + setOptions(newUrlState.options); + } + if (newUrlState && newUrlState.timerange) { + setTimeRange(newUrlState.timerange); + } + if (newUrlState && newUrlState.chartOptions) { + setChartOptions(newUrlState.chartOptions); + } + }; + return ( { - if (newUrlState && newUrlState.options) { - setOptions(newUrlState.options); - } - if (newUrlState && newUrlState.timerange) { - setTimeRange(newUrlState.timerange); - } - }} - onInitialize={newUrlState => { - if (newUrlState && newUrlState.options) { - setOptions(newUrlState.options); - } - if (newUrlState && newUrlState.timerange) { - setTimeRange(newUrlState.timerange); - } - }} + onChange={handleChange} + onInitialize={handleChange} /> ); }; @@ -100,6 +108,22 @@ function isMetricExplorerOptions(subject: any): subject is MetricsExplorerOption } } +function isMetricExplorerChartOptions(subject: any): subject is MetricsExplorerChartOptions { + const ChartOptions = t.type({ + yAxisMode: t.union(values(MetricsExplorerYAxisMode).map(v => t.literal(v as string))), + type: t.union(values(MetricsExplorerChartType).map(v => t.literal(v as string))), + stack: t.boolean, + }); + const result = ChartOptions.decode(subject); + + try { + ThrowReporter.report(result); + return true; + } catch (e) { + return false; + } +} + function isMetricExplorerTimeOption(subject: any): subject is MetricsExplorerTimeOptions { const TimeRange = t.type({ from: t.string, @@ -124,6 +148,9 @@ const mapToUrlState = (value: any): MetricsExplorerUrlState | undefined => { if (value.timerange && isMetricExplorerTimeOption(value.timerange)) { set(finalState, 'timerange', value.timerange); } + if (value.chartOptions && isMetricExplorerChartOptions(value.chartOptions)) { + set(finalState, 'chartOptions', value.chartOptions); + } return finalState; } }; diff --git a/x-pack/legacy/plugins/infra/public/graphql/introspection.json b/x-pack/legacy/plugins/infra/public/graphql/introspection.json index 02557715a9b666..d919353f1cbe3f 100644 --- a/x-pack/legacy/plugins/infra/public/graphql/introspection.json +++ b/x-pack/legacy/plugins/infra/public/graphql/introspection.json @@ -2383,7 +2383,8 @@ "description": "", "isDeprecated": false, "deprecationReason": null - } + }, + { "name": "custom", "description": "", "isDeprecated": false, "deprecationReason": null } ], "possibleTypes": null }, diff --git a/x-pack/legacy/plugins/infra/public/graphql/types.ts b/x-pack/legacy/plugins/infra/public/graphql/types.ts index 5f085f67e73faa..c93d51e1efc833 100644 --- a/x-pack/legacy/plugins/infra/public/graphql/types.ts +++ b/x-pack/legacy/plugins/infra/public/graphql/types.ts @@ -560,6 +560,7 @@ export enum InfraMetric { nginxRequestRate = 'nginxRequestRate', nginxActiveConnections = 'nginxActiveConnections', nginxRequestsPerConnection = 'nginxRequestsPerConnection', + custom = 'custom', } // ==================================================== diff --git a/x-pack/legacy/plugins/infra/public/hooks/use_track_metric.tsx b/x-pack/legacy/plugins/infra/public/hooks/use_track_metric.tsx index 63fb02ff15ccf1..54359cf0aa6920 100644 --- a/x-pack/legacy/plugins/infra/public/hooks/use_track_metric.tsx +++ b/x-pack/legacy/plugins/infra/public/hooks/use_track_metric.tsx @@ -5,7 +5,10 @@ */ import { useEffect } from 'react'; -import { trackUiMetric } from '../../../../../../src/legacy/core_plugins/ui_metric/public'; +import { + createUiStatsReporter, + METRIC_TYPE, +} from '../../../../../../src/legacy/core_plugins/ui_metric/public'; /** * Note: The UI Metric plugin will take care of sending this data to the telemetry server. @@ -17,17 +20,32 @@ import { trackUiMetric } from '../../../../../../src/legacy/core_plugins/ui_metr type ObservabilityApp = 'infra_metrics' | 'infra_logs' | 'apm' | 'uptime'; +const trackerCache = new Map>(); + +function getTrackerForApp(app: string) { + const cached = trackerCache.get(app); + if (cached) { + return cached; + } + + const tracker = createUiStatsReporter(app); + trackerCache.set(app, tracker); + + return tracker; +} + interface TrackOptions { app: ObservabilityApp; + metricType?: METRIC_TYPE; delay?: number; // in ms } - type EffectDeps = unknown[]; type TrackMetricOptions = TrackOptions & { metric: string }; +export { METRIC_TYPE }; export function useTrackMetric( - { app, metric, delay = 0 }: TrackMetricOptions, + { app, metric, metricType = METRIC_TYPE.COUNT, delay = 0 }: TrackMetricOptions, effectDependencies: EffectDeps = [] ) { useEffect(() => { @@ -35,7 +53,8 @@ export function useTrackMetric( if (delay > 0) { decoratedMetric += `__delayed_${delay}ms`; } - const id = setTimeout(() => trackUiMetric(app, decoratedMetric), Math.max(delay, 0)); + const trackUiMetric = getTrackerForApp(app); + const id = setTimeout(() => trackUiMetric(metricType, decoratedMetric), Math.max(delay, 0)); return () => clearTimeout(id); }, effectDependencies); } diff --git a/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/index.tsx b/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/index.tsx index 02563b9029e2de..3bd64da88254bf 100644 --- a/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/index.tsx @@ -33,6 +33,8 @@ export const MetricsExplorerPage = injectI18n( data, currentTimerange, options, + chartOptions, + setChartOptions, handleAggregationChange, handleMetricsChange, handleFilterQuerySubmit, @@ -64,12 +66,14 @@ export const MetricsExplorerPage = injectI18n( derivedIndexPattern={derivedIndexPattern} timeRange={currentTimerange} options={options} + chartOptions={chartOptions} onRefresh={handleRefresh} onTimeChange={handleTimeChange} onGroupByChange={handleGroupByChange} onFilterQuerySubmit={handleFilterQuerySubmit} onMetricsChange={handleMetricsChange} onAggregationChange={handleAggregationChange} + onChartOptionsChange={setChartOptions} /> {error ? ( { const [refreshSignal, setRefreshSignal] = useState(0); const [afterKey, setAfterKey] = useState(null); - const { options, currentTimerange, setTimeRange, setOptions } = useContext( - MetricsExplorerOptionsContainer.Context - ); + const { + options, + currentTimerange, + chartOptions, + setChartOptions, + setTimeRange, + setOptions, + } = useContext(MetricsExplorerOptionsContainer.Context); const { loading, error, data } = useMetricsExplorerData( options, source, @@ -101,6 +106,8 @@ export const useMetricsExplorerState = ( data, currentTimerange, options, + chartOptions, + setChartOptions, handleAggregationChange, handleMetricsChange, handleFilterQuerySubmit, diff --git a/x-pack/legacy/plugins/infra/public/utils/fixtures/metrics_explorer.ts b/x-pack/legacy/plugins/infra/public/utils/fixtures/metrics_explorer.ts index b9bcf5f68be316..d553f03c4ed342 100644 --- a/x-pack/legacy/plugins/infra/public/utils/fixtures/metrics_explorer.ts +++ b/x-pack/legacy/plugins/infra/public/utils/fixtures/metrics_explorer.ts @@ -13,6 +13,9 @@ import { import { MetricsExplorerOptions, MetricsExplorerTimeOptions, + MetricsExplorerChartType, + MetricsExplorerYAxisMode, + MetricsExplorerChartOptions, } from '../../containers/metrics_explorer/use_metrics_explorer_options'; export const options: MetricsExplorerOptions = { @@ -38,6 +41,12 @@ export const source = { }, }; +export const chartOptions: MetricsExplorerChartOptions = { + type: MetricsExplorerChartType.line, + yAxisMode: MetricsExplorerYAxisMode.fromZero, + stack: false, +}; + export const derivedIndexPattern = { title: 'metricbeat-*', fields: [] }; export const timeRange: MetricsExplorerTimeOptions = { diff --git a/x-pack/legacy/plugins/infra/server/graphql/metrics/schema.gql.ts b/x-pack/legacy/plugins/infra/server/graphql/metrics/schema.gql.ts index 3218bffe959457..00422f5dd0774d 100644 --- a/x-pack/legacy/plugins/infra/server/graphql/metrics/schema.gql.ts +++ b/x-pack/legacy/plugins/infra/server/graphql/metrics/schema.gql.ts @@ -35,6 +35,7 @@ export const metricsSchema: any = gql` nginxRequestRate nginxActiveConnections nginxRequestsPerConnection + custom } type InfraMetricData { diff --git a/x-pack/legacy/plugins/infra/server/graphql/types.ts b/x-pack/legacy/plugins/infra/server/graphql/types.ts index d161dfac59f318..b09d93f4338fa8 100644 --- a/x-pack/legacy/plugins/infra/server/graphql/types.ts +++ b/x-pack/legacy/plugins/infra/server/graphql/types.ts @@ -588,6 +588,7 @@ export enum InfraMetric { nginxRequestRate = 'nginxRequestRate', nginxActiveConnections = 'nginxActiveConnections', nginxRequestsPerConnection = 'nginxRequestsPerConnection', + custom = 'custom', } // ==================================================== diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/adapter_types.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/adapter_types.ts index d5b58a3fb59f32..49d72e1b4794b1 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/adapter_types.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/adapter_types.ts @@ -29,6 +29,11 @@ export interface InfraMetricsAdapter { ): Promise; } +export enum InfraMetricModelQueryType { + lucene = 'lucene', + kuery = 'kuery', +} + export enum InfraMetricModelMetricType { avg = 'avg', max = 'max', @@ -42,7 +47,7 @@ export enum InfraMetricModelMetricType { } export interface InfraMetricModel { - id: string; + id: InfraMetric; requires: string[]; index_pattern: string | string[]; interval: string; @@ -60,7 +65,7 @@ export interface InfraMetricModelSeries { terms_field?: string; terms_size?: number; terms_order_by?: string; - filter?: string; + filter?: { query: string; language: InfraMetricModelQueryType }; } export interface InfraMetricModelBasicMetric { diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts index b91c2912188ce6..79a6f368aed092 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts @@ -56,7 +56,9 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter { const requests = options.metrics.map(metricId => { const model = metricModels[metricId](timeField, indexPattern, interval); - const filters = [{ match: { [nodeField]: options.nodeId } }]; + const filters = model.map_field_to + ? [{ match: { [model.map_field_to]: options.nodeId } }] + : [{ match: { [nodeField]: options.nodeId } }]; return this.framework.makeTSVBRequest(req, model, timerange, filters); }); return Promise.all(requests) diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_cpu_kernel.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_cpu_kernel.ts index 3fd1144b6770b7..1aff438aaf07aa 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_cpu_kernel.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_cpu_kernel.ts @@ -5,9 +5,10 @@ */ import { InfraMetricModelCreator, InfraMetricModelMetricType } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const containerCpuKernel: InfraMetricModelCreator = (timeField, indexPattern, interval) => ({ - id: 'containerCpuKernel', + id: InfraMetric.containerCpuKernel, requires: ['docker.cpu'], index_pattern: indexPattern, interval, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_cpu_usage.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_cpu_usage.ts index bdb52b8a9d7b57..81290cec4805fd 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_cpu_usage.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_cpu_usage.ts @@ -5,9 +5,10 @@ */ import { InfraMetricModelCreator, InfraMetricModelMetricType } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const containerCpuUsage: InfraMetricModelCreator = (timeField, indexPattern, interval) => ({ - id: 'containerCpuUsage', + id: InfraMetric.containerCpuUsage, requires: ['docker.cpu'], index_pattern: indexPattern, interval, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_disk_io_bytes.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_disk_io_bytes.ts index 348407ee0c9f89..9e028b918ec9e4 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_disk_io_bytes.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_disk_io_bytes.ts @@ -5,13 +5,14 @@ */ import { InfraMetricModelCreator, InfraMetricModelMetricType } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const containerDiskIOBytes: InfraMetricModelCreator = ( timeField, indexPattern, interval ) => ({ - id: 'containerDiskIOBytes', + id: InfraMetric.containerDiskIOBytes, requires: ['docker.disk'], index_pattern: indexPattern, interval, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_diskio_ops.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_diskio_ops.ts index 84cec24e539b54..561cdf519353ea 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_diskio_ops.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_diskio_ops.ts @@ -5,9 +5,10 @@ */ import { InfraMetricModelCreator, InfraMetricModelMetricType } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const containerDiskIOOps: InfraMetricModelCreator = (timeField, indexPattern, interval) => ({ - id: 'containerDiskIOOps', + id: InfraMetric.containerDiskIOOps, requires: ['docker.disk'], index_pattern: indexPattern, interval, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_memory.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_memory.ts index 9844fde182fe79..dc984f54753285 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_memory.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_memory.ts @@ -5,9 +5,10 @@ */ import { InfraMetricModelCreator, InfraMetricModelMetricType } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const containerMemory: InfraMetricModelCreator = (timeField, indexPattern, interval) => ({ - id: 'containerMemory', + id: InfraMetric.containerMemory, requires: ['docker.memory'], index_pattern: indexPattern, interval, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_network_traffic.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_network_traffic.ts index 9c1ea8920f2186..a993d85cb41656 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_network_traffic.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_network_traffic.ts @@ -5,13 +5,14 @@ */ import { InfraMetricModelCreator, InfraMetricModelMetricType } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const containerNetworkTraffic: InfraMetricModelCreator = ( timeField, indexPattern, interval ) => ({ - id: 'containerNetworkTraffic', + id: InfraMetric.containerNetworkTraffic, requires: ['docker.network'], index_pattern: indexPattern, interval, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_overview.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_overview.ts index 24d1a93540104d..5e33f23a6983d5 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_overview.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/container/container_overview.ts @@ -5,9 +5,10 @@ */ import { InfraMetricModelCreator, InfraMetricModelMetricType } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const containerOverview: InfraMetricModelCreator = (timeField, indexPattern, interval) => ({ - id: 'containerOverview', + id: InfraMetric.containerOverview, requires: ['docker'], index_pattern: indexPattern, interval, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_cpu_usage.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_cpu_usage.ts index 0736149236c2ac..4988ee6b1ba0bb 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_cpu_usage.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_cpu_usage.ts @@ -9,13 +9,14 @@ import { InfraMetricModelMetricType, InfraMetricModel, } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const hostCpuUsage: InfraMetricModelCreator = ( timeField, indexPattern, interval ): InfraMetricModel => ({ - id: 'hostCpuUsage', + id: InfraMetric.hostCpuUsage, requires: ['system.cpu'], index_pattern: indexPattern, interval, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_filesystem.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_filesystem.ts index 6673d9293c8605..7377c830b21b17 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_filesystem.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_filesystem.ts @@ -9,13 +9,14 @@ import { InfraMetricModelMetricType, InfraMetricModel, } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const hostFilesystem: InfraMetricModelCreator = ( timeField, indexPattern, interval ): InfraMetricModel => ({ - id: 'hostFilesystem', + id: InfraMetric.hostFilesystem, requires: ['system.filesystem'], filter: 'system.filesystem.device_name:\\/*', index_pattern: indexPattern, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_k8s_cpu_cap.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_k8s_cpu_cap.ts index 21ef42a7dfc5cd..d671148a9c2100 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_k8s_cpu_cap.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_k8s_cpu_cap.ts @@ -9,13 +9,14 @@ import { InfraMetricModelMetricType, InfraMetricModel, } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const hostK8sCpuCap: InfraMetricModelCreator = ( timeField, indexPattern, interval ): InfraMetricModel => ({ - id: 'hostK8sCpuCap', + id: InfraMetric.hostK8sCpuCap, map_field_to: 'kubernetes.node.name', requires: ['kubernetes.node'], index_pattern: indexPattern, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_k8s_disk_cap.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_k8s_disk_cap.ts index f501485945a224..5c64809192dd74 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_k8s_disk_cap.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_k8s_disk_cap.ts @@ -9,13 +9,14 @@ import { InfraMetricModelMetricType, InfraMetricModel, } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const hostK8sDiskCap: InfraMetricModelCreator = ( timeField, indexPattern, interval ): InfraMetricModel => ({ - id: 'hostK8sDiskCap', + id: InfraMetric.hostK8sDiskCap, map_field_to: 'kubernetes.node.name', requires: ['kubernetes.node'], index_pattern: indexPattern, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_k8s_memory_cap.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_k8s_memory_cap.ts index 0381f86c7a49cc..9977a97db043ce 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_k8s_memory_cap.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_k8s_memory_cap.ts @@ -9,13 +9,14 @@ import { InfraMetricModelMetricType, InfraMetricModel, } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const hostK8sMemoryCap: InfraMetricModelCreator = ( timeField, indexPattern, interval ): InfraMetricModel => ({ - id: 'hostK8sMemoryCap', + id: InfraMetric.hostK8sMemoryCap, map_field_to: 'kubernetes.node.name', requires: ['kubernetes.node'], index_pattern: indexPattern, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_k8s_overview.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_k8s_overview.ts index e6739b02ec4a42..9930c3f0460668 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_k8s_overview.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_k8s_overview.ts @@ -9,13 +9,14 @@ import { InfraMetricModelMetricType, InfraMetricModel, } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const hostK8sOverview: InfraMetricModelCreator = ( timeField, indexPattern, interval ): InfraMetricModel => ({ - id: 'hostK8sOverview', + id: InfraMetric.hostK8sOverview, requires: ['kubernetes'], index_pattern: indexPattern, interval, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_k8s_pod_cap.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_k8s_pod_cap.ts index 95817a9a61b76f..3efd57a77e97da 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_k8s_pod_cap.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_k8s_pod_cap.ts @@ -9,13 +9,14 @@ import { InfraMetricModelMetricType, InfraMetricModel, } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const hostK8sPodCap: InfraMetricModelCreator = ( timeField, indexPattern, interval ): InfraMetricModel => ({ - id: 'hostK8sPodCap', + id: InfraMetric.hostK8sPodCap, requires: ['kubernetes.node'], map_field_to: 'kubernetes.node.name', index_pattern: indexPattern, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_load.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_load.ts index 4b397ca7507724..587998e73b93de 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_load.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_load.ts @@ -9,13 +9,14 @@ import { InfraMetricModelMetricType, InfraMetricModel, } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const hostLoad: InfraMetricModelCreator = ( timeField, indexPattern, interval ): InfraMetricModel => ({ - id: 'hostLoad', + id: InfraMetric.hostLoad, requires: ['system.cpu'], index_pattern: indexPattern, interval, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_memory_usage.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_memory_usage.ts index 8ac9a73a0e9e64..cf1d64f51ffb32 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_memory_usage.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_memory_usage.ts @@ -9,13 +9,14 @@ import { InfraMetricModelMetricType, InfraMetricModel, } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const hostMemoryUsage: InfraMetricModelCreator = ( timeField, indexPattern, interval ): InfraMetricModel => ({ - id: 'hostMemoryUsage', + id: InfraMetric.hostMemoryUsage, requires: ['system.memory'], index_pattern: indexPattern, interval, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_network_traffic.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_network_traffic.ts index 3095338ee5343f..15edff3f110116 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_network_traffic.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_network_traffic.ts @@ -9,13 +9,14 @@ import { InfraMetricModelMetricType, InfraMetricModel, } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const hostNetworkTraffic: InfraMetricModelCreator = ( timeField, indexPattern, interval ): InfraMetricModel => ({ - id: 'hostNetworkTraffic', + id: InfraMetric.hostNetworkTraffic, requires: ['system.network'], index_pattern: indexPattern, interval, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_system_overview.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_system_overview.ts index bf4080f625d169..1caa78fb5da882 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_system_overview.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/host/host_system_overview.ts @@ -9,13 +9,14 @@ import { InfraMetricModelMetricType, InfraMetricModel, } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const hostSystemOverview: InfraMetricModelCreator = ( timeField, indexPattern, interval ): InfraMetricModel => ({ - id: 'hostSystemOverview', + id: InfraMetric.hostSystemOverview, requires: ['system.cpu', 'system.memory', 'system.load', 'system.network'], index_pattern: indexPattern, interval, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/nginx/nginx_active_connections.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/nginx/nginx_active_connections.ts index 87c810c25d0904..9d8b30c8bf9675 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/nginx/nginx_active_connections.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/nginx/nginx_active_connections.ts @@ -9,13 +9,14 @@ import { InfraMetricModelMetricType, InfraMetricModel, } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const nginxActiveConnections: InfraMetricModelCreator = ( timeField, indexPattern, interval ): InfraMetricModel => ({ - id: 'nginxActiveConnections', + id: InfraMetric.nginxActiveConnections, requires: ['nginx.stubstatus'], index_pattern: indexPattern, interval, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/nginx/nginx_hits.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/nginx/nginx_hits.ts index 7d09c1e8e367f5..bc7340bd8ac235 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/nginx/nginx_hits.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/nginx/nginx_hits.ts @@ -8,14 +8,17 @@ import { InfraMetricModelCreator, InfraMetricModelMetricType, InfraMetricModel, + InfraMetricModelQueryType, } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; + export const nginxHits: InfraMetricModelCreator = ( timeField, indexPattern, interval ): InfraMetricModel => ({ - id: 'nginxHits', + id: InfraMetric.nginxHits, requires: ['nginx.access'], index_pattern: indexPattern, interval, @@ -31,7 +34,10 @@ export const nginxHits: InfraMetricModelCreator = ( }, ], split_mode: 'filter', - filter: 'http.response.status_code:[200 TO 299]', + filter: { + query: 'http.response.status_code:[200 TO 299]', + language: InfraMetricModelQueryType.lucene, + }, }, { id: '300s', @@ -42,7 +48,10 @@ export const nginxHits: InfraMetricModelCreator = ( }, ], split_mode: 'filter', - filter: 'http.response.status_code:[300 TO 399]', + filter: { + query: 'http.response.status_code:[300 TO 399]', + language: InfraMetricModelQueryType.lucene, + }, }, { id: '400s', @@ -53,7 +62,10 @@ export const nginxHits: InfraMetricModelCreator = ( }, ], split_mode: 'filter', - filter: 'http.response.status_code:[400 TO 499]', + filter: { + query: 'http.response.status_code:[400 TO 499]', + language: InfraMetricModelQueryType.lucene, + }, }, { id: '500s', @@ -64,7 +76,10 @@ export const nginxHits: InfraMetricModelCreator = ( }, ], split_mode: 'filter', - filter: 'http.response.status_code:[500 TO 599]', + filter: { + query: 'http.response.status_code:[500 TO 599]', + language: InfraMetricModelQueryType.lucene, + }, }, ], }); diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/nginx/nginx_request_rate.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/nginx/nginx_request_rate.ts index a3cdd23f430e8d..b3d6118e057afd 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/nginx/nginx_request_rate.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/nginx/nginx_request_rate.ts @@ -9,13 +9,14 @@ import { InfraMetricModelMetricType, InfraMetricModel, } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const nginxRequestRate: InfraMetricModelCreator = ( timeField, indexPattern, interval ): InfraMetricModel => ({ - id: 'nginxRequestRate', + id: InfraMetric.nginxRequestRate, requires: ['nginx.stubstatus'], index_pattern: indexPattern, interval, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/nginx/nginx_requests_per_connection.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/nginx/nginx_requests_per_connection.ts index 97b766cec31713..65114cdda06062 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/nginx/nginx_requests_per_connection.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/nginx/nginx_requests_per_connection.ts @@ -9,13 +9,14 @@ import { InfraMetricModelMetricType, InfraMetricModel, } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const nginxRequestsPerConnection: InfraMetricModelCreator = ( timeField, indexPattern, interval ): InfraMetricModel => ({ - id: 'nginxRequestsPerConnection', + id: InfraMetric.nginxRequestsPerConnection, requires: ['nginx.stubstatus'], index_pattern: indexPattern, interval, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/pod/pod_cpu_usage.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/pod/pod_cpu_usage.ts index 4e186dc9e4cdc2..d4c0fce42b51ca 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/pod/pod_cpu_usage.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/pod/pod_cpu_usage.ts @@ -9,13 +9,14 @@ import { InfraMetricModelMetricType, InfraMetricModel, } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const podCpuUsage: InfraMetricModelCreator = ( timeField, indexPattern, interval ): InfraMetricModel => ({ - id: 'podCpuUsage', + id: InfraMetric.podCpuUsage, requires: ['kubernetes.pod'], index_pattern: indexPattern, interval, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/pod/pod_log_usage.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/pod/pod_log_usage.ts index a424b417688de8..b1f184672fa73d 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/pod/pod_log_usage.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/pod/pod_log_usage.ts @@ -9,13 +9,14 @@ import { InfraMetricModelMetricType, InfraMetricModel, } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const podLogUsage: InfraMetricModelCreator = ( timeField, indexPattern, interval ): InfraMetricModel => ({ - id: 'podLogUsage', + id: InfraMetric.podLogUsage, requires: ['kubernetes.pod'], index_pattern: indexPattern, interval, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/pod/pod_memory_usage.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/pod/pod_memory_usage.ts index eea2062ad52f18..720e11b2b60683 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/pod/pod_memory_usage.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/pod/pod_memory_usage.ts @@ -9,13 +9,14 @@ import { InfraMetricModelMetricType, InfraMetricModel, } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const podMemoryUsage: InfraMetricModelCreator = ( timeField, indexPattern, interval ): InfraMetricModel => ({ - id: 'podMemoryUsage', + id: InfraMetric.podMemoryUsage, requires: ['kubernetes.pod'], index_pattern: indexPattern, interval, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/pod/pod_network_traffic.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/pod/pod_network_traffic.ts index ff462d57ddd4e3..9547fe75e6bbcf 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/pod/pod_network_traffic.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/pod/pod_network_traffic.ts @@ -9,13 +9,14 @@ import { InfraMetricModelMetricType, InfraMetricModel, } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const podNetworkTraffic: InfraMetricModelCreator = ( timeField, indexPattern, interval ): InfraMetricModel => ({ - id: 'podNetworkTraffic', + id: InfraMetric.podNetworkTraffic, requires: ['kubernetes.pod'], index_pattern: indexPattern, interval, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/pod/pod_overview.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/pod/pod_overview.ts index f475e18fcec170..bcdfa350f6aeb5 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/pod/pod_overview.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/models/pod/pod_overview.ts @@ -9,13 +9,14 @@ import { InfraMetricModelMetricType, InfraMetricModel, } from '../../adapter_types'; +import { InfraMetric } from '../../../../../graphql/types'; export const podOverview: InfraMetricModelCreator = ( timeField, indexPattern, interval ): InfraMetricModel => ({ - id: 'podOverview', + id: InfraMetric.podOverview, requires: ['kubernetes.pod'], index_pattern: indexPattern, interval, diff --git a/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/create_metrics_model.ts b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/create_metrics_model.ts index b5159479aec084..dd8e0edad76728 100644 --- a/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/create_metrics_model.ts +++ b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/create_metrics_model.ts @@ -6,9 +6,10 @@ import { InfraMetricModel, InfraMetricModelMetricType } from '../../../lib/adapters/metrics'; import { MetricsExplorerAggregation, MetricsExplorerRequest } from '../types'; +import { InfraMetric } from '../../../graphql/types'; export const createMetricModel = (options: MetricsExplorerRequest): InfraMetricModel => { return { - id: 'custom', + id: InfraMetric.custom, requires: [], index_pattern: options.indexPattern, interval: options.timerange.interval, diff --git a/x-pack/legacy/plugins/maps/public/angular/map.html b/x-pack/legacy/plugins/maps/public/angular/map.html index 13e6c038327c54..0eefcc7da9cd73 100644 --- a/x-pack/legacy/plugins/maps/public/angular/map.html +++ b/x-pack/legacy/plugins/maps/public/angular/map.html @@ -2,6 +2,7 @@
{ class MockSchemas {} @@ -25,7 +25,7 @@ const rightSource = { term: 'geo.dest', }; -const leftJoin = new LeftInnerJoin({ +const leftJoin = new InnerJoin({ leftField: 'iso2', right: rightSource }); @@ -73,7 +73,7 @@ describe('joinPropertiesToFeature', () => { it('Should coerce to string before joining', () => { - const leftJoin = new LeftInnerJoin({ + const leftJoin = new InnerJoin({ leftField: 'zipcode', right: rightSource }); @@ -115,7 +115,7 @@ describe('joinPropertiesToFeature', () => { it('Should handle falsy values', () => { - const leftJoin = new LeftInnerJoin({ + const leftJoin = new InnerJoin({ leftField: 'code', right: rightSource }); diff --git a/x-pack/legacy/plugins/maps/public/layers/layer.js b/x-pack/legacy/plugins/maps/public/layers/layer.js index f67ec10a1ec71d..d4851a9c414e94 100644 --- a/x-pack/legacy/plugins/maps/public/layers/layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/layer.js @@ -268,8 +268,8 @@ export class AbstractLayer { return false; } - syncLayerWithMb() { - //no-op by default + syncLayerWithMB() { + throw new Error('Should implement AbstractLayer#syncLayerWithMB'); } updateDueToExtent(source, prevMeta = {}, nextMeta = {}) { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js index b0f2c0bd0f5d1e..1d52dcc147c5b3 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js @@ -303,7 +303,9 @@ export class ESSearchSource extends AbstractESSource { const properties = indexPattern.flattenHit(hit); indexPattern.metaFields.forEach(metaField => { - delete properties[metaField]; + if (!this._descriptor.tooltipProperties.includes(metaField)) { + delete properties[metaField]; + } }); return properties; } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_join_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js similarity index 94% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_join_source.js rename to x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js index cd2b3151948c2b..ad404d9af1eadf 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_join_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js @@ -54,9 +54,9 @@ export function extractPropertiesMap(rawEsData, propertyNames, countPropertyName return propertiesMap; } -export class ESJoinSource extends AbstractESSource { +export class ESTermSource extends AbstractESSource { - static type = 'ES_JOIN_SOURCE'; + static type = 'ES_TERM_SOURCE'; static renderEditor({}) { @@ -65,11 +65,7 @@ export class ESJoinSource extends AbstractESSource { } hasCompleteConfig() { - if (_.has(this._descriptor, 'indexPatternId') && _.has(this._descriptor, 'term')) { - return true; - } - - return false; + return (_.has(this._descriptor, 'indexPatternId') && _.has(this._descriptor, 'term')); } getIndexPatternIds() { @@ -107,7 +103,7 @@ export class ESJoinSource extends AbstractESSource { searchSource.setField('aggs', aggConfigs.toDsl()); const requestName = `${this._descriptor.indexPatternTitle}.${this._descriptor.term}`; - const requestDesc = this.getJoinDescription(leftSourceName, leftFieldName); + const requestDesc = this._getRequestDescription(leftSourceName, leftFieldName); const rawEsData = await this._runEsQuery(requestName, searchSource, requestDesc); const metricPropertyNames = configStates @@ -130,7 +126,7 @@ export class ESJoinSource extends AbstractESSource { return false; } - getJoinDescription(leftSourceName, leftFieldName) { + _getRequestDescription(leftSourceName, leftFieldName) { const metrics = this._getValidMetrics().map(metric => { return metric.type !== 'count' ? `${metric.type} ${metric.field}` : 'count'; }); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_join_source.test.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js similarity index 96% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_join_source.test.js rename to x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js index 7e12a3880dc503..65bfd993c871a8 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_join_source.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ESJoinSource, extractPropertiesMap } from './es_join_source'; +import { ESTermSource, extractPropertiesMap } from './es_term_source'; jest.mock('../vector_layer', () => {}); jest.mock('ui/vis/editors/default/schemas', () => ({ @@ -37,7 +37,7 @@ const metricExamples = [ describe('getMetricFields', () => { it('should add default "count" metric when no metrics are provided', () => { - const source = new ESJoinSource({ + const source = new ESTermSource({ indexPatternTitle: indexPatternTitle, term: termFieldName, }); @@ -51,7 +51,7 @@ describe('getMetricFields', () => { }); it('should remove incomplete metric configurations', () => { - const source = new ESJoinSource({ + const source = new ESTermSource({ indexPatternTitle: indexPatternTitle, term: termFieldName, metrics: metricExamples, @@ -76,7 +76,7 @@ describe('_makeAggConfigs', () => { describe('no metrics', () => { let aggConfigs; beforeAll(() => { - const source = new ESJoinSource({ + const source = new ESTermSource({ indexPatternTitle: indexPatternTitle, term: termFieldName, }); @@ -112,7 +112,7 @@ describe('_makeAggConfigs', () => { describe('metrics', () => { let aggConfigs; beforeAll(() => { - const source = new ESJoinSource({ + const source = new ESTermSource({ indexPatternTitle: indexPatternTitle, term: 'myTermField', metrics: metricExamples diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js index d395f8ead1baaa..829ce8ea599f81 100644 --- a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js @@ -8,7 +8,7 @@ import turf from 'turf'; import React from 'react'; import { AbstractLayer } from './layer'; import { VectorStyle } from './styles/vector_style'; -import { LeftInnerJoin } from './joins/left_inner_join'; +import { InnerJoin } from './joins/inner_join'; import { GEO_JSON_TYPE, FEATURE_ID_PROPERTY_NAME, @@ -88,7 +88,7 @@ export class VectorLayer extends AbstractLayer { this._joins = []; if (options.layerDescriptor.joins) { options.layerDescriptor.joins.forEach((joinDescriptor) => { - this._joins.push(new LeftInnerJoin(joinDescriptor, this._source.getInspectorAdapters())); + this._joins.push(new InnerJoin(joinDescriptor, this._source.getInspectorAdapters())); }); } } @@ -430,9 +430,9 @@ export class VectorLayer extends AbstractLayer { let isFeatureVisible = true; for (let j = 0; j < joinStates.length; j++) { const joinState = joinStates[j]; - const leftInnerJoin = joinState.join; - const rightMetricFields = leftInnerJoin.getRightMetricFields(); - const canJoinOnCurrent = leftInnerJoin.joinPropertiesToFeature(feature, joinState.propertiesMap, rightMetricFields); + const InnerJoin = joinState.join; + const rightMetricFields = InnerJoin.getRightMetricFields(); + const canJoinOnCurrent = InnerJoin.joinPropertiesToFeature(feature, joinState.propertiesMap, rightMetricFields); isFeatureVisible = isFeatureVisible && canJoinOnCurrent; } diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.test.js b/x-pack/legacy/plugins/maps/public/layers/vector_layer.test.js index 85937c2783cce6..0a07582c57856d 100644 --- a/x-pack/legacy/plugins/maps/public/layers/vector_layer.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/vector_layer.test.js @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('./joins/left_inner_join', () => ({ - LeftInnerJoin: Object +jest.mock('./joins/inner_join', () => ({ + InnerJoin: Object })); jest.mock('./tooltips/join_tooltip_property', () => ({ diff --git a/x-pack/legacy/plugins/ml/public/access_denied/index.html b/x-pack/legacy/plugins/ml/public/access_denied/index.html index 33a31c655366dd..e085e089b27283 100644 --- a/x-pack/legacy/plugins/ml/public/access_denied/index.html +++ b/x-pack/legacy/plugins/ml/public/access_denied/index.html @@ -1,4 +1,4 @@ - +
diff --git a/x-pack/legacy/plugins/ml/public/components/navigation_menu/navigation_menu.tsx b/x-pack/legacy/plugins/ml/public/components/navigation_menu/navigation_menu.tsx index 752b0ec4640aad..dfbc2614411548 100644 --- a/x-pack/legacy/plugins/ml/public/components/navigation_menu/navigation_menu.tsx +++ b/x-pack/legacy/plugins/ml/public/components/navigation_menu/navigation_menu.tsx @@ -6,39 +6,40 @@ import React, { Fragment, FC } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +// @ts-ignore +import { isFullLicense } from '../../license/check_license'; + import { TopNav } from './top_nav'; import { Tabs } from './tabs'; +const tabSupport = [ + 'jobs', + 'settings', + 'data_frames', + 'datavisualizer', + 'filedatavisualizer', + 'timeseriesexplorer', + 'access-denied', + 'explorer', +]; + interface Props { - dateFormat: string; - disableLinks: boolean; - forceRefresh: () => void; - showTabs: boolean; tabId: string; - timeHistory: any; - timefilter: any; } -export const NavigationMenu: FC = ({ - dateFormat, - disableLinks, - forceRefresh, - showTabs, - tabId, - timeHistory, - timefilter, -}) => ( - - - - - - - {showTabs && } - -); +export const NavigationMenu: FC = ({ tabId }) => { + const disableLinks = isFullLicense() === false; + const showTabs = tabSupport.includes(tabId); + + return ( + + + + + + + {showTabs && } + + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/components/navigation_menu/navigation_menu_react_wrapper_directive.js b/x-pack/legacy/plugins/ml/public/components/navigation_menu/navigation_menu_react_wrapper_directive.js index b9853967c64dbc..7c8764c45b964f 100644 --- a/x-pack/legacy/plugins/ml/public/components/navigation_menu/navigation_menu_react_wrapper_directive.js +++ b/x-pack/legacy/plugins/ml/public/components/navigation_menu/navigation_menu_react_wrapper_directive.js @@ -7,47 +7,28 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { NavigationMenu } from './navigation_menu'; -import { isFullLicense } from '../../license/check_license'; -import { timeHistory } from 'ui/timefilter/time_history'; + import { uiModules } from 'ui/modules'; -import { timefilter } from 'ui/timefilter'; const module = uiModules.get('apps/ml'); -import { mlTimefilterRefresh$ } from '../../services/timefilter_refresh_service'; import 'ui/directives/kbn_href'; +import chrome from 'ui/chrome'; +import { timefilter } from 'ui/timefilter'; +import { timeHistory } from 'ui/timefilter/time_history'; +import { NavigationMenuContext } from '../../util/context_utils'; + +import { NavigationMenu } from './navigation_menu'; -module.directive('mlNavMenu', function (config) { +module.directive('mlNavMenu', function () { return { restrict: 'E', transclude: true, link: function (scope, element, attrs) { - const { name } = attrs; - let showTabs = false; - - if (name === 'jobs' || - name === 'settings' || - name === 'data_frames' || - name === 'datavisualizer' || - name === 'filedatavisualizer' || - name === 'timeseriesexplorer' || - name === 'access-denied' || - name === 'explorer') { - showTabs = true; - } - - const props = { - dateFormat: config.get('dateFormat'), - disableLinks: (isFullLicense() === false), - showTabs, - tabId: name, - timeHistory, - timefilter, - forceRefresh: () => mlTimefilterRefresh$.next() - }; - - ReactDOM.render(React.createElement(NavigationMenu, props), + ReactDOM.render( + + + , element[0] ); diff --git a/x-pack/legacy/plugins/ml/public/components/navigation_menu/top_nav/top_nav.tsx b/x-pack/legacy/plugins/ml/public/components/navigation_menu/top_nav/top_nav.tsx index 7da5668f08129d..54f24d0765f5c7 100644 --- a/x-pack/legacy/plugins/ml/public/components/navigation_menu/top_nav/top_nav.tsx +++ b/x-pack/legacy/plugins/ml/public/components/navigation_menu/top_nav/top_nav.tsx @@ -4,36 +4,37 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, Fragment, useState, useEffect } from 'react'; +import React, { FC, Fragment, useContext, useState, useEffect } from 'react'; import { EuiSuperDatePicker } from '@elastic/eui'; -import { TimeHistory, TimeRange } from 'src/legacy/ui/public/timefilter/time_history'; -import { Timefilter } from 'ui/timefilter'; +import { TimeHistory, TimeRange } from 'ui/timefilter/time_history'; -interface Props { - dateFormat: string; - forceRefresh: () => void; - timeHistory: TimeHistory; - timefilter: Timefilter; -} +import { mlTimefilterRefresh$ } from '../../../services/timefilter_refresh_service'; +import { NavigationMenuContext } from '../../../util/context_utils'; interface Duration { start: string; end: string; } -function getRecentlyUsedRanges(timeHistory: TimeHistory): Duration[] { - return timeHistory.get().map(({ from, to }: TimeRange) => { - return { - start: from, - end: to, - }; - }); +function getRecentlyUsedRangesFactory(timeHistory: TimeHistory) { + return function(): Duration[] { + return timeHistory.get().map(({ from, to }: TimeRange) => { + return { + start: from, + end: to, + }; + }); + }; } -export const TopNav: FC = ({ dateFormat, forceRefresh, timeHistory, timefilter }) => { +export const TopNav: FC = () => { + const navigationMenuContext = useContext(NavigationMenuContext); + const timefilter = navigationMenuContext.timefilter; + const getRecentlyUsedRanges = getRecentlyUsedRangesFactory(navigationMenuContext.timeHistory); + const [refreshInterval, setRefreshInterval] = useState(timefilter.getRefreshInterval()); const [time, setTime] = useState(timefilter.getTime()); - const [recentlyUsedRanges, setRecentlyUsedRanges] = useState(getRecentlyUsedRanges(timeHistory)); + const [recentlyUsedRanges, setRecentlyUsedRanges] = useState(getRecentlyUsedRanges()); const [isAutoRefreshSelectorEnabled, setIsAutoRefreshSelectorEnabled] = useState( timefilter.isAutoRefreshSelectorEnabled ); @@ -41,6 +42,8 @@ export const TopNav: FC = ({ dateFormat, forceRefresh, timeHistory, timef timefilter.isTimeRangeSelectorEnabled ); + const dateFormat = navigationMenuContext.chrome.getUiSettingsClient().get('dateFormat'); + useEffect(() => { timefilter.on('refreshIntervalUpdate', timefilterUpdateListener); timefilter.on('timeUpdate', timefilterUpdateListener); @@ -70,7 +73,7 @@ export const TopNav: FC = ({ dateFormat, forceRefresh, timeHistory, timef // Update timefilter for controllers listening for changes timefilter.setTime(newTime); setTime(newTime); - setRecentlyUsedRanges(getRecentlyUsedRanges(timeHistory)); + setRecentlyUsedRanges(getRecentlyUsedRanges()); } function updateInterval({ @@ -101,7 +104,7 @@ export const TopNav: FC = ({ dateFormat, forceRefresh, timeHistory, timef isAutoRefreshOnly={!isTimeRangeSelectorEnabled} refreshInterval={refreshInterval.value} onTimeChange={updateFilter} - onRefresh={forceRefresh} + onRefresh={() => mlTimefilterRefresh$.next()} onRefreshChange={updateInterval} recentlyUsedRanges={recentlyUsedRanges} dateFormat={dateFormat} diff --git a/x-pack/legacy/plugins/ml/public/data_frame/common/transform.ts b/x-pack/legacy/plugins/ml/public/data_frame/common/transform.ts index e66abc322d1aca..17721b1142b63c 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/common/transform.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame/common/transform.ts @@ -47,6 +47,7 @@ export interface CreateRequestBody extends PreviewRequestBody { export interface DataFrameTransformPivotConfig extends CreateRequestBody { id: DataFrameTransformId; + mode?: string; // added property on client side to allow filtering by this field } // Don't allow intervals of '0', don't allow floating intervals. diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/access_denied/directive.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/access_denied/directive.tsx index fd8b3bc480ec58..ba43564d1573ba 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/access_denied/directive.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/access_denied/directive.tsx @@ -14,8 +14,13 @@ import uiChrome from 'ui/chrome'; const module = uiModules.get('apps/ml', ['react']); import { I18nContext } from 'ui/i18n'; +import chrome from 'ui/chrome'; +import { timefilter } from 'ui/timefilter'; +import { timeHistory } from 'ui/timefilter/time_history'; import { InjectorService } from '../../../../common/types/angular'; +import { NavigationMenuContext } from '../../../util/context_utils'; + import { Page } from './page'; module.directive('mlDataFrameAccessDenied', ($injector: InjectorService) => { @@ -34,9 +39,14 @@ module.directive('mlDataFrameAccessDenied', ($injector: InjectorService) => { kbnUrl.redirect('/data_frames'); }; - const props = { goToKibana, retry }; - - ReactDOM.render({React.createElement(Page, props)}, element[0]); + ReactDOM.render( + + + + + , + element[0] + ); element.on('$destroy', () => { ReactDOM.unmountComponentAtNode(element[0]); diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/access_denied/page.test.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/access_denied/page.test.tsx index d38cf18b4a78d1..378600ac44e1a0 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/access_denied/page.test.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/access_denied/page.test.tsx @@ -12,6 +12,10 @@ import { I18nProvider } from '@kbn/i18n/react'; import { Page } from './page'; +jest.mock('../../../components/navigation_menu/navigation_menu', () => ({ + NavigationMenu: () =>
, +})); + afterEach(cleanup); describe('Data Frame: Access denied ', () => { diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/access_denied/page.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/access_denied/page.tsx index fa41b5490b7cd7..0966bb184afeb2 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/access_denied/page.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/access_denied/page.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { SFC } from 'react'; +import React, { FC, Fragment } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -24,70 +24,75 @@ import { EuiTitle, } from '@elastic/eui'; +import { NavigationMenu } from '../../../components/navigation_menu/navigation_menu'; + interface PageProps { goToKibana: () => void; retry: () => void; } -export const Page: SFC = ({ goToKibana, retry }) => ( - - - - - -

- -

-
-
-
- - - - -

- kibana_user, - dataFrameUserParam: ( - data_frame_transforms_user - ), - br:
, - }} - /> -

-
-
- - - - - - - - - - - - - -
-
-
+export const Page: FC = ({ goToKibana, retry }) => ( + + + + + + + +

+ +

+
+
+
+ + + + +

+ kibana_user, + dataFrameUserParam: ( + data_frame_transforms_user + ), + br:
, + }} + /> +

+
+
+ + + + + + + + + + + + + +
+
+
+
); diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/access_denied/route.ts b/x-pack/legacy/plugins/ml/public/data_frame/pages/access_denied/route.ts index 63689b4ec551e1..50cf8d05aa434a 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/access_denied/route.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/access_denied/route.ts @@ -9,7 +9,7 @@ import uiRoutes from 'ui/routes'; // @ts-ignore import { getDataFrameBreadcrumbs } from '../../breadcrumbs'; -const template = ``; +const template = ``; uiRoutes.when('/data_frames/access-denied', { template, diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/directive.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/directive.tsx index 3b86138027d1c4..928d5dfce3760b 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/directive.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/directive.tsx @@ -7,6 +7,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import chrome from 'ui/chrome'; // @ts-ignore import { uiModules } from 'ui/modules'; const module = uiModules.get('apps/ml', ['react']); @@ -15,6 +16,7 @@ import { IndexPattern } from 'ui/index_patterns'; import { I18nContext } from 'ui/i18n'; import { IPrivate } from 'ui/private'; import { timefilter } from 'ui/timefilter'; +import { timeHistory } from 'ui/timefilter/time_history'; import { InjectorService } from '../../../../common/types/angular'; // @ts-ignore @@ -26,6 +28,7 @@ type CreateSearchItems = () => { combinedQuery: any; }; +import { NavigationMenuContext } from '../../../util/context_utils'; import { KibanaContext } from '../../common'; import { Page } from './page'; @@ -56,9 +59,11 @@ module.directive('mlNewDataFrame', ($injector: InjectorService) => { ReactDOM.render( - - {React.createElement(Page)} - + + + + + , element[0] ); diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/page.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/page.tsx index 3d554a62638bee..2aaa1fb8a6ac34 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/page.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/page.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { SFC } from 'react'; +import React, { FC, Fragment } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -20,39 +20,43 @@ import { EuiTitle, } from '@elastic/eui'; +import { NavigationMenu } from '../../../components/navigation_menu/navigation_menu'; import { Wizard } from './components/wizard'; -export const Page: SFC = () => ( - - - - - -

- -   - -

-
-
-
- - - - -
-
+export const Page: FC = () => ( + + + + + + + +

+ +   + +

+
+
+
+ + + + +
+
+
); diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/route.ts b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/route.ts index 20f102ea1d484b..e58819fd45a0d3 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/route.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/route.ts @@ -21,7 +21,7 @@ import { getDataFrameIndexOrSearchBreadcrumbs, } from '../../breadcrumbs'; -const wizardTemplate = ``; +const wizardTemplate = ``; uiRoutes.when('/data_frames/new_transform/step/pivot?', { template: wizardTemplate, diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/__snapshots__/page.test.tsx.snap b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/__snapshots__/page.test.tsx.snap index c2beb3b9126408..88f8d418397526 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/__snapshots__/page.test.tsx.snap +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/__snapshots__/page.test.tsx.snap @@ -1,62 +1,67 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Data Frame: Job List Minimal initialization 1`] = ` - - + + - - - -

- - -   - - -

-
-
- - - - - - - - - - -
- - - - - - -
-
+ + +

+ + +   + + +

+
+
+ + + + + + + + + + + + + + + + + + + + `; diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/_transform_table.scss b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/_transform_table.scss index a9d5ad6d48e63e..dbfd6c9c429293 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/_transform_table.scss +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/_transform_table.scss @@ -14,3 +14,10 @@ animation: none !important; } } +.mlTransformProgressBar { + margin-bottom: $euiSizeM; +} + +.mlTaskStateBadge, .mlTaskModeBadge { + max-width: 100px; +} diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/columns.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/columns.tsx index 8ca08085b50876..a99aeb6839cd0b 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/columns.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/columns.tsx @@ -22,6 +22,7 @@ import { DATA_FRAME_TASK_STATE, DataFrameTransformListColumn, DataFrameTransformListRow, + DataFrameTransformState, } from './common'; import { getActions } from './actions'; @@ -31,6 +32,29 @@ enum TASK_STATE_COLOR { stopped = 'hollow', } +export const getTaskStateBadge = ( + state: DataFrameTransformState['task_state'], + reason?: DataFrameTransformState['reason'] +) => { + const color = TASK_STATE_COLOR[state]; + + if (state === DATA_FRAME_TASK_STATE.FAILED && reason !== undefined) { + return ( + + + {state} + + + ); + } + + return ( + + {state} + + ); +}; + export const getColumns = ( expandedRowItemIds: DataFrameTransformId[], setExpandedRowItemIds: React.Dispatch> @@ -104,27 +128,16 @@ export const getColumns = ( sortable: (item: DataFrameTransformListRow) => item.state.task_state, truncateText: true, render(item: DataFrameTransformListRow) { - const color = TASK_STATE_COLOR[item.state.task_state]; - - if (item.state.task_state === DATA_FRAME_TASK_STATE.FAILED) { - return ( - - {item.state.task_state} - - ); - } - - return {item.state.task_state}; + return getTaskStateBadge(item.state.task_state, item.state.reason); }, width: '100px', }, { name: i18n.translate('xpack.ml.dataframe.mode', { defaultMessage: 'Mode' }), - sortable: (item: DataFrameTransformListRow) => - typeof item.config.sync !== 'undefined' ? 'continuous' : 'batch', + sortable: (item: DataFrameTransformListRow) => item.config.mode, truncateText: true, render(item: DataFrameTransformListRow) { - const mode = typeof item.config.sync !== 'undefined' ? 'continuous' : 'batch'; + const mode = item.config.mode; const color = 'hollow'; return {mode}; }, diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/common.ts b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/common.ts index 9eec66285163e2..1fb90b2d60e20d 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/common.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/common.ts @@ -14,6 +14,25 @@ export enum DATA_FRAME_TASK_STATE { STOPPED = 'stopped', } +export enum DATA_FRAME_MODE { + BATCH = 'batch', + CONTINUOUS = 'continuous', +} + +export interface Clause { + type: string; + value: string; + match: string; +} + +export interface Query { + ast: { + clauses: Clause[]; + }; + text: string; + syntax: any; +} + export interface DataFrameTransformState { checkpoint: number; current_position: Dictionary; diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_list.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_list.tsx index faa3a5a712dc49..badc4eabd541db 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_list.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_list.tsx @@ -8,7 +8,7 @@ import React, { Fragment, SFC, useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiButtonEmpty, EuiCallOut, EuiEmptyPrompt, SortDirection } from '@elastic/eui'; +import { EuiBadge, EuiButtonEmpty, EuiCallOut, EuiEmptyPrompt, SortDirection } from '@elastic/eui'; import { DataFrameTransformId, @@ -16,11 +16,16 @@ import { useRefreshTransformList, } from '../../../../common'; import { checkPermission } from '../../../../../privilege/check_privilege'; +import { getTaskStateBadge } from './columns'; import { DataFrameTransformListColumn, DataFrameTransformListRow, ItemIdToExpandedRowMap, + DATA_FRAME_TASK_STATE, + DATA_FRAME_MODE, + Query, + Clause, } from './common'; import { getTransformsFactory } from '../../services/transform_service'; import { getColumns } from './columns'; @@ -44,15 +49,26 @@ function getItemIdToExpandedRowMap( ); } +function stringMatch(str: string | undefined, substr: string) { + return ( + typeof str === 'string' && + typeof substr === 'string' && + (str.toLowerCase().match(substr.toLowerCase()) === null) === false + ); +} + export const DataFrameTransformList: SFC = () => { const [isInitialized, setIsInitialized] = useState(false); const [isLoading, setIsLoading] = useState(false); const [blockRefresh, setBlockRefresh] = useState(false); + const [filterActive, setFilterActive] = useState(false); const [transforms, setTransforms] = useState([]); + const [filteredTransforms, setFilteredTransforms] = useState([]); const [expandedRowItemIds, setExpandedRowItemIds] = useState([]); const [errorMessage, setErrorMessage] = useState(undefined); + const [searchError, setSearchError] = useState(undefined); const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); @@ -72,10 +88,88 @@ export const DataFrameTransformList: SFC = () => { blockRefresh ); // Subscribe to the refresh observable to trigger reloading the transform list. - useRefreshTransformList({ isLoading: setIsLoading, onRefresh: () => getTransforms(true) }); + useRefreshTransformList({ + isLoading: setIsLoading, + onRefresh: () => getTransforms(true), + }); // Call useRefreshInterval() after the subscription above is set up. useRefreshInterval(setBlockRefresh); + const onQueryChange = ({ query, error }: { query: Query; error: any }) => { + if (error) { + setSearchError(error.message); + } else { + let clauses: Clause[] = []; + if (query && query.ast !== undefined && query.ast.clauses !== undefined) { + clauses = query.ast.clauses; + } + if (clauses.length > 0) { + setFilterActive(true); + filterTransforms(clauses); + } else { + setFilterActive(false); + } + setSearchError(undefined); + } + }; + + const filterTransforms = (clauses: Clause[]) => { + setIsLoading(true); + // keep count of the number of matches we make as we're looping over the clauses + // we only want to return transforms which match all clauses, i.e. each search term is ANDed + // { transform-one: { transform: { id: transform-one, config: {}, state: {}, ... }, count: 0 }, transform-two: {...} } + const matches: Record = transforms.reduce((p: Record, c) => { + p[c.id] = { + transform: c, + count: 0, + }; + return p; + }, {}); + + clauses.forEach(c => { + // the search term could be negated with a minus, e.g. -bananas + const bool = c.match === 'must'; + let ts = []; + + if (c.type === 'term') { + // filter term based clauses, e.g. bananas + // match on id and description + // if the term has been negated, AND the matches + if (bool === true) { + ts = transforms.filter( + transform => + stringMatch(transform.id, c.value) === bool || + stringMatch(transform.config.description, c.value) === bool + ); + } else { + ts = transforms.filter( + transform => + stringMatch(transform.id, c.value) === bool && + stringMatch(transform.config.description, c.value) === bool + ); + } + } else { + // filter other clauses, i.e. the mode and status filters + if (Array.isArray(c.value)) { + // the status value is an array of string(s) e.g. ['failed', 'stopped'] + ts = transforms.filter(transform => c.value.includes(transform.state.task_state)); + } else { + ts = transforms.filter(transform => transform.config.mode === c.value); + } + } + + ts.forEach(t => matches[t.id].count++); + }); + + // loop through the matches and return only transforms which have match all the clauses + const filtered = Object.values(matches) + .filter(m => (m && m.count) >= clauses.length) + .map(m => m.transform); + + setFilteredTransforms(filtered); + setIsLoading(false); + }; + // Before the transforms have been loaded for the first time, display the loading indicator only. // Otherwise a user would see 'No data frame transforms found' during the initial loading. if (!isInitialized) { @@ -143,6 +237,41 @@ export const DataFrameTransformList: SFC = () => { hidePerPageOptions: false, }; + const search = { + onChange: onQueryChange, + box: { + incremental: true, + }, + filters: [ + { + type: 'field_value_selection', + field: 'state.task_state', + name: i18n.translate('xpack.ml.dataframe.statusFilter', { defaultMessage: 'Status' }), + multiSelect: 'or', + options: Object.values(DATA_FRAME_TASK_STATE).map(val => ({ + value: val, + name: val, + view: getTaskStateBadge(val), + })), + }, + { + type: 'field_value_selection', + field: 'config.mode', + name: i18n.translate('xpack.ml.dataframe.modeFilter', { defaultMessage: 'Mode' }), + multiSelect: false, + options: Object.values(DATA_FRAME_MODE).map(val => ({ + value: val, + name: val, + view: ( + + {val} + + ), + })), + }, + ], + }; + const onTableChange = ({ page = { index: 0, size: 10 }, sort = { field: DataFrameTransformListColumn.id, direction: SortDirection.ASC }, @@ -165,15 +294,17 @@ export const DataFrameTransformList: SFC = () => { diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_table.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_table.tsx index 9677026bd0f7d4..aa810105a6a54a 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_table.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_table.tsx @@ -22,8 +22,10 @@ import { ItemIdToExpandedRowMap } from './common'; export const ProgressBar = ({ isLoading = false }) => { return ( - {isLoading && } - {!isLoading && } + {isLoading && } + {!isLoading && ( + + )} ); }; diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/directive.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/directive.tsx index b0a89a9adcba6a..b1a862a914ab2b 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/directive.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/directive.tsx @@ -10,8 +10,12 @@ import ReactDOM from 'react-dom'; // @ts-ignore import { uiModules } from 'ui/modules'; const module = uiModules.get('apps/ml', ['react']); +import chrome from 'ui/chrome'; +import { timefilter } from 'ui/timefilter'; +import { timeHistory } from 'ui/timefilter/time_history'; import { I18nContext } from 'ui/i18n'; +import { NavigationMenuContext } from '../../../util/context_utils'; import { Page } from './page'; module.directive('mlDataFramePage', () => { @@ -19,7 +23,14 @@ module.directive('mlDataFramePage', () => { scope: {}, restrict: 'E', link: (scope: ng.IScope, element: ng.IAugmentedJQuery) => { - ReactDOM.render({React.createElement(Page)}, element[0]); + ReactDOM.render( + + + + + , + element[0] + ); element.on('$destroy', () => { ReactDOM.unmountComponentAtNode(element[0]); diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/page.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/page.tsx index 326581be2e8106..c0186c0a33c59d 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/page.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/page.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { SFC, useState } from 'react'; +import React, { FC, Fragment, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -23,59 +23,63 @@ import { EuiTitle, } from '@elastic/eui'; +import { NavigationMenu } from '../../../components/navigation_menu/navigation_menu'; import { useRefreshTransformList } from '../../common'; import { CreateTransformButton } from './components/create_transform_button'; import { DataFrameTransformList } from './components/transform_list'; import { RefreshTransformListButton } from './components/refresh_transform_list_button'; -export const Page: SFC = () => { +export const Page: FC = () => { const [isLoading, setIsLoading] = useState(false); const { refresh } = useRefreshTransformList({ isLoading: setIsLoading }); return ( - - - - - -

- -   - -

-
-
- - - - - - - - - - -
- - - - - - -
-
+ + + + + + + +

+ +   + +

+
+
+ + + + + + + + + + +
+ + + + + + +
+
+
); }; diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/route.ts b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/route.ts index 26f3c897cb25e0..6f3b4576b10ce4 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/route.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/route.ts @@ -15,7 +15,7 @@ import { loadIndexPatterns } from '../../../util/index_utils'; // @ts-ignore import { getDataFrameBreadcrumbs } from '../../breadcrumbs'; -const template = ``; +const template = ``; uiRoutes.when('/data_frames/?', { template, diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/get_transforms.ts b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/get_transforms.ts index 8f1aac1f15330f..33060090765220 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/get_transforms.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/get_transforms.ts @@ -16,6 +16,7 @@ import { DataFrameTransformListRow, DataFrameTransformState, DataFrameTransformStats, + DATA_FRAME_MODE, } from '../../components/transform_list/common'; interface DataFrameTransformStateStats { @@ -92,6 +93,12 @@ export const getTransformsFactory = ( if (stats === undefined) { return reducedtableRows; } + + config.mode = + typeof config.sync !== 'undefined' + ? DATA_FRAME_MODE.CONTINUOUS + : DATA_FRAME_MODE.BATCH; + // Table with expandable rows requires `id` on the outer most level reducedtableRows.push({ config, diff --git a/x-pack/legacy/plugins/ml/public/datavisualizer/datavisualizer.html b/x-pack/legacy/plugins/ml/public/datavisualizer/datavisualizer.html index d39ae5b57c1b0e..7f84fe2678ab4d 100644 --- a/x-pack/legacy/plugins/ml/public/datavisualizer/datavisualizer.html +++ b/x-pack/legacy/plugins/ml/public/datavisualizer/datavisualizer.html @@ -1,4 +1,4 @@ - +
diff --git a/x-pack/legacy/plugins/ml/public/datavisualizer/selector/datavisualizer_selector.js b/x-pack/legacy/plugins/ml/public/datavisualizer/selector/datavisualizer_selector.js index b4a7ca32e45f63..9e5df0de494265 100644 --- a/x-pack/legacy/plugins/ml/public/datavisualizer/selector/datavisualizer_selector.js +++ b/x-pack/legacy/plugins/ml/public/datavisualizer/selector/datavisualizer_selector.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { Fragment } from 'react'; import { EuiButton, @@ -22,7 +22,12 @@ import { import { isFullLicense } from '../../license/check_license'; import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; +import chrome from 'ui/chrome'; import { timefilter } from 'ui/timefilter'; +import { timeHistory } from 'ui/timefilter/time_history'; + +import { NavigationMenuContext } from '../../util/context_utils'; +import { NavigationMenu } from '../../components/navigation_menu/navigation_menu'; function startTrialDescription() { return ( @@ -32,159 +37,151 @@ function startTrialDescription() { defaultMessage="To experience the full Machine Learning features that a {platinumSubscriptionLink} offers, start a 30-day trial." values={{ platinumSubscriptionLink: ( - + - ) + ), }} /> ); } - export const DatavisualizerSelector = injectI18n(function (props) { - timefilter.disableTimeRangeSelector(); timefilter.disableAutoRefreshSelector(); - const startTrialVisible = (isFullLicense() === false); + const startTrialVisible = isFullLicense() === false; return ( - - - - - -

+ + + + + + + +

+ +

+
+
+
+ + + + -

-
-
-
- - - - - - - - - - - - } - title={ - - } - description={ - - } - betaBadgeLabel={props.intl.formatMessage({ - id: 'xpack.ml.datavisualizer.selector.experimentalBadgeLabel', - defaultMessage: 'Experimental' - })} - betaBadgeTooltipContent={ - - } - footer={ - + + + + + + + } + title={ - - } - data-test-subj="mlDataVisualizerCardImportData" - /> - - - } - title={ - - } - description={ - - } - footer={ - + } + description={ + + } + betaBadgeLabel={props.intl.formatMessage({ + id: 'xpack.ml.datavisualizer.selector.experimentalBadgeLabel', + defaultMessage: 'Experimental', + })} + betaBadgeTooltipContent={ + + } + footer={ + + + + } + data-test-subj="mlDataVisualizerCardImportData" + /> + + + } + title={ + + } + description={ - - } - data-test-subj="mlDataVisualizerCardIndexData" - /> - - - {startTrialVisible === true && - - - - - - - } - description={startTrialDescription()} - footer={ - + + } + data-test-subj="mlDataVisualizerCardIndexData" + /> + + + {startTrialVisible === true && ( + + + + + + - - } - /> - - - - } -
-
+ } + description={startTrialDescription()} + footer={ + + + + } + /> + + + + )} + + + ); }); diff --git a/x-pack/legacy/plugins/ml/public/datavisualizer/selector/directive.js b/x-pack/legacy/plugins/ml/public/datavisualizer/selector/directive.js index 008f182b90415b..84d2d662c1872c 100644 --- a/x-pack/legacy/plugins/ml/public/datavisualizer/selector/directive.js +++ b/x-pack/legacy/plugins/ml/public/datavisualizer/selector/directive.js @@ -18,7 +18,6 @@ import uiRoutes from 'ui/routes'; const template = `
- `; diff --git a/x-pack/legacy/plugins/ml/public/explorer/explorer.html b/x-pack/legacy/plugins/ml/public/explorer/explorer.html index 94e448d35956a5..db5b12b7eeb9f8 100644 --- a/x-pack/legacy/plugins/ml/public/explorer/explorer.html +++ b/x-pack/legacy/plugins/ml/public/explorer/explorer.html @@ -1,4 +1,4 @@ - +
diff --git a/x-pack/legacy/plugins/ml/public/file_datavisualizer/file_datavisualizer.js b/x-pack/legacy/plugins/ml/public/file_datavisualizer/file_datavisualizer.js index 3a0eeba4890ddd..c6ee2e2761d9c5 100644 --- a/x-pack/legacy/plugins/ml/public/file_datavisualizer/file_datavisualizer.js +++ b/x-pack/legacy/plugins/ml/public/file_datavisualizer/file_datavisualizer.js @@ -7,13 +7,22 @@ import { FileDataVisualizerView } from './components/file_datavisualizer_view'; import React from 'react'; + +import chrome from 'ui/chrome'; import { timefilter } from 'ui/timefilter'; +import { timeHistory } from 'ui/timefilter/time_history'; + +import { NavigationMenuContext } from '../util/context_utils'; +import { NavigationMenu } from '../components/navigation_menu/navigation_menu'; export function FileDataVisualizerPage({ indexPatterns, kibanaConfig }) { timefilter.disableTimeRangeSelector(); timefilter.disableAutoRefreshSelector(); return ( - + + + + ); } diff --git a/x-pack/legacy/plugins/ml/public/file_datavisualizer/file_datavisualizer_directive.js b/x-pack/legacy/plugins/ml/public/file_datavisualizer/file_datavisualizer_directive.js index ee5731edcd7d83..c8431f4a376c11 100644 --- a/x-pack/legacy/plugins/ml/public/file_datavisualizer/file_datavisualizer_directive.js +++ b/x-pack/legacy/plugins/ml/public/file_datavisualizer/file_datavisualizer_directive.js @@ -24,7 +24,6 @@ import uiRoutes from 'ui/routes'; const template = `
- `; diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/directive.js b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/directive.js index 0b39a98284cd01..e39d599d0f3c47 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/directive.js +++ b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/directive.js @@ -17,10 +17,14 @@ import { checkGetJobsPrivilege } from 'plugins/ml/privilege/check_privilege'; import { getMlNodeCount } from 'plugins/ml/ml_nodes_check/check_ml_nodes'; import { getJobManagementBreadcrumbs } from 'plugins/ml/jobs/breadcrumbs'; import { loadNewJobDefaults } from 'plugins/ml/jobs/new_job/utils/new_job_defaults'; +import { NavigationMenuContext } from '../../util/context_utils'; +import chrome from 'ui/chrome'; import uiRoutes from 'ui/routes'; +import { timefilter } from 'ui/timefilter'; +import { timeHistory } from 'ui/timefilter/time_history'; -const template = ``; +const template = ``; uiRoutes .when('/jobs/?', { @@ -45,7 +49,9 @@ module.directive('jobsPage', function () { link: (scope, element) => { ReactDOM.render( - {React.createElement(JobsPage, { angularWrapperScope: scope })} + + + , element[0] ); diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/jobs.js b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/jobs.js index 2f363d5db17cb5..5bdf9c1944e43a 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/jobs.js +++ b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/jobs.js @@ -4,11 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ +import React, { Fragment } from 'react'; -import { JobsListView } from './components/jobs_list_view'; -import React from 'react'; +import { NavigationMenu } from '../../components/navigation_menu/navigation_menu'; +import { JobsListView } from './components/jobs_list_view'; export const JobsPage = (props) => ( - + + + + ); diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job/advanced/new_job.html b/x-pack/legacy/plugins/ml/public/jobs/new_job/advanced/new_job.html index efaf36d0ecfe45..49f7c63d6a34eb 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job/advanced/new_job.html +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job/advanced/new_job.html @@ -1,4 +1,4 @@ - +
diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/multi_metric/create_job/create_job.html b/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/multi_metric/create_job/create_job.html index 523551bb97052d..239a85b26a9d6b 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/multi_metric/create_job/create_job.html +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/multi_metric/create_job/create_job.html @@ -1,4 +1,4 @@ - +
diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/population/create_job/create_job.html b/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/population/create_job/create_job.html index 4bcb7eb7226a91..cc8e23756ac11f 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/population/create_job/create_job.html +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/population/create_job/create_job.html @@ -1,4 +1,4 @@ - + diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/recognize/create_job/create_job.html b/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/recognize/create_job/create_job.html index c97d5dde131175..5839bf7c60d04e 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/recognize/create_job/create_job.html +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/recognize/create_job/create_job.html @@ -1,4 +1,4 @@ - +
diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/single_metric/create_job/create_job.html b/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/single_metric/create_job/create_job.html index d639ddd1cf44b9..dd5b9af1a9ef3d 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/single_metric/create_job/create_job.html +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/single_metric/create_job/create_job.html @@ -1,4 +1,4 @@ - +
diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/index_or_search/index_or_search.html b/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/index_or_search/index_or_search.html index 02e8cbfdae7f70..90334010fcbfc6 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/index_or_search/index_or_search.html +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/index_or_search/index_or_search.html @@ -1,4 +1,4 @@ - +
diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type.html b/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type.html index da0bf3d38d4d51..57c7a3bd2bfbeb 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type.html +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type.html @@ -1,4 +1,4 @@ - +
diff --git a/x-pack/legacy/plugins/ml/public/settings/__snapshots__/settings.test.js.snap b/x-pack/legacy/plugins/ml/public/settings/__snapshots__/settings.test.js.snap deleted file mode 100644 index 9305677b043729..00000000000000 --- a/x-pack/legacy/plugins/ml/public/settings/__snapshots__/settings.test.js.snap +++ /dev/null @@ -1,76 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Settings Renders settings page 1`] = ` - - - - - -

- -

-
-
- - - - - - - - - - - - -
-
-
-`; diff --git a/x-pack/legacy/plugins/ml/public/settings/calendars/edit/__snapshots__/new_calendar.test.js.snap b/x-pack/legacy/plugins/ml/public/settings/calendars/edit/__snapshots__/new_calendar.test.js.snap index 52c91e01591036..9a48bb551dcdea 100644 --- a/x-pack/legacy/plugins/ml/public/settings/calendars/edit/__snapshots__/new_calendar.test.js.snap +++ b/x-pack/legacy/plugins/ml/public/settings/calendars/edit/__snapshots__/new_calendar.test.js.snap @@ -1,40 +1,45 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`NewCalendar Renders new calendar form 1`] = ` - - + + - - - + + + + + `; diff --git a/x-pack/legacy/plugins/ml/public/settings/calendars/edit/directive.js b/x-pack/legacy/plugins/ml/public/settings/calendars/edit/directive.js index ca9d86a6572a75..2d5427f091deff 100644 --- a/x-pack/legacy/plugins/ml/public/settings/calendars/edit/directive.js +++ b/x-pack/legacy/plugins/ml/public/settings/calendars/edit/directive.js @@ -17,13 +17,16 @@ import { checkGetJobsPrivilege, checkPermission } from '../../../privilege/check import { checkMlNodesAvailable } from '../../../ml_nodes_check/check_ml_nodes'; import { getCreateCalendarBreadcrumbs, getEditCalendarBreadcrumbs } from '../../breadcrumbs'; +import chrome from 'ui/chrome'; import uiRoutes from 'ui/routes'; - +import { timefilter } from 'ui/timefilter'; +import { timeHistory } from 'ui/timefilter/time_history'; import { I18nContext } from 'ui/i18n'; +import { NavigationMenuContext } from '../../../util/context_utils'; + const template = `
- `; @@ -63,7 +66,9 @@ module.directive('mlNewCalendar', function ($route) { ReactDOM.render( - {React.createElement(NewCalendar, props)} + + + , element[0] ); diff --git a/x-pack/legacy/plugins/ml/public/settings/calendars/edit/new_calendar.js b/x-pack/legacy/plugins/ml/public/settings/calendars/edit/new_calendar.js index 91d70e4890cb44..307fbf564be206 100644 --- a/x-pack/legacy/plugins/ml/public/settings/calendars/edit/new_calendar.js +++ b/x-pack/legacy/plugins/ml/public/settings/calendars/edit/new_calendar.js @@ -6,11 +6,11 @@ -import React, { - Component -} from 'react'; +import React, { Component, Fragment } from 'react'; import { PropTypes } from 'prop-types'; +import { injectI18n } from '@kbn/i18n/react'; + import { EuiPage, EuiPageContent, @@ -18,14 +18,16 @@ import { } from '@elastic/eui'; import chrome from 'ui/chrome'; +import { toastNotifications } from 'ui/notify'; + +import { NavigationMenu } from '../../../components/navigation_menu/navigation_menu'; + import { getCalendarSettingsData, validateCalendarId } from './utils'; import { CalendarForm } from './calendar_form/'; import { NewEventModal } from './new_event_modal/'; import { ImportModal } from './import_modal'; import { ml } from '../../../services/ml_api_service'; -import { toastNotifications } from 'ui/notify'; -import { injectI18n } from '@kbn/i18n/react'; export const NewCalendar = injectI18n(class NewCalendar extends Component { static propTypes = { @@ -333,39 +335,42 @@ export const NewCalendar = injectI18n(class NewCalendar extends Component { } return ( - - - - - {modal} - + + + + + + + {modal} + + ); } }); diff --git a/x-pack/legacy/plugins/ml/public/settings/calendars/edit/new_calendar.test.js b/x-pack/legacy/plugins/ml/public/settings/calendars/edit/new_calendar.test.js index 3bf7a8ade705b7..52ca426b9bd801 100644 --- a/x-pack/legacy/plugins/ml/public/settings/calendars/edit/new_calendar.test.js +++ b/x-pack/legacy/plugins/ml/public/settings/calendars/edit/new_calendar.test.js @@ -4,13 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ - - +jest.mock('../../../components/navigation_menu/navigation_menu', () => ({ + NavigationMenu: () =>
+})); jest.mock('../../../privilege/check_privilege', () => ({ checkPermission: () => true })); jest.mock('../../../license/check_license', () => ({ - hasLicenseExpired: () => false + hasLicenseExpired: () => false, + isFullLicense: () => false })); jest.mock('../../../privilege/get_privileges', () => ({ getPrivileges: () => {} @@ -18,9 +20,6 @@ jest.mock('../../../privilege/get_privileges', () => ({ jest.mock('../../../ml_nodes_check/check_ml_nodes', () => ({ mlNodesAvailable: () => true })); -jest.mock('ui/chrome', () => ({ - getBasePath: jest.fn() -})); jest.mock('../../../services/ml_api_service', () => ({ ml: { calendars: () => { diff --git a/x-pack/legacy/plugins/ml/public/settings/calendars/list/__snapshots__/calendars_list.test.js.snap b/x-pack/legacy/plugins/ml/public/settings/calendars/list/__snapshots__/calendars_list.test.js.snap index d582c96b410cf8..40f67b5043c866 100644 --- a/x-pack/legacy/plugins/ml/public/settings/calendars/list/__snapshots__/calendars_list.test.js.snap +++ b/x-pack/legacy/plugins/ml/public/settings/calendars/list/__snapshots__/calendars_list.test.js.snap @@ -1,69 +1,74 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`CalendarsList Renders calendar list with calendars 1`] = ` - - + + - - - - + + + + + + `; diff --git a/x-pack/legacy/plugins/ml/public/settings/calendars/list/calendars_list.js b/x-pack/legacy/plugins/ml/public/settings/calendars/list/calendars_list.js index c24147b8123b86..0b5657b62ebf3c 100644 --- a/x-pack/legacy/plugins/ml/public/settings/calendars/list/calendars_list.js +++ b/x-pack/legacy/plugins/ml/public/settings/calendars/list/calendars_list.js @@ -6,9 +6,7 @@ -import React, { - Component -} from 'react'; +import React, { Component, Fragment } from 'react'; import { PropTypes } from 'prop-types'; import { @@ -19,6 +17,7 @@ import { EUI_MODAL_CONFIRM_BUTTON, } from '@elastic/eui'; +import { NavigationMenu } from '../../../components/navigation_menu/navigation_menu'; import { CalendarsListHeader } from './header'; import { CalendarsListTable } from './table/'; import { ml } from '../../../services/ml_api_service'; @@ -147,29 +146,32 @@ export const CalendarsList = injectI18n(class CalendarsList extends Component { } return ( - - - - 0} - /> - - {destroyModal} - + + + + + + 0} + /> + + {destroyModal} + + ); } }); diff --git a/x-pack/legacy/plugins/ml/public/settings/calendars/list/calendars_list.test.js b/x-pack/legacy/plugins/ml/public/settings/calendars/list/calendars_list.test.js index af6f0272b78d7a..84211597f313c4 100644 --- a/x-pack/legacy/plugins/ml/public/settings/calendars/list/calendars_list.test.js +++ b/x-pack/legacy/plugins/ml/public/settings/calendars/list/calendars_list.test.js @@ -4,13 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ +import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers'; +import React from 'react'; +import { ml } from '../../../services/ml_api_service'; +import { CalendarsList } from './calendars_list'; +jest.mock('../../../components/navigation_menu/navigation_menu', () => ({ + NavigationMenu: () =>
+})); jest.mock('../../../privilege/check_privilege', () => ({ checkPermission: () => true })); jest.mock('../../../license/check_license', () => ({ - hasLicenseExpired: () => false + hasLicenseExpired: () => false, + isFullLicense: () => false })); jest.mock('../../../privilege/get_privileges', () => ({ getPrivileges: () => {} @@ -18,9 +26,6 @@ jest.mock('../../../privilege/get_privileges', () => ({ jest.mock('../../../ml_nodes_check/check_ml_nodes', () => ({ mlNodesAvailable: () => true })); -jest.mock('ui/chrome', () => ({ - getBasePath: jest.fn() -})); jest.mock('../../../services/ml_api_service', () => ({ ml: { calendars: () => { @@ -30,12 +35,6 @@ jest.mock('../../../services/ml_api_service', () => ({ } })); -import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers'; -import React from 'react'; -import { ml } from '../../../services/ml_api_service'; - -import { CalendarsList } from './calendars_list'; - const testingState = { loading: false, calendars: [ diff --git a/x-pack/legacy/plugins/ml/public/settings/calendars/list/directive.js b/x-pack/legacy/plugins/ml/public/settings/calendars/list/directive.js index 3d72eb7912ac6e..4f16433b987d50 100644 --- a/x-pack/legacy/plugins/ml/public/settings/calendars/list/directive.js +++ b/x-pack/legacy/plugins/ml/public/settings/calendars/list/directive.js @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ - import 'ngreact'; import React from 'react'; import ReactDOM from 'react-dom'; @@ -17,27 +16,28 @@ import { checkGetJobsPrivilege, checkPermission } from '../../../privilege/check import { getMlNodeCount } from '../../../ml_nodes_check/check_ml_nodes'; import { getCalendarManagementBreadcrumbs } from '../../breadcrumbs'; +import chrome from 'ui/chrome'; import uiRoutes from 'ui/routes'; - +import { timefilter } from 'ui/timefilter'; +import { timeHistory } from 'ui/timefilter/time_history'; import { I18nContext } from 'ui/i18n'; +import { NavigationMenuContext } from '../../../util/context_utils'; + const template = `
- `; -uiRoutes - .when('/settings/calendars_list', { - template, - k7Breadcrumbs: getCalendarManagementBreadcrumbs, - resolve: { - CheckLicense: checkFullLicense, - privileges: checkGetJobsPrivilege, - mlNodeCount: getMlNodeCount, - } - }); - +uiRoutes.when('/settings/calendars_list', { + template, + k7Breadcrumbs: getCalendarManagementBreadcrumbs, + resolve: { + CheckLicense: checkFullLicense, + privileges: checkGetJobsPrivilege, + mlNodeCount: getMlNodeCount, + }, +}); import { CalendarsList } from './calendars_list'; @@ -54,10 +54,12 @@ module.directive('mlCalendarsList', function () { ReactDOM.render( - {React.createElement(CalendarsList, props)} + + + , element[0] ); - } + }, }; }); diff --git a/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/__snapshots__/edit_filter_list.test.js.snap b/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/__snapshots__/edit_filter_list.test.js.snap index 43464755a4b791..e19c61350f8c79 100644 --- a/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/__snapshots__/edit_filter_list.test.js.snap +++ b/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/__snapshots__/edit_filter_list.test.js.snap @@ -1,852 +1,892 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`EditFilterList adds new items to filter list 1`] = ` - - + + - - - - - - + + + + - - - - - - + + + + - - - - - - + + + + + + + + `; exports[`EditFilterList renders after selecting an item and deleting it 1`] = ` - - + + - - - - - - + + + + - - - - - - + + + + - - - - - - + + + + + + + + `; exports[`EditFilterList renders after selecting an item and deleting it 2`] = ` - - + + - - - - - - + + + + - - - - - - + + + + - - - - - - + + + + + + + + `; exports[`EditFilterList renders the edit page for a new filter list and updates ID 1`] = ` - - + + - - - - - - + + + + - - - - - - + + + + - - - - - - + + + + + + + + `; exports[`EditFilterList renders the edit page for a new filter list and updates ID 2`] = ` - - + + - - - - - - + + + + - - - - - - + + + + - - - - - - + + + + + + + + `; exports[`EditFilterList renders the edit page for an existing filter list and updates description 1`] = ` - - + + - - - - - - + + + + - - - - - - + + + + - - - - - - + + + + + + + + `; exports[`EditFilterList renders the edit page for an existing filter list and updates description 2`] = ` - - + + - - - - - - + + + + - - - - - - + + + + - - - - - - + + + + + + + + `; exports[`EditFilterList updates the items per page 1`] = ` - - + + - - - - - - + + + + - - - - - - + + + + - - - - - - + + + + + + + + `; diff --git a/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/directive.js b/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/directive.js index e51d700eb90e6e..af3ba07d1b655a 100644 --- a/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/directive.js +++ b/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/directive.js @@ -9,8 +9,6 @@ import 'ngreact'; import React from 'react'; import ReactDOM from 'react-dom'; -import { I18nContext } from 'ui/i18n'; - import { uiModules } from 'ui/modules'; const module = uiModules.get('apps/ml', ['react']); @@ -20,11 +18,16 @@ import { checkGetJobsPrivilege, checkPermission } from 'plugins/ml/privilege/che import { getMlNodeCount } from 'plugins/ml/ml_nodes_check/check_ml_nodes'; import { EditFilterList } from './edit_filter_list'; +import chrome from 'ui/chrome'; import uiRoutes from 'ui/routes'; +import { timefilter } from 'ui/timefilter'; +import { timeHistory } from 'ui/timefilter/time_history'; +import { I18nContext } from 'ui/i18n'; + +import { NavigationMenuContext } from '../../../util/context_utils'; const template = `
- `; @@ -62,7 +65,9 @@ module.directive('mlEditFilterList', function ($route) { ReactDOM.render( - {React.createElement(EditFilterList, props)} + + + , element[0] ); diff --git a/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/edit_filter_list.js b/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/edit_filter_list.js index 81b12dc78b987e..2a27dd0fc97740 100644 --- a/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/edit_filter_list.js +++ b/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/edit_filter_list.js @@ -11,9 +11,7 @@ */ import PropTypes from 'prop-types'; -import React, { - Component -} from 'react'; +import React, { Component, Fragment } from 'react'; import { EuiButton, @@ -33,6 +31,7 @@ import { toastNotifications } from 'ui/notify'; import { EditFilterListHeader } from './header'; import { EditFilterListToolbar } from './toolbar'; import { ItemsGrid } from '../../../components/items_grid'; +import { NavigationMenu } from '../../../components/navigation_menu/navigation_menu'; import { isValidFilterListId, saveFilterList @@ -309,71 +308,74 @@ export const EditFilterList = injectI18n(class extends Component { const totalItemCount = (items !== undefined) ? items.length : 0; return ( - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - + } + fill + > + + + + + + + ); } }); diff --git a/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/edit_filter_list.test.js b/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/edit_filter_list.test.js index 75d6d34f0a5865..035913d5dc4656 100644 --- a/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/edit_filter_list.test.js +++ b/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/edit_filter_list.test.js @@ -4,6 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +jest.mock('../../../components/navigation_menu/navigation_menu', () => ({ + NavigationMenu: () =>
+})); + // Define the required mocks used for loading, saving and validating the filter list. jest.mock('./utils', () => ({ isValidFilterListId: () => true, @@ -19,7 +23,7 @@ const mockTestFilter = { items: ['google.com', 'google.co.uk', 'elastic.co', 'youtube.com'], used_by: { detectors: ['high info content'], - jobs: ['dns_exfiltration'] + jobs: ['dns_exfiltration'], }, }; jest.mock('../../../services/ml_api_service', () => ({ @@ -27,12 +31,11 @@ jest.mock('../../../services/ml_api_service', () => ({ filters: { filters: () => { return Promise.resolve(mockTestFilter); - } - } - } + }, + }, + }, })); - import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import React from 'react'; @@ -40,14 +43,11 @@ import { EditFilterList } from './edit_filter_list'; const props = { canCreateFilter: true, - canDeleteFilter: true + canDeleteFilter: true, }; function prepareEditTest() { - - const wrapper = shallowWithIntl( - - ); + const wrapper = shallowWithIntl(); // Cannot find a way to generate the snapshot after the Promise in the mock ml.filters // has resolved. @@ -61,11 +61,8 @@ function prepareEditTest() { } describe('EditFilterList', () => { - test('renders the edit page for a new filter list and updates ID', () => { - const wrapper = shallowWithIntl( - - ); + const wrapper = shallowWithIntl(); expect(wrapper).toMatchSnapshot(); const instance = wrapper.instance(); @@ -114,5 +111,4 @@ describe('EditFilterList', () => { wrapper.update(); expect(wrapper).toMatchSnapshot(); }); - }); diff --git a/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/__snapshots__/filter_lists.test.js.snap b/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/__snapshots__/filter_lists.test.js.snap index b95a7966ad80df..129e9a0f83caaa 100644 --- a/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/__snapshots__/filter_lists.test.js.snap +++ b/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/__snapshots__/filter_lists.test.js.snap @@ -1,41 +1,46 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Filter Lists renders a list of filters 1`] = ` - - + + - - + + - - + ] + } + refreshFilterLists={[Function]} + selectedFilterLists={Array []} + setSelectedFilterLists={[Function]} + /> + + + `; diff --git a/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/directive.js b/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/directive.js index f27f6e0609e261..95cdebf3518f7f 100644 --- a/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/directive.js +++ b/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/directive.js @@ -9,8 +9,6 @@ import 'ngreact'; import React from 'react'; import ReactDOM from 'react-dom'; -import { I18nContext } from 'ui/i18n'; - import { uiModules } from 'ui/modules'; const module = uiModules.get('apps/ml', ['react']); @@ -20,11 +18,16 @@ import { checkGetJobsPrivilege, checkPermission } from 'plugins/ml/privilege/che import { getMlNodeCount } from 'plugins/ml/ml_nodes_check/check_ml_nodes'; import { FilterLists } from './filter_lists'; +import chrome from 'ui/chrome'; import uiRoutes from 'ui/routes'; +import { timefilter } from 'ui/timefilter'; +import { timeHistory } from 'ui/timefilter/time_history'; +import { I18nContext } from 'ui/i18n'; + +import { NavigationMenuContext } from '../../../util/context_utils'; const template = `
- `; @@ -52,7 +55,9 @@ module.directive('mlFilterLists', function () { ReactDOM.render( - {React.createElement(FilterLists, props)} + + + , element[0] ); diff --git a/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/filter_lists.js b/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/filter_lists.js index f940e51d6f0ae5..8ea2bb89299137 100644 --- a/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/filter_lists.js +++ b/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/filter_lists.js @@ -9,9 +9,7 @@ * React table for displaying a table of filter lists. */ -import React, { - Component -} from 'react'; +import React, { Component, Fragment } from 'react'; import { PropTypes } from 'prop-types'; import { @@ -23,6 +21,8 @@ import { injectI18n } from '@kbn/i18n/react'; import { toastNotifications } from 'ui/notify'; +import { NavigationMenu } from '../../../components/navigation_menu/navigation_menu'; + import { FilterListsHeader } from './header'; import { FilterListsTable } from './table'; import { ml } from '../../../services/ml_api_service'; @@ -88,26 +88,29 @@ export const FilterLists = injectI18n(class extends Component { const { canCreateFilter, canDeleteFilter } = this.props; return ( - - - - - - + + + + + + + + + ); } }); diff --git a/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/filter_lists.test.js b/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/filter_lists.test.js index 2aa95f86048295..3c6a564ec92b4c 100644 --- a/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/filter_lists.test.js +++ b/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/filter_lists.test.js @@ -4,9 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import React from 'react'; + +import { FilterLists } from './filter_lists'; +jest.mock('../../../components/navigation_menu/navigation_menu', () => ({ + NavigationMenu: () =>
+})); jest.mock('../../../privilege/check_privilege', () => ({ - checkPermission: () => true + checkPermission: () => true, })); // Mock the call for loading the list of filters. @@ -23,28 +30,19 @@ jest.mock('../../../services/ml_api_service', () => ({ filters: { filtersStats: () => { return Promise.resolve([mockTestFilter]); - } - } - } + }, + }, + }, })); -import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import React from 'react'; - -import { FilterLists } from './filter_lists'; - const props = { canCreateFilter: true, - canDeleteFilter: true + canDeleteFilter: true, }; describe('Filter Lists', () => { - test('renders a list of filters', () => { - - const wrapper = shallowWithIntl( - - ); + const wrapper = shallowWithIntl(); // Cannot find a way to generate the snapshot after the Promise in the mock ml.filters // has resolved. @@ -54,5 +52,4 @@ describe('Filter Lists', () => { wrapper.update(); expect(wrapper).toMatchSnapshot(); }); - }); diff --git a/x-pack/legacy/plugins/ml/public/settings/settings.js b/x-pack/legacy/plugins/ml/public/settings/settings.js index ab4f807cae7284..3e1b7d724f1880 100644 --- a/x-pack/legacy/plugins/ml/public/settings/settings.js +++ b/x-pack/legacy/plugins/ml/public/settings/settings.js @@ -6,7 +6,7 @@ -import React from 'react'; +import React, { Fragment } from 'react'; import { PropTypes } from 'prop-types'; import { @@ -20,67 +20,73 @@ import { EuiTitle } from '@elastic/eui'; -import chrome from 'ui/chrome'; - import { FormattedMessage } from '@kbn/i18n/react'; +import { useNavigationMenuContext } from '../util/context_utils'; +import { NavigationMenu } from '../components/navigation_menu/navigation_menu'; + export function Settings({ canGetFilters, canGetCalendars }) { + const basePath = useNavigationMenuContext().chrome.getBasePath(); + return ( - - - - - -

- -

-
-
+ + + + + + + +

+ +

+
+
- - - - - - + + + + + + - - - - - - + + + + + + -
-
-
+
+
+
+ ); } diff --git a/x-pack/legacy/plugins/ml/public/settings/settings.test.js b/x-pack/legacy/plugins/ml/public/settings/settings.test.js index 0d5cc000854e1b..3e7129bbc0718c 100644 --- a/x-pack/legacy/plugins/ml/public/settings/settings.test.js +++ b/x-pack/legacy/plugins/ml/public/settings/settings.test.js @@ -4,46 +4,65 @@ * you may not use this file except in compliance with the Elastic License. */ - - -jest.mock('ui/chrome', () => ({ - getBasePath: jest.fn() -})); - -import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import PropTypes from 'prop-types'; import React from 'react'; +import * as ContextUtils from '../util/context_utils'; import { Settings } from './settings'; +const navigationMenuMock = ContextUtils.navigationMenuMock; +const mountOptions = { + context: { NavigationMenuContext: navigationMenuMock }, + childContextTypes: { NavigationMenuContext: PropTypes.object } +}; + +jest.mock('../components/navigation_menu/navigation_menu', () => ({ + NavigationMenu: () =>
+})); +jest.spyOn(ContextUtils, 'useNavigationMenuContext').mockImplementation(() => navigationMenuMock); + describe('Settings', () => { + test('Renders settings page with all buttons enabled.', () => { + const wrapper = mountWithIntl(, mountOptions); - test('Renders settings page', () => { - const wrapper = shallowWithIntl( - - ); + const filterButton = wrapper + .find('[data-test-subj="ml_filter_lists_button"]') + .find('EuiButtonEmpty'); + expect(filterButton.prop('isDisabled')).toBe(false); - expect(wrapper).toMatchSnapshot(); + const calendarButton = wrapper + .find('[data-test-subj="ml_calendar_mng_button"]') + .find('EuiButtonEmpty'); + expect(calendarButton.prop('isDisabled')).toBe(false); }); test('Filter Lists button disabled if canGetFilters is false', () => { - const wrapper = mountWithIntl( - - ); + const wrapper = mountWithIntl(, mountOptions); - const button = wrapper.find('[data-test-subj="ml_filter_lists_button"]'); - const filterButton = button.find('EuiButtonEmpty'); + const filterButton = wrapper + .find('[data-test-subj="ml_filter_lists_button"]') + .find('EuiButtonEmpty'); expect(filterButton.prop('isDisabled')).toBe(true); + + const calendarButton = wrapper + .find('[data-test-subj="ml_calendar_mng_button"]') + .find('EuiButtonEmpty'); + expect(calendarButton.prop('isDisabled')).toBe(false); }); test('Calendar management button disabled if canGetCalendars is false', () => { - const wrapper = mountWithIntl( - - ); + const wrapper = mountWithIntl(, mountOptions); - const button = wrapper.find('[data-test-subj="ml_calendar_mng_button"]'); - const calendarButton = button.find('EuiButtonEmpty'); + const filterButton = wrapper + .find('[data-test-subj="ml_filter_lists_button"]') + .find('EuiButtonEmpty'); + expect(filterButton.prop('isDisabled')).toBe(false); + + const calendarButton = wrapper + .find('[data-test-subj="ml_calendar_mng_button"]') + .find('EuiButtonEmpty'); expect(calendarButton.prop('isDisabled')).toBe(true); }); - }); diff --git a/x-pack/legacy/plugins/ml/public/settings/settings_directive.js b/x-pack/legacy/plugins/ml/public/settings/settings_directive.js index 68ac5330895587..ab03c548f53cd6 100644 --- a/x-pack/legacy/plugins/ml/public/settings/settings_directive.js +++ b/x-pack/legacy/plugins/ml/public/settings/settings_directive.js @@ -15,15 +15,17 @@ const module = uiModules.get('apps/ml', ['react']); import { checkFullLicense } from '../license/check_license'; import { checkGetJobsPrivilege, checkPermission } from '../privilege/check_privilege'; import { getMlNodeCount } from '../ml_nodes_check/check_ml_nodes'; +import { NavigationMenuContext } from '../util/context_utils'; import { getSettingsBreadcrumbs } from './breadcrumbs'; import { I18nContext } from 'ui/i18n'; +import chrome from 'ui/chrome'; import uiRoutes from 'ui/routes'; import { timefilter } from 'ui/timefilter'; +import { timeHistory } from 'ui/timefilter/time_history'; const template = `
- `; @@ -56,12 +58,9 @@ module.directive('mlSettings', function () { ReactDOM.render( - {React.createElement( - Settings, { - canGetFilters, - canGetCalendars - }) - } + + + , element[0] ); diff --git a/x-pack/legacy/plugins/ml/public/timeseriesexplorer/timeseriesexplorer.html b/x-pack/legacy/plugins/ml/public/timeseriesexplorer/timeseriesexplorer.html index 880e8466e10bd6..300832d05c7413 100644 --- a/x-pack/legacy/plugins/ml/public/timeseriesexplorer/timeseriesexplorer.html +++ b/x-pack/legacy/plugins/ml/public/timeseriesexplorer/timeseriesexplorer.html @@ -1,4 +1,4 @@ - +
diff --git a/x-pack/legacy/plugins/ml/public/util/context_utils.tsx b/x-pack/legacy/plugins/ml/public/util/context_utils.tsx new file mode 100644 index 00000000000000..88d340bd6aa6b2 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/util/context_utils.tsx @@ -0,0 +1,66 @@ +/* + * 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, { useContext } from 'react'; + +import { Chrome } from 'ui/chrome'; +import { Timefilter } from 'ui/timefilter'; +import { TimeHistory } from 'ui/timefilter/time_history'; + +export const ChromeContext = React.createContext({} as Chrome); +export const TimefilterContext = React.createContext({} as Timefilter); +export const TimeHistoryContext = React.createContext({} as TimeHistory); + +interface NavigationMenuContextValue { + chrome: Chrome; + timefilter: Timefilter; + timeHistory: TimeHistory; +} +export const NavigationMenuContext = React.createContext({ + chrome: {} as Chrome, + timefilter: {} as Timefilter, + timeHistory: {} as TimeHistory, +}); + +export const useNavigationMenuContext = () => { + return useContext(NavigationMenuContext); +}; + +// testing mocks +export const chromeMock = { + getBasePath: () => 'basePath', + getUiSettingsClient: () => { + return { + get: (key: string) => { + switch (key) { + case 'dateFormat': + case 'timepicker:timeDefaults': + return {}; + case 'timepicker:refreshIntervalDefaults': + return { pause: false, value: 0 }; + default: + throw new Error(`Unexpected config key: ${key}`); + } + }, + }; + }, +} as Chrome; + +export const timefilterMock = ({ + getRefreshInterval: () => '30s', + getTime: () => ({ from: 0, to: 0 }), + on: (event: string, reload: () => void) => {}, +} as unknown) as Timefilter; + +export const timeHistoryMock = ({ + get: () => [{ from: 0, to: 0 }], +} as unknown) as TimeHistory; + +export const navigationMenuMock = { + chrome: chromeMock, + timefilter: timefilterMock, + timeHistory: timeHistoryMock, +}; diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/high_mean_cpu_iowait_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/high_mean_cpu_iowait_ecs.json index 2025d5d94f750d..46e04488206dad 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/high_mean_cpu_iowait_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/high_mean_cpu_iowait_ecs.json @@ -31,7 +31,7 @@ ] }, "analysis_limits": { - "model_memory_limit": "256mb" + "model_memory_limit": "25mb" }, "data_description": { "time_field": "@timestamp", diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/max_disk_utilization_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/max_disk_utilization_ecs.json index 5267245750e963..fd69dc98538cf7 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/max_disk_utilization_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/max_disk_utilization_ecs.json @@ -31,7 +31,7 @@ ] }, "analysis_limits": { - "model_memory_limit": "256mb" + "model_memory_limit": "25mb" }, "data_description": { "time_field": "@timestamp", diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/metricbeat_outages_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/metricbeat_outages_ecs.json index ba6179d31064bf..8b7a4fa9ca08ba 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/metricbeat_outages_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/metricbeat_outages_ecs.json @@ -16,7 +16,7 @@ ] }, "analysis_limits": { - "model_memory_limit": "256mb" + "model_memory_limit": "15mb" }, "data_description": { "time_field": "@timestamp", diff --git a/x-pack/legacy/plugins/notifications/config.js b/x-pack/legacy/plugins/notifications/config.js deleted file mode 100644 index 996db49542646c..00000000000000 --- a/x-pack/legacy/plugins/notifications/config.js +++ /dev/null @@ -1,51 +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. - */ - -/** - * User-configurable settings for xpack.notifications via configuration schema - * - * @param {Object} Joi - HapiJS Joi module that allows for schema validation - * @return {Object} config schema - */ -export const config = (Joi) => { - return Joi.object({ - enabled: Joi.boolean().default(true), - email: Joi.object({ - enabled: Joi.boolean().default(false), - smtp: Joi.object({ - host: Joi.string().default('localhost'), - port: Joi.number().default(25), - require_tls: Joi.boolean().default(false), - pool: Joi.boolean().default(false), - auth: Joi.object({ - username: Joi.string(), - password: Joi.string() - }).default(), - }).default(), - defaults: Joi.object({ - from: Joi.string(), - to: Joi.array().single().items(Joi.string()), - cc: Joi.array().single().items(Joi.string()), - bcc: Joi.array().single().items(Joi.string()), - }).default(), - }).default(), - slack: Joi.object({ - enabled: Joi.boolean().default(false), - token: Joi.string().required(), - defaults: Joi.object({ - channel: Joi.string(), - as_user: Joi.boolean().default(false), - icon_emoji: Joi.string(), - icon_url: Joi.string(), - link_names: Joi.boolean().default(true), - mrkdwn: Joi.boolean().default(true), - unfurl_links: Joi.boolean().default(true), - unfurl_media: Joi.boolean().default(true), - username: Joi.string(), - }).default(), - }) - }).default(); -}; diff --git a/x-pack/legacy/plugins/notifications/index.js b/x-pack/legacy/plugins/notifications/index.js deleted file mode 100644 index e6de33e9685e0e..00000000000000 --- a/x-pack/legacy/plugins/notifications/index.js +++ /dev/null @@ -1,24 +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 { resolve } from 'path'; -import { init } from './init'; -import { config } from './config'; - -/** - * Invokes plugin modules to instantiate the Notification plugin for Kibana - * - * @param kibana {Object} Kibana plugin instance - * @return {Object} Notification Kibana plugin object - */ -export const notifications = (kibana) => new kibana.Plugin({ - require: ['kibana', 'xpack_main'], - id: 'notifications', - configPrefix: 'xpack.notifications', - publicDir: resolve(__dirname, 'public'), - init, - config, -}); diff --git a/x-pack/legacy/plugins/notifications/init.js b/x-pack/legacy/plugins/notifications/init.js deleted file mode 100644 index 67c34b336f7373..00000000000000 --- a/x-pack/legacy/plugins/notifications/init.js +++ /dev/null @@ -1,38 +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 { - notificationService, - createEmailAction, - createSlackAction, - LoggerAction, -} from './server'; -import { notificationServiceSendRoute } from './server/routes/api/v1/notifications'; - -/** - * Initialize the Action Service with various actions provided by X-Pack, when configured. - * - * @param server {Object} HapiJS server instance - */ -export function init(server) { - const config = server.config(); - - // the logger - notificationService.setAction(new LoggerAction({ server })); - - if (config.get('xpack.notifications.email.enabled')) { - notificationService.setAction(createEmailAction(server)); - } - - if (config.get('xpack.notifications.slack.enabled')) { - notificationService.setAction(createSlackAction(server)); - } - - notificationServiceSendRoute(server, notificationService); - - // expose the notification service for other plugins - server.expose('notificationService', notificationService); -} diff --git a/x-pack/legacy/plugins/notifications/server/email/create_email_action.js b/x-pack/legacy/plugins/notifications/server/email/create_email_action.js deleted file mode 100644 index 5e3b63e4b7cd36..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/email/create_email_action.js +++ /dev/null @@ -1,58 +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 { EmailAction } from './email_action'; - -/** - * Create a Nodemailer transporter options object from the config. - * - * @param {Object} config The server configuration. - * @return {Object} An object that configures Nodemailer. - */ -export function optionsFromConfig(config) { - return { - host: config.get('xpack.notifications.email.smtp.host'), - port: config.get('xpack.notifications.email.smtp.port'), - requireTLS: config.get('xpack.notifications.email.smtp.require_tls'), - pool: config.get('xpack.notifications.email.smtp.pool'), - auth: { - user: config.get('xpack.notifications.email.smtp.auth.username'), - pass: config.get('xpack.notifications.email.smtp.auth.password'), - }, - }; -} - -/** - * Create a Nodemailer defaults object from the config. - * - * Defaults include things like the default "from" email address. - * - * @param {Object} config The server configuration. - * @return {Object} An object that configures Nodemailer on a per-message basis. - */ -export function defaultsFromConfig(config) { - return { - from: config.get('xpack.notifications.email.defaults.from'), - to: config.get('xpack.notifications.email.defaults.to'), - cc: config.get('xpack.notifications.email.defaults.cc'), - bcc: config.get('xpack.notifications.email.defaults.bcc'), - }; -} - -/** - * Create a new Email Action based on the configuration. - * - * @param {Object} server The server object. - * @return {EmailAction} A new email action based on the kibana.yml configuration. - */ -export function createEmailAction(server, { _options = optionsFromConfig, _defaults = defaultsFromConfig } = { }) { - const config = server.config(); - - const options = _options(config); - const defaults = _defaults(config); - - return new EmailAction({ server, options, defaults }); -} diff --git a/x-pack/legacy/plugins/notifications/server/email/create_email_action.test.js b/x-pack/legacy/plugins/notifications/server/email/create_email_action.test.js deleted file mode 100644 index fb9d46cdbe71ef..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/email/create_email_action.test.js +++ /dev/null @@ -1,96 +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 { EmailAction } from './email_action'; -import { - createEmailAction, - defaultsFromConfig, - optionsFromConfig, -} from './create_email_action'; - -describe('create_email_action', () => { - - test('optionsFromConfig uses config without modification', () => { - const get = key => { - const suffixes = [ - 'host', - 'port', - 'require_tls', - 'pool', - 'auth.username', - 'auth.password', - ]; - const value = suffixes.find(suffix => { - return `xpack.notifications.email.smtp.${suffix}` === key; - }); - - if (value === undefined) { - throw new Error(`Unknown config key used ${key}`); - } - - return value; - }; - - expect(optionsFromConfig({ get })).toEqual({ - host: 'host', - port: 'port', - requireTLS: 'require_tls', - pool: 'pool', - auth: { - user: 'auth.username', - pass: 'auth.password', - }, - }); - }); - - test('defaultsFromConfig uses config without modification', () => { - const get = key => { - const suffixes = [ - 'from', - 'to', - 'cc', - 'bcc', - ]; - const value = suffixes.find(suffix => { - return `xpack.notifications.email.defaults.${suffix}` === key; - }); - - if (value === undefined) { - throw new Error(`Unknown config key used ${key}`); - } - - return value; - }; - - expect(defaultsFromConfig({ get })).toEqual({ - from: 'from', - to: 'to', - cc: 'cc', - bcc: 'bcc', - }); - }); - - test('createEmailAction', async () => { - const config = { }; - const server = { config: jest.fn().mockReturnValue(config) }; - const _options = jest.fn().mockReturnValue({ options: true }); - const defaults = { defaults: true }; - const _defaults = jest.fn().mockReturnValue(defaults); - - const action = createEmailAction(server, { _options, _defaults }); - - expect(action instanceof EmailAction).toBe(true); - expect(action.defaults).toBe(defaults); - - expect(server.config).toHaveBeenCalledTimes(1); - expect(server.config).toHaveBeenCalledWith(); - expect(_options).toHaveBeenCalledTimes(1); - expect(_options).toHaveBeenCalledWith(config); - expect(_defaults).toHaveBeenCalledTimes(1); - expect(_defaults).toHaveBeenCalledWith(config); - }); - -}); diff --git a/x-pack/legacy/plugins/notifications/server/email/email_action.js b/x-pack/legacy/plugins/notifications/server/email/email_action.js deleted file mode 100644 index a30b88a798d272..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/email/email_action.js +++ /dev/null @@ -1,103 +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 nodemailer from 'nodemailer'; - -import { Action, ActionResult } from '../'; - -export const EMAIL_ACTION_ID = 'xpack-notifications-email'; - -/** - * Email Action enables generic sending of emails, when configured. - */ -export class EmailAction extends Action { - - /** - * Create a new Action capable of sending emails. - * - * @param {Object} server Kibana server object. - * @param {Object} options Configuration options for Nodemailer. - * @param {Object} defaults Default fields used when sending emails. - * @param {Object} _nodemailer Exposed for tests. - */ - constructor({ server, options, defaults = { }, _nodemailer = nodemailer }) { - super({ server, id: EMAIL_ACTION_ID, name: 'Email' }); - - this.transporter = _nodemailer.createTransport(options, defaults); - this.defaults = defaults; - } - - getMissingFields(notification) { - const missingFields = []; - - if (!Boolean(this.defaults.to) && !Boolean(notification.to)) { - missingFields.push({ - field: 'to', - name: 'To', - type: 'email', - }); - } - - if (!Boolean(this.defaults.from) && !Boolean(notification.from)) { - missingFields.push({ - field: 'from', - name: 'From', - type: 'email', - }); - } - - if (!Boolean(notification.subject)) { - missingFields.push({ - field: 'subject', - name: 'Subject', - type: 'text', - }); - } - - if (!Boolean(notification.markdown)) { - missingFields.push({ - field: 'markdown', - name: 'Body', - type: 'markdown', - }); - } - - return missingFields; - } - - async doPerformHealthCheck() { - // this responds with a boolean 'true' response, otherwise throws an Error - const response = await this.transporter.verify(); - - return new ActionResult({ - message: `Email action SMTP configuration has been verified.`, - response: { - verified: response - }, - }); - } - - async doPerformAction(notification) { - // Note: This throws an Error upon failure - const response = await this.transporter.sendMail({ - // email routing - from: notification.from, - to: notification.to, - cc: notification.cc, - bcc: notification.bcc, - // email content - subject: notification.subject, - html: notification.markdown, - text: notification.markdown, - }); - - return new ActionResult({ - message: `Sent email for '${notification.subject}'.`, - response, - }); - } - -} diff --git a/x-pack/legacy/plugins/notifications/server/email/email_action.test.js b/x-pack/legacy/plugins/notifications/server/email/email_action.test.js deleted file mode 100644 index 90ef5a3f59a780..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/email/email_action.test.js +++ /dev/null @@ -1,166 +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 { ActionResult } from '../'; -import { EMAIL_ACTION_ID, EmailAction } from './email_action'; - -describe('EmailAction', () => { - - const server = { }; - const options = { options: true }; - const defaults = { defaults: true }; - const transporter = { - // see beforeEach - }; - const _nodemailer = { - // see beforeEach - }; - - let action; - - beforeEach(() => { - transporter.verify = jest.fn(); - transporter.sendMail = jest.fn(); - _nodemailer.createTransport = jest.fn().mockReturnValue(transporter); - - action = new EmailAction({ server, options, defaults, _nodemailer }); - }); - - test('id and name to be from constructor', () => { - expect(action.getId()).toBe(EMAIL_ACTION_ID); - expect(action.getName()).toBe('Email'); - expect(action.transporter).toBe(transporter); - - expect(_nodemailer.createTransport).toHaveBeenCalledTimes(1); - expect(_nodemailer.createTransport).toHaveBeenCalledWith(options, defaults); - }); - - describe('getMissingFields', () => { - - test('returns missing fields', () => { - const to = { field: 'to', name: 'To', type: 'email' }; - const from = { field: 'from', name: 'From', type: 'email' }; - const subject = { field: 'subject', name: 'Subject', type: 'text' }; - const markdown = { field: 'markdown', name: 'Body', type: 'markdown' }; - - const missing = [ - { defaults: { }, notification: { }, missing: [ to, from, subject, markdown, ], }, - { defaults: { }, notification: { from: 'b@c.co', subject: 'subject', markdown: 'body', }, missing: [ to, ], }, - { defaults: { from: 'b@c.co', }, notification: { subject: 'subject', markdown: 'body', }, missing: [ to, ], }, - { defaults: { }, notification: { to: 'a@b.co', subject: 'subject', markdown: 'body', }, missing: [ from, ], }, - { defaults: { to: 'a@b.co', }, notification: { subject: 'subject', markdown: 'body', }, missing: [ from, ], }, - { defaults: { }, notification: { to: 'a@b.co', from: 'b@c.co', markdown: 'body', }, missing: [ subject, ], }, - { defaults: { }, notification: { to: 'a@b.co', from: 'b@c.co', subject: 'subject', }, missing: [ markdown, ], }, - ]; - - missing.forEach(check => { - const newDefaultsAction = new EmailAction({ server, defaults: check.defaults, _nodemailer }); - - expect(newDefaultsAction.getMissingFields(check.notification)).toEqual(check.missing); - }); - }); - - test('returns [] when all fields exist', () => { - const exists = [ - { defaults: { }, notification: { to: 'a@b.co', from: 'b@c.co', subject: 'subject', markdown: 'body', }, }, - { defaults: { to: 'a@b.co', }, notification: { from: 'b@c.co', subject: 'subject', markdown: 'body', }, }, - { defaults: { from: 'b@c.co', }, notification: { to: 'a@b.co', subject: 'subject', markdown: 'body', }, }, - { defaults: { to: 'a@b.co', from: 'b@c.co', }, notification: { subject: 'subject', markdown: 'body', }, }, - ]; - - exists.forEach(check => { - const newDefaultsAction = new EmailAction({ server, defaults: check.defaults, _nodemailer }); - - expect(newDefaultsAction.getMissingFields(check.notification)).toEqual([]); - }); - }); - - }); - - describe('doPerformHealthCheck', () => { - - test('rethrows Error for failure', async () => { - const error = new Error('TEST - expected'); - - transporter.verify.mockRejectedValue(error); - - await expect(action.doPerformHealthCheck()) - .rejects - .toThrow(error); - - expect(transporter.verify).toHaveBeenCalledTimes(1); - expect(transporter.verify).toHaveBeenCalledWith(); - }); - - test('returns ActionResult for success', async () => { - transporter.verify.mockResolvedValue(true); - - const result = await action.doPerformHealthCheck(); - - expect(result instanceof ActionResult).toBe(true); - expect(result.isOk()).toBe(true); - expect(result.getMessage()).toMatch('Email action SMTP configuration has been verified.'); - expect(result.getResponse()).toEqual({ - verified: true - }); - - expect(transporter.verify).toHaveBeenCalledTimes(1); - expect(transporter.verify).toHaveBeenCalledWith(); - }); - - }); - - describe('doPerformAction', () => { - const email = { subject: 'email', markdown: 'body' }; - - test('rethrows Error for failure', async () => { - const error = new Error('TEST - expected'); - - transporter.sendMail.mockRejectedValue(error); - - await expect(action.doPerformAction(email)) - .rejects - .toThrow(error); - - expect(transporter.sendMail).toHaveBeenCalledTimes(1); - expect(transporter.sendMail).toHaveBeenCalledWith({ - to: undefined, - from: undefined, - cc: undefined, - bcc: undefined, - subject: email.subject, - html: email.markdown, - text: email.markdown, - }); - }); - - test('returns ActionResult for success', async () => { - const response = { fake: true }; - - transporter.sendMail.mockResolvedValue(response); - - const result = await action.doPerformAction(email); - - expect(result instanceof ActionResult).toBe(true); - expect(result.isOk()).toBe(true); - expect(result.getMessage()).toMatch(`Sent email for '${email.subject}'.`); - expect(result.getResponse()).toBe(response); - - expect(transporter.sendMail).toHaveBeenCalledTimes(1); - expect(transporter.sendMail).toHaveBeenCalledWith({ - to: undefined, - from: undefined, - cc: undefined, - bcc: undefined, - subject: email.subject, - html: email.markdown, - text: email.markdown, - }); - }); - - }); - -}); diff --git a/x-pack/legacy/plugins/notifications/server/email/index.js b/x-pack/legacy/plugins/notifications/server/email/index.js deleted file mode 100644 index ff784e71bab738..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/email/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { createEmailAction } from './create_email_action'; diff --git a/x-pack/legacy/plugins/notifications/server/index.js b/x-pack/legacy/plugins/notifications/server/index.js deleted file mode 100644 index d877b5bb704301..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/index.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { - notificationService, - Action, - ActionResult, -} from './service'; -export { createEmailAction } from './email'; -export { createSlackAction } from './slack'; -export { LoggerAction } from './logger'; diff --git a/x-pack/legacy/plugins/notifications/server/logger/index.js b/x-pack/legacy/plugins/notifications/server/logger/index.js deleted file mode 100644 index c9d4686ad4ef38..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/logger/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { LoggerAction } from './logger_action'; diff --git a/x-pack/legacy/plugins/notifications/server/logger/logger_action.js b/x-pack/legacy/plugins/notifications/server/logger/logger_action.js deleted file mode 100644 index 189f3638677cbc..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/logger/logger_action.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Action, ActionResult } from '../'; - -export const LOGGER_ACTION_ID = 'xpack-notifications-logger'; - -/** - * Logger Action enables generic logging of information into Kibana's logs. - * - * This is mostly useful for debugging. - */ -export class LoggerAction extends Action { - - constructor({ server }) { - super({ server, id: LOGGER_ACTION_ID, name: 'Log' }); - } - - getMissingFields() { - return []; - } - - async doPerformHealthCheck() { - return new ActionResult({ - message: `Logger action is always usable.`, - response: { }, - }); - } - - async doPerformAction(notification) { - this.server.log([LOGGER_ACTION_ID, 'info'], notification); - - return new ActionResult({ - message: 'Logged data returned as response.', - response: notification - }); - } - -} diff --git a/x-pack/legacy/plugins/notifications/server/logger/logger_action.test.js b/x-pack/legacy/plugins/notifications/server/logger/logger_action.test.js deleted file mode 100644 index 894399c0dc8bdc..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/logger/logger_action.test.js +++ /dev/null @@ -1,50 +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 { ActionResult } from '../'; -import { LOGGER_ACTION_ID, LoggerAction } from './logger_action'; - -describe('LoggerAction', () => { - - const action = new LoggerAction({ server: { } }); - - test('id and name to be from constructor', () => { - expect(action.id).toBe(LOGGER_ACTION_ID); - expect(action.name).toBe('Log'); - }); - - test('getMissingFields to return []', () => { - expect(action.getMissingFields()).toEqual([]); - }); - - test('doPerformHealthCheck returns ActionResult', async () => { - const result = await action.doPerformHealthCheck(); - - expect(result instanceof ActionResult).toBe(true); - expect(result.isOk()).toBe(true); - expect(result.getMessage()).toMatch('Logger action is always usable.'); - expect(result.getResponse()).toEqual({ }); - }); - - test('doPerformAction logs and returns ActionResult', async () => { - const notification = { fake: true }; - - const logger = jest.fn(); - const server = { log: logger }; - const action = new LoggerAction({ server }); - - const result = await action.doPerformAction(notification); - - expect(result instanceof ActionResult).toBe(true); - expect(result.isOk()).toBe(true); - expect(result.getMessage()).toMatch('Logged data returned as response.'); - expect(result.getResponse()).toBe(notification); - - expect(logger).toHaveBeenCalledTimes(1); - expect(logger).toHaveBeenCalledWith([LOGGER_ACTION_ID, 'info'], notification); - }); - -}); diff --git a/x-pack/legacy/plugins/notifications/server/routes/api/v1/notifications/notify.js b/x-pack/legacy/plugins/notifications/server/routes/api/v1/notifications/notify.js deleted file mode 100644 index 88934583d500e2..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/routes/api/v1/notifications/notify.js +++ /dev/null @@ -1,107 +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 Joi from 'joi'; -import { boomify } from 'boom'; - -/** - * Check the incoming request parameters to see if the action should be allowed to fire. - * - * @param {Object|null} action The action selected by the user. - * @param {String} actionId The ID of the requested action from the user. - * @param {Object} data The incoming data from the user. - * @returns {Object|null} The error object, or null if no error. - */ -export function checkForErrors(action, actionId, data) { - if (action === null) { - return { - message: `Unrecognized action: '${actionId}'.`, - }; - } else { - let validLicense = false; - - try { - validLicense = action.isLicenseValid(); - } catch (e) { - // validLicense === false - } - - if (validLicense === false) { - return { - message: `Unable to perform '${action.name}' action due to the current license.`, - }; - } - } - - const fields = action.getMissingFields(data); - - if (fields.length !== 0) { - return { - message: `Unable to perform '${action.name}' action due to missing required fields.`, - fields - }; - } - - return null; -} - -/** - * Attempt to send the {@code data} as a notification. - * - * @param {Object} server Kibana server object. - * @param {NotificationService} notificationService The notification service singleton. - * @param {String} actionId The specified action's ID. - * @param {Function} data The notification data to send via the specified action. - * @param {Function} _checkForErrors Exposed for testing. - */ -export async function sendNotification(server, notificationService, actionId, data, { _checkForErrors = checkForErrors } = { }) { - const action = notificationService.getActionForId(actionId); - const error = _checkForErrors(action, actionId, data); - - if (error === null) { - return action.performAction(data) - .then(result => result.toJson()) - .catch(err => boomify(err)); // by API definition, this should never happen as performAction isn't allow to throw errrors - } - - server.log(['actions', 'error'], error.message); - - return { - status_code: 400, - ok: false, - message: `Error: ${error.message}`, - error, - }; -} - -/** - * Notification Service route to perform actions (aka send data). - */ -export function notificationServiceSendRoute(server, notificationService) { - server.route({ - method: 'POST', - path: '/api/notifications/v1/notify', - config: { - validate: { - payload: Joi.object({ - action: Joi.string().required(), - data: Joi.object({ - from: Joi.string(), - to: Joi.string(), - subject: Joi.string().required(), - markdown: Joi.string(), - }).required() - }) - } - }, - handler: (req) => { - const actionId = req.payload.action; - const data = req.payload.data; - - sendNotification(server, notificationService, actionId, data); - }, - }); -} diff --git a/x-pack/legacy/plugins/notifications/server/routes/api/v1/notifications/notify.test.js b/x-pack/legacy/plugins/notifications/server/routes/api/v1/notifications/notify.test.js deleted file mode 100644 index 0a50ef9cc1ba4b..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/routes/api/v1/notifications/notify.test.js +++ /dev/null @@ -1,164 +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 { checkForErrors, sendNotification } from './notify'; -import { boomify } from 'boom'; - -describe('notifications/routes/send', () => { - - const id = 'notifications-test'; - const notification = { fake: true }; - - describe('checkForErrors', () => { - - it('returns unrecognized action for null action', () => { - expect(checkForErrors(null, id, { })).toEqual({ - message: `Unrecognized action: '${id}'.`, - }); - }); - - it('returns invalid license if license check throws an error', () => { - const action = { - name: 'Test Action', - isLicenseValid: () => { - throw new Error(); - }, - }; - - expect(checkForErrors(action, id, { })).toEqual({ - message: `Unable to perform '${action.name}' action due to the current license.`, - }); - }); - - it('returns invalid license if license is invalid', () => { - const action = { - name: 'Test Action', - isLicenseValid: () => false, - }; - - expect(checkForErrors(action, id, { })).toEqual({ - message: `Unable to perform '${action.name}' action due to the current license.`, - }); - }); - - it('returns fields related to missing data', () => { - const fields = [ { field: 1 } ]; - const action = { - name: 'Test Action', - isLicenseValid: () => true, - getMissingFields: (data) => { - expect(data).toBe(notification); - - return fields; - }, - }; - - const error = checkForErrors(action, id, notification); - - expect(error).toEqual({ - message: `Unable to perform '${action.name}' action due to missing required fields.`, - fields - }); - }); - - it('returns null if action is usable', () => { - const notification = { fake: true }; - const action = { - name: 'Test Action', - isLicenseValid: () => true, - getMissingFields: (data) => { - expect(data).toBe(notification); - - return []; - }, - }; - - expect(checkForErrors(action, id, notification)).toBeNull(); - }); - - }); - - describe('sendNotification', () => { - - it('replies with error object for bad request', async () => { - const error = { - message: 'TEST - expected', - fields: [ { fake: 1 } ], - }; - const action = { }; - const server = { - log: jest.fn(), - }; - const notificationService = { - getActionForId: jest.fn().mockReturnValue(action), - }; - const checkForErrors = jest.fn().mockReturnValue(error); - - const sendResponse = await sendNotification(server, notificationService, id, notification, { _checkForErrors: checkForErrors }); - - expect(notificationService.getActionForId).toHaveBeenCalledTimes(1); - expect(notificationService.getActionForId).toHaveBeenCalledWith(id); - expect(checkForErrors).toHaveBeenCalledTimes(1); - expect(checkForErrors).toHaveBeenCalledWith(action, id, notification); - expect(server.log).toHaveBeenCalledTimes(1); - expect(server.log).toHaveBeenCalledWith(['actions', 'error'], error.message); - - expect(sendResponse).toEqual({ - status_code: 400, - ok: false, - message: `Error: ${error.message}`, - error, - }); - }); - - it('replies with action result JSON', async () => { - const response = { ok: true, message: 'Test' }; - const result = { - toJson: () => response, - }; - const action = { - performAction: jest.fn().mockReturnValue(Promise.resolve(result)) - }; - const server = { }; - const notificationService = { - getActionForId: jest.fn().mockReturnValue(action), - }; - const checkForErrors = jest.fn().mockReturnValue(null); - - const sendResponse = await sendNotification(server, notificationService, id, notification, { _checkForErrors: checkForErrors }); - - expect(notificationService.getActionForId).toHaveBeenCalledTimes(1); - expect(notificationService.getActionForId).toHaveBeenCalledWith(id); - expect(checkForErrors).toHaveBeenCalledTimes(1); - expect(checkForErrors).toHaveBeenCalledWith(action, id, notification); - - expect(sendResponse).toEqual(response); - }); - - it('replies with unexpected result error', async () => { - const error = new Error(); - const action = { - performAction: jest.fn().mockReturnValue(Promise.reject(error)) - }; - const server = { }; - const notificationService = { - getActionForId: jest.fn().mockReturnValue(action), - }; - const checkForErrors = jest.fn().mockReturnValue(null); - - const sendResponse = await sendNotification(server, notificationService, id, notification, { _checkForErrors: checkForErrors }); - - expect(notificationService.getActionForId).toHaveBeenCalledTimes(1); - expect(notificationService.getActionForId).toHaveBeenCalledWith(id); - expect(checkForErrors).toHaveBeenCalledTimes(1); - expect(checkForErrors).toHaveBeenCalledWith(action, id, notification); - - expect(sendResponse).toEqual(boomify(error)); - }); - - }); - -}); diff --git a/x-pack/legacy/plugins/notifications/server/service/README.md b/x-pack/legacy/plugins/notifications/server/service/README.md deleted file mode 100644 index 1d9a2f2889a3c9..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/service/README.md +++ /dev/null @@ -1,575 +0,0 @@ -# Notification Service / Actions - -Use this service to send notifications to users. An example of a notification is sending an email or -explicitly adding something to the Kibana log. - -Notifications are inherently asynchronous actions because of the likelihood that any notification is -interacting with a remote server or service. - -## Referencing the Notification Service - -Note: Both of these may change in the future. - -### Server Side - -```js -const notificationService = server.plugins.notifications.notificationService; - -const action = notificationService.getActionForId('xpack-notifications-logger'); -const result = action.performAction({ - arbitrary: 'payload', - can: 'have multiple', - fields: [ 1, 2, 3 ] -}); -``` - -### HTTP - -```http -POST /api/notifications/v1/notify -{ - "action": "xpack-notifications-logger", - "data": { - "arbitrary": "payload", - "can": "have multiple", - "fields": [ 1, 2, 3 ] - } -} -``` - -## Interfaces - -There are two interfaces that are important from this package. `NotificationService`, which is exposed as -a singleton from the plugin: `server.plugins.notifications.notificationService`. And `Action`, which -provides an abstract JavaScript `class` to implement new `Action`s. - -### NotificationService Interface - -The `NotificationService` currently has four methods defined with very distinct purposes: - -1. `setAction` is intended for plugin authors to add actions that do not exist with the basic notifications -service. -2. `removeAction` is intended for replacing existing plugins (e.g., augmenting another action). -3. `getActionForId` enables explicitly fetching an action by its known ID. -4. `getActionsForData` enables discovering compatible actions given an arbitrary set of data. - -Note: Mutating the Notification Service should generally only be done based on very specific reasons, -such as plugin initialization or the user dynamically configuring a service's availability (e.g., -if we support a secure data store, the user could theoretically provide authentication details for an email -server). - -It is also possible that the user will want to configure multiple variants of the same action, such as -multiple email notifications with differing defaults. In that case, the action's ID may need to be -reconsidered. - -#### `setAction()` - -This is the only way to add an `Action` instance. Instances are expected to be extensions of the `Action` -class defined here. If the provided action already exists, then the old one is removed. - -##### Syntax - -```js -notificationService.setAction(action); -``` - -###### Parameters - -| Field | Type | Description | -|-------|------|-------------| -| `action` | Action | The unique action to support. | - -###### Returns - -Nothing. - -##### Example - -Create a simple logging action that can be triggered generically. - -```js -class LoggerAction extends Action { - - constructor({ server }) { - super({ server, id: 'xpack-notifications-logger', name: 'Log' }); - } - - getMissingFields() { - return []; - } - - async doPerformHealthCheck() { - return new ActionResult({ - message: `Logger action is always usable.`, - response: { }, - }); - } - - async doPerformAction(notification) { - this.server.log(['logger', 'info'], notification); - - return new ActionResult({ - message: 'Logged data returned as response.', - response: notification - }); - } - -} - -// It's possible that someone may choose to make the LoggerAction's log level configurable, so -// replacing it could be done by re-setting it, which means any follow-on usage would use the new level -// (or, possibly, you could create different actions entirely for different levels) -notificationService.setAction(new LoggerAction({ server })); -``` - -#### `removeAction()` - -Remove an action that has been set. - -##### Syntax - -```js -const action = notificationService.removeAction(actionId); -``` - -###### Parameters - -| Field | Type | Description | -|-------|------|-------------| -| `id` | String | ID of the action to remove. | - -###### Returns - -| Type | Description | -|------|-------------| -| Action \| null | The action that was removed. `null` otherwise. | - -##### Example - -```js -const action = notificationService.removeAction('xpack-notifications-logger'); - -if (action !== null) { - // removed; otherwise it didn't exist (maybe it was already removed) -} -``` - -#### `getActionForId()` - -Retrieve a specific `Action` from the Notification Service. - -##### Syntax - -```js -const action = notificationService.getActionForId(actionId); -``` - -###### Parameters - -| Field | Type | Description | -|-------|------|-------------| -| `id` | String | ID of the action to retrieve. | - -###### Returns - -| Type | Description | -|------|-------------| -| Action \| null | The action that was requested. `null` otherwise. | - -##### Example - -```js -// In this case, the ID is known from the earlier example -const action = notificationService.getActionForId('xpack-notifications-logger'); - -if (action !== null) { - // otherwise it didn't exist -} -``` - -#### `getActionsForData()` - -Retrieve any `Action`s from the Notification Service that accept the supplied data, which is useful for -discovery. - -##### Syntax - -```js -const actions = notificationService.getActionsForData(notification); -``` - -###### Parameters - -| Field | Type | Description | -|-------|------|-------------| -| `notification` | Object | Payload to send notification. | - -###### Returns - -| Type | Description | -|------|-------------| -| Action[] | The actions that accept a subset of the data. Empty array otherwise. | - -##### Example - -```js -// In this case, the ID is known from the earlier example -const actions = notificationService.getActionsForData({ - arbitrary: 'payload', - can: 'have multiple', - fields: [ 1, 2, 3 ] -}); - -if (action.length !== 0) { - // otherwise nothing matches -} -``` - -### Action Interface - -From the perspective of developers that want to make use of `Action`s, there are three relevant methods: - -1. `getMissingFields` provides an array of fields as well as the expected data type that did not exist in the -supplied data. -2. `performHealthCheck` attempts to perform a health check against the actions backing service. -3. `performAction` attempts to perform the purpose of the action (e.g., send the email) using the supplied -data. - -For developers to create new `Action`s, there are three related methods: - -1. `getMissingFields` provides an array of fields as well as the expected data type that did not exist in the -supplied data. -2. `doPerformHealthCheck` attempts to perform a health check against the actions backing service. - - `performHealthCheck` invokes this method and wraps it in order to catch `Error`s. -3. `doPerformAction` attempts to perform the purpose of the action (e.g., send the email) using the supplied -data. - - `performAction` invokes this method and wraps it in order to catch `Error`s. - -Every method, excluding `getMissingFields`, is asynchronous. - -#### `getMissingFields()` - -This method enables the building of "Sharing"-style UIs that allow the same payload to be shared across -many different actions. This is the same approach taken in iOS and Android sharing frameworks. - -##### Syntax - -```js -action.getMissingFields({ - arbitrary: 'payload', - can: 'have multiple', - fields: [ 1, 2, 3 ] -}); -``` - -###### Parameters - -| Field | Type | Description | -|-------|------|-------------| -| `notification` | Object | The data that you want to try to use with the action. | - -###### Returns - -| Type | Description | -|------|-------------| -| Object[] | The fields that were not present in the `notification` object. Empty array otherwise. | - -The object definition should match: - -| Field | Type | Description | -|-------|------|-------------| -| `field` | String | The JSON field name that was expected. | -| `name` | String | The user-readable name of the field (e.g., for a generated UI). | -| `type` | String | The type of data (`email`, `text`, `markdown`, `number`, `date`, `boolean`). | - -NOTE: This method _never_ throws an `Error`. - -##### Example - -Create a simple action whose fields can be checked automatically. - -```js -// Action Users -const action = notificationService.getActionForId(actionId); -const fields = action.getMissingFields({ - arbitrary: 'payload', - can: 'have multiple', - fields: [ 1, 2, 3 ] -}); - -if (fields.length !== 0) { - // there's some fields missing so this action should not be used yet -} - -// Action Creators -class FakeAction extends Action { - - constructor({ server, defaults = { } }) { - super({ server, id: 'xpack-notifications-fake', name: 'Fake' }); - - this.defaults = defaults; - } - - getMissingFields(notification) { - const missingFields = []; - - if (!Boolean(this.defaults.to) && !Boolean(notification.to)) { - missingFields.push({ - field: 'to', - name: 'To', - type: 'email', - }); - } - - return missingFields; - } - - // ... other methods ... - -} -``` - -#### `performHealthCheck()` - -This method enables the health status of third party services to be polled. The current approach only allows -an `Action`'s health to be in a boolean state: either it's up and expected to work, or it's down. - -The health check is interesting because some third party services that we anticipate supporting are -inherently untrustworthy when it comes to supporting health checks (e.g., email servers). Therefore, it -should not be expected that the health check will block usage of actions -- only provide feedback to the -user, which we can ideally provide in a future notification center within the UI. - -##### Syntax - -```js -const result = await action.performHealthCheck(); -``` - -###### Parameters - -None. - -###### Returns - -| Type | Description | -|------|-------------| -| ActionResult | The result of the health check. | - -An `ActionResult` defines a few methods: - -| Method | Type | Description | -|-------|------|-------------| -| `isOk()` | Boolean | `true` if there was no error and it is believed to be "up". | -| `getError()` | Object \| undefined | JSON error object. | -| `getResponse()` | Object \| undefined | JSON response from the server. If the server returns a field, it should be wrapped. | -| `getMessage()` | String | Human readable message describing the state. | - -NOTE: This method _never_ throws an `Error`. - -##### Example - -Create a simple action whose fields can be checked automatically. - -```js -const action = notificationService.getActionForId(actionId); -const result = await action.performHealthCheck(); - -if (result.isOk()) { - // theoretically the action is in a usable state -} else { - // theoretically the action is not in a usable state (but some services may have broken health checks!) -} -``` - -#### `performAction()` - -This method enables the actual usage of the `Action` for action users. - -##### Syntax - -```js -const result = await action.performAction({ - arbitrary: 'payload', - can: 'have multiple', - fields: [ 1, 2, 3 ] -}); -``` - -###### Parameters - -| Field | Type | Description | -|-------|------|-------------| -| `notification` | Object | The data that you want to try to use with the action. | - -###### Returns - -| Type | Description | -|------|-------------| -| ActionResult | The result of the health check. | - -An `ActionResult` defines a few methods: - -| Method | Type | Description | -|-------|------|-------------| -| `isOk()` | Boolean | `true` if there was no error and it is believed to be "up". | -| `getError()` | Object \| undefined | JSON error object. | -| `getResponse()` | Object \| undefined | JSON response from the server. If the server returns a field, it should be wrapped. | -| `getMessage()` | String | Human readable message describing the state. | - -NOTE: This method _never_ throws an `Error`. - -##### Example - -Create a simple action whose fields can be checked automatically. - -```js -const action = notificationService.getActionForId(actionId); -const result = await action.performAction({ - arbitrary: 'payload', - can: 'have multiple', - fields: [ 1, 2, 3 ] -}); - -if (result.isOk()) { - // theoretically the action is in a usable state -} else { - // theoretically the action is not in a usable state (but some services may have broken health checks!) -} -``` - -#### `doPerformHealthCheck()` - -This method is for `Action` creators. This performs the actual work to check the health of the action's -associated service as best as possible. - -This method should be thought of as a `protected` method only and it should never be called directly -outside of tests. - -##### Syntax - -Do not call this method directly outside of tests. - -```js -const result = await action.doPerformHealthCheck(); -``` - -###### Parameters - -None. - -###### Returns - -| Type | Description | -|------|-------------| -| ActionResult | The result of the health check. | - -An `ActionResult` defines a few methods: - -| Method | Type | Description | -|-------|------|-------------| -| `isOk()` | Boolean | `true` if there was no error and it is believed to be "up". | -| `getError()` | Object \| undefined | JSON error object. | -| `getResponse()` | Object \| undefined | JSON response from the server. If the server returns a field, it should be wrapped. | -| `getMessage()` | String | Human readable message describing the state. | - -NOTE: This method can throw an `Error` in lieu of returning an `ActionResult`. - -##### Example - -Create a simple action whose health status can be checked automatically. - -```js -class FakeAction extends Action { - - constructor({ server }) { - super({ server, id: 'xpack-notifications-fake', name: 'Fake' }); - } - - async doPerformHealthCheck() { - // this responds with a boolean 'true' response, otherwise throws an Error - const response = await this.transporter.verify(); - - return new ActionResult({ - message: `Fake action configuration has been verified.`, - response: { - verified: true - }, - }); - } - - // ... other methods ... - -} -``` - -#### `doPerformAction()` - -This method is for `Action` creators. This performs the actual function of the action. - -This method should be thought of as a `protected` method only and it should never be called directly -outside of tests. - -##### Syntax - -Do not call this method directly outside of tests. - -```js -const result = await action.doPerformAction(); -``` - -###### Parameters - -| Field | Type | Description | -|-------|------|-------------| -| `notification` | Object | The data that you want to try to use with the action. | - -###### Returns - -| Type | Description | -|------|-------------| -| ActionResult | The result of the health check. | - -An `ActionResult` defines a few methods: - -| Method | Type | Description | -|-------|------|-------------| -| `isOk()` | Boolean | `true` if there was no error and it is believed to be "up". | -| `getError()` | Object \| undefined | JSON error object. | -| `getResponse()` | Object \| undefined | JSON response from the server. If the server returns a field, it should be wrapped. | -| `getMessage()` | String | Human readable message describing the state. | - -NOTE: This method can throw an `Error` in lieu of returning an `ActionResult`. - -##### Example - -Create a simple action whose health status can be checked automatically. - -```js -class FakeAction extends Action { - - constructor({ server }) { - super({ server, id: 'xpack-notifications-fake', name: 'Fake' }); - } - - async doPerformAction(notification) { - // Note: This throws an Error upon failure - const response = await this.transporter.sendMail({ - from: notification.from, - to: notification.to, - cc: notification.cc, - bcc: notification.bcc, - subject: notification.subject, - html: notification.markdown, - text: notification.markdown, - }); - - return new ActionResult({ - message: `Success! Sent email for '${notification.subject}'.`, - response, - }); - } - - // ... other methods ... - -} -``` diff --git a/x-pack/legacy/plugins/notifications/server/service/action.js b/x-pack/legacy/plugins/notifications/server/service/action.js deleted file mode 100644 index 6077ff6e2b41a9..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/service/action.js +++ /dev/null @@ -1,143 +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 { ActionResult } from './action_result'; - -/** - * Actions represent a singular, generic "action", such as "Send to Email". - * - * Note: Implementations of Action are inherently server-side operations. It may or may not be desiable to fire - * these actions from the UI (triggering a server-side call), but these should be called for such a purpose. - */ -export class Action { - - /** - * Create a new Action. - * - * The suggested ID is the name of the plugin that provides it, and the unique portion. - * For example: "core-email" if core provided email. - * - * @param {Object} server The Kibana server object. - * @param {String} id The unique identifier for the action. - * @param {String} name User-friendly name for the action. - */ - constructor({ server, id, name }) { - this.server = server; - this.id = id; - this.name = name; - } - - /** - * Get the unique ID of the Action. - * - * @return {String} - */ - getId() { - return this.id; - } - - /** - * Get the user-friendly name of the Action. - * - * @return {String} - */ - getName() { - return this.name; - } - - /** - * Determine if this action can use the {@code notification}. This is useful if you use the action service - * generically, such as using it from a generic UI. - * - * This is intended to be a simple check of the {@code notification}, rather than an asynchronous action. - * - * @param {Object} notification The notification data to use. - * @return {Array} Array defining missing fields. Empty if none. - */ - getMissingFields() { - return []; - } - - /** - * Implementers must override to perform the health check. - * - * This should not be called directly outside of tests to ensure that any error is wrapped properly. - * - * Note: Some services do not provide consistent, reliable health checks, such as email. As such, - * implementers must weigh the nature of false negatives versus the utility of having this check. - * - * @return {Promise} The result of the health check, which must be an {@code ActionResult}. - * @throws {Error} if there is an unexpected failure occurs. - */ - async doPerformHealthCheck() { - throw new Error(`[doPerformHealthCheck] is not implemented for '${this.name}' action.`); - } - - /** - * Verify that the action can be used to the best of the ability of the service that it is using. - * - * @return {Promise} Always an {@code ActionResult}. - */ - async performHealthCheck() { - try { - return await this.doPerformHealthCheck(); - } catch (error) { - return new ActionResult({ - message: `Unable to perform '${this.name}' health check: ${error.message}.`, - error - }); - } - } - - /** - * Implementers must override to perform the action using the {@code notification}. - * - * This should not be called directly to ensure that any error is wrapped properly. - * - * @param {Object} notification The notification data to use. - * @return {Promise} The result of the action, which must be a {@code ActionResult}. - * @throws {Error} if the method is not implemented or an unexpected failure occurs. - */ - async doPerformAction(notification) { - throw new Error(`[doPerformAction] is not implemented for '${this.name}' action: ${JSON.stringify(notification)}`); - } - - /** - * Check to see if the current license allows actions. - * - * @return {Boolean} true when it is usable - * @throws {Error} if there is an unexpected issue checking the license - */ - isLicenseValid() { - return this.server.plugins.xpack_main.info.license.isNotBasic(); - } - - /** - * Perform the action using the {@code notification}. - * - * Actions automatically fail if the license check fails. - * - * Note to implementers: override doPerformAction instead of this method to help guarantee proper handling. - * - * @param {Object} notification The notification data to use. - * @return {Promise} The result of the action, which must be a {@code ActionResult}. - */ - async performAction(notification) { - try { - if (!this.isLicenseValid()) { - throw new Error(`The current license does not allow '${this.name}' action.`); - } - - return await this.doPerformAction(notification); - } catch (error) { - return new ActionResult({ - message: `Unable to perform '${this.name}' action: ${error.message}.`, - error - }); - } - } - -} diff --git a/x-pack/legacy/plugins/notifications/server/service/action.test.js b/x-pack/legacy/plugins/notifications/server/service/action.test.js deleted file mode 100644 index a6fa5b28131318..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/service/action.test.js +++ /dev/null @@ -1,242 +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 { Action } from './action'; -import { ActionResult } from './action_result'; - -describe('Action', () => { - - const server = { }; - const id = 'notifications-test'; - const unimplementedName = 'Unimplemented'; - const throwsErrorName = 'Throws Error'; - const passThruName = 'Test Action'; - const action = new Action({ server, id, name: unimplementedName }); - const notification = { - fake: true, - }; - - test('id and name to be from constructor', () => { - expect(action.server).toBe(server); - expect(action.getId()).toBe(id); - expect(action.getName()).toBe(unimplementedName); - }); - - test('getMissingFields returns an empty array', () => { - expect(action.getMissingFields()).toEqual([]); - expect(action.getMissingFields(notification)).toEqual([]); - }); - - test('doPerformHealthChecks throws error indicating that it is not implemented', async () => { - await expect(action.doPerformHealthCheck()) - .rejects - .toThrow(`[doPerformHealthCheck] is not implemented for '${unimplementedName}' action.`); - }); - - describe('performHealthChecks', () => { - - class ThrowsErrorHealthCheckAction extends Action { - constructor() { - super({ server: { }, id, name: throwsErrorName }); - } - - async doPerformHealthCheck() { - throw new Error('TEST - expected'); - } - } - - class PassThruHealthCheckAction extends Action { - constructor(result) { - super({ server: { }, id, name: passThruName }); - this.result = result; - } - - async doPerformHealthCheck() { - return this.result; - } - } - - test('runs against unimplemented doPerformHealthChecks', async () => { - const result = await action.performHealthCheck(); - - expect(result instanceof ActionResult).toBe(true); - expect(result.isOk()).toBe(false); - expect(result.getMessage()) - .toMatch(new RegExp(`^Unable to perform '${unimplementedName}' health check: \\[doPerformHealthCheck\\] is not.*`)); - }); - - test('runs against failing doPerformHealthChecks', async () => { - const failAction = new ThrowsErrorHealthCheckAction(); - const result = await failAction.performHealthCheck(); - - expect(result instanceof ActionResult).toBe(true); - expect(result.isOk()).toBe(false); - expect(result.getMessage()) - .toMatch(new RegExp(`^Unable to perform '${throwsErrorName}' health check: TEST - expected`)); - }); - - test('runs against succeeding result', async () => { - const expectedResult = new ActionResult({ message: 'Blah', response: { ok: true } }); - const succeedsAction = new PassThruHealthCheckAction(expectedResult); - const result = await succeedsAction.performHealthCheck(); - - expect(result).toBe(expectedResult); - }); - - }); - - test('doPerformAction throws error indicating that it is not implemented', async () => { - await expect(action.doPerformAction(notification)) - .rejects - .toThrow(`[doPerformAction] is not implemented for '${unimplementedName}' action: {"fake":true}`); - }); - - describe('isLicenseValid', () => { - - test('server variable is not exposed as expected', () => { - expect(() => action.isLicenseValid()).toThrow(Error); - }); - - test('returns false is license is not valid', () => { - const unlicensedServer = { - plugins: { - xpack_main: { - info: { - license: { - isNotBasic: () => false - } - } - } - } - }; - const unlicensedAction = new Action({ server: unlicensedServer, id, name: unimplementedName }); - - expect(unlicensedAction.isLicenseValid()).toBe(false); - }); - - test('returns true is license is not valid', () => { - const licensedServer = { - plugins: { - xpack_main: { - info: { - license: { - isNotBasic: () => true - } - } - } - } - }; - const licensedAction = new Action({ server: licensedServer, id, name: unimplementedName }); - - expect(licensedAction.isLicenseValid()).toBe(true); - }); - - }); - - describe('performAction', () => { - - // valid license - const server = { - plugins: { - xpack_main: { - info: { - license: { - isNotBasic: () => true - } - } - } - } - }; - - class ThrowsErrorAction extends Action { - constructor() { - super({ server, id, name: throwsErrorName }); - } - - async doPerformAction() { - throw new Error('TEST - expected'); - } - } - - class PassThruAction extends Action { - constructor(result) { - super({ server, id, name: passThruName }); - this.result = result; - } - - async doPerformAction() { - return this.result; - } - } - - describe('fails license check', () => { - - // handles the case when xpack_main definitions change - test('because of bad reference', async () => { - // server is an empty object, so a reference fails early in the chain (mostly a way to give devs a way to find this error) - const result = await action.performAction(notification); - - expect(result instanceof ActionResult).toBe(true); - expect(result.isOk()).toBe(false); - }); - - test('because license is invalid or basic', async () => { - const unlicensedServer = { - plugins: { - xpack_main: { - info: { - license: { - isNotBasic: () => false - } - } - } - } - }; - const unlicensedAction = new Action({ server: unlicensedServer, id, name: unimplementedName }); - const result = await unlicensedAction.performAction(notification); - - expect(result instanceof ActionResult).toBe(true); - expect(result.isOk()).toBe(false); - expect(result.getMessage()) - .toMatch( - `Unable to perform '${unimplementedName}' action: ` + - `The current license does not allow '${unimplementedName}' action.` - ); - }); - - }); - - test('runs against unimplemented doPerformAction', async () => { - const licensedAction = new Action({ server, id, name: unimplementedName }); - const result = await licensedAction.performAction(notification); - - expect(result instanceof ActionResult).toBe(true); - expect(result.isOk()).toBe(false); - expect(result.getMessage()) - .toMatch(new RegExp(`^Unable to perform '${unimplementedName}' action: \\[doPerformAction\\] is not.*`)); - }); - - test('runs against failing doPerformAction', async () => { - const failAction = new ThrowsErrorAction(); - const result = await failAction.performAction(notification); - - expect(result instanceof ActionResult).toBe(true); - expect(result.isOk()).toBe(false); - expect(result.getMessage()) - .toMatch(new RegExp(`^Unable to perform '${throwsErrorName}' action: TEST - expected`)); - }); - - test('runs against succeeding result', async () => { - const expectedResult = new ActionResult({ message: 'Blah', response: { ok: true } }); - const succeedsAction = new PassThruAction(expectedResult); - const result = await succeedsAction.performAction(notification); - - expect(result).toBe(expectedResult); - }); - - }); - -}); diff --git a/x-pack/legacy/plugins/notifications/server/service/action_result.js b/x-pack/legacy/plugins/notifications/server/service/action_result.js deleted file mode 100644 index 35507045c898ce..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/service/action_result.js +++ /dev/null @@ -1,73 +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. - */ - -/** - * Action Results represent generic, predictable responses from Actions. - */ -export class ActionResult { - - /** - * Create a new Action Result. - * - * Success is determined by the existence of an error. - * - * @param {String} message The message to display about the result, presumably in a Toast. - * @param {Object|undefined} response The response from the "other" side. - * @param {Object|undefined} error The error, if any. - */ - constructor({ message, response, error }) { - this.message = message; - this.response = response; - this.error = error; - this.ok = !Boolean(error); - } - - /** - * Get the error caused by the action. - * - * @returns {Object|undefined} The error response, or {@code undefined} if no error. - */ - getError() { - return this.error; - } - - /** - * Get the message displayable to the user. - * - * @returns {String} The message. - */ - getMessage() { - return this.message; - } - - /** - * The raw JSON response from the action. - * - * @returns {Object|undefined} The JSON response. - */ - getResponse() { - return this.response; - } - - /** - * Determine if the action succeeded. - * - * @returns {Boolean} {@code true} for success. - */ - isOk() { - return this.ok; - } - - toJson() { - return { - ok: this.ok, - error: this.error, - message: this.message, - response: this.response, - }; - } - -} diff --git a/x-pack/legacy/plugins/notifications/server/service/action_result.test.js b/x-pack/legacy/plugins/notifications/server/service/action_result.test.js deleted file mode 100644 index 128ddb1dd8b92f..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/service/action_result.test.js +++ /dev/null @@ -1,54 +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 { ActionResult } from './action_result'; - -describe('ActionResult', () => { - - const message = 'this is a message'; - const response = { other: { side: { response: true } } }; - const error = { message: `Error: ${message}` }; - - const okResult = new ActionResult({ message, response }); - const notOkResult = new ActionResult({ message, response, error }); - - test('getError returns supplied error or undefined', () => { - expect(okResult.getError()).toBeUndefined(); - expect(notOkResult.getError()).toBe(error); - }); - - test('getMessage returns supplied message', () => { - expect(okResult.getMessage()).toBe(message); - expect(notOkResult.getMessage()).toBe(message); - }); - - test('getResponse returns supplied response', () => { - expect(okResult.getResponse()).toBe(response); - expect(notOkResult.getResponse()).toBe(response); - }); - - test('isOk returns based on having an error', () => { - expect(okResult.isOk()).toBe(true); - expect(notOkResult.isOk()).toBe(false); - }); - - test('toJson', () => { - expect(okResult.toJson()).toEqual({ - ok: true, - error: undefined, - message, - response, - }); - - expect(notOkResult.toJson()).toEqual({ - ok: false, - error, - message, - response, - }); - }); - -}); diff --git a/x-pack/legacy/plugins/notifications/server/service/index.js b/x-pack/legacy/plugins/notifications/server/service/index.js deleted file mode 100644 index dd64e16abc7135..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/service/index.js +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { notificationService } from './notification_service'; -export { Action } from './action'; -export { ActionResult } from './action_result'; diff --git a/x-pack/legacy/plugins/notifications/server/service/notification_service.js b/x-pack/legacy/plugins/notifications/server/service/notification_service.js deleted file mode 100644 index 5804532d8be68f..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/service/notification_service.js +++ /dev/null @@ -1,86 +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. - */ - -/** - * Notification Service represents a service that contains generic "actions", such as "Send to Email" - * that are added at startup by plugins to enable notifications to be sent either by the user manually - * or via the some scheduled / automated task. - */ -export class NotificationService { - - constructor() { - this.actions = []; - } - - /** - * Add a new action to the action service. - * - * @param {Action} action An implementation of Action. - */ - setAction = (action) => { - this.removeAction(action.id); - - this.actions.push(action); - } - - /** - * Remove an existing action from the action service. - * - * @param {String} id The ID of the action to remove. - * @return {Action} The action that was removed, or null. - */ - removeAction = (id) => { - const index = this.actions.findIndex(action => action.id === id); - - if (index !== -1) { - const removedActions = this.actions.splice(index, 1); - return removedActions[0]; - } - - return null; - } - - /** - * Get action with the specified {@code id}, if any. - * - * This is useful when you know that an action is provided, such as one provided by your own plugin, - * and you want to use it to handle things in a consistent way. - * - * @param {String} id The ID of the Action. - * @return {Action} The Action that matches the ID, or null. - */ - getActionForId = (id) => { - const index = this.actions.findIndex(action => action.id === id); - - if (index !== -1) { - return this.actions[index]; - } - - return null; - } - - /** - * Get actions that will accept the {@code data}. - * - * @param {Object} data The data object to pass to actions. - * @return {Array} An array of Actions that can be used with the data, if any. Empty if none. - */ - getActionsForData = (data) => { - return this.actions.filter(action => { - try { - return action.getMissingFields(data).length === 0; - } catch (err) { - return false; - } - }); - } - -} - -/** - * A singleton reference to the notification service intended to be used across Kibana. - */ -export const notificationService = new NotificationService(); diff --git a/x-pack/legacy/plugins/notifications/server/service/notification_service.test.js b/x-pack/legacy/plugins/notifications/server/service/notification_service.test.js deleted file mode 100644 index 903d7ea0c5d922..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/service/notification_service.test.js +++ /dev/null @@ -1,134 +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 { Action } from './action'; -import { NotificationService } from './notification_service'; - -class TestAction extends Action { - constructor({ server, id }) { - super({ server, id, name: 'TestAction' }); - } - - getMissingFields() { - return []; - } -} - -// always returns a missing field -class MissingFieldTestAction extends Action { - constructor({ server, id }) { - super({ server, id, name: 'MissingFieldTestAction' }); - } - - getMissingFields() { - return [ { field: 'subject', name: 'Subject', type: 'text' } ]; - } -} - -describe('NotificationService', () => { - - const server = { }; - const actionId = 'notifications-test'; - const action = new TestAction({ server, id: actionId }); - - let notificationService; - - beforeEach(() => { - notificationService = new NotificationService(); - }); - - test('initializes with no default actions', () => { - expect(notificationService.actions).toEqual([]); - }); - - describe('setAction', () => { - - test('adds the action', () => { - notificationService.setAction(action); - - expect(notificationService.actions[0]).toBe(action); - }); - - test('removes any action with the same ID first, then adds the action', () => { - notificationService.setAction({ id: actionId }); - notificationService.setAction(action); - - expect(notificationService.actions).toHaveLength(1); - expect(notificationService.actions[0]).toBe(action); - }); - - }); - - describe('removeAction', () => { - - test('returns null if the action does not exist', () => { - expect(notificationService.removeAction(actionId)).toBe(null); - - notificationService.setAction(action); - - expect(notificationService.removeAction('not-' + actionId)).toBe(null); - expect(notificationService.actions[0]).toBe(action); - }); - - test('returns the removed action', () => { - notificationService.setAction(action); - - expect(notificationService.removeAction(actionId)).toBe(action); - expect(notificationService.actions).toEqual([]); - }); - - }); - - describe('getActionForId', () => { - - test('returns null if the action does not exist', () => { - expect(notificationService.getActionForId(actionId)).toBe(null); - - notificationService.setAction(action); - - expect(notificationService.getActionForId('not-' + actionId)).toBe(null); - expect(notificationService.actions[0]).toBe(action); - }); - - test('returns the action', () => { - notificationService.setAction(action); - - expect(notificationService.getActionForId(actionId)).toBe(action); - expect(notificationService.actions[0]).toBe(action); - }); - - }); - - describe('getActionsForData', () => { - - test('returns [] if no corresponding action exists', () => { - expect(notificationService.getActionsForData({})).toEqual([]); - - notificationService.setAction(new MissingFieldTestAction({ server, id: 'always-missing' })); - - expect(notificationService.getActionsForData({})).toEqual([]); - expect(notificationService.actions).toHaveLength(1); - }); - - test('returns the actions that match', () => { - notificationService.setAction(action); - - expect(notificationService.getActionsForData({})).toEqual([ action ]); - expect(notificationService.actions[0]).toBe(action); - - const otherActionId = 'other-' + actionId; - - notificationService.setAction(new MissingFieldTestAction({ server, id: 'always-missing' })); - notificationService.setAction(new TestAction({ server, id: otherActionId })); - - const actions = notificationService.getActionsForData({}); - - expect(actions.map(action => action.id)).toEqual([ actionId, otherActionId ]); - }); - - }); - -}); diff --git a/x-pack/legacy/plugins/notifications/server/slack/create_slack_action.js b/x-pack/legacy/plugins/notifications/server/slack/create_slack_action.js deleted file mode 100644 index ecffcadc9b4dac..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/slack/create_slack_action.js +++ /dev/null @@ -1,56 +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 { SlackAction } from './slack_action'; - -/** - * Create a Slack options object from the config. - * - * @param {Object} config The server configuration. - * @return {Object} An object that configures Slack. - */ -export function optionsFromConfig(config) { - return { - token: config.get('xpack.notifications.slack.token') - }; -} - -/** - * Create a Slack defaults object from the config. - * - * Defaults include things like the default channel that messages are posted to. - * - * @param {Object} config The server configuration. - * @return {Object} An object that configures Slack on a per-message basis. - */ -export function defaultsFromConfig(config) { - return { - channel: config.get('xpack.notifications.slack.defaults.channel'), - as_user: config.get('xpack.notifications.slack.defaults.as_user'), - icon_emoji: config.get('xpack.notifications.slack.defaults.icon_emoji'), - icon_url: config.get('xpack.notifications.slack.defaults.icon_url'), - link_names: config.get('xpack.notifications.slack.defaults.link_names'), - mrkdwn: config.get('xpack.notifications.slack.defaults.mrkdwn'), - unfurl_links: config.get('xpack.notifications.slack.defaults.unfurl_links'), - unfurl_media: config.get('xpack.notifications.slack.defaults.unfurl_media'), - username: config.get('xpack.notifications.slack.defaults.username'), - }; -} - -/** - * Create a new Slack Action based on the configuration. - * - * @param {Object} server The server object. - * @return {SlackAction} A new Slack Action based on the kibana.yml configuration. - */ -export function createSlackAction(server, { _options = optionsFromConfig, _defaults = defaultsFromConfig } = { }) { - const config = server.config(); - - const options = _options(config); - const defaults = _defaults(config); - - return new SlackAction({ server, options, defaults }); -} diff --git a/x-pack/legacy/plugins/notifications/server/slack/create_slack_action.test.js b/x-pack/legacy/plugins/notifications/server/slack/create_slack_action.test.js deleted file mode 100644 index 68d669985568a9..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/slack/create_slack_action.test.js +++ /dev/null @@ -1,94 +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 { SlackAction } from './slack_action'; -import { - createSlackAction, - defaultsFromConfig, - optionsFromConfig, -} from './create_slack_action'; - -describe('create_slack_action', () => { - - test('optionsFromConfig uses config without modification', () => { - const get = key => { - const suffixes = [ - 'token', - ]; - const value = suffixes.find(suffix => { - return `xpack.notifications.slack.${suffix}` === key; - }); - - if (value === undefined) { - throw new Error(`Unknown config key used ${key}`); - } - - return value; - }; - - expect(optionsFromConfig({ get })).toEqual({ - token: 'token', - }); - }); - - test('defaultsFromConfig uses config without modification', () => { - const get = key => { - const suffixes = [ - 'channel', - 'as_user', - 'icon_emoji', - 'icon_url', - 'link_names', - 'mrkdwn', - 'unfurl_links', - 'unfurl_media', - 'username', - ]; - const value = suffixes.find(suffix => { - return `xpack.notifications.slack.defaults.${suffix}` === key; - }); - - if (value === undefined) { - throw new Error(`Unknown config key used ${key}`); - } - - return value; - }; - - expect(defaultsFromConfig({ get })).toEqual({ - channel: 'channel', - as_user: 'as_user', - icon_emoji: 'icon_emoji', - icon_url: 'icon_url', - link_names: 'link_names', - mrkdwn: 'mrkdwn', - unfurl_links: 'unfurl_links', - unfurl_media: 'unfurl_media', - username: 'username', - }); - }); - - test('createSlackAction', async () => { - const config = { }; - const server = { config: jest.fn().mockReturnValue(config) }; - const _options = jest.fn().mockReturnValue({ options: true }); - const defaults = { defaults: true }; - const _defaults = jest.fn().mockReturnValue(defaults); - - const action = createSlackAction(server, { _options, _defaults }); - - expect(action instanceof SlackAction).toBe(true); - expect(action.defaults).toBe(defaults); - - expect(server.config).toHaveBeenCalledTimes(1); - expect(server.config).toHaveBeenCalledWith(); - expect(_options).toHaveBeenCalledTimes(1); - expect(_options).toHaveBeenCalledWith(config); - expect(_defaults).toHaveBeenCalledTimes(1); - expect(_defaults).toHaveBeenCalledWith(config); - }); - -}); diff --git a/x-pack/legacy/plugins/notifications/server/slack/index.js b/x-pack/legacy/plugins/notifications/server/slack/index.js deleted file mode 100644 index da7cd204e88877..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/slack/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { createSlackAction } from './create_slack_action'; diff --git a/x-pack/legacy/plugins/notifications/server/slack/slack_action.js b/x-pack/legacy/plugins/notifications/server/slack/slack_action.js deleted file mode 100644 index 236ce1af9c360b..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/slack/slack_action.js +++ /dev/null @@ -1,116 +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 { WebClient } from '@slack/client'; - -import { Action, ActionResult } from '../'; - -export const SLACK_ACTION_ID = 'xpack-notifications-slack'; - -/** - * Create a new Slack {@code WebClient}. - * - * Currently the only option expected is {@code token}. - * - * @param {Object} options Slack API options. - * @returns {WebClient} Always. - */ -export function webClientCreator(options) { - return new WebClient(options.token); -} - -/** - * Slack Action enables generic sending of Slack messages. - */ -export class SlackAction extends Action { - - /** - * Create a new Action capable of sending Slack messages. - * - * @param {Object} server Kibana server object. - * @param {Object} options Configuration options for the Slack WebClient. Currently only expect "token" field. - * @param {Object} defaults Default fields used when sending messages. - * @param {Function} _webClientCreator Exposed for tests. - */ - constructor({ server, options, defaults = { }, _webClientCreator = webClientCreator }) { - super({ server, id: SLACK_ACTION_ID, name: 'Slack' }); - - this.client = _webClientCreator(options); - this.defaults = defaults; - } - - getMissingFields(data) { - const missingFields = []; - - if (!Boolean(this.defaults.channel) && !Boolean(data.channel)) { - missingFields.push({ - field: 'channel', - name: 'Channel', - type: 'text', - }); - } - - if (!Boolean(data.subject)) { - missingFields.push({ - field: 'subject', - name: 'Message', - type: 'markdown', - }); - } - - return missingFields; - } - - async doPerformHealthCheck() { - const response = await this.client.api.test(); - - if (response.ok) { - return new ActionResult({ - message: `Slack action configuration has been verified.`, - response, - }); - } - - return new ActionResult({ - message: `Slack action configuration could not be verified.`, - response, - error: response.error || { message: 'Unknown Error' }, - }); - } - - /** - * Render the message based on whether or not a {@code markdown} body was supplied. - */ - renderMessage({ subject, markdown }) { - const attachments = []; - - if (markdown) { - attachments.push({ text: markdown }); - } - - return { text: subject, attachments }; - } - - async doPerformAction({ subject, markdown, channel }) { - // NOTE: When we want to support files, then we should look into using client.files.upload({ ... }) - // without _also_ sending chat message because the file upload endpoint supports chat behavior - // in addition to files, but the reverse is not true. - const slackChannel = channel || this.defaults.channel; - - const response = await this.client.chat.postMessage({ - ...this.defaults, - ...this.renderMessage({ subject, markdown }), - channel: slackChannel, - }); - - return new ActionResult({ - message: `Posted Slack message to channel '${slackChannel}'.`, - response, - error: response.error, - }); - } - -} diff --git a/x-pack/legacy/plugins/notifications/server/slack/slack_action.test.js b/x-pack/legacy/plugins/notifications/server/slack/slack_action.test.js deleted file mode 100644 index 833047a580abb7..00000000000000 --- a/x-pack/legacy/plugins/notifications/server/slack/slack_action.test.js +++ /dev/null @@ -1,277 +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 { WebClient } from '@slack/client'; -import { ActionResult } from '../'; -import { - SLACK_ACTION_ID, - SlackAction, - webClientCreator, -} from './slack_action'; - -describe('SlackAction', () => { - - const server = { }; - const options = { options: true }; - const defaults = { defaults: true }; - const client = { - api: { - // see beforeEach - }, - chat: { - // see beforeEach - } - }; - let _webClientCreator; - - let action; - - beforeEach(() => { - client.api.test = jest.fn(); - client.chat.postMessage = jest.fn(); - _webClientCreator = jest.fn().mockReturnValue(client); - - action = new SlackAction({ server, options, defaults, _webClientCreator }); - }); - - test('webClientCreator creates a WebClient', () => { - expect(webClientCreator('faketoken') instanceof WebClient).toBe(true); - }); - - test('id and name to be from constructor', () => { - expect(action.getId()).toBe(SLACK_ACTION_ID); - expect(action.getName()).toBe('Slack'); - expect(action.client).toBe(client); - - expect(_webClientCreator).toHaveBeenCalledTimes(1); - expect(_webClientCreator).toHaveBeenCalledWith(options); - }); - - describe('getMissingFields', () => { - - test('returns missing fields', () => { - const channel = { field: 'channel', name: 'Channel', type: 'text' }; - const subject = { field: 'subject', name: 'Message', type: 'markdown' }; - - const missing = [ - { defaults: { }, notification: { }, missing: [ channel, subject, ], }, - { defaults: { }, notification: { channel: '#kibana', }, missing: [ subject, ], }, - { defaults: { channel: '#kibana', }, notification: { }, missing: [ subject, ], }, - { defaults: { }, notification: { subject: 'subject', }, missing: [ channel, ], }, - ]; - - missing.forEach(check => { - const newDefaultsAction = new SlackAction({ server, options, defaults: check.defaults, _webClientCreator }); - - expect(newDefaultsAction.getMissingFields(check.notification)).toEqual(check.missing); - }); - }); - - test('returns [] when all fields exist', () => { - const exists = [ - { defaults: { }, notification: { channel: '#kibana', subject: 'subject', }, }, - { defaults: { channel: '#kibana', }, notification: { subject: 'subject', }, }, - ]; - - exists.forEach(check => { - const newDefaultsAction = new SlackAction({ server, options, defaults: check.defaults, _webClientCreator }); - - expect(newDefaultsAction.getMissingFields(check.notification)).toEqual([]); - }); - }); - - }); - - describe('doPerformHealthCheck', () => { - - test('rethrows Error for failure', async () => { - const error = new Error('TEST - expected'); - - client.api.test.mockRejectedValue(error); - - await expect(action.doPerformHealthCheck()) - .rejects - .toThrow(error); - - expect(client.api.test).toHaveBeenCalledTimes(1); - expect(client.api.test).toHaveBeenCalledWith(); - }); - - test('returns ActionResult if not ok with error', async () => { - const response = { ok: false, error: { expected: true } }; - - client.api.test.mockResolvedValue(response); - - const result = await action.doPerformHealthCheck(); - - expect(result instanceof ActionResult).toBe(true); - expect(result.isOk()).toBe(false); - expect(result.getMessage()).toMatch('Slack action configuration could not be verified.'); - expect(result.getResponse()).toBe(response); - expect(result.getError()).toBe(response.error); - - expect(client.api.test).toHaveBeenCalledTimes(1); - expect(client.api.test).toHaveBeenCalledWith(); - }); - - test('returns ActionResult if not ok with default error', async () => { - const response = { ok: false }; - - client.api.test.mockResolvedValue(response); - - const result = await action.doPerformHealthCheck(); - - expect(result instanceof ActionResult).toBe(true); - expect(result.isOk()).toBe(false); - expect(result.getMessage()).toMatch('Slack action configuration could not be verified.'); - expect(result.getResponse()).toBe(response); - expect(result.getError()).toEqual({ message: 'Unknown Error' }); - - expect(client.api.test).toHaveBeenCalledTimes(1); - expect(client.api.test).toHaveBeenCalledWith(); - }); - - test('returns ActionResult for success', async () => { - const response = { ok: true }; - - client.api.test.mockResolvedValue(response); - - const result = await action.doPerformHealthCheck(); - - expect(result instanceof ActionResult).toBe(true); - expect(result.isOk()).toBe(true); - expect(result.getMessage()).toMatch('Slack action configuration has been verified.'); - expect(result.getResponse()).toBe(response); - - expect(client.api.test).toHaveBeenCalledTimes(1); - expect(client.api.test).toHaveBeenCalledWith(); - }); - - }); - - describe('renderMessage', () => { - - test('does not contain attachments', () => { - const message = { subject: 'subject' }; - const response = action.renderMessage(message); - - expect(response).toMatchObject({ - text: message.subject, - attachments: [ ] - }); - }); - - test('contains attachments', () => { - const message = { subject: 'subject', markdown: 'markdown' }; - const response = action.renderMessage(message); - - expect(response).toMatchObject({ - text: message.subject, - attachments: [ - { - text: message.markdown - } - ] - }); - }); - - }); - - describe('doPerformAction', () => { - const message = { channel: '#kibana', subject: 'subject', markdown: 'body', }; - - test('rethrows Error for failure', async () => { - const error = new Error('TEST - expected'); - - client.chat.postMessage.mockRejectedValue(error); - - await expect(action.doPerformAction(message)) - .rejects - .toThrow(error); - - expect(client.chat.postMessage).toHaveBeenCalledTimes(1); - expect(client.chat.postMessage).toHaveBeenCalledWith({ - ...defaults, - ...action.renderMessage(message), - channel: message.channel, - }); - }); - - test('returns ActionResult for failure without Error', async () => { - const response = { fake: true, error: { expected: true } }; - - client.chat.postMessage.mockResolvedValue(response); - - const result = await action.doPerformAction(message); - - expect(result instanceof ActionResult).toBe(true); - expect(result.isOk()).toBe(false); - expect(result.getMessage()).toMatch(`Posted Slack message to channel '${message.channel}'.`); - expect(result.getResponse()).toBe(response); - expect(result.getError()).toBe(response.error); - - expect(client.chat.postMessage).toHaveBeenCalledTimes(1); - expect(client.chat.postMessage).toHaveBeenCalledWith({ - ...defaults, - ...action.renderMessage(message), - channel: message.channel, - }); - }); - - - test('returns ActionResult for success', async () => { - const response = { fake: true }; - - client.chat.postMessage.mockResolvedValue(response); - - const result = await action.doPerformAction(message); - - expect(result instanceof ActionResult).toBe(true); - expect(result.isOk()).toBe(true); - expect(result.getMessage()).toMatch(`Posted Slack message to channel '${message.channel}'.`); - expect(result.getResponse()).toBe(response); - - expect(client.chat.postMessage).toHaveBeenCalledTimes(1); - expect(client.chat.postMessage).toHaveBeenCalledWith({ - ...defaults, - ...action.renderMessage(message), - channel: message.channel, - }); - }); - - test('returns ActionResult for success with default channel', async () => { - const response = { fake: false }; - - client.chat.postMessage.mockResolvedValue(response); - - const channelDefaults = { - ...defaults, - channel: '#kibana', - }; - const noChannelMessage = { - ...message, - channel: undefined, - }; - const newDefaultsAction = new SlackAction({ server, options, defaults: channelDefaults, _webClientCreator }); - - const result = await newDefaultsAction.doPerformAction(noChannelMessage); - - expect(result instanceof ActionResult).toBe(true); - expect(result.isOk()).toBe(true); - expect(result.getMessage()).toMatch(`Posted Slack message to channel '${channelDefaults.channel}'.`); - expect(result.getResponse()).toBe(response); - - expect(client.chat.postMessage).toHaveBeenCalledTimes(1); - expect(client.chat.postMessage).toHaveBeenCalledWith({ - ...defaults, - ...action.renderMessage(noChannelMessage), - channel: channelDefaults.channel, - }); - }); - - }); - -}); diff --git a/x-pack/legacy/plugins/remote_clusters/__jest__/client_integration/helpers/setup_environment.js b/x-pack/legacy/plugins/remote_clusters/__jest__/client_integration/helpers/setup_environment.js index ba02da248ce122..c3bf8668af8dc5 100644 --- a/x-pack/legacy/plugins/remote_clusters/__jest__/client_integration/helpers/setup_environment.js +++ b/x-pack/legacy/plugins/remote_clusters/__jest__/client_integration/helpers/setup_environment.js @@ -13,7 +13,9 @@ import { fatalError, toastNotifications } from 'ui/notify'; // eslint-disable-li import { init as initBreadcrumb } from '../../../public/app/services/breadcrumb'; import { init as initHttp } from '../../../public/app/services/http'; import { init as initNotification } from '../../../public/app/services/notification'; +import { init as initUiMetric } from '../../../public/app/services/ui_metric'; import { init as initHttpRequests } from './http_requests'; +import { createUiStatsReporter } from '../../../../../../../src/legacy/core_plugins/ui_metric/public'; export const setupEnvironment = () => { chrome.breadcrumbs = { @@ -23,6 +25,7 @@ export const setupEnvironment = () => { initHttp(axios.create({ adapter: axiosXhrAdapter }), (path) => path); initBreadcrumb(() => {}, MANAGEMENT_BREADCRUMB); initNotification(toastNotifications, fatalError); + initUiMetric(createUiStatsReporter); const { server, httpRequestsMockHelpers } = initHttpRequests(); diff --git a/x-pack/legacy/plugins/remote_clusters/public/app/app.js b/x-pack/legacy/plugins/remote_clusters/public/app/app.js index 4062f6e476b8f0..6fea66ad03c9c4 100644 --- a/x-pack/legacy/plugins/remote_clusters/public/app/app.js +++ b/x-pack/legacy/plugins/remote_clusters/public/app/app.js @@ -9,7 +9,7 @@ import PropTypes from 'prop-types'; import { Switch, Route, Redirect } from 'react-router-dom'; import { CRUD_APP_BASE_PATH, UIM_APP_LOAD } from './constants'; -import { registerRouter, setUserHasLeftApp, trackUiMetric } from './services'; +import { registerRouter, setUserHasLeftApp, trackUiMetric, METRIC_TYPE } from './services'; import { RemoteClusterList, RemoteClusterAdd, RemoteClusterEdit } from './sections'; export class App extends Component { @@ -34,7 +34,7 @@ export class App extends Component { } componentDidMount() { - trackUiMetric(UIM_APP_LOAD); + trackUiMetric(METRIC_TYPE.LOADED, UIM_APP_LOAD); } componentWillUnmount() { diff --git a/x-pack/legacy/plugins/remote_clusters/public/app/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js b/x-pack/legacy/plugins/remote_clusters/public/app/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js index 6677d8f0c6fbda..9c128ed6f744ae 100644 --- a/x-pack/legacy/plugins/remote_clusters/public/app/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js +++ b/x-pack/legacy/plugins/remote_clusters/public/app/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js @@ -21,7 +21,7 @@ import { } from '@elastic/eui'; import { CRUD_APP_BASE_PATH, UIM_SHOW_DETAILS_CLICK } from '../../../constants'; -import { getRouterLinkProps, trackUiMetric } from '../../../services'; +import { getRouterLinkProps, trackUiMetric, METRIC_TYPE } from '../../../services'; import { ConnectionStatus, RemoveClusterButtonProvider } from '../components'; export class RemoteClusterTable extends Component { @@ -91,7 +91,7 @@ export class RemoteClusterTable extends Component { { - trackUiMetric(UIM_SHOW_DETAILS_CLICK); + trackUiMetric(METRIC_TYPE.CLICK, UIM_SHOW_DETAILS_CLICK); openDetailPanel(name); }} > diff --git a/x-pack/legacy/plugins/remote_clusters/public/app/services/api.js b/x-pack/legacy/plugins/remote_clusters/public/app/services/api.js index 77a26221f7be74..d7df62eef95ee6 100644 --- a/x-pack/legacy/plugins/remote_clusters/public/app/services/api.js +++ b/x-pack/legacy/plugins/remote_clusters/public/app/services/api.js @@ -5,7 +5,7 @@ */ import { UIM_CLUSTER_ADD, UIM_CLUSTER_UPDATE } from '../constants'; -import { trackUserRequest } from './track_ui_metric'; +import { trackUserRequest } from './ui_metric'; import { sendGet, sendPost, sendPut, sendDelete } from './http'; export async function loadClusters() { diff --git a/x-pack/legacy/plugins/remote_clusters/public/app/services/index.js b/x-pack/legacy/plugins/remote_clusters/public/app/services/index.js index 51308b4c0788e5..218576cc5195bd 100644 --- a/x-pack/legacy/plugins/remote_clusters/public/app/services/index.js +++ b/x-pack/legacy/plugins/remote_clusters/public/app/services/index.js @@ -40,4 +40,5 @@ export { export { trackUiMetric, -} from './track_ui_metric'; + METRIC_TYPE, +} from './ui_metric'; diff --git a/x-pack/legacy/plugins/remote_clusters/public/app/services/track_ui_metric.js b/x-pack/legacy/plugins/remote_clusters/public/app/services/track_ui_metric.js deleted file mode 100644 index 7b2f0d04078621..00000000000000 --- a/x-pack/legacy/plugins/remote_clusters/public/app/services/track_ui_metric.js +++ /dev/null @@ -1,26 +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 { trackUiMetric as track } from '../../../../../../../src/legacy/core_plugins/ui_metric/public'; -import { UIM_APP_NAME } from '../constants'; - -export function trackUiMetric(actionType) { - track(UIM_APP_NAME, actionType); -} - -/** - * Transparently return provided request Promise, while allowing us to track - * a successful completion of the request. - */ -export function trackUserRequest(request, actionType) { - // Only track successful actions. - return request.then(response => { - trackUiMetric(actionType); - // We return the response immediately without waiting for the tracking request to resolve, - // to avoid adding additional latency. - return response; - }); -} diff --git a/x-pack/legacy/plugins/remote_clusters/public/app/services/ui_metric.ts b/x-pack/legacy/plugins/remote_clusters/public/app/services/ui_metric.ts index 16f09ee109738f..36a23476c18739 100644 --- a/x-pack/legacy/plugins/remote_clusters/public/app/services/ui_metric.ts +++ b/x-pack/legacy/plugins/remote_clusters/public/app/services/ui_metric.ts @@ -5,13 +5,28 @@ */ import { UIM_APP_NAME } from '../constants'; +import { + createUiStatsReporter, + METRIC_TYPE, +} from '../../../../../../../src/legacy/core_plugins/ui_metric/public'; -export let track: any; +export let trackUiMetric: ReturnType; +export { METRIC_TYPE }; -export function init(_track: any): void { - track = _track; +export function init(getReporter: typeof createUiStatsReporter): void { + trackUiMetric = getReporter(UIM_APP_NAME); } -export function trackUiMetric(actionType: string): any { - return track(UIM_APP_NAME, actionType); +/** + * Transparently return provided request Promise, while allowing us to track + * a successful completion of the request. + */ +export function trackUserRequest(request: Promise, eventName: string) { + // Only track successful actions. + return request.then((response: any) => { + trackUiMetric(METRIC_TYPE.COUNT, eventName); + // We return the response immediately without waiting for the tracking request to resolve, + // to avoid adding additional latency. + return response; + }); } diff --git a/x-pack/legacy/plugins/remote_clusters/public/app/store/actions/remove_clusters.js b/x-pack/legacy/plugins/remote_clusters/public/app/store/actions/remove_clusters.js index c9cbc377f9e291..e85a40e8ed2898 100644 --- a/x-pack/legacy/plugins/remote_clusters/public/app/store/actions/remove_clusters.js +++ b/x-pack/legacy/plugins/remote_clusters/public/app/store/actions/remove_clusters.js @@ -12,6 +12,7 @@ import { UIM_CLUSTER_REMOVE, UIM_CLUSTER_REMOVE_MANY } from '../../constants'; import { removeClusterRequest as sendRemoveClusterRequest, trackUiMetric, + METRIC_TYPE, } from '../../services'; import { @@ -83,7 +84,7 @@ export const removeClusters = (names) => async (dispatch, getState) => { if (itemsDeleted.length > 0) { // Only track successful requests. - trackUiMetric(names.length > 1 ? UIM_CLUSTER_REMOVE_MANY : UIM_CLUSTER_REMOVE); + trackUiMetric(METRIC_TYPE.COUNT, names.length > 1 ? UIM_CLUSTER_REMOVE_MANY : UIM_CLUSTER_REMOVE); if (itemsDeleted.length === 1) { toasts.addSuccess(i18n.translate('xpack.remoteClusters.removeAction.successSingleNotificationTitle', { diff --git a/x-pack/legacy/plugins/remote_clusters/public/plugin.js b/x-pack/legacy/plugins/remote_clusters/public/plugin.js index 3ea7c8c13d4584..c370bf7243d1e6 100644 --- a/x-pack/legacy/plugins/remote_clusters/public/plugin.js +++ b/x-pack/legacy/plugins/remote_clusters/public/plugin.js @@ -35,7 +35,7 @@ export class Plugin { if (getInjectedVar('remoteClustersUiEnabled')) { const { management: { getSection, breadcrumb: managementBreadcrumb }, - uiMetric: { track }, + uiMetric: { createUiStatsReporter }, } = pluginsStart; const esSection = getSection('elasticsearch'); @@ -49,7 +49,7 @@ export class Plugin { // Initialize services initBreadcrumbs(setBreadcrumbs, managementBreadcrumb); initDocumentation(`${elasticWebsiteUrl}guide/en/elasticsearch/reference/${docLinkVersion}/`); - initUiMetric(track); + initUiMetric(createUiStatsReporter); initNotification(toasts, fatalError); const unmountReactApp = () => { diff --git a/x-pack/legacy/plugins/remote_clusters/public/shim.ts b/x-pack/legacy/plugins/remote_clusters/public/shim.ts index 213624d8e0d587..83975fa4bd0fe8 100644 --- a/x-pack/legacy/plugins/remote_clusters/public/shim.ts +++ b/x-pack/legacy/plugins/remote_clusters/public/shim.ts @@ -9,7 +9,7 @@ import { management, MANAGEMENT_BREADCRUMB } from 'ui/management'; import { fatalError } from 'ui/notify'; import { DOC_LINK_VERSION, ELASTIC_WEBSITE_URL } from 'ui/documentation_links'; -import { trackUiMetric as track } from '../../../../../src/legacy/core_plugins/ui_metric/public'; +import { createUiStatsReporter } from '../../../../../src/legacy/core_plugins/ui_metric/public'; export function createShim() { const { @@ -35,7 +35,7 @@ export function createShim() { breadcrumb: MANAGEMENT_BREADCRUMB, }, uiMetric: { - track, + createUiStatsReporter, }, }, }; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css b/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css index 63500d1dadfccb..ccf9427eb58cab 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css +++ b/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css @@ -17,7 +17,6 @@ /* hide unusable controls */ .kbnGlobalNav, kbn-top-nav, -.kbnToaster__container, filter-bar, ::-webkit-scrollbar, .euiNavDrawer { diff --git a/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css b/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css index 02abc3b361b958..cfad5bb631c760 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css +++ b/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css @@ -17,7 +17,6 @@ /* hide unusable controls */ .kbnGlobalNav, kbn-top-nav, -.kbnToaster__container, filter-bar, ::-webkit-scrollbar, .euiNavDrawer { diff --git a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_review.test.js b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_review.test.js index aa6ff1325c1178..7acbbf800d63da 100644 --- a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_review.test.js +++ b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_review.test.js @@ -19,10 +19,6 @@ jest.mock('ui/chrome', () => ({ jest.mock('lodash/function/debounce', () => fn => fn); -jest.mock('../../../../../../src/legacy/core_plugins/ui_metric/public', () => ({ - trackUiMetric: jest.fn(), -})); - const { setup } = pageHelpers.jobCreate; describe('Create Rollup Job, step 5: Metrics', () => { diff --git a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_list.test.js b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_list.test.js index 5a629c094a6322..09aa13b9fefe47 100644 --- a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_list.test.js +++ b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_list.test.js @@ -28,10 +28,6 @@ jest.mock('ui/chrome', () => ({ } })); -jest.mock('../../../../../../src/legacy/core_plugins/ui_metric/public', () => ({ - trackUiMetric: jest.fn(), -})); - jest.mock('../../public/crud_app/services', () => { const services = require.requireActual('../../public/crud_app/services'); return { diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/app.js b/x-pack/legacy/plugins/rollup/public/crud_app/app.js index e039bf1d40fccd..0e42194097492d 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/app.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/app.js @@ -10,7 +10,7 @@ import { HashRouter, Switch, Route, Redirect } from 'react-router-dom'; import { UIM_APP_LOAD } from '../../common'; import { CRUD_APP_BASE_PATH } from './constants'; -import { registerRouter, setUserHasLeftApp, trackUiMetric } from './services'; +import { registerRouter, setUserHasLeftApp, trackUiMetric, METRIC_TYPE } from './services'; import { JobList, JobCreate } from './sections'; class ShareRouter extends Component { @@ -41,7 +41,7 @@ class ShareRouter extends Component { export class App extends Component { // eslint-disable-line react/no-multi-comp componentDidMount() { - trackUiMetric(UIM_APP_LOAD); + trackUiMetric(METRIC_TYPE.LOADED, UIM_APP_LOAD); } componentWillUnmount() { diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js index 445b436c123d2f..1b9d77dd128ab0 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js @@ -33,7 +33,7 @@ import { UIM_DETAIL_PANEL_METRICS_TAB_CLICK, UIM_DETAIL_PANEL_JSON_TAB_CLICK, } from '../../../../../common'; -import { trackUiMetric } from '../../../services'; +import { trackUiMetric, METRIC_TYPE } from '../../../services'; import { JobActionMenu, @@ -114,7 +114,7 @@ export class DetailPanelUi extends Component { renderedTabs.push( { - trackUiMetric(tabToUiMetricMap[tab]); + trackUiMetric(METRIC_TYPE.CLICK, tabToUiMetricMap[tab]); openDetailPanel({ panelType: tab, jobId: id }); }} isSelected={isSelected} diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.js b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.js index 21d8a5a984ee88..e1c885156faa66 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.js @@ -30,7 +30,7 @@ import { } from '@elastic/eui'; import { UIM_SHOW_DETAILS_CLICK } from '../../../../../common'; -import { trackUiMetric } from '../../../services'; +import { trackUiMetric, METRIC_TYPE } from '../../../services'; import { JobActionMenu, JobStatus } from '../../components'; const COLUMNS = [{ @@ -259,7 +259,7 @@ export class JobTableUi extends Component { content = ( { - trackUiMetric(UIM_SHOW_DETAILS_CLICK); + trackUiMetric(METRIC_TYPE.CLICK, UIM_SHOW_DETAILS_CLICK); openDetailPanel(job.id); }} > diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/services/index.js b/x-pack/legacy/plugins/rollup/public/crud_app/services/index.js index 7cc1a0abfdd3cd..fdff4ebc89733c 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/services/index.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/services/index.js @@ -95,4 +95,5 @@ export { export { trackUiMetric, + METRIC_TYPE, } from './track_ui_metric'; diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/services/track_ui_metric.js b/x-pack/legacy/plugins/rollup/public/crud_app/services/track_ui_metric.js index efb97e05a05e61..de4d43b3f8c1ab 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/services/track_ui_metric.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/services/track_ui_metric.js @@ -4,12 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { trackUiMetric as track } from '../../../../../../../src/legacy/core_plugins/ui_metric/public'; +import { createUiStatsReporter, METRIC_TYPE } from '../../../../../../../src/legacy/core_plugins/ui_metric/public'; import { UIM_APP_NAME } from '../../../common'; -export function trackUiMetric(actionType) { - track(UIM_APP_NAME, actionType); -} +export const trackUiMetric = createUiStatsReporter(UIM_APP_NAME); +export { METRIC_TYPE }; /** * Transparently return provided request Promise, while allowing us to track @@ -18,7 +17,7 @@ export function trackUiMetric(actionType) { export function trackUserRequest(request, actionType) { // Only track successful actions. return request.then(response => { - trackUiMetric(actionType); + trackUiMetric(METRIC_TYPE.LOADED, actionType); // We return the response immediately without waiting for the tracking request to resolve, // to avoid adding additional latency. return response; 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 0c175b1fe07b43..53be16ed2843c1 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 @@ -8,7 +8,7 @@ import { EuiIcon } from '@elastic/eui'; import { transparentize } from 'polished'; import React from 'react'; import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; - +import styled from 'styled-components'; import euiStyled from '../../../../../common/eui_styled_components'; interface SuggestionItemProps { @@ -64,7 +64,7 @@ const SuggestionItemField = euiStyled.div` padding: ${props => props.theme.eui.euiSizeXS}; `; -const SuggestionItemIconField = SuggestionItemField.extend<{ suggestionType: string }>` +const SuggestionItemIconField = styled(SuggestionItemField)<{ suggestionType: string }>` background-color: ${props => transparentize(0.9, getEuiIconColor(props.theme, props.suggestionType))}; color: ${props => getEuiIconColor(props.theme, props.suggestionType)}; @@ -73,12 +73,12 @@ const SuggestionItemIconField = SuggestionItemField.extend<{ suggestionType: str width: ${props => props.theme.eui.euiSizeXL}; `; -const SuggestionItemTextField = SuggestionItemField.extend` +const SuggestionItemTextField = styled(SuggestionItemField)` flex: 2 0 0; font-family: ${props => props.theme.eui.euiCodeFontFamily}; `; -const SuggestionItemDescriptionField = SuggestionItemField.extend` +const SuggestionItemDescriptionField = styled(SuggestionItemField)` flex: 3 0 0; p { diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/columns.tsx b/x-pack/legacy/plugins/siem/public/components/event_details/columns.tsx index e0accfc9643372..a8f8b6bc1365da 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/event_details/columns.tsx @@ -183,9 +183,10 @@ export const getColumns = ({ }, { field: 'valuesConcatenated', + name: i18n.BLANK, + render: () => null, sortable: false, truncateText: true, - render: () => null, width: '1px', }, ]; diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/translations.ts b/x-pack/legacy/plugins/siem/public/components/event_details/translations.ts index c38778f2dc3c3e..c5e37b2cd7451c 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/event_details/translations.ts @@ -26,6 +26,10 @@ export const DESCRIPTION = i18n.translate('xpack.siem.eventDetails.description', defaultMessage: 'Description', }); +export const BLANK = i18n.translate('xpack.siem.eventDetails.blank', { + defaultMessage: ' ', +}); + export const PLACEHOLDER = i18n.translate('xpack.siem.eventDetails.filter.placeholder', { defaultMessage: 'Filter by Field, Value, or Description...', }); diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx index 531894b5e487dc..ac999b4287fcc4 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx @@ -14,12 +14,11 @@ import { ActionCreator } from 'typescript-fsa'; import { State, timelineSelectors } from '../../store'; import { DataProvider } from '../timeline/data_providers/data_provider'; - import { FlyoutButton } from './button'; import { Pane } from './pane'; import { timelineActions } from '../../store/actions'; import { DEFAULT_TIMELINE_WIDTH } from '../timeline/body/helpers'; -import { trackUiAction as track } from '../../lib/track_usage'; +import { trackUiAction as track, METRIC_TYPE } from '../../lib/track_usage'; /** The height in pixels of the flyout header, exported for use in height calculations */ export const flyoutHeaderHeight: number = 60; @@ -100,7 +99,7 @@ export const FlyoutComponent = pure( show={!show} timelineId={timelineId} onOpen={() => { - track('open_timeline'); + track(METRIC_TYPE.LOADED, 'open_timeline'); showTimeline!({ id: timelineId, show: true }); }} /> diff --git a/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/add_entities_to_kql.test.ts b/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/add_entities_to_kql.test.ts index a666a3280c4969..c039cc2f88646e 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/add_entities_to_kql.test.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/add_entities_to_kql.test.ts @@ -7,6 +7,16 @@ import { entityToKql, entitiesToKql, addEntitiesToKql } from './add_entities_to_kql'; describe('add_entities_to_kql', () => { + // Suppress warnings about invalid RISON as this is what we are testing + /* eslint-disable no-console */ + const originalError = console.log; + beforeAll(() => { + console.log = jest.fn(); + }); + + afterAll(() => { + console.log = originalError; + }); describe('#entityToKql', () => { test('returns empty string with no entity names defined and an empty entity string', () => { const entity = entityToKql([], ''); diff --git a/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/replace_kql_query_location_for_host_page.test.ts b/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/replace_kql_query_location_for_host_page.test.ts index 6b304cb8b6a46f..48e6e1c942cc43 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/replace_kql_query_location_for_host_page.test.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/replace_kql_query_location_for_host_page.test.ts @@ -6,7 +6,17 @@ import { replaceKqlQueryLocationForHostPage } from './replace_kql_query_location_for_host_page'; +// Suppress warnings about invalid RISON as this is what we are testing +/* eslint-disable no-console */ +const originalError = console.log; describe('replace_kql_query_location_for_host_page', () => { + beforeAll(() => { + console.log = jest.fn(); + }); + + afterAll(() => { + console.log = originalError; + }); test('replaces host details and type details for a page', () => { const replacement = replaceKqlQueryLocationForHostPage( '(filterQuery:(expression:\'process.name: "some-name"\',kind:kuery),queryLocation:hosts.details,type:details)' diff --git a/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/replace_kql_query_location_for_network_page.test.ts b/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/replace_kql_query_location_for_network_page.test.ts index a79d827dd82f79..5a31bbf2cb6769 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/replace_kql_query_location_for_network_page.test.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/replace_kql_query_location_for_network_page.test.ts @@ -5,8 +5,17 @@ */ import { replaceKqlQueryLocationForNetworkPage } from './replace_kql_query_location_for_network_page'; - +// Suppress warnings about invalid RISON as this is what we are testing +/* eslint-disable no-console */ +const originalError = console.log; describe('replace_kql_query_location_for_host_page', () => { + beforeAll(() => { + console.log = jest.fn(); + }); + + afterAll(() => { + console.log = originalError; + }); test('replaces host details and type details for a page', () => { const replacement = replaceKqlQueryLocationForNetworkPage( '(filterQuery:(expression:\'process.name: "some-name"\',kind:kuery),queryLocation:network.details,type:details)' diff --git a/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/rison_helpers.test.ts b/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/rison_helpers.test.ts index 14241a2578837c..60d876fab2bf9d 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/rison_helpers.test.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/rison_helpers.test.ts @@ -7,7 +7,17 @@ import { decodeRison, isRisonObject, isRegularString } from './rison_helpers'; describe('rison_helpers', () => { + // Suppress warnings about invalid RISON as this is what we are testing + /* eslint-disable no-console */ + const originalError = console.log; describe('#decodeRison', () => { + beforeAll(() => { + console.log = jest.fn(); + }); + + afterAll(() => { + console.log = originalError; + }); test('returns null if given a bad value for RISON', () => { const expected = decodeRison('some invalid value'); expect(expected).toEqual(null); diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.tsx index 823f41065fbb8d..94f6dcb22a637f 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.tsx @@ -8,7 +8,7 @@ import * as React from 'react'; import styled from 'styled-components'; import { getHostsUrl, getNetworkUrl, getOverviewUrl, getTimelinesUrl } from '../../link_to'; -import { trackUiAction as track } from '../../../lib/track_usage'; +import { trackUiAction as track, METRIC_TYPE } from '../../../lib/track_usage'; import * as i18n from '../translations'; @@ -101,7 +101,7 @@ export class TabNavigation extends React.PureComponent { - track(`tab_${tab.id}`); + track(METRIC_TYPE.CLICK, `tab_${tab.id}`); }} > {tab.name} diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx index 5cb5c9ddae2337..6b576cca24b1e6 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; +import { EuiButtonIcon, EuiToolTip, EuiIcon } from '@elastic/eui'; import * as React from 'react'; +import styled from 'styled-components'; import { ACTION_COLUMN_WIDTH, PositionedIcon } from './common_styles'; import { DeleteTimelines, OnOpenTimeline, OpenTimelineResult } from '../types'; @@ -13,6 +14,11 @@ import { DeleteTimelineModalButton } from '../delete_timeline_modal'; import * as i18n from '../translations'; +const HeaderIcon = styled(EuiIcon)` + position: relative; + left: 9px; +`; + /** * Returns the action columns (e.g. delete, open duplicate timeline) */ @@ -26,7 +32,12 @@ export const getActionsColumns = ({ showDeleteAction: boolean; }) => { const deleteTimelineColumn = { - align: 'center', + align: 'right', + name: ( + + + + ), field: 'savedObjectId', render: (savedObjectId: string, { title }: OpenTimelineResult) => ( @@ -42,7 +53,12 @@ export const getActionsColumns = ({ }; const openAsDuplicateColumn = { - align: 'center', + align: 'right', + name: ( + + + + ), field: 'savedObjectId', render: (savedObjectId: string, timelineResult: OpenTimelineResult) => ( diff --git a/x-pack/legacy/plugins/siem/public/components/page/add_to_kql/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/add_to_kql/index.test.tsx index 67cabfa4914cff..1c33e7c89b7558 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/add_to_kql/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/add_to_kql/index.test.tsx @@ -91,14 +91,17 @@ describe('AddToKql Component', () => { limit: 10, }, hosts: { + activePage: 0, limit: 10, direction: 'desc', sortField: 'lastSeen', }, events: { + activePage: 0, limit: 10, }, uncommonProcesses: { + activePage: 0, limit: 10, }, }, @@ -141,6 +144,7 @@ describe('AddToKql Component', () => { expect(store.getState().network.page).toEqual({ queries: { topNFlow: { + activePage: 0, limit: 10, flowDirection: 'uniDirectional', flowTarget: 'source', @@ -150,6 +154,7 @@ describe('AddToKql Component', () => { }, }, dns: { + activePage: 0, limit: 10, dnsSortField: { field: 'queryCount', diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.test.tsx index 360f6b9c6dfd74..0e37ebaacf0ce7 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.test.tsx @@ -33,7 +33,7 @@ describe('Authentication Table Component', () => { ; updateTableActivePage: ActionCreator<{ activePage: number; hostsType: hostsModel.HostsType; @@ -57,7 +56,7 @@ interface AuthenticationTableDispatchProps { }>; } -export declare type AuthTableColumns = [ +export type AuthTableColumns = [ Columns, Columns, Columns, @@ -86,8 +85,8 @@ const rowItems: ItemsPerRow[] = [ const AuthenticationTableComponent = pure( ({ - fakeTotalCount, data, + fakeTotalCount, id, limit, loading, @@ -141,7 +140,6 @@ const makeMapStateToProps = () => { export const AuthenticationTable = connect( makeMapStateToProps, { - updateLimitPagination: hostsActions.updateAuthenticationsLimit, updateTableActivePage: hostsActions.updateTableActivePage, updateTableLimit: hostsActions.updateTableLimit, } diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/translations.ts b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/translations.ts index 7fc30b06d47573..43a2565841c8f9 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/translations.ts @@ -79,13 +79,3 @@ export const ROWS_10 = i18n.translate('xpack.siem.authenticationsTable.rows', { values: { numRows: 10 }, defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', }); - -export const ROWS_20 = i18n.translate('xpack.siem.authenticationsTable.rows', { - values: { numRows: 20 }, - defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', -}); - -export const ROWS_50 = i18n.translate('xpack.siem.authenticationsTable.rows', { - values: { numRows: 50 }, - defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', -}); diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/events_table/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/page/hosts/events_table/__snapshots__/index.test.tsx.snap index 3bdc7c23a890ad..d7fd4e05887bc9 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/events_table/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/events_table/__snapshots__/index.test.tsx.snap @@ -136,12 +136,11 @@ exports[`Load More Events Table Component rendering it renders the events table }, ] } - hasNextPage={true} + fakeTotalCount={50} id="events" - loadMore={[MockFunction]} + loadPage={[MockFunction]} loading={false} - nextCursor="1546878704036" - tiebreaker="10624" + showMorePagesIndicator={true} totalCount={15546} type="page" /> diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/events_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/events_table/index.test.tsx index 51fd59ee7d4518..5acad330680b7c 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/events_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/events_table/index.test.tsx @@ -18,7 +18,7 @@ import { mockData } from './mock'; import * as i18n from './translations'; describe('Load More Events Table Component', () => { - const loadMore = jest.fn(); + const loadPage = jest.fn(); const state: State = mockGlobalState; let store = createStore(state, apolloClientObservable); @@ -33,12 +33,15 @@ describe('Load More Events Table Component', () => { i.node)} - hasNextPage={getOr(false, 'hasNextPage', mockData.Events.pageInfo)!} + fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.Events.pageInfo)} id="events" loading={false} - loadMore={loadMore} - nextCursor={getOr(null, 'endCursor.value', mockData.Events.pageInfo)} - tiebreaker={getOr(null, 'endCursor.tiebreaker', mockData.Events.pageInfo)!} + loadPage={loadPage} + showMorePagesIndicator={getOr( + false, + 'showMorePagesIndicator', + mockData.Events.pageInfo + )} totalCount={mockData.Events.totalCount} type={hostsModel.HostsType.page} /> diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/events_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/events_table/index.tsx index f2bf704c6dcb26..8ccc434a8a849b 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/events_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/events_table/index.tsx @@ -16,33 +16,53 @@ import { Ecs } from '../../../../graphql/types'; import { hostsModel, hostsSelectors, State } from '../../../../store'; import { getEmptyTagValue, getOrEmptyTag } from '../../../empty_value'; import { HostDetailsLink, IPDetailsLink } from '../../../links'; -import { Columns, ItemsPerRow, LoadMoreTable } from '../../../load_more_table'; +import { Columns, ItemsPerRow, PaginatedTable } from '../../../paginated_table'; import { getRowItemDraggable, getRowItemDraggables, OverflowField } from '../../../tables/helpers'; import { PreferenceFormattedDate } from '../../../formatted_date'; import { LocalizedDateTooltip } from '../../../localized_date_tooltip'; import * as i18n from './translations'; - +const tableType = hostsModel.HostsTableType.events; interface OwnProps { data: Ecs[]; - loading: boolean; - hasNextPage: boolean; + fakeTotalCount: number; id: string; - nextCursor: string; - tiebreaker: string; + loading: boolean; + loadPage: (newActivePage: number) => void; + showMorePagesIndicator: boolean; totalCount: number; - loadMore: (cursor: string, tiebreaker: string) => void; type: hostsModel.HostsType; } interface EventsTableReduxProps { + activePage: number; limit: number; } interface EventsTableDispatchProps { - updateLimitPagination: ActionCreator<{ limit: number; hostsType: hostsModel.HostsType }>; + updateTableActivePage: ActionCreator<{ + activePage: number; + hostsType: hostsModel.HostsType; + tableType: hostsModel.HostsTableType; + }>; + updateTableLimit: ActionCreator<{ + limit: number; + hostsType: hostsModel.HostsType; + tableType: hostsModel.HostsTableType; + }>; } +export type EventsTableColumns = [ + Columns, + Columns, + Columns, + Columns, + Columns, + Columns, + Columns, + Columns +]; + type EventsTableProps = OwnProps & EventsTableReduxProps & EventsTableDispatchProps; const rowItems: ItemsPerRow[] = [ @@ -54,33 +74,24 @@ const rowItems: ItemsPerRow[] = [ text: i18n.ROWS_10, numberOfRow: 10, }, - { - text: i18n.ROWS_20, - numberOfRow: 20, - }, - { - text: i18n.ROWS_50, - numberOfRow: 50, - }, ]; const EventsTableComponent = pure( ({ + fakeTotalCount, + showMorePagesIndicator, data, - hasNextPage, id, limit, loading, - loadMore, - tiebreaker, + loadPage, totalCount, - nextCursor, - updateLimitPagination, type, + updateTableActivePage, + updateTableLimit, }) => ( - ( limit={limit} loading={loading} loadingTitle={i18n.EVENTS} - loadMore={() => loadMore(nextCursor, tiebreaker)} + loadPage={newActivePage => loadPage(newActivePage)} pageOfItems={data} + showMorePagesIndicator={showMorePagesIndicator} + totalCount={fakeTotalCount} updateLimitPagination={newLimit => - updateLimitPagination({ limit: newLimit, hostsType: type }) + updateTableLimit({ + hostsType: type, + limit: newLimit, + tableType, + }) } + updateActivePage={newPage => + updateTableActivePage({ + activePage: newPage, + hostsType: type, + tableType, + }) + } + updateProps={{ totalCount }} /> ) ); @@ -109,22 +134,12 @@ const makeMapStateToProps = () => { export const EventsTable = connect( makeMapStateToProps, { - updateLimitPagination: hostsActions.updateEventsLimit, + updateTableActivePage: hostsActions.updateTableActivePage, + updateTableLimit: hostsActions.updateTableLimit, } )(EventsTableComponent); -const getEventsColumns = ( - pageType: hostsModel.HostsType -): [ - Columns, - Columns, - Columns, - Columns, - Columns, - Columns, - Columns, - Columns -] => [ +const getEventsColumns = (pageType: hostsModel.HostsType): EventsTableColumns => [ { field: 'node', name: i18n.TIMESTAMP, diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/events_table/mock.ts b/x-pack/legacy/plugins/siem/public/components/page/hosts/events_table/mock.ts index 41b717fbea85ca..a1997912f2b953 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/events_table/mock.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/events_table/mock.ts @@ -10,11 +10,9 @@ export const mockData: { Events: EventsData } = { Events: { totalCount: 15546, pageInfo: { - hasNextPage: true, - endCursor: { - value: '1546878704036', - tiebreaker: '10624', - }, + activePage: 1, + fakeTotalCount: 50, + showMorePagesIndicator: true, }, edges: [ { diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/events_table/translations.ts b/x-pack/legacy/plugins/siem/public/components/page/hosts/events_table/translations.ts index 783955a7b4cb8a..e4999f079a6610 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/events_table/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/events_table/translations.ts @@ -57,13 +57,3 @@ export const ROWS_10 = i18n.translate('xpack.siem.eventsTable.rows', { values: { numRows: 10 }, defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', }); - -export const ROWS_20 = i18n.translate('xpack.siem.eventsTable.rows', { - values: { numRows: 20 }, - defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', -}); - -export const ROWS_50 = i18n.translate('xpack.siem.eventsTable.rows', { - values: { numRows: 50 }, - defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', -}); diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/mock.ts b/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/mock.ts index eaa5b983702fd2..d9a93272c09866 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/mock.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/mock.ts @@ -43,10 +43,9 @@ export const mockData: { Hosts: HostsData; DateFields: string[] } = { }, ], pageInfo: { - endCursor: { - value: 'aa7ca589f1b8220002f2fc61c64cfbf1', - }, - hasNextPage: false, + activePage: 1, + fakeTotalCount: 50, + showMorePagesIndicator: true, }, }, DateFields: ['lastBeat'], diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/__snapshots__/index.test.tsx.snap index cc6a6db773aa6f..6a790220f7c812 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/__snapshots__/index.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Load More Table Component rendering it renders the default Hosts table 1`] = ` +exports[`Hosts Table rendering it renders the default Hosts table 1`] = ` diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/columns.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/columns.tsx index b553f5eb90d910..76e1e64122c6e6 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/columns.tsx @@ -9,7 +9,6 @@ import moment from 'moment'; import React from 'react'; import { StaticIndexPattern } from 'ui/index_patterns'; -import { HostItem, HostFields, OsFields } from '../../../../graphql/types'; import { escapeQueryValue } from '../../../../lib/keury'; import { hostsModel } from '../../../../store'; import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper'; @@ -17,23 +16,18 @@ import { escapeDataProviderId } from '../../../drag_and_drop/helpers'; import { getEmptyTagValue } from '../../../empty_value'; import { PreferenceFormattedDate } from '../../../formatted_date'; import { HostDetailsLink } from '../../../links'; -import { Columns } from '../../../load_more_table'; import { LocalizedDateTooltip } from '../../../localized_date_tooltip'; import { IS_OPERATOR } from '../../../timeline/data_providers/data_provider'; import { Provider } from '../../../timeline/data_providers/provider'; import { AddToKql } from '../../add_to_kql'; +import { HostsTableColumns } from './'; import * as i18n from './translations'; export const getHostsColumns = ( type: hostsModel.HostsType, indexPattern: StaticIndexPattern -): [ - Columns, - Columns, - Columns, - Columns -] => [ +): HostsTableColumns => [ { field: 'node.host.name', name: i18n.NAME, diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.test.tsx index 7fbcd22118876b..de79f73a33d2dd 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.test.tsx @@ -24,8 +24,8 @@ import { HostsTable } from './index'; import { mockData } from './mock'; import { KibanaConfigContext } from '../../../../lib/adapters/framework/kibana_framework_adapter'; -describe('Load More Table Component', () => { - const loadMore = jest.fn(); +describe('Hosts Table', () => { + const loadPage = jest.fn(); const state: State = mockGlobalState; let store = createStore(state, apolloClientObservable); @@ -43,10 +43,14 @@ describe('Load More Table Component', () => { data={mockData.Hosts.edges} id="hostsQuery" indexPattern={mockIndexPattern} - hasNextPage={getOr(false, 'hasNextPage', mockData.Hosts.pageInfo)!} + fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.Hosts.pageInfo)} loading={false} - loadMore={loadMore} - nextCursor={getOr(null, 'endCursor.value', mockData.Hosts.pageInfo)} + loadPage={loadPage} + showMorePagesIndicator={getOr( + false, + 'showMorePagesIndicator', + mockData.Hosts.pageInfo + )} totalCount={mockData.Hosts.totalCount} type={hostsModel.HostsType.page} /> @@ -67,9 +71,13 @@ describe('Load More Table Component', () => { loading={false} data={mockData.Hosts.edges} totalCount={mockData.Hosts.totalCount} - hasNextPage={getOr(false, 'hasNextPage', mockData.Hosts.pageInfo)!} - nextCursor={getOr(null, 'endCursor.value', mockData.Hosts.pageInfo)} - loadMore={loadMore} + fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.Hosts.pageInfo)} + showMorePagesIndicator={getOr( + false, + 'showMorePagesIndicator', + mockData.Hosts.pageInfo + )} + loadPage={loadPage} type={hostsModel.HostsType.page} /> @@ -86,9 +94,13 @@ describe('Load More Table Component', () => { loading={false} data={mockData.Hosts.edges} totalCount={mockData.Hosts.totalCount} - hasNextPage={getOr(false, 'hasNextPage', mockData.Hosts.pageInfo)!} - nextCursor={getOr(null, 'endCursor.value', mockData.Hosts.pageInfo)} - loadMore={loadMore} + fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.Hosts.pageInfo)} + showMorePagesIndicator={getOr( + false, + 'showMorePagesIndicator', + mockData.Hosts.pageInfo + )} + loadPage={loadPage} type={hostsModel.HostsType.page} /> @@ -97,6 +109,7 @@ describe('Load More Table Component', () => { }); test('Initial value of the store', () => { expect(store.getState().hosts.page.queries.hosts).toEqual({ + activePage: 0, direction: 'desc', sortField: 'lastSeen', limit: 10, @@ -124,6 +137,7 @@ describe('Load More Table Component', () => { wrapper.update(); expect(store.getState().hosts.page.queries.hosts).toEqual({ + activePage: 0, direction: 'asc', sortField: 'hostName', limit: 10, diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.tsx index 2d773b22722d66..6045f6487c601d 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.tsx @@ -25,22 +25,24 @@ import { Columns, Criteria, ItemsPerRow, - LoadMoreTable, + PaginatedTable, SortingBasicTable, -} from '../../../load_more_table'; +} from '../../../paginated_table'; import { getHostsColumns } from './columns'; import * as i18n from './translations'; +const tableType = hostsModel.HostsTableType.hosts; + interface OwnProps { data: HostsEdges[]; - loading: boolean; + fakeTotalCount: number; id: string; indexPattern: StaticIndexPattern; - hasNextPage: boolean; - nextCursor: string; + loading: boolean; + loadPage: (newActivePage: number) => void; + showMorePagesIndicator: boolean; totalCount: number; - loadMore: (cursor: string) => void; type: hostsModel.HostsType; } @@ -51,13 +53,29 @@ interface HostsTableReduxProps { } interface HostsTableDispatchProps { - updateLimitPagination: ActionCreator<{ limit: number; hostsType: hostsModel.HostsType }>; updateHostsSort: ActionCreator<{ sort: HostsSortField; hostsType: hostsModel.HostsType; }>; + updateTableActivePage: ActionCreator<{ + activePage: number; + hostsType: hostsModel.HostsType; + tableType: hostsModel.HostsTableType; + }>; + updateTableLimit: ActionCreator<{ + limit: number; + hostsType: hostsModel.HostsType; + tableType: hostsModel.HostsTableType; + }>; } +export type HostsTableColumns = [ + Columns, + Columns, + Columns, + Columns +]; + type HostsTableProps = OwnProps & HostsTableReduxProps & HostsTableDispatchProps; const rowItems: ItemsPerRow[] = [ @@ -69,26 +87,13 @@ const rowItems: ItemsPerRow[] = [ text: i18n.ROWS_10, numberOfRow: 10, }, - { - text: i18n.ROWS_20, - numberOfRow: 20, - }, - { - text: i18n.ROWS_50, - numberOfRow: 50, - }, ]; class HostsTableComponent extends React.PureComponent { private memoizedColumns: ( type: hostsModel.HostsType, indexPattern: StaticIndexPattern - ) => [ - Columns, - Columns, - Columns, - Columns - ]; + ) => HostsTableColumns; private memoizedSorting: ( trigger: string, sortField: HostsFields, @@ -105,20 +110,23 @@ class HostsTableComponent extends React.PureComponent { const { data, direction, - hasNextPage, + fakeTotalCount, id, indexPattern, limit, loading, + loadPage, + showMorePagesIndicator, totalCount, sortField, type, + updateTableActivePage, + updateTableLimit, } = this.props; return ( - { limit={limit} loading={loading} loadingTitle={i18n.HOSTS} - loadMore={this.wrappedLoadMore} + loadPage={newActivePage => loadPage(newActivePage)} onChange={this.onChange} pageOfItems={data} + showMorePagesIndicator={showMorePagesIndicator} sorting={this.memoizedSorting(`${sortField}-${direction}`, sortField, direction)} - updateLimitPagination={this.wrappedUpdateLimitPagination} + totalCount={fakeTotalCount} + updateLimitPagination={newLimit => + updateTableLimit({ + hostsType: type, + limit: newLimit, + tableType, + }) + } + updateActivePage={newPage => + updateTableActivePage({ + activePage: newPage, + hostsType: type, + tableType, + }) + } + updateProps={{ direction, sortField, totalCount }} /> ); } @@ -142,20 +166,10 @@ class HostsTableComponent extends React.PureComponent { direction: Direction ): SortingBasicTable => ({ field: getNodeField(sortField), direction }); - private wrappedUpdateLimitPagination = (newLimit: number) => - this.props.updateLimitPagination({ limit: newLimit, hostsType: this.props.type }); - - private wrappedLoadMore = () => this.props.loadMore(this.props.nextCursor); - private getMemoizeHostsColumns = ( type: hostsModel.HostsType, indexPattern: StaticIndexPattern - ): [ - Columns, - Columns, - Columns, - Columns - ] => getHostsColumns(type, indexPattern); + ): HostsTableColumns => getHostsColumns(type, indexPattern); private onChange = (criteria: Criteria) => { if (criteria.sort != null) { @@ -205,7 +219,8 @@ const makeMapStateToProps = () => { export const HostsTable = connect( makeMapStateToProps, { - updateLimitPagination: hostsActions.updateHostsLimit, updateHostsSort: hostsActions.updateHostsSort, + updateTableActivePage: hostsActions.updateTableActivePage, + updateTableLimit: hostsActions.updateTableLimit, } )(HostsTableComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/mock.ts b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/mock.ts index c0c32544acd9d4..b5a9c925c599ad 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/mock.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/mock.ts @@ -52,10 +52,9 @@ export const mockData: { Hosts: HostsData } = { }, ], pageInfo: { - endCursor: { - value: 'aa7ca589f1b8220002f2fc61c64cfbf1', - }, - hasNextPage: true, + activePage: 1, + fakeTotalCount: 50, + showMorePagesIndicator: true, }, }, }; diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/translations.ts b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/translations.ts index 7126914b34fed6..2f4ea82018743f 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/translations.ts @@ -57,13 +57,3 @@ export const ROWS_10 = i18n.translate('xpack.siem.hostsTable.rows', { values: { numRows: 10 }, defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', }); - -export const ROWS_20 = i18n.translate('xpack.siem.hostsTable.rows', { - values: { numRows: 20 }, - defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', -}); - -export const ROWS_50 = i18n.translate('xpack.siem.hostsTable.rows', { - values: { numRows: 50 }, - defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', -}); diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/__snapshots__/index.test.tsx.snap index 2b7e0ca54dbee7..4fc36c860ae626 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/__snapshots__/index.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`UncommonProcess Table Component rendering it renders the default Uncommon process table 1`] = ` +exports[`Uncommon Process Table Component rendering it renders the default Uncommon process table 1`] = ` diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.test.tsx index 1bcc6bcffb6707..bccd0324884215 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.test.tsx @@ -16,8 +16,8 @@ import { getEmptyValue } from '../../../empty_value'; import { getArgs, UncommonProcessTable } from '.'; import { mockData } from './mock'; -describe('UncommonProcess Table Component', () => { - const loadMore = jest.fn(); +describe('Uncommon Process Table Component', () => { + const loadPage = jest.fn(); describe('rendering', () => { test('it renders the default Uncommon process table', () => { @@ -25,11 +25,15 @@ describe('UncommonProcess Table Component', () => { @@ -44,11 +48,15 @@ describe('UncommonProcess Table Component', () => { @@ -69,11 +77,15 @@ describe('UncommonProcess Table Component', () => { @@ -95,11 +107,15 @@ describe('UncommonProcess Table Component', () => { @@ -121,11 +137,15 @@ describe('UncommonProcess Table Component', () => { @@ -147,11 +167,15 @@ describe('UncommonProcess Table Component', () => { @@ -173,11 +197,15 @@ describe('UncommonProcess Table Component', () => { @@ -198,11 +226,15 @@ describe('UncommonProcess Table Component', () => { @@ -223,11 +255,15 @@ describe('UncommonProcess Table Component', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.tsx index 7d4804585b81af..858fb032c033b5 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.tsx @@ -14,19 +14,19 @@ import { UncommonProcessesEdges, UncommonProcessItem } from '../../../../graphql import { hostsModel, hostsSelectors, State } from '../../../../store'; import { defaultToEmptyTag, getEmptyValue } from '../../../empty_value'; import { HostDetailsLink } from '../../../links'; -import { Columns, ItemsPerRow, LoadMoreTable } from '../../../load_more_table'; +import { Columns, ItemsPerRow, PaginatedTable } from '../../../paginated_table'; import * as i18n from './translations'; import { getRowItemDraggables } from '../../../tables/helpers'; - +const tableType = hostsModel.HostsTableType.uncommonProcesses; interface OwnProps { data: UncommonProcessesEdges[]; - loading: boolean; - hasNextPage: boolean; + fakeTotalCount: number; id: string; - nextCursor: string; + loading: boolean; + loadPage: (newActivePage: number) => void; + showMorePagesIndicator: boolean; totalCount: number; - loadMore: (cursor: string) => void; type: hostsModel.HostsType; } @@ -35,9 +35,27 @@ interface UncommonProcessTableReduxProps { } interface UncommonProcessTableDispatchProps { - updateLimitPagination: ActionCreator<{ limit: number; hostsType: hostsModel.HostsType }>; + updateTableActivePage: ActionCreator<{ + activePage: number; + hostsType: hostsModel.HostsType; + tableType: hostsModel.HostsTableType; + }>; + updateTableLimit: ActionCreator<{ + limit: number; + hostsType: hostsModel.HostsType; + tableType: hostsModel.HostsTableType; + }>; } +export type UncommonProcessTableColumns = [ + Columns, + Columns, + Columns, + Columns, + Columns, + Columns +]; + type UncommonProcessTableProps = OwnProps & UncommonProcessTableReduxProps & UncommonProcessTableDispatchProps; @@ -51,14 +69,6 @@ const rowItems: ItemsPerRow[] = [ text: i18n.ROWS_10, numberOfRow: 10, }, - { - text: i18n.ROWS_20, - numberOfRow: 20, - }, - { - text: i18n.ROWS_50, - numberOfRow: 50, - }, ]; export const getArgs = (args: string[] | null | undefined): string | null => { @@ -72,19 +82,19 @@ export const getArgs = (args: string[] | null | undefined): string | null => { const UncommonProcessTableComponent = pure( ({ data, - hasNextPage, + fakeTotalCount, id, limit, loading, - loadMore, + loadPage, totalCount, - nextCursor, - updateLimitPagination, + showMorePagesIndicator, + updateTableActivePage, + updateTableLimit, type, }) => ( - ( limit={limit} loading={loading} loadingTitle={i18n.UNCOMMON_PROCESSES} - loadMore={() => loadMore(nextCursor)} + loadPage={newActivePage => loadPage(newActivePage)} pageOfItems={data} + showMorePagesIndicator={showMorePagesIndicator} + totalCount={fakeTotalCount} updateLimitPagination={newLimit => - updateLimitPagination({ limit: newLimit, hostsType: type }) + updateTableLimit({ + hostsType: type, + limit: newLimit, + tableType, + }) + } + updateActivePage={newPage => + updateTableActivePage({ + activePage: newPage, + hostsType: type, + tableType, + }) } + updateProps={{ totalCount }} /> ) ); @@ -110,18 +134,12 @@ const makeMapStateToProps = () => { export const UncommonProcessTable = connect( makeMapStateToProps, { - updateLimitPagination: hostsActions.updateUncommonProcessesLimit, + updateTableActivePage: hostsActions.updateTableActivePage, + updateTableLimit: hostsActions.updateTableLimit, } )(UncommonProcessTableComponent); -const getUncommonColumns = (): [ - Columns, - Columns, - Columns, - Columns, - Columns, - Columns -] => [ +const getUncommonColumns = (): UncommonProcessTableColumns => [ { name: i18n.NAME, truncateText: false, diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/mock.ts b/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/mock.ts index 08dd571cff8825..bcd76706e30354 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/mock.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/mock.ts @@ -111,10 +111,9 @@ export const mockData: { UncommonProcess: UncommonProcessesData } = { }, ], pageInfo: { - endCursor: { - value: 'aa7ca589f1b8220002f2fc61c64cfbf1', - }, - hasNextPage: true, + activePage: 1, + fakeTotalCount: 50, + showMorePagesIndicator: true, }, }, }; diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/translations.ts b/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/translations.ts index 215946c6d177c9..23e9e3c9790e21 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/translations.ts @@ -58,13 +58,3 @@ export const ROWS_10 = i18n.translate('xpack.siem.uncommonProcessTable.rows', { values: { numRows: 10 }, defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', }); - -export const ROWS_20 = i18n.translate('xpack.siem.uncommonProcessTable.rows', { - values: { numRows: 20 }, - defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', -}); - -export const ROWS_50 = i18n.translate('xpack.siem.uncommonProcessTable.rows', { - values: { numRows: 50 }, - defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', -}); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/__snapshots__/index.test.tsx.snap index 3741dfa4e6fd5d..db98d8e6c3703f 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/__snapshots__/index.test.tsx.snap @@ -63,8 +63,8 @@ exports[`Domains Table Component Rendering it renders the default Domains table }, ] } + fakeTotalCount={50} flowTarget="source" - hasNextPage={false} id="domains" indexPattern={ Object { @@ -152,9 +152,9 @@ exports[`Domains Table Component Rendering it renders the default Domains table } } ip="10.10.10.10" - loadMore={[MockFunction]} + loadPage={[MockFunction]} loading={false} - nextCursor="10" + showMorePagesIndicator={true} totalCount={1} type="details" /> diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/columns.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/columns.tsx index 63dc9140b22d7d..d539d1121a014d 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/columns.tsx @@ -34,6 +34,15 @@ import { AddToKql } from '../../add_to_kql'; import * as i18n from './translations'; +export type DomainsColumns = [ + Columns, + Columns, + Columns, + Columns, + Columns, + Columns +]; + export const getDomainsColumns = ( indexPattern: StaticIndexPattern, ip: string, @@ -41,14 +50,7 @@ export const getDomainsColumns = ( flowTarget: FlowTarget, type: networkModel.NetworkType, tableId: string -): [ - Columns, - Columns, - Columns, - Columns, - Columns, - Columns -] => [ +): DomainsColumns => [ { field: `node.${flowTarget}.domainName`, name: i18n.DOMAIN_NAME, diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/index.test.tsx index c1e0c8483cbccc..ee100aa24dd4d2 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/index.test.tsx @@ -24,7 +24,7 @@ import { DomainsTable } from '.'; import { mockDomainsData } from './mock'; describe('Domains Table Component', () => { - const loadMore = jest.fn(); + const loadPage = jest.fn(); const ip = '10.10.10.10'; const state: State = mockGlobalState; @@ -40,14 +40,18 @@ describe('Domains Table Component', () => { @@ -65,14 +69,18 @@ describe('Domains Table Component', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/index.tsx index c38ba5a29aa7aa..c50bfb75e62727 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/index.tsx @@ -21,22 +21,23 @@ import { } from '../../../../graphql/types'; import { networkModel, networkSelectors, State } from '../../../../store'; import { FlowDirectionSelect } from '../../../flow_controls/flow_direction_select'; -import { Criteria, ItemsPerRow, LoadMoreTable, SortingBasicTable } from '../../../load_more_table'; +import { Criteria, ItemsPerRow, PaginatedTable, SortingBasicTable } from '../../../paginated_table'; import { getDomainsColumns } from './columns'; import * as i18n from './translations'; +const tableType = networkModel.IpDetailsTableType.domains; interface OwnProps { data: DomainsEdges[]; flowTarget: FlowTarget; - loading: boolean; - hasNextPage: boolean; + fakeTotalCount: number; id: string; indexPattern: StaticIndexPattern; ip: string; - nextCursor: string; + loading: boolean; + loadPage: (newActivePage: number) => void; + showMorePagesIndicator: boolean; totalCount: number; - loadMore: (cursor: string) => void; type: networkModel.NetworkType; } @@ -59,6 +60,10 @@ interface DomainsTableDispatchProps { domainsSort: DomainsSortField; networkType: networkModel.NetworkType; }>; + updateTableActivePage: ActionCreator<{ + activePage: number; + tableType: networkModel.IpDetailsTableType; + }>; } type DomainsTableProps = OwnProps & DomainsTableReduxProps & DomainsTableDispatchProps; @@ -72,14 +77,6 @@ const rowItems: ItemsPerRow[] = [ text: i18n.ROWS_10, numberOfRow: 10, }, - { - text: i18n.ROWS_20, - numberOfRow: 20, - }, - { - text: i18n.ROWS_50, - numberOfRow: 50, - }, ]; export const DomainsTableId = 'domains-table'; @@ -89,23 +86,24 @@ class DomainsTableComponent extends React.PureComponent { const { data, domainsSortField, - hasNextPage, + fakeTotalCount, + flowDirection, + flowTarget, id, indexPattern, ip, limit, loading, - loadMore, + loadPage, + showMorePagesIndicator, totalCount, - nextCursor, - updateDomainsLimit, - flowDirection, - flowTarget, type, + updateDomainsLimit, + updateTableActivePage, } = this.props; return ( - { type, DomainsTableId )} - hasNextPage={hasNextPage} + showMorePagesIndicator={showMorePagesIndicator} headerCount={totalCount} headerSupplement={ { limit={limit} loading={loading} loadingTitle={i18n.DOMAINS} - loadMore={() => loadMore(nextCursor)} + loadPage={newActivePage => loadPage(newActivePage)} onChange={this.onChange} pageOfItems={data} sorting={getSortField(domainsSortField, flowTarget)} + totalCount={fakeTotalCount} + updateActivePage={newPage => + updateTableActivePage({ + activePage: newPage, + tableType, + }) + } updateLimitPagination={newLimit => updateDomainsLimit({ limit: newLimit, networkType: type }) } + updateProps={{ domainsSortField, flowDirection, flowTarget, totalCount }} /> ); } @@ -174,6 +180,7 @@ export const DomainsTable = connect( updateDomainsLimit: networkActions.updateDomainsLimit, updateDomainsDirection: networkActions.updateDomainsFlowDirection, updateDomainsSort: networkActions.updateDomainsSort, + updateTableActivePage: networkActions.updateIpDetailsTableActivePage, } )(DomainsTableComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/mock.ts b/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/mock.ts index cec6aba436b091..624db09f039438 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/mock.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/mock.ts @@ -68,9 +68,8 @@ export const mockDomainsData: DomainsData = { }, ], pageInfo: { - endCursor: { - value: '10', - }, - hasNextPage: false, + activePage: 1, + fakeTotalCount: 50, + showMorePagesIndicator: true, }, }; diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/translations.ts b/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/translations.ts index 44e068b7589f30..c5b02c6a2331ca 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/network/domains_table/translations.ts @@ -120,16 +120,6 @@ export const ROWS_10 = i18n.translate('xpack.siem.network.ipDetails.domainsTable defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', }); -export const ROWS_20 = i18n.translate('xpack.siem.network.ipDetails.domainsTable.rows', { - values: { numRows: 20 }, - defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', -}); - -export const ROWS_50 = i18n.translate('xpack.siem.network.ipDetails.domainsTable.rows', { - values: { numRows: 50 }, - defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', -}); - export const MORE = i18n.translate('xpack.siem.network.ipDetails.domainsTable.moreDescription', { defaultMessage: 'More ...', }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/__snapshots__/index.test.tsx.snap index 65d227ffc2fe85..0b45139e24cb13 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/__snapshots__/index.test.tsx.snap @@ -136,11 +136,11 @@ exports[`NetworkTopNFlow Table Component rendering it renders the default Networ }, ] } - hasNextPage={true} + fakeTotalCount={50} id="dns" - loadMore={[MockFunction]} + loadPage={[MockFunction]} loading={false} - nextCursor="10" + showMorePagesIndicator={true} totalCount={80} type="page" /> diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/columns.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/columns.tsx index a63c8b13b97910..1d8be96b1264be 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/columns.tsx @@ -18,16 +18,15 @@ import { PreferenceFormattedBytes } from '../../../formatted_bytes'; import { Provider } from '../../../timeline/data_providers/provider'; import * as i18n from './translations'; - -export const getNetworkDnsColumns = ( - type: networkModel.NetworkType -): [ +export type NetworkDnsColumns = [ Columns, Columns, Columns, Columns, Columns -] => [ +]; + +export const getNetworkDnsColumns = (type: networkModel.NetworkType): NetworkDnsColumns => [ { field: `node.${NetworkDnsFields.dnsName}`, name: i18n.REGISTERED_DOMAIN, diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.test.tsx index ca7b859a0c0920..5e2525dce2e9c1 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.test.tsx @@ -18,7 +18,7 @@ import { NetworkDnsTable } from '.'; import { mockData } from './mock'; describe('NetworkTopNFlow Table Component', () => { - const loadMore = jest.fn(); + const loadPage = jest.fn(); const state: State = mockGlobalState; let store = createStore(state, apolloClientObservable); @@ -33,11 +33,15 @@ describe('NetworkTopNFlow Table Component', () => { @@ -55,11 +59,15 @@ describe('NetworkTopNFlow Table Component', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.tsx index ddff55552b5056..4cf86165c9c671 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.tsx @@ -12,19 +12,21 @@ import { ActionCreator } from 'typescript-fsa'; import { networkActions } from '../../../../store/actions'; import { NetworkDnsEdges, NetworkDnsFields, NetworkDnsSortField } from '../../../../graphql/types'; import { networkModel, networkSelectors, State } from '../../../../store'; -import { Criteria, ItemsPerRow, LoadMoreTable } from '../../../load_more_table'; +import { Criteria, ItemsPerRow, PaginatedTable } from '../../../paginated_table'; import { getNetworkDnsColumns } from './columns'; import { IsPtrIncluded } from './is_ptr_included'; import * as i18n from './translations'; +const tableType = networkModel.NetworkTableType.dns; + interface OwnProps { data: NetworkDnsEdges[]; - loading: boolean; - hasNextPage: boolean; + fakeTotalCount: number; id: string; - nextCursor: string; - loadMore: (cursor: string) => void; + loading: boolean; + loadPage: (newActivePage: number) => void; + showMorePagesIndicator: boolean; totalCount: number; type: networkModel.NetworkType; } @@ -48,6 +50,10 @@ interface NetworkDnsTableDispatchProps { isPtrIncluded: boolean; networkType: networkModel.NetworkType; }>; + updateTableActivePage: ActionCreator<{ + activePage: number; + tableType: networkModel.NetworkTableType; + }>; } type NetworkDnsTableProps = OwnProps & NetworkDnsTableReduxProps & NetworkDnsTableDispatchProps; @@ -61,14 +67,6 @@ const rowItems: ItemsPerRow[] = [ text: i18n.ROWS_10, numberOfRow: 10, }, - { - text: i18n.ROWS_20, - numberOfRow: 20, - }, - { - text: i18n.ROWS_50, - numberOfRow: 50, - }, ]; class NetworkDnsTableComponent extends React.PureComponent { @@ -76,21 +74,21 @@ class NetworkDnsTableComponent extends React.PureComponent const { data, dnsSortField, - hasNextPage, + fakeTotalCount, + id, isPtrIncluded, limit, loading, - loadMore, - id, - nextCursor, + loadPage, + showMorePagesIndicator, totalCount, type, updateDnsLimit, + updateTableActivePage, } = this.props; return ( - @@ -103,14 +101,23 @@ class NetworkDnsTableComponent extends React.PureComponent limit={limit} loading={loading} loadingTitle={i18n.TOP_DNS_DOMAINS} - loadMore={() => loadMore(nextCursor)} + loadPage={newActivePage => loadPage(newActivePage)} onChange={this.onChange} pageOfItems={data} + showMorePagesIndicator={showMorePagesIndicator} sorting={{ field: `node.${dnsSortField.field}`, direction: dnsSortField.direction, }} + totalCount={fakeTotalCount} + updateActivePage={newPage => + updateTableActivePage({ + activePage: newPage, + tableType, + }) + } updateLimitPagination={newLimit => updateDnsLimit({ limit: newLimit, networkType: type })} + updateProps={{ isPtrIncluded, totalCount, dnsSortField }} /> ); } @@ -143,6 +150,7 @@ const makeMapStateToProps = () => { export const NetworkDnsTable = connect( makeMapStateToProps, { + updateTableActivePage: networkActions.updateNetworkPageTableActivePage, updateDnsLimit: networkActions.updateDnsLimit, updateDnsSort: networkActions.updateDnsSort, updateIsPtrIncluded: networkActions.updateIsPtrIncluded, diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/mock.ts b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/mock.ts index 7846afb3f00fbe..29ea5f9d12588a 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/mock.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/mock.ts @@ -122,8 +122,9 @@ export const mockData: { NetworkDns: NetworkDnsData } = { }, ], pageInfo: { - endCursor: { value: '10' }, - hasNextPage: true, + activePage: 1, + fakeTotalCount: 50, + showMorePagesIndicator: true, }, }, }; diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/translations.ts b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/translations.ts index 65bcf0410686a8..c641e899f1db15 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/translations.ts @@ -63,13 +63,3 @@ export const ROWS_10 = i18n.translate('xpack.siem.networkDnsTable.rows', { values: { numRows: 10 }, defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', }); - -export const ROWS_20 = i18n.translate('xpack.siem.networkDnsTable.rows', { - values: { numRows: 20 }, - defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', -}); - -export const ROWS_50 = i18n.translate('xpack.siem.networkDnsTable.rows', { - values: { numRows: 50 }, - defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', -}); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/__snapshots__/index.test.tsx.snap index 2a24a6499326f5..01921a8c255d96 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/__snapshots__/index.test.tsx.snap @@ -51,7 +51,7 @@ exports[`NetworkTopNFlow Table Component rendering it renders the default Networ }, ] } - hasNextPage={true} + fakeTotalCount={50} id="topNFlow" indexPattern={ Object { @@ -138,9 +138,9 @@ exports[`NetworkTopNFlow Table Component rendering it renders the default Networ "title": "filebeat-*,auditbeat-*,packetbeat-*", } } - loadMore={[MockFunction]} + loadPage={[MockFunction]} loading={false} - nextCursor="10" + showMorePagesIndicator={true} totalCount={524} type="page" /> diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/columns.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/columns.tsx index c884fc78d51c4a..f087835ea76189 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/columns.tsx @@ -32,20 +32,22 @@ import * as i18n from './translations'; import { getRowItemDraggables } from '../../../tables/helpers'; import { PreferenceFormattedBytes } from '../../../formatted_bytes'; -export const getNetworkTopNFlowColumns = ( - indexPattern: StaticIndexPattern, - flowDirection: FlowDirection, - flowTarget: FlowTarget, - type: networkModel.NetworkType, - tableId: string -): [ +export type NetworkTopNFlowColumns = [ Columns, Columns, Columns, Columns, Columns, Columns -] => [ +]; + +export const getNetworkTopNFlowColumns = ( + indexPattern: StaticIndexPattern, + flowDirection: FlowDirection, + flowTarget: FlowTarget, + type: networkModel.NetworkType, + tableId: string +): NetworkTopNFlowColumns => [ { name: getIpTitle(flowTarget), truncateText: false, diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.test.tsx index b293cee22dcb14..34c35f5a476b8f 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.test.tsx @@ -24,7 +24,7 @@ import { NetworkTopNFlowTable, NetworkTopNFlowTableId } from '.'; import { mockData } from './mock'; describe('NetworkTopNFlow Table Component', () => { - const loadMore = jest.fn(); + const loadPage = jest.fn(); const state: State = mockGlobalState; let store = createStore(state, apolloClientObservable); @@ -39,12 +39,16 @@ describe('NetworkTopNFlow Table Component', () => { @@ -66,12 +70,16 @@ describe('NetworkTopNFlow Table Component', () => { @@ -103,12 +111,16 @@ describe('NetworkTopNFlow Table Component', () => { @@ -145,12 +157,16 @@ describe('NetworkTopNFlow Table Component', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.tsx index 7772deac6dff63..b0d7865dae3f9f 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.tsx @@ -22,20 +22,22 @@ import { import { networkModel, networkSelectors, State } from '../../../../store'; import { FlowDirectionSelect } from '../../../flow_controls/flow_direction_select'; import { FlowTargetSelect } from '../../../flow_controls/flow_target_select'; -import { Criteria, ItemsPerRow, LoadMoreTable } from '../../../load_more_table'; +import { Criteria, ItemsPerRow, PaginatedTable } from '../../../paginated_table'; import { getNetworkTopNFlowColumns } from './columns'; import * as i18n from './translations'; +const tableType = networkModel.NetworkTableType.topNFlow; + interface OwnProps { data: NetworkTopNFlowEdges[]; + fakeTotalCount: number; id: string; indexPattern: StaticIndexPattern; loading: boolean; - hasNextPage: boolean; - nextCursor: string; + loadPage: (newActivePage: number) => void; + showMorePagesIndicator: boolean; totalCount: number; - loadMore: (cursor: string) => void; type: networkModel.NetworkType; } @@ -47,6 +49,10 @@ interface NetworkTopNFlowTableReduxProps { } interface NetworkTopNFlowTableDispatchProps { + updateTableActivePage: ActionCreator<{ + activePage: number; + tableType: networkModel.NetworkTableType; + }>; updateTopNFlowDirection: ActionCreator<{ flowDirection: FlowDirection; networkType: networkModel.NetworkType; @@ -77,14 +83,6 @@ const rowItems: ItemsPerRow[] = [ text: i18n.ROWS_10, numberOfRow: 10, }, - { - text: i18n.ROWS_20, - numberOfRow: 20, - }, - { - text: i18n.ROWS_50, - numberOfRow: 50, - }, ]; export const NetworkTopNFlowTableId = 'networkTopNFlow-top-talkers'; @@ -93,20 +91,21 @@ class NetworkTopNFlowTableComponent extends React.PureComponent @@ -163,13 +161,22 @@ class NetworkTopNFlowTableComponent extends React.PureComponent loadMore(nextCursor)} + loadPage={newActivePage => loadPage(newActivePage)} onChange={this.onChange} pageOfItems={data} + showMorePagesIndicator={showMorePagesIndicator} sorting={{ field, direction: topNFlowSort.direction }} + totalCount={fakeTotalCount} + updateActivePage={newPage => + updateTableActivePage({ + activePage: newPage, + tableType, + }) + } updateLimitPagination={newLimit => updateTopNFlowLimit({ limit: newLimit, networkType: type }) } + updateProps={{ flowDirection, flowTarget, totalCount, topNFlowSort, field }} /> ); } @@ -208,6 +215,7 @@ export const NetworkTopNFlowTable = connect( updateTopNFlowSort: networkActions.updateTopNFlowSort, updateTopNFlowTarget: networkActions.updateTopNFlowTarget, updateTopNFlowDirection: networkActions.updateTopNFlowDirection, + updateTableActivePage: networkActions.updateNetworkPageTableActivePage, } )(NetworkTopNFlowTableComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/mock.ts b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/mock.ts index dbb7ca6d212ffe..ee8b99cf9f5ede 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/mock.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/mock.ts @@ -47,10 +47,9 @@ export const mockData: { NetworkTopNFlow: NetworkTopNFlowData } = { }, ], pageInfo: { - endCursor: { - value: '10', - }, - hasNextPage: true, + activePage: 1, + fakeTotalCount: 50, + showMorePagesIndicator: true, }, }, }; diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/translations.ts b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/translations.ts index bd8155cb02f93d..44b61603c043bd 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/translations.ts @@ -120,13 +120,3 @@ export const ROWS_10 = i18n.translate('xpack.siem.networkTopNFlowTable.rows', { values: { numRows: 10 }, defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', }); - -export const ROWS_20 = i18n.translate('xpack.siem.networkTopNFlowTable.rows', { - values: { numRows: 20 }, - defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', -}); - -export const ROWS_50 = i18n.translate('xpack.siem.networkTopNFlowTable.rows', { - values: { numRows: 50 }, - defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', -}); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/__snapshots__/index.test.tsx.snap index a06919e2b7679f..4d7294e390ac61 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/__snapshots__/index.test.tsx.snap @@ -78,11 +78,11 @@ exports[`Tls Table Component Rendering it renders the default Domains table 1`] }, ] } - hasNextPage={false} + fakeTotalCount={50} id="tls" - loadMore={[MockFunction]} + loadPage={[MockFunction]} loading={false} - nextCursor="10" + showMorePagesIndicator={true} totalCount={1} type="details" /> diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/columns.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/columns.tsx index d67fd5c3a959a3..7578c5decc8519 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/columns.tsx @@ -15,15 +15,15 @@ import { PreferenceFormattedDate } from '../../../formatted_date'; import * as i18n from './translations'; -export const getTlsColumns = ( - tableId: string -): [ +export type TlsColumns = [ Columns, Columns, Columns, Columns, Columns -] => [ +]; + +export const getTlsColumns = (tableId: string): TlsColumns => [ { field: 'node', name: i18n.ISSUER, diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.test.tsx index d2c47b4da54fc6..9e16d313e2b501 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.test.tsx @@ -18,7 +18,7 @@ import { TlsTable } from '.'; import { mockTlsData } from './mock'; describe('Tls Table Component', () => { - const loadMore = jest.fn(); + const loadPage = jest.fn(); const state: State = mockGlobalState; let store = createStore(state, apolloClientObservable); @@ -33,11 +33,11 @@ describe('Tls Table Component', () => { @@ -55,11 +55,11 @@ describe('Tls Table Component', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.tsx index 971a7d0bdec466..6b57e13c7791d8 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.tsx @@ -12,18 +12,19 @@ import { ActionCreator } from 'redux'; import { networkActions } from '../../../../store/network'; import { TlsEdges, TlsSortField, TlsFields } from '../../../../graphql/types'; import { networkModel, networkSelectors, State } from '../../../../store'; -import { Criteria, ItemsPerRow, LoadMoreTable, SortingBasicTable } from '../../../load_more_table'; +import { Criteria, ItemsPerRow, PaginatedTable, SortingBasicTable } from '../../../paginated_table'; import { getTlsColumns } from './columns'; import * as i18n from './translations'; +const tableType = networkModel.IpDetailsTableType.tls; interface OwnProps { data: TlsEdges[]; - loading: boolean; - hasNextPage: boolean; + fakeTotalCount: number; id: string; - nextCursor: string; + loading: boolean; + loadPage: (newActivePage: number) => void; + showMorePagesIndicator: boolean; totalCount: number; - loadMore: (cursor: string) => void; type: networkModel.NetworkType; } @@ -33,6 +34,10 @@ interface TlsTableReduxProps { } interface TlsTableDispatchProps { + updateTableActivePage: ActionCreator<{ + activePage: number; + tableType: networkModel.IpDetailsTableType; + }>; updateTlsLimit: ActionCreator<{ limit: number; networkType: networkModel.NetworkType; @@ -54,14 +59,6 @@ const rowItems: ItemsPerRow[] = [ text: i18n.ROWS_10, numberOfRow: 10, }, - { - text: i18n.ROWS_20, - numberOfRow: 20, - }, - { - text: i18n.ROWS_50, - numberOfRow: 50, - }, ]; export const tlsTableId = 'tls-table'; @@ -70,21 +67,22 @@ class TlsTableComponent extends React.PureComponent { public render() { const { data, - tlsSortField, - hasNextPage, + fakeTotalCount, + id, limit, loading, - loadMore, - id, + loadPage, + showMorePagesIndicator, + tlsSortField, totalCount, - nextCursor, - updateTlsLimit, type, + updateTableActivePage, + updateTlsLimit, } = this.props; return ( - { limit={limit} loading={loading} loadingTitle={i18n.TRANSPORT_LAYER_SECURITY} - loadMore={() => loadMore(nextCursor)} + loadPage={newActivePage => loadPage(newActivePage)} onChange={this.onChange} pageOfItems={data} sorting={getSortField(tlsSortField)} + totalCount={fakeTotalCount} + updateActivePage={newPage => + updateTableActivePage({ + activePage: newPage, + tableType, + }) + } updateLimitPagination={newLimit => updateTlsLimit({ limit: newLimit, networkType: type })} + updateProps={{ tlsSortField, totalCount }} /> ); } @@ -129,6 +135,7 @@ const makeMapStateToProps = () => { export const TlsTable = connect( makeMapStateToProps, { + updateTableActivePage: networkActions.updateIpDetailsTableActivePage, updateTlsLimit: networkActions.updateTlsLimit, updateTlsSort: networkActions.updateTlsSort, } diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/mock.ts b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/mock.ts index c30aaa3a590e4f..77148bf50c0385 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/mock.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/mock.ts @@ -50,9 +50,8 @@ export const mockTlsData: TlsData = { }, ], pageInfo: { - endCursor: { - value: '10', - }, - hasNextPage: false, + activePage: 1, + fakeTotalCount: 50, + showMorePagesIndicator: true, }, }; diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/translations.ts b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/translations.ts index 47e8607c38dacb..eaed9485385b29 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/translations.ts @@ -63,16 +63,6 @@ export const ROWS_10 = i18n.translate('xpack.siem.network.ipDetails.tlsTable.row defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', }); -export const ROWS_20 = i18n.translate('xpack.siem.network.ipDetails.tlsTable.rows', { - values: { numRows: 20 }, - defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', -}); - -export const ROWS_50 = i18n.translate('xpack.siem.network.ipDetails.tlsTable.rows', { - values: { numRows: 50 }, - defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', -}); - export const MORE = i18n.translate('xpack.siem.network.ipDetails.tlsTable.moreDescription', { defaultMessage: 'More ...', }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/__snapshots__/index.test.tsx.snap index 031234c552dbc5..9e731d8b05eb8c 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/__snapshots__/index.test.tsx.snap @@ -70,12 +70,12 @@ exports[`Users Table Component Rendering it renders the default Users table 1`] }, ] } + fakeTotalCount={3} flowTarget="source" - hasNextPage={false} id="user" - loadMore={[MockFunction]} + loadPage={[MockFunction]} loading={false} - nextCursor="10" + showMorePagesIndicator={true} totalCount={1} type="details" /> diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/columns.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/columns.tsx index 497f10eb209c52..b17ec74fa05401 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/columns.tsx @@ -11,16 +11,15 @@ import { Columns } from '../../../load_more_table'; import * as i18n from './translations'; import { getRowItemDraggables, getRowItemDraggable } from '../../../tables/helpers'; -export const getUsersColumns = ( - flowTarget: FlowTarget, - tableId: string -): [ +export type UsersColumns = [ Columns, Columns, Columns, Columns, Columns -] => [ +]; + +export const getUsersColumns = (flowTarget: FlowTarget, tableId: string): UsersColumns => [ { field: 'node.user.name', name: i18n.USER_NAME, diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.test.tsx index 56ee4ac3e1501b..75aa472f4a9e67 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.test.tsx @@ -19,7 +19,7 @@ import { UsersTable } from '.'; import { mockUsersData } from './mock'; describe('Users Table Component', () => { - const loadMore = jest.fn(); + const loadPage = jest.fn(); const state: State = mockGlobalState; let store = createStore(state, apolloClientObservable); @@ -35,11 +35,11 @@ describe('Users Table Component', () => { @@ -58,11 +58,15 @@ describe('Users Table Component', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.tsx index 8d9e8baedf0bc0..d8a5007e6e6470 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.tsx @@ -12,21 +12,22 @@ import { ActionCreator } from 'redux'; import { networkActions } from '../../../../store/network'; import { FlowTarget, UsersEdges, UsersFields, UsersSortField } from '../../../../graphql/types'; import { networkModel, networkSelectors, State } from '../../../../store'; -import { Criteria, ItemsPerRow, LoadMoreTable, SortingBasicTable } from '../../../load_more_table'; +import { Criteria, ItemsPerRow, PaginatedTable, SortingBasicTable } from '../../../paginated_table'; import { getUsersColumns } from './columns'; import * as i18n from './translations'; import { assertUnreachable } from '../../../../lib/helpers'; +const tableType = networkModel.IpDetailsTableType.users; interface OwnProps { data: UsersEdges[]; flowTarget: FlowTarget; - loading: boolean; - hasNextPage: boolean; + fakeTotalCount: number; id: string; - nextCursor: string; + loading: boolean; + loadPage: (newActivePage: number) => void; + showMorePagesIndicator: boolean; totalCount: number; - loadMore: (cursor: string) => void; type: networkModel.NetworkType; } @@ -36,6 +37,10 @@ interface UsersTableReduxProps { } interface UsersTableDispatchProps { + updateTableActivePage: ActionCreator<{ + activePage: number; + tableType: networkModel.IpDetailsTableType; + }>; updateUsersLimit: ActionCreator<{ limit: number; networkType: networkModel.NetworkType; @@ -57,14 +62,6 @@ const rowItems: ItemsPerRow[] = [ text: i18n.ROWS_10, numberOfRow: 10, }, - { - text: i18n.ROWS_20, - numberOfRow: 20, - }, - { - text: i18n.ROWS_50, - numberOfRow: 50, - }, ]; export const usersTableId = 'users-table'; @@ -73,23 +70,24 @@ class UsersTableComponent extends React.PureComponent { public render() { const { data, - usersSortField, - hasNextPage, + fakeTotalCount, + flowTarget, + id, limit, loading, - loadMore, - id, + loadPage, + showMorePagesIndicator, totalCount, - nextCursor, - updateUsersLimit, - flowTarget, type, + updateTableActivePage, + updateUsersLimit, + usersSortField, } = this.props; return ( - { limit={limit} loading={loading} loadingTitle={i18n.USERS} - loadMore={() => loadMore(nextCursor)} + loadPage={newActivePage => loadPage(newActivePage)} onChange={this.onChange} pageOfItems={data} sorting={getSortField(usersSortField)} + totalCount={fakeTotalCount} + updateActivePage={newPage => + updateTableActivePage({ + activePage: newPage, + tableType, + }) + } updateLimitPagination={newLimit => updateUsersLimit({ limit: newLimit, networkType: type })} + updateProps={{ flowTarget, totalCount, usersSortField }} /> ); } @@ -134,6 +140,7 @@ const makeMapStateToProps = () => { export const UsersTable = connect( makeMapStateToProps, { + updateTableActivePage: networkActions.updateIpDetailsTableActivePage, updateUsersLimit: networkActions.updateUsersLimit, updateUsersSort: networkActions.updateUsersSort, } diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/mock.ts b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/mock.ts index d9e12b6d7da365..9a5de66a91a3ec 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/mock.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/mock.ts @@ -59,10 +59,8 @@ export const mockUsersData: UsersData = { ], totalCount: 3, pageInfo: { - endCursor: { - value: '10', - tiebreaker: null, - }, - hasNextPage: false, + activePage: 1, + fakeTotalCount: 3, + showMorePagesIndicator: true, }, }; diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/translations.ts b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/translations.ts index dffd3b90e31f39..f7fe07510f6ba5 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/translations.ts @@ -63,16 +63,6 @@ export const ROWS_10 = i18n.translate('xpack.siem.network.ipDetails.usersTable.r defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', }); -export const ROWS_20 = i18n.translate('xpack.siem.network.ipDetails.usersTable.rows', { - values: { numRows: 20 }, - defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', -}); - -export const ROWS_50 = i18n.translate('xpack.siem.network.ipDetails.usersTable.rows', { - values: { numRows: 50 }, - defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', -}); - export const MORE = i18n.translate('xpack.siem.network.ipDetails.usersTable.moreDescription', { defaultMessage: 'More ...', }); diff --git a/x-pack/legacy/plugins/siem/public/components/paginated_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/paginated_table/index.tsx index 0ad5f6daa81574..126025d1a7cbc0 100644 --- a/x-pack/legacy/plugins/siem/public/components/paginated_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/paginated_table/index.tsx @@ -21,6 +21,14 @@ import styled from 'styled-components'; import { Direction } from '../../graphql/types'; import { AuthTableColumns } from '../page/hosts/authentications_table'; +import { DomainsColumns } from '../page/network/domains_table/columns'; +import { EventsTableColumns } from '../page/hosts/events_table'; +import { HostsTableColumns } from '../page/hosts/hosts_table'; +import { NetworkDnsColumns } from '../page/network/network_dns_table/columns'; +import { NetworkTopNFlowColumns } from '../page/network/network_top_n_flow_table/columns'; +import { TlsColumns } from '../page/network/tls_table/columns'; +import { UncommonProcessTableColumns } from '../page/hosts/uncommon_process_table'; +import { UsersColumns } from '../page/network/users_table/columns'; import { HeaderPanel } from '../header_panel'; import { LoadingPanel } from '../loading'; import { useStateToaster } from '../toasters'; @@ -46,14 +54,25 @@ export interface Criteria { sort?: SortingBasicTable; } -declare type HostsTableColumns = [ +declare type HostsTableColumnsTest = [ Columns, Columns, Columns, Columns ]; -declare type BasicTableColumns = AuthTableColumns | HostsTableColumns; +declare type BasicTableColumns = + | AuthTableColumns + | DomainsColumns + | DomainsColumns + | EventsTableColumns + | HostsTableColumns + | HostsTableColumnsTest + | NetworkDnsColumns + | NetworkTopNFlowColumns + | TlsColumns + | UncommonProcessTableColumns + | UsersColumns; declare type SiemTables = BasicTableProps; @@ -133,6 +152,12 @@ export const PaginatedTable = memo( } }, effectDeps); + useEffect(() => { + if (!isEmpty(pageOfItems) && isEmptyTable) { + setEmptyTable(false); + } + }, [pageOfItems]); + const onButtonClick = () => { setPopoverOpen(!isPopoverOpen); }; @@ -160,9 +185,7 @@ export const PaginatedTable = memo( loadPage(newActivePage); updateActivePage(newActivePage); }; - if (!isEmpty(pageOfItems) && isEmptyTable) { - setEmptyTable(false); - } + if (loading && isEmptyTable) { return ( diff --git a/x-pack/legacy/plugins/siem/public/components/stat_items/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/stat_items/__snapshots__/index.test.tsx.snap index d34a2b346fbffb..fb0e584d032bc7 100644 --- a/x-pack/legacy/plugins/siem/public/components/stat_items/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/stat_items/__snapshots__/index.test.tsx.snap @@ -32,10 +32,10 @@ exports[`Stat Items Component disable charts it renders the default widget 1`] = >

-- @@ -275,10 +275,10 @@ exports[`Stat Items Component disable charts it renders the default widget 2`] = >

-- @@ -591,10 +591,10 @@ exports[`Stat Items Component rendering kpis with charts it renders the default >

1,714 @@ -834,10 +834,10 @@ exports[`Stat Items Component rendering kpis with charts it renders the default key="stat-items-field-uniqueDestinationIps" >

2,359 @@ -934,12 +934,12 @@ exports[`Stat Items Component rendering kpis with charts it renders the default >

- + > + + +
+ + + +
+
+
+
- + > + + +
+ + + +
+
+
+
diff --git a/x-pack/legacy/plugins/siem/public/components/stat_items/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/stat_items/index.test.tsx index 8b4739e76766d4..e171e1d853c001 100644 --- a/x-pack/legacy/plugins/siem/public/components/stat_items/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/stat_items/index.test.tsx @@ -30,8 +30,6 @@ import { mockGlobalState, apolloClientObservable } from '../../mock'; import { State, createStore } from '../../store'; import { Provider as ReduxStoreProvider } from 'react-redux'; import { KpiNetworkData, KpiHostsData } from '../../graphql/types'; -jest.mock('../charts/barchart'); -jest.mock('../charts/areachart'); const from = new Date('2019-06-15T06:00:00.000Z').valueOf(); const to = new Date('2019-06-18T06:00:00.000Z').valueOf(); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/footer/mock.ts b/x-pack/legacy/plugins/siem/public/components/timeline/footer/mock.ts index e0f210c692a1fe..f6aaf9475f2c4e 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/footer/mock.ts +++ b/x-pack/legacy/plugins/siem/public/components/timeline/footer/mock.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EventsData } from '../../../graphql/types'; +import { EventsTimelineData } from '../../../graphql/types'; -export const mockData: { Events: EventsData } = { +export const mockData: { Events: EventsTimelineData } = { Events: { totalCount: 15546, pageInfo: { diff --git a/x-pack/legacy/plugins/siem/public/containers/authentications/index.tsx b/x-pack/legacy/plugins/siem/public/containers/authentications/index.tsx index c18eaf7942b108..35b6bca987bec7 100644 --- a/x-pack/legacy/plugins/siem/public/containers/authentications/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/authentications/index.tsx @@ -26,14 +26,14 @@ import { authenticationsQuery } from './index.gql_query'; const ID = 'authenticationQuery'; export interface AuthenticationArgs { + authentications: AuthenticationsEdges[]; id: string; inspect: inputsModel.InspectQuery; - authentications: AuthenticationsEdges[]; - totalCount: number; - pageInfo: PageInfoPaginated; loading: boolean; loadPage: (newActivePage: number) => void; + pageInfo: PageInfoPaginated; refetch: inputsModel.Refetch; + totalCount: number; } export interface OwnProps extends QueryTemplatePaginatedProps { diff --git a/x-pack/legacy/plugins/siem/public/containers/domains/index.gql_query.ts b/x-pack/legacy/plugins/siem/public/containers/domains/index.gql_query.ts index bfca7fe1d78774..8266a83bcad5ea 100644 --- a/x-pack/legacy/plugins/siem/public/containers/domains/index.gql_query.ts +++ b/x-pack/legacy/plugins/siem/public/containers/domains/index.gql_query.ts @@ -13,7 +13,7 @@ export const domainsQuery = gql` $flowDirection: FlowDirection! $flowTarget: FlowTarget! $ip: String! - $pagination: PaginationInput! + $pagination: PaginationInputPaginated! $sort: DomainsSortField! $timerange: TimerangeInput! $defaultIndex: [String!]! @@ -57,10 +57,9 @@ export const domainsQuery = gql` } } pageInfo { - endCursor { - value - } - hasNextPage + activePage + fakeTotalCount + showMorePagesIndicator } inspect @include(if: $inspect) { dsl diff --git a/x-pack/legacy/plugins/siem/public/containers/domains/index.tsx b/x-pack/legacy/plugins/siem/public/containers/domains/index.tsx index 586a795cd2a905..7f71a4356b0787 100644 --- a/x-pack/legacy/plugins/siem/public/containers/domains/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/domains/index.tsx @@ -17,28 +17,28 @@ import { GetDomainsQuery, FlowDirection, FlowTarget, - PageInfo, + PageInfoPaginated, } from '../../graphql/types'; import { inputsModel, networkModel, networkSelectors, State, inputsSelectors } from '../../store'; import { createFilter } from '../helpers'; -import { QueryTemplate, QueryTemplateProps } from '../query_template'; - +import { generateTablePaginationOptions } from '../../components/paginated_table/helpers'; +import { QueryTemplatePaginated, QueryTemplatePaginatedProps } from '../query_template_paginated'; import { domainsQuery } from './index.gql_query'; const ID = 'domainsQuery'; export interface DomainsArgs { + domains: DomainsEdges[]; id: string; inspect: inputsModel.InspectQuery; - domains: DomainsEdges[]; - totalCount: number; - pageInfo: PageInfo; loading: boolean; - loadMore: (cursor: string) => void; + loadPage: (newActivePage: number) => void; + pageInfo: PageInfoPaginated; refetch: inputsModel.Refetch; + totalCount: number; } -export interface OwnProps extends QueryTemplateProps { +export interface OwnProps extends QueryTemplatePaginatedProps { children: (args: DomainsArgs) => React.ReactNode; flowTarget: FlowTarget; ip: string; @@ -46,34 +46,36 @@ export interface OwnProps extends QueryTemplateProps { } export interface DomainsComponentReduxProps { - isInspected: boolean; - limit: number; + activePage: number; domainsSortField: DomainsSortField; flowDirection: FlowDirection; + isInspected: boolean; + limit: number; } type DomainsProps = OwnProps & DomainsComponentReduxProps; -class DomainsComponentQuery extends QueryTemplate< +class DomainsComponentQuery extends QueryTemplatePaginated< DomainsProps, GetDomainsQuery.Query, GetDomainsQuery.Variables > { public render() { const { - id = ID, - isInspected, + activePage, children, domainsSortField, + endDate, filterQuery, + flowDirection, + flowTarget, + id = ID, ip, + isInspected, + limit, skip, sourceId, startDate, - endDate, - limit, - flowTarget, - flowDirection, } = this.props; return ( @@ -82,35 +84,28 @@ class DomainsComponentQuery extends QueryTemplate< notifyOnNetworkStatusChange skip={skip} variables={{ + defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + filterQuery: createFilter(filterQuery), + flowDirection, + flowTarget, + inspect: isInspected, + ip, + pagination: generateTablePaginationOptions(activePage, limit), + sort: domainsSortField, sourceId, timerange: { interval: '12h', from: startDate!, to: endDate!, }, - ip, - flowDirection, - flowTarget, - sort: domainsSortField, - pagination: { - limit, - cursor: null, - tiebreaker: null, - }, - filterQuery: createFilter(filterQuery), - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), - inspect: isInspected, }} > {({ data, loading, fetchMore, refetch }) => { const domains = getOr([], `source.Domains.edges`, data); this.setFetchMore(fetchMore); - this.setFetchMoreOptions((newCursor: string) => ({ + this.setFetchMoreOptions((newActivePage: number) => ({ variables: { - pagination: { - cursor: newCursor, - limit: limit + parseInt(newCursor, 10), - }, + pagination: generateTablePaginationOptions(newActivePage, limit), }, updateQuery: (prev, { fetchMoreResult }) => { if (!fetchMoreResult) { @@ -122,21 +117,21 @@ class DomainsComponentQuery extends QueryTemplate< ...fetchMoreResult.source, Domains: { ...fetchMoreResult.source.Domains, - edges: [...prev.source.Domains.edges, ...fetchMoreResult.source.Domains.edges], + edges: [...fetchMoreResult.source.Domains.edges], }, }, }; }, })); return children({ + domains, id, inspect: getOr(null, 'source.Domains.inspect', data), - refetch, loading, - totalCount: getOr(0, 'source.Domains.totalCount', data), - domains, + loadPage: this.wrappedLoadMore, pageInfo: getOr({}, 'source.Domains.pageInfo', data), - loadMore: this.wrappedLoadMore, + refetch, + totalCount: getOr(0, 'source.Domains.totalCount', data), }); }} diff --git a/x-pack/legacy/plugins/siem/public/containers/events/index.gql_query.ts b/x-pack/legacy/plugins/siem/public/containers/events/index.gql_query.ts index 1b2e0cf69cc1ec..c8610dcd39add2 100644 --- a/x-pack/legacy/plugins/siem/public/containers/events/index.gql_query.ts +++ b/x-pack/legacy/plugins/siem/public/containers/events/index.gql_query.ts @@ -10,7 +10,7 @@ export const eventsQuery = gql` query GetEventsQuery( $sourceId: ID! $timerange: TimerangeInput! - $pagination: PaginationInput! + $pagination: PaginationInputPaginated! $sortField: SortField! $filterQuery: String $defaultIndex: [String!]! @@ -27,11 +27,9 @@ export const eventsQuery = gql` ) { totalCount pageInfo { - endCursor { - value - tiebreaker - } - hasNextPage + activePage + fakeTotalCount + showMorePagesIndicator } inspect @include(if: $inspect) { dsl diff --git a/x-pack/legacy/plugins/siem/public/containers/events/index.tsx b/x-pack/legacy/plugins/siem/public/containers/events/index.tsx index 3a1bc685b5a21c..6bdf9cece4a899 100644 --- a/x-pack/legacy/plugins/siem/public/containers/events/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/events/index.tsx @@ -11,46 +11,49 @@ import { connect } from 'react-redux'; import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; -import { Direction, Ecs, GetEventsQuery, PageInfo } from '../../graphql/types'; +import { Direction, Ecs, GetEventsQuery, PageInfoPaginated } from '../../graphql/types'; import { hostsModel, hostsSelectors, inputsModel, State, inputsSelectors } from '../../store'; import { createFilter, getDefaultFetchPolicy } from '../helpers'; -import { QueryTemplate, QueryTemplateProps } from '../query_template'; - +import { QueryTemplatePaginated, QueryTemplatePaginatedProps } from '../query_template_paginated'; +import { generateTablePaginationOptions } from '../../components/paginated_table/helpers'; import { eventsQuery } from './index.gql_query'; const ID = 'eventsQuery'; export interface EventsArgs { + events: Ecs[]; id: string; inspect: inputsModel.InspectQuery; - events: Ecs[]; loading: boolean; - loadMore: (cursor: string, tiebreaker: string) => void; - pageInfo: PageInfo; + loadPage: (newActivePage: number) => void; + pageInfo: PageInfoPaginated; refetch: inputsModel.Refetch; totalCount: number; } -export interface OwnProps extends QueryTemplateProps { +export interface OwnProps extends QueryTemplatePaginatedProps { children?: (args: EventsArgs) => React.ReactNode; type: hostsModel.HostsType; } export interface EventsComponentReduxProps { + activePage: number; isInspected: boolean; limit: number; } type EventsProps = OwnProps & EventsComponentReduxProps; -class EventsComponentQuery extends QueryTemplate< +class EventsComponentQuery extends QueryTemplatePaginated< EventsProps, GetEventsQuery.Query, GetEventsQuery.Variables > { public render() { const { + activePage, children, + endDate, filterQuery, id = ID, isInspected, @@ -58,7 +61,6 @@ class EventsComponentQuery extends QueryTemplate< skip, sourceId, startDate, - endDate, } = this.props; return ( @@ -67,36 +69,28 @@ class EventsComponentQuery extends QueryTemplate< notifyOnNetworkStatusChange skip={skip} variables={{ + defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), filterQuery: createFilter(filterQuery), - sourceId, - pagination: { - limit, - cursor: null, - tiebreaker: null, - }, + inspect: isInspected, + pagination: generateTablePaginationOptions(activePage, limit), sortField: { sortFieldId: 'timestamp', direction: Direction.desc, }, + sourceId, timerange: { interval: '12h', from: startDate!, to: endDate!, }, - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), - inspect: isInspected, }} > {({ data, loading, fetchMore, refetch }) => { const events = getOr([], 'source.Events.edges', data); this.setFetchMore(fetchMore); - this.setFetchMoreOptions((newCursor: string, tiebreaker?: string) => ({ + this.setFetchMoreOptions((newActivePage: number) => ({ variables: { - pagination: { - cursor: newCursor, - tiebreaker, - limit, - }, + pagination: generateTablePaginationOptions(newActivePage, limit), }, updateQuery: (prev, { fetchMoreResult }) => { if (!fetchMoreResult) { @@ -108,21 +102,21 @@ class EventsComponentQuery extends QueryTemplate< ...fetchMoreResult.source, Events: { ...fetchMoreResult.source.Events, - edges: [...prev.source.Events.edges, ...fetchMoreResult.source.Events.edges], + edges: [...fetchMoreResult.source.Events.edges], }, }, }; }, })); return children!({ + events, id, inspect: getOr(null, 'source.Events.inspect', data), - refetch, loading, - totalCount: getOr(0, 'source.Events.totalCount', data), + loadPage: this.wrappedLoadMore, pageInfo: getOr({}, 'source.Events.pageInfo', data), - events, - loadMore: this.wrappedLoadMore, + refetch, + totalCount: getOr(0, 'source.Events.totalCount', data), }); }} diff --git a/x-pack/legacy/plugins/siem/public/containers/hosts/hosts_table.gql_query.ts b/x-pack/legacy/plugins/siem/public/containers/hosts/hosts_table.gql_query.ts index e5c8fe35fdfa61..672ea70b09ad21 100644 --- a/x-pack/legacy/plugins/siem/public/containers/hosts/hosts_table.gql_query.ts +++ b/x-pack/legacy/plugins/siem/public/containers/hosts/hosts_table.gql_query.ts @@ -10,7 +10,7 @@ export const HostsTableQuery = gql` query GetHostsTableQuery( $sourceId: ID! $timerange: TimerangeInput! - $pagination: PaginationInput! + $pagination: PaginationInputPaginated! $sort: HostsSortField! $filterQuery: String $defaultIndex: [String!]! @@ -44,10 +44,9 @@ export const HostsTableQuery = gql` } } pageInfo { - endCursor { - value - } - hasNextPage + activePage + fakeTotalCount + showMorePagesIndicator } inspect @include(if: $inspect) { dsl diff --git a/x-pack/legacy/plugins/siem/public/containers/hosts/index.tsx b/x-pack/legacy/plugins/siem/public/containers/hosts/index.tsx index 4b63ce95358573..a5dae9219623b2 100644 --- a/x-pack/legacy/plugins/siem/public/containers/hosts/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/hosts/index.tsx @@ -17,32 +17,33 @@ import { GetHostsTableQuery, HostsEdges, HostsFields, - PageInfo, + PageInfoPaginated, } from '../../graphql/types'; import { hostsModel, hostsSelectors, inputsModel, State, inputsSelectors } from '../../store'; import { createFilter } from '../helpers'; -import { QueryTemplate, QueryTemplateProps } from '../query_template'; +import { QueryTemplatePaginated, QueryTemplatePaginatedProps } from '../query_template_paginated'; import { HostsTableQuery } from './hosts_table.gql_query'; +import { generateTablePaginationOptions } from '../../components/paginated_table/helpers'; export { HostsFilter } from './filter'; const ID = 'hostsQuery'; export interface HostsArgs { + endDate: number; + hosts: HostsEdges[]; id: string; inspect: inputsModel.InspectQuery; - hosts: HostsEdges[]; - totalCount: number; - pageInfo: PageInfo; loading: boolean; - loadMore: (cursor: string) => void; + loadPage: (newActivePage: number) => void; + pageInfo: PageInfoPaginated; refetch: inputsModel.Refetch; startDate: number; - endDate: number; + totalCount: number; } -export interface OwnProps extends QueryTemplateProps { +export interface OwnProps extends QueryTemplatePaginatedProps { children: (args: HostsArgs) => React.ReactNode; type: hostsModel.HostsType; startDate: number; @@ -50,6 +51,7 @@ export interface OwnProps extends QueryTemplateProps { } export interface HostsComponentReduxProps { + activePage: number; isInspected: boolean; limit: number; sortField: HostsFields; @@ -58,7 +60,7 @@ export interface HostsComponentReduxProps { type HostsProps = OwnProps & HostsComponentReduxProps; -class HostsComponentQuery extends QueryTemplate< +class HostsComponentQuery extends QueryTemplatePaginated< HostsProps, GetHostsTableQuery.Query, GetHostsTableQuery.Variables @@ -75,6 +77,7 @@ class HostsComponentQuery extends QueryTemplate< public render() { const { + activePage, id = ID, isInspected, children, @@ -99,11 +102,7 @@ class HostsComponentQuery extends QueryTemplate< direction, field: sortField, }, - pagination: { - limit, - cursor: null, - tiebreaker: null, - }, + pagination: generateTablePaginationOptions(activePage, limit), filterQuery: createFilter(filterQuery), defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), inspect: isInspected, @@ -118,12 +117,9 @@ class HostsComponentQuery extends QueryTemplate< > {({ data, loading, fetchMore, refetch }) => { this.setFetchMore(fetchMore); - this.setFetchMoreOptions((newCursor: string) => ({ + this.setFetchMoreOptions((newActivePage: number) => ({ variables: { - pagination: { - cursor: newCursor, - limit: limit + parseInt(newCursor, 10), - }, + pagination: generateTablePaginationOptions(newActivePage, limit), }, updateQuery: (prev, { fetchMoreResult }) => { if (!fetchMoreResult) { @@ -135,23 +131,23 @@ class HostsComponentQuery extends QueryTemplate< ...fetchMoreResult.source, Hosts: { ...fetchMoreResult.source.Hosts, - edges: [...prev.source.Hosts.edges, ...fetchMoreResult.source.Hosts.edges], + edges: [...fetchMoreResult.source.Hosts.edges], }, }, }; }, })); return children({ + endDate, + hosts: this.memoizedHosts(JSON.stringify(variables), get('source', data)), id, inspect: getOr(null, 'source.Hosts.inspect', data), - refetch, loading, - totalCount: getOr(0, 'source.Hosts.totalCount', data), - hosts: this.memoizedHosts(JSON.stringify(variables), get('source', data)), - startDate, - endDate, + loadPage: this.wrappedLoadMore, pageInfo: getOr({}, 'source.Hosts.pageInfo', data), - loadMore: this.wrappedLoadMore, + refetch, + startDate, + totalCount: getOr(0, 'source.Hosts.totalCount', data), }); }} diff --git a/x-pack/legacy/plugins/siem/public/containers/network_dns/index.gql_query.ts b/x-pack/legacy/plugins/siem/public/containers/network_dns/index.gql_query.ts index 03c547257634f7..365d93ee7e7560 100644 --- a/x-pack/legacy/plugins/siem/public/containers/network_dns/index.gql_query.ts +++ b/x-pack/legacy/plugins/siem/public/containers/network_dns/index.gql_query.ts @@ -12,7 +12,7 @@ export const networkDnsQuery = gql` $sort: NetworkDnsSortField! $isPtrIncluded: Boolean! $timerange: TimerangeInput! - $pagination: PaginationInput! + $pagination: PaginationInputPaginated! $filterQuery: String $defaultIndex: [String!]! $inspect: Boolean! @@ -42,10 +42,9 @@ export const networkDnsQuery = gql` } } pageInfo { - endCursor { - value - } - hasNextPage + activePage + fakeTotalCount + showMorePagesIndicator } inspect @include(if: $inspect) { dsl diff --git a/x-pack/legacy/plugins/siem/public/containers/network_dns/index.tsx b/x-pack/legacy/plugins/siem/public/containers/network_dns/index.tsx index 1c9b7beaa3dfb6..3eca8ee08d07a4 100644 --- a/x-pack/legacy/plugins/siem/public/containers/network_dns/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/network_dns/index.tsx @@ -15,12 +15,12 @@ import { GetNetworkDnsQuery, NetworkDnsEdges, NetworkDnsSortField, - PageInfo, + PageInfoPaginated, } from '../../graphql/types'; import { inputsModel, networkModel, networkSelectors, State, inputsSelectors } from '../../store'; +import { generateTablePaginationOptions } from '../../components/paginated_table/helpers'; import { createFilter } from '../helpers'; -import { QueryTemplate, QueryTemplateProps } from '../query_template'; - +import { QueryTemplatePaginated, QueryTemplatePaginatedProps } from '../query_template_paginated'; import { networkDnsQuery } from './index.gql_query'; const ID = 'networkDnsQuery'; @@ -28,81 +28,76 @@ const ID = 'networkDnsQuery'; export interface NetworkDnsArgs { id: string; inspect: inputsModel.InspectQuery; - networkDns: NetworkDnsEdges[]; - totalCount: number; - pageInfo: PageInfo; loading: boolean; - loadMore: (cursor: string) => void; + loadPage: (newActivePage: number) => void; + networkDns: NetworkDnsEdges[]; + pageInfo: PageInfoPaginated; refetch: inputsModel.Refetch; + totalCount: number; } -export interface OwnProps extends QueryTemplateProps { +export interface OwnProps extends QueryTemplatePaginatedProps { children: (args: NetworkDnsArgs) => React.ReactNode; type: networkModel.NetworkType; } export interface NetworkDnsComponentReduxProps { - isInspected: boolean; - limit: number; + activePage: number; dnsSortField: NetworkDnsSortField; + isInspected: boolean; isPtrIncluded: boolean; + limit: number; } type NetworkDnsProps = OwnProps & NetworkDnsComponentReduxProps; -class NetworkDnsComponentQuery extends QueryTemplate< +class NetworkDnsComponentQuery extends QueryTemplatePaginated< NetworkDnsProps, GetNetworkDnsQuery.Query, GetNetworkDnsQuery.Variables > { public render() { const { - id = ID, - isInspected, + activePage, children, dnsSortField, + endDate, filterQuery, + id = ID, + isInspected, isPtrIncluded, + limit, skip, sourceId, startDate, - endDate, - limit, } = this.props; return ( - query={networkDnsQuery} fetchPolicy="cache-and-network" notifyOnNetworkStatusChange + query={networkDnsQuery} skip={skip} variables={{ + defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + filterQuery: createFilter(filterQuery), + inspect: isInspected, + isPtrIncluded, + pagination: generateTablePaginationOptions(activePage, limit), + sort: dnsSortField, sourceId, timerange: { interval: '12h', from: startDate!, to: endDate!, }, - sort: dnsSortField, - isPtrIncluded, - pagination: { - limit, - cursor: null, - tiebreaker: null, - }, - filterQuery: createFilter(filterQuery), - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), - inspect: isInspected, }} > {({ data, loading, fetchMore, refetch }) => { const networkDns = getOr([], `source.NetworkDns.edges`, data); this.setFetchMore(fetchMore); - this.setFetchMoreOptions((newCursor: string) => ({ + this.setFetchMoreOptions((newActivePage: number) => ({ variables: { - pagination: { - cursor: newCursor, - limit: limit + parseInt(newCursor, 10), - }, + pagination: generateTablePaginationOptions(newActivePage, limit), }, updateQuery: (prev, { fetchMoreResult }) => { if (!fetchMoreResult) { @@ -114,10 +109,7 @@ class NetworkDnsComponentQuery extends QueryTemplate< ...fetchMoreResult.source, NetworkDns: { ...fetchMoreResult.source.NetworkDns, - edges: [ - ...prev.source.NetworkDns.edges, - ...fetchMoreResult.source.NetworkDns.edges, - ], + edges: [...fetchMoreResult.source.NetworkDns.edges], }, }, }; @@ -126,12 +118,12 @@ class NetworkDnsComponentQuery extends QueryTemplate< return children({ id, inspect: getOr(null, 'source.NetworkDns.inspect', data), - refetch, loading, - totalCount: getOr(0, 'source.NetworkDns.totalCount', data), + loadPage: this.wrappedLoadMore, networkDns, pageInfo: getOr({}, 'source.NetworkDns.pageInfo', data), - loadMore: this.wrappedLoadMore, + refetch, + totalCount: getOr(0, 'source.NetworkDns.totalCount', data), }); }} diff --git a/x-pack/legacy/plugins/siem/public/containers/network_top_n_flow/index.gql_query.ts b/x-pack/legacy/plugins/siem/public/containers/network_top_n_flow/index.gql_query.ts index 299e1bb5926d1f..3e3f3ef306e3b0 100644 --- a/x-pack/legacy/plugins/siem/public/containers/network_top_n_flow/index.gql_query.ts +++ b/x-pack/legacy/plugins/siem/public/containers/network_top_n_flow/index.gql_query.ts @@ -11,7 +11,7 @@ export const networkTopNFlowQuery = gql` $sourceId: ID! $flowDirection: FlowDirection! $filterQuery: String - $pagination: PaginationInput! + $pagination: PaginationInputPaginated! $sort: NetworkTopNFlowSortField! $flowTarget: FlowTarget! $timerange: TimerangeInput! @@ -63,10 +63,9 @@ export const networkTopNFlowQuery = gql` } } pageInfo { - endCursor { - value - } - hasNextPage + activePage + fakeTotalCount + showMorePagesIndicator } inspect @include(if: $inspect) { dsl diff --git a/x-pack/legacy/plugins/siem/public/containers/network_top_n_flow/index.tsx b/x-pack/legacy/plugins/siem/public/containers/network_top_n_flow/index.tsx index d496734f7b1163..27c90721a6c1ee 100644 --- a/x-pack/legacy/plugins/siem/public/containers/network_top_n_flow/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/network_top_n_flow/index.tsx @@ -17,12 +17,12 @@ import { GetNetworkTopNFlowQuery, NetworkTopNFlowEdges, NetworkTopNFlowSortField, - PageInfo, + PageInfoPaginated, } from '../../graphql/types'; import { inputsModel, networkModel, networkSelectors, State, inputsSelectors } from '../../store'; +import { generateTablePaginationOptions } from '../../components/paginated_table/helpers'; import { createFilter } from '../helpers'; -import { QueryTemplate, QueryTemplateProps } from '../query_template'; - +import { QueryTemplatePaginated, QueryTemplatePaginatedProps } from '../query_template_paginated'; import { networkTopNFlowQuery } from './index.gql_query'; const ID = 'networkTopNFlowQuery'; @@ -30,84 +30,79 @@ const ID = 'networkTopNFlowQuery'; export interface NetworkTopNFlowArgs { id: string; inspect: inputsModel.InspectQuery; - networkTopNFlow: NetworkTopNFlowEdges[]; - totalCount: number; - pageInfo: PageInfo; loading: boolean; - loadMore: (cursor: string) => void; + loadPage: (newActivePage: number) => void; + networkTopNFlow: NetworkTopNFlowEdges[]; + pageInfo: PageInfoPaginated; refetch: inputsModel.Refetch; + totalCount: number; } -export interface OwnProps extends QueryTemplateProps { +export interface OwnProps extends QueryTemplatePaginatedProps { children: (args: NetworkTopNFlowArgs) => React.ReactNode; type: networkModel.NetworkType; } export interface NetworkTopNFlowComponentReduxProps { + activePage: number; + flowDirection: FlowDirection; + flowTarget: FlowTarget; isInspected: boolean; limit: number; - flowDirection: FlowDirection; topNFlowSort: NetworkTopNFlowSortField; - flowTarget: FlowTarget; } type NetworkTopNFlowProps = OwnProps & NetworkTopNFlowComponentReduxProps; -class NetworkTopNFlowComponentQuery extends QueryTemplate< +class NetworkTopNFlowComponentQuery extends QueryTemplatePaginated< NetworkTopNFlowProps, GetNetworkTopNFlowQuery.Query, GetNetworkTopNFlowQuery.Variables > { public render() { const { - id = ID, - isInspected, + activePage, children, + endDate, filterQuery, + flowDirection, + flowTarget, + id = ID, + isInspected, + limit, skip, sourceId, startDate, - endDate, - limit, - flowDirection, topNFlowSort, - flowTarget, } = this.props; return ( - query={networkTopNFlowQuery} fetchPolicy="cache-and-network" notifyOnNetworkStatusChange + query={networkTopNFlowQuery} skip={skip} variables={{ + defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + filterQuery: createFilter(filterQuery), + flowDirection, + flowTarget, + inspect: isInspected, + pagination: generateTablePaginationOptions(activePage, limit), + sort: topNFlowSort, sourceId, timerange: { interval: '12h', from: startDate!, to: endDate!, }, - sort: topNFlowSort, - flowDirection, - flowTarget, - pagination: { - limit, - cursor: null, - tiebreaker: null, - }, - filterQuery: createFilter(filterQuery), - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), - inspect: isInspected, }} > {({ data, loading, fetchMore, refetch }) => { const networkTopNFlow = getOr([], `source.NetworkTopNFlow.edges`, data); this.setFetchMore(fetchMore); - this.setFetchMoreOptions((newCursor: string) => ({ + this.setFetchMoreOptions((newActivePage: number) => ({ variables: { - pagination: { - cursor: newCursor, - limit: limit + parseInt(newCursor, 10), - }, + pagination: generateTablePaginationOptions(newActivePage, limit), }, updateQuery: (prev, { fetchMoreResult }) => { if (!fetchMoreResult) { @@ -119,10 +114,7 @@ class NetworkTopNFlowComponentQuery extends QueryTemplate< ...fetchMoreResult.source, NetworkTopNFlow: { ...fetchMoreResult.source.NetworkTopNFlow, - edges: [ - ...prev.source.NetworkTopNFlow.edges, - ...fetchMoreResult.source.NetworkTopNFlow.edges, - ], + edges: [...fetchMoreResult.source.NetworkTopNFlow.edges], }, }, }; @@ -131,12 +123,12 @@ class NetworkTopNFlowComponentQuery extends QueryTemplate< return children({ id, inspect: getOr(null, 'source.NetworkTopNFlow.inspect', data), - refetch, loading, - totalCount: getOr(0, 'source.NetworkTopNFlow.totalCount', data), + loadPage: this.wrappedLoadMore, networkTopNFlow, pageInfo: getOr({}, 'source.NetworkTopNFlow.pageInfo', data), - loadMore: this.wrappedLoadMore, + refetch, + totalCount: getOr(0, 'source.NetworkTopNFlow.totalCount', data), }); }} diff --git a/x-pack/legacy/plugins/siem/public/containers/tls/index.gql_query.ts b/x-pack/legacy/plugins/siem/public/containers/tls/index.gql_query.ts index 87570b0422364b..15d5a8eb8a4836 100644 --- a/x-pack/legacy/plugins/siem/public/containers/tls/index.gql_query.ts +++ b/x-pack/legacy/plugins/siem/public/containers/tls/index.gql_query.ts @@ -12,7 +12,7 @@ export const tlsQuery = gql` $filterQuery: String $flowTarget: FlowTarget! $ip: String! - $pagination: PaginationInput! + $pagination: PaginationInputPaginated! $sort: TlsSortField! $timerange: TimerangeInput! $defaultIndex: [String!]! @@ -44,10 +44,9 @@ export const tlsQuery = gql` } } pageInfo { - endCursor { - value - } - hasNextPage + activePage + fakeTotalCount + showMorePagesIndicator } inspect @include(if: $inspect) { dsl diff --git a/x-pack/legacy/plugins/siem/public/containers/tls/index.tsx b/x-pack/legacy/plugins/siem/public/containers/tls/index.tsx index 606ae3f70849f0..4030af726d91a2 100644 --- a/x-pack/legacy/plugins/siem/public/containers/tls/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/tls/index.tsx @@ -11,11 +11,17 @@ import { connect } from 'react-redux'; import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; -import { FlowTarget, PageInfo, TlsEdges, TlsSortField, GetTlsQuery } from '../../graphql/types'; +import { + FlowTarget, + PageInfoPaginated, + TlsEdges, + TlsSortField, + GetTlsQuery, +} from '../../graphql/types'; import { inputsModel, networkModel, networkSelectors, State, inputsSelectors } from '../../store'; import { createFilter } from '../helpers'; -import { QueryTemplate, QueryTemplateProps } from '../query_template'; - +import { generateTablePaginationOptions } from '../../components/paginated_table/helpers'; +import { QueryTemplatePaginated, QueryTemplatePaginatedProps } from '../query_template_paginated'; import { tlsQuery } from './index.gql_query'; const ID = 'tlsQuery'; @@ -23,15 +29,15 @@ const ID = 'tlsQuery'; export interface TlsArgs { id: string; inspect: inputsModel.InspectQuery; - tls: TlsEdges[]; - totalCount: number; - pageInfo: PageInfo; loading: boolean; - loadMore: (cursor: string) => void; + loadPage: (newActivePage: number) => void; + pageInfo: PageInfoPaginated; refetch: inputsModel.Refetch; + tls: TlsEdges[]; + totalCount: number; } -export interface OwnProps extends QueryTemplateProps { +export interface OwnProps extends QueryTemplatePaginatedProps { children: (args: TlsArgs) => React.ReactNode; flowTarget: FlowTarget; ip: string; @@ -39,6 +45,7 @@ export interface OwnProps extends QueryTemplateProps { } export interface TlsComponentReduxProps { + activePage: number; isInspected: boolean; limit: number; tlsSortField: TlsSortField; @@ -46,21 +53,26 @@ export interface TlsComponentReduxProps { type TlsProps = OwnProps & TlsComponentReduxProps; -class TlsComponentQuery extends QueryTemplate { +class TlsComponentQuery extends QueryTemplatePaginated< + TlsProps, + GetTlsQuery.Query, + GetTlsQuery.Variables +> { public render() { const { - id = ID, - isInspected, + activePage, children, - tlsSortField, + endDate, filterQuery, + flowTarget, + id = ID, ip, + isInspected, + limit, skip, sourceId, startDate, - endDate, - limit, - flowTarget, + tlsSortField, } = this.props; return ( @@ -69,34 +81,27 @@ class TlsComponentQuery extends QueryTemplate {({ data, loading, fetchMore, refetch }) => { const tls = getOr([], 'source.Tls.edges', data); this.setFetchMore(fetchMore); - this.setFetchMoreOptions((newCursor: string) => ({ + this.setFetchMoreOptions((newActivePage: number) => ({ variables: { - pagination: { - cursor: newCursor, - limit: limit + parseInt(newCursor, 10), - }, + pagination: generateTablePaginationOptions(newActivePage, limit), }, updateQuery: (prev, { fetchMoreResult }) => { if (!fetchMoreResult) { @@ -108,7 +113,7 @@ class TlsComponentQuery extends QueryTemplate diff --git a/x-pack/legacy/plugins/siem/public/containers/uncommon_processes/index.gql_query.ts b/x-pack/legacy/plugins/siem/public/containers/uncommon_processes/index.gql_query.ts index 5aa4cd4d75bba6..d984de020faa1e 100644 --- a/x-pack/legacy/plugins/siem/public/containers/uncommon_processes/index.gql_query.ts +++ b/x-pack/legacy/plugins/siem/public/containers/uncommon_processes/index.gql_query.ts @@ -10,7 +10,7 @@ export const uncommonProcessesQuery = gql` query GetUncommonProcessesQuery( $sourceId: ID! $timerange: TimerangeInput! - $pagination: PaginationInput! + $pagination: PaginationInputPaginated! $filterQuery: String $defaultIndex: [String!]! $inspect: Boolean! @@ -45,10 +45,9 @@ export const uncommonProcessesQuery = gql` } } pageInfo { - endCursor { - value - } - hasNextPage + activePage + fakeTotalCount + showMorePagesIndicator } inspect @include(if: $inspect) { dsl diff --git a/x-pack/legacy/plugins/siem/public/containers/uncommon_processes/index.tsx b/x-pack/legacy/plugins/siem/public/containers/uncommon_processes/index.tsx index a83139f61f8055..2054a3b9a44140 100644 --- a/x-pack/legacy/plugins/siem/public/containers/uncommon_processes/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/uncommon_processes/index.tsx @@ -11,10 +11,15 @@ import { connect } from 'react-redux'; import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; -import { GetUncommonProcessesQuery, PageInfo, UncommonProcessesEdges } from '../../graphql/types'; +import { + GetUncommonProcessesQuery, + PageInfoPaginated, + UncommonProcessesEdges, +} from '../../graphql/types'; import { hostsModel, hostsSelectors, inputsModel, State, inputsSelectors } from '../../store'; +import { generateTablePaginationOptions } from '../../components/paginated_table/helpers'; import { createFilter, getDefaultFetchPolicy } from '../helpers'; -import { QueryTemplate, QueryTemplateProps } from '../query_template'; +import { QueryTemplatePaginated, QueryTemplatePaginatedProps } from '../query_template_paginated'; import { uncommonProcessesQuery } from './index.gql_query'; @@ -22,43 +27,45 @@ const ID = 'uncommonProcessesQuery'; export interface UncommonProcessesArgs { id: string; - uncommonProcesses: UncommonProcessesEdges[]; - totalCount: number; - pageInfo: PageInfo; + inspect: inputsModel.InspectQuery; loading: boolean; - loadMore: (cursor: string) => void; + loadPage: (newActivePage: number) => void; + pageInfo: PageInfoPaginated; refetch: inputsModel.Refetch; - inspect: inputsModel.InspectQuery; + totalCount: number; + uncommonProcesses: UncommonProcessesEdges[]; } -export interface OwnProps extends QueryTemplateProps { +export interface OwnProps extends QueryTemplatePaginatedProps { children: (args: UncommonProcessesArgs) => React.ReactNode; type: hostsModel.HostsType; } export interface UncommonProcessesComponentReduxProps { + activePage: number; isInspected: boolean; limit: number; } type UncommonProcessesProps = OwnProps & UncommonProcessesComponentReduxProps; -class UncommonProcessesComponentQuery extends QueryTemplate< +class UncommonProcessesComponentQuery extends QueryTemplatePaginated< UncommonProcessesProps, GetUncommonProcessesQuery.Query, GetUncommonProcessesQuery.Variables > { public render() { const { - id = ID, + activePage, children, + endDate, filterQuery, + id = ID, isInspected, + limit, skip, sourceId, startDate, - endDate, - limit, } = this.props; return ( @@ -67,31 +74,24 @@ class UncommonProcessesComponentQuery extends QueryTemplate< notifyOnNetworkStatusChange skip={skip} variables={{ + defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + filterQuery: createFilter(filterQuery), + inspect: isInspected, + pagination: generateTablePaginationOptions(activePage, limit), sourceId, timerange: { interval: '12h', from: startDate!, to: endDate!, }, - pagination: { - limit, - cursor: null, - tiebreaker: null, - }, - filterQuery: createFilter(filterQuery), - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), - inspect: isInspected, }} > {({ data, loading, fetchMore, refetch }) => { const uncommonProcesses = getOr([], 'source.UncommonProcesses.edges', data); this.setFetchMore(fetchMore); - this.setFetchMoreOptions((newCursor: string) => ({ + this.setFetchMoreOptions((newActivePage: number) => ({ variables: { - pagination: { - cursor: newCursor, - limit: limit + parseInt(newCursor, 10), - }, + pagination: generateTablePaginationOptions(newActivePage, limit), }, updateQuery: (prev, { fetchMoreResult }) => { if (!fetchMoreResult) { @@ -103,10 +103,7 @@ class UncommonProcessesComponentQuery extends QueryTemplate< ...fetchMoreResult.source, UncommonProcesses: { ...fetchMoreResult.source.UncommonProcesses, - edges: [ - ...prev.source.UncommonProcesses.edges, - ...fetchMoreResult.source.UncommonProcesses.edges, - ], + edges: [...fetchMoreResult.source.UncommonProcesses.edges], }, }, }; @@ -116,11 +113,11 @@ class UncommonProcessesComponentQuery extends QueryTemplate< id, inspect: getOr(null, 'source.UncommonProcesses.inspect', data), loading, + loadPage: this.wrappedLoadMore, + pageInfo: getOr({}, 'source.UncommonProcesses.pageInfo', data), refetch, totalCount: getOr(0, 'source.UncommonProcesses.totalCount', data), uncommonProcesses, - pageInfo: getOr({}, 'source.UncommonProcesses.pageInfo', data), - loadMore: this.wrappedLoadMore, }); }} diff --git a/x-pack/legacy/plugins/siem/public/containers/users/index.gql_query.ts b/x-pack/legacy/plugins/siem/public/containers/users/index.gql_query.ts index abdfa96ba7e0a3..3fc1cdfd160dba 100644 --- a/x-pack/legacy/plugins/siem/public/containers/users/index.gql_query.ts +++ b/x-pack/legacy/plugins/siem/public/containers/users/index.gql_query.ts @@ -12,7 +12,7 @@ export const usersQuery = gql` $filterQuery: String $flowTarget: FlowTarget! $ip: String! - $pagination: PaginationInput! + $pagination: PaginationInputPaginated! $sort: UsersSortField! $timerange: TimerangeInput! $defaultIndex: [String!]! @@ -45,10 +45,9 @@ export const usersQuery = gql` } } pageInfo { - endCursor { - value - } - hasNextPage + activePage + fakeTotalCount + showMorePagesIndicator } inspect @include(if: $inspect) { dsl diff --git a/x-pack/legacy/plugins/siem/public/containers/users/index.tsx b/x-pack/legacy/plugins/siem/public/containers/users/index.tsx index c59acf5e187618..12c561c482ef3f 100644 --- a/x-pack/legacy/plugins/siem/public/containers/users/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/users/index.tsx @@ -14,13 +14,14 @@ import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import { GetUsersQuery, FlowTarget, - PageInfo, + PageInfoPaginated, UsersEdges, UsersSortField, } from '../../graphql/types'; import { inputsModel, networkModel, networkSelectors, State, inputsSelectors } from '../../store'; import { createFilter } from '../helpers'; -import { QueryTemplate, QueryTemplateProps } from '../query_template'; +import { generateTablePaginationOptions } from '../../components/paginated_table/helpers'; +import { QueryTemplatePaginated, QueryTemplatePaginatedProps } from '../query_template_paginated'; import { usersQuery } from './index.gql_query'; @@ -29,15 +30,15 @@ const ID = 'usersQuery'; export interface UsersArgs { id: string; inspect: inputsModel.InspectQuery; - users: UsersEdges[]; - totalCount: number; - pageInfo: PageInfo; loading: boolean; - loadMore: (cursor: string) => void; + loadPage: (newActivePage: number) => void; + pageInfo: PageInfoPaginated; refetch: inputsModel.Refetch; + totalCount: number; + users: UsersEdges[]; } -export interface OwnProps extends QueryTemplateProps { +export interface OwnProps extends QueryTemplatePaginatedProps { children: (args: UsersArgs) => React.ReactNode; flowTarget: FlowTarget; ip: string; @@ -45,6 +46,7 @@ export interface OwnProps extends QueryTemplateProps { } export interface UsersComponentReduxProps { + activePage: number; isInspected: boolean; limit: number; usersSortField: UsersSortField; @@ -52,25 +54,26 @@ export interface UsersComponentReduxProps { type UsersProps = OwnProps & UsersComponentReduxProps; -class UsersComponentQuery extends QueryTemplate< +class UsersComponentQuery extends QueryTemplatePaginated< UsersProps, GetUsersQuery.Query, GetUsersQuery.Variables > { public render() { const { - id = ID, - isInspected, + activePage, children, - usersSortField, + endDate, filterQuery, + flowTarget, + id = ID, ip, + isInspected, + limit, skip, sourceId, startDate, - endDate, - limit, - flowTarget, + usersSortField, } = this.props; return ( @@ -79,34 +82,27 @@ class UsersComponentQuery extends QueryTemplate< notifyOnNetworkStatusChange skip={skip} variables={{ + defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + filterQuery: createFilter(filterQuery), + flowTarget, + inspect: isInspected, + ip, + pagination: generateTablePaginationOptions(activePage, limit), + sort: usersSortField, sourceId, timerange: { interval: '12h', from: startDate!, to: endDate!, }, - ip, - flowTarget, - sort: usersSortField, - pagination: { - limit, - cursor: null, - tiebreaker: null, - }, - filterQuery: createFilter(filterQuery), - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), - inspect: isInspected, }} > {({ data, loading, fetchMore, refetch }) => { const users = getOr([], `source.Users.edges`, data); this.setFetchMore(fetchMore); - this.setFetchMoreOptions((newCursor: string) => ({ + this.setFetchMoreOptions((newActivePage: number) => ({ variables: { - pagination: { - cursor: newCursor, - limit: limit + parseInt(newCursor, 10), - }, + pagination: generateTablePaginationOptions(newActivePage, limit), }, updateQuery: (prev, { fetchMoreResult }) => { if (!fetchMoreResult) { @@ -118,7 +114,7 @@ class UsersComponentQuery extends QueryTemplate< ...fetchMoreResult.source, Users: { ...fetchMoreResult.source.Users, - edges: [...prev.source.Users.edges, ...fetchMoreResult.source.Users.edges], + edges: [...fetchMoreResult.source.Users.edges], }, }, }; @@ -127,12 +123,12 @@ class UsersComponentQuery extends QueryTemplate< return children({ id, inspect: getOr(null, 'source.Users.inspect', data), - refetch, loading, + loadPage: this.wrappedLoadMore, + pageInfo: getOr({}, 'source.Users.pageInfo', data), + refetch, totalCount: getOr(0, 'source.Users.totalCount', data), users, - pageInfo: getOr({}, 'source.Users.pageInfo', data), - loadMore: this.wrappedLoadMore, }); }} diff --git a/x-pack/legacy/plugins/siem/public/graphql/introspection.json b/x-pack/legacy/plugins/siem/public/graphql/introspection.json index 3b86740c73dc7e..d63c489fc93170 100644 --- a/x-pack/legacy/plugins/siem/public/graphql/introspection.json +++ b/x-pack/legacy/plugins/siem/public/graphql/introspection.json @@ -721,7 +721,11 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "PaginationInput", "ofType": null } + "ofType": { + "kind": "INPUT_OBJECT", + "name": "PaginationInputPaginated", + "ofType": null + } }, "defaultValue": null }, @@ -989,7 +993,11 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "PaginationInput", "ofType": null } + "ofType": { + "kind": "INPUT_OBJECT", + "name": "PaginationInputPaginated", + "ofType": null + } }, "defaultValue": null }, @@ -1221,7 +1229,11 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "PaginationInput", "ofType": null } + "ofType": { + "kind": "INPUT_OBJECT", + "name": "PaginationInputPaginated", + "ofType": null + } }, "defaultValue": null }, @@ -1324,7 +1336,11 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "PaginationInput", "ofType": null } + "ofType": { + "kind": "INPUT_OBJECT", + "name": "PaginationInputPaginated", + "ofType": null + } }, "defaultValue": null }, @@ -1417,7 +1433,11 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "PaginationInput", "ofType": null } + "ofType": { + "kind": "INPUT_OBJECT", + "name": "PaginationInputPaginated", + "ofType": null + } }, "defaultValue": null }, @@ -1675,7 +1695,11 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "PaginationInput", "ofType": null } + "ofType": { + "kind": "INPUT_OBJECT", + "name": "PaginationInputPaginated", + "ofType": null + } }, "defaultValue": null }, @@ -1762,7 +1786,11 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "PaginationInput", "ofType": null } + "ofType": { + "kind": "INPUT_OBJECT", + "name": "PaginationInputPaginated", + "ofType": null + } }, "defaultValue": null }, @@ -1935,7 +1963,11 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "PaginationInput", "ofType": null } + "ofType": { + "kind": "INPUT_OBJECT", + "name": "PaginationInputPaginated", + "ofType": null + } }, "defaultValue": null }, @@ -3087,39 +3119,6 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "INPUT_OBJECT", - "name": "PaginationInput", - "description": "", - "fields": null, - "inputFields": [ - { - "name": "limit", - "description": "The limit parameter allows you to configure the maximum amount of items to be returned", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "cursor", - "description": "The cursor parameter defines the next result you want to fetch", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "tiebreaker", - "description": "The tiebreaker parameter allow to be more precise to fetch the next item", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, { "kind": "INPUT_OBJECT", "name": "SortField", @@ -3195,7 +3194,7 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "PageInfo", "ofType": null } + "ofType": { "kind": "OBJECT", "name": "PageInfoPaginated", "ofType": null } }, "isDeprecated": false, "deprecationReason": null @@ -5193,29 +5192,35 @@ "possibleTypes": null }, { - "kind": "OBJECT", - "name": "PageInfo", + "kind": "INPUT_OBJECT", + "name": "PaginationInput", "description": "", - "fields": [ + "fields": null, + "inputFields": [ { - "name": "endCursor", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "CursorType", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "name": "limit", + "description": "The limit parameter allows you to configure the maximum amount of items to be returned", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + }, + "defaultValue": null }, { - "name": "hasNextPage", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null + "name": "cursor", + "description": "The cursor parameter defines the next result you want to fetch", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + }, + { + "name": "tiebreaker", + "description": "The tiebreaker parameter allow to be more precise to fetch the next item", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null } ], - "inputFields": null, - "interfaces": [], + "interfaces": null, "enumValues": null, "possibleTypes": null }, @@ -5411,6 +5416,33 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "PageInfo", + "description": "", + "fields": [ + { + "name": "endCursor", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "CursorType", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "hasNextPage", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "TimelineDetailsData", @@ -5708,7 +5740,7 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "PageInfo", "ofType": null } + "ofType": { "kind": "OBJECT", "name": "PageInfoPaginated", "ofType": null } }, "isDeprecated": false, "deprecationReason": null @@ -6248,7 +6280,7 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "PageInfo", "ofType": null } + "ofType": { "kind": "OBJECT", "name": "PageInfoPaginated", "ofType": null } }, "isDeprecated": false, "deprecationReason": null @@ -6604,7 +6636,7 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "PageInfo", "ofType": null } + "ofType": { "kind": "OBJECT", "name": "PageInfoPaginated", "ofType": null } }, "isDeprecated": false, "deprecationReason": null @@ -6853,7 +6885,7 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "PageInfo", "ofType": null } + "ofType": { "kind": "OBJECT", "name": "PageInfoPaginated", "ofType": null } }, "isDeprecated": false, "deprecationReason": null @@ -7494,7 +7526,7 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "PageInfo", "ofType": null } + "ofType": { "kind": "OBJECT", "name": "PageInfoPaginated", "ofType": null } }, "isDeprecated": false, "deprecationReason": null @@ -7817,7 +7849,7 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "PageInfo", "ofType": null } + "ofType": { "kind": "OBJECT", "name": "PageInfoPaginated", "ofType": null } }, "isDeprecated": false, "deprecationReason": null @@ -8148,7 +8180,7 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "PageInfo", "ofType": null } + "ofType": { "kind": "OBJECT", "name": "PageInfoPaginated", "ofType": null } }, "isDeprecated": false, "deprecationReason": null @@ -10551,6 +10583,69 @@ ], "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "EventsTimelineData", + "description": "", + "fields": [ + { + "name": "edges", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "EcsEdges", "ofType": null } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "pageInfo", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "PageInfo", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "inspect", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "OsFields", diff --git a/x-pack/legacy/plugins/siem/public/graphql/types.ts b/x-pack/legacy/plugins/siem/public/graphql/types.ts index 3246ef1ff49a38..3f4298853df3f6 100644 --- a/x-pack/legacy/plugins/siem/public/graphql/types.ts +++ b/x-pack/legacy/plugins/siem/public/graphql/types.ts @@ -338,7 +338,7 @@ export interface EventsData { totalCount: number; - pageInfo: PageInfo; + pageInfo: PageInfoPaginated; inspect?: Inspect | null; } @@ -801,12 +801,6 @@ export interface SshEcsFields { signature?: ToStringArray | null; } -export interface PageInfo { - endCursor?: CursorType | null; - - hasNextPage?: boolean | null; -} - export interface TimelineData { edges: TimelineEdges[]; @@ -839,6 +833,12 @@ export interface TimelineNonEcsData { value?: ToStringArray | null; } +export interface PageInfo { + endCursor?: CursorType | null; + + hasNextPage?: boolean | null; +} + export interface TimelineDetailsData { data?: DetailItem[] | null; @@ -872,7 +872,7 @@ export interface HostsData { totalCount: number; - pageInfo: PageInfo; + pageInfo: PageInfoPaginated; inspect?: Inspect | null; } @@ -958,7 +958,7 @@ export interface DomainsData { totalCount: number; - pageInfo: PageInfo; + pageInfo: PageInfoPaginated; inspect?: Inspect | null; } @@ -1010,7 +1010,7 @@ export interface TlsData { totalCount: number; - pageInfo: PageInfo; + pageInfo: PageInfoPaginated; inspect?: Inspect | null; } @@ -1042,7 +1042,7 @@ export interface UsersData { totalCount: number; - pageInfo: PageInfo; + pageInfo: PageInfoPaginated; inspect?: Inspect | null; } @@ -1154,7 +1154,7 @@ export interface NetworkTopNFlowData { totalCount: number; - pageInfo: PageInfo; + pageInfo: PageInfoPaginated; inspect?: Inspect | null; } @@ -1202,7 +1202,7 @@ export interface NetworkDnsData { totalCount: number; - pageInfo: PageInfo; + pageInfo: PageInfoPaginated; inspect?: Inspect | null; } @@ -1274,7 +1274,7 @@ export interface UncommonProcessesData { totalCount: number; - pageInfo: PageInfo; + pageInfo: PageInfoPaginated; inspect?: Inspect | null; } @@ -1483,6 +1483,16 @@ export interface ResponseFavoriteTimeline { favorite?: FavoriteTimelineResult[] | null; } +export interface EventsTimelineData { + edges: EcsEdges[]; + + totalCount: number; + + pageInfo: PageInfo; + + inspect?: Inspect | null; +} + export interface OsFields { platform?: string | null; @@ -1549,6 +1559,12 @@ export interface PaginationInputPaginated { querySize: number; } +export interface SortField { + sortFieldId: string; + + direction: Direction; +} + export interface PaginationInput { /** The limit parameter allows you to configure the maximum amount of items to be returned */ limit: number; @@ -1558,12 +1574,6 @@ export interface PaginationInput { tiebreaker?: string | null; } -export interface SortField { - sortFieldId: string; - - direction: Direction; -} - export interface LastTimeDetails { hostName?: string | null; @@ -1781,7 +1791,7 @@ export interface AuthenticationsSourceArgs { defaultIndex: string[]; } export interface EventsSourceArgs { - pagination: PaginationInput; + pagination: PaginationInputPaginated; sortField: SortField; @@ -1825,7 +1835,7 @@ export interface HostsSourceArgs { timerange: TimerangeInput; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sort: HostsSortField; @@ -1865,7 +1875,7 @@ export interface DomainsSourceArgs { ip: string; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sort: DomainsSortField; @@ -1884,7 +1894,7 @@ export interface TlsSourceArgs { ip: string; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sort: TlsSortField; @@ -1901,7 +1911,7 @@ export interface UsersSourceArgs { ip: string; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sort: UsersSortField; @@ -1947,7 +1957,7 @@ export interface NetworkTopNFlowSourceArgs { flowTarget: FlowTarget; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sort: NetworkTopNFlowSortField; @@ -1962,7 +1972,7 @@ export interface NetworkDnsSourceArgs { isPtrIncluded: boolean; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sort: NetworkDnsSortField; @@ -1991,7 +2001,7 @@ export interface OverviewHostSourceArgs { export interface UncommonProcessesSourceArgs { timerange: TimerangeInput; - pagination: PaginationInput; + pagination: PaginationInputPaginated; filterQuery?: string | null; @@ -2286,7 +2296,7 @@ export namespace GetDomainsQuery { flowDirection: FlowDirection; flowTarget: FlowTarget; ip: string; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sort: DomainsSortField; timerange: TimerangeInput; defaultIndex: string[]; @@ -2378,17 +2388,13 @@ export namespace GetDomainsQuery { }; export type PageInfo = { - __typename?: 'PageInfo'; - - endCursor?: EndCursor | null; + __typename?: 'PageInfoPaginated'; - hasNextPage?: boolean | null; - }; + activePage: number; - export type EndCursor = { - __typename?: 'CursorType'; + fakeTotalCount: number; - value?: string | null; + showMorePagesIndicator: boolean; }; export type Inspect = { @@ -2404,7 +2410,7 @@ export namespace GetEventsQuery { export type Variables = { sourceId: string; timerange: TimerangeInput; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sortField: SortField; filterQuery?: string | null; defaultIndex: string[]; @@ -2438,19 +2444,13 @@ export namespace GetEventsQuery { }; export type PageInfo = { - __typename?: 'PageInfo'; - - endCursor?: EndCursor | null; - - hasNextPage?: boolean | null; - }; + __typename?: 'PageInfoPaginated'; - export type EndCursor = { - __typename?: 'CursorType'; + activePage: number; - value?: string | null; + fakeTotalCount: number; - tiebreaker?: string | null; + showMorePagesIndicator: boolean; }; export type Inspect = { @@ -2635,7 +2635,7 @@ export namespace GetHostsTableQuery { export type Variables = { sourceId: string; timerange: TimerangeInput; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sort: HostsSortField; filterQuery?: string | null; defaultIndex: string[]; @@ -2711,17 +2711,13 @@ export namespace GetHostsTableQuery { }; export type PageInfo = { - __typename?: 'PageInfo'; - - endCursor?: EndCursor | null; + __typename?: 'PageInfoPaginated'; - hasNextPage?: boolean | null; - }; + activePage: number; - export type EndCursor = { - __typename?: 'CursorType'; + fakeTotalCount: number; - value?: string | null; + showMorePagesIndicator: boolean; }; export type Inspect = { @@ -3195,7 +3191,7 @@ export namespace GetNetworkDnsQuery { sort: NetworkDnsSortField; isPtrIncluded: boolean; timerange: TimerangeInput; - pagination: PaginationInput; + pagination: PaginationInputPaginated; filterQuery?: string | null; defaultIndex: string[]; inspect: boolean; @@ -3258,17 +3254,13 @@ export namespace GetNetworkDnsQuery { }; export type PageInfo = { - __typename?: 'PageInfo'; - - endCursor?: EndCursor | null; + __typename?: 'PageInfoPaginated'; - hasNextPage?: boolean | null; - }; + activePage: number; - export type EndCursor = { - __typename?: 'CursorType'; + fakeTotalCount: number; - value?: string | null; + showMorePagesIndicator: boolean; }; export type Inspect = { @@ -3285,7 +3277,7 @@ export namespace GetNetworkTopNFlowQuery { sourceId: string; flowDirection: FlowDirection; filterQuery?: string | null; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sort: NetworkTopNFlowSortField; flowTarget: FlowTarget; timerange: TimerangeInput; @@ -3398,17 +3390,13 @@ export namespace GetNetworkTopNFlowQuery { }; export type PageInfo = { - __typename?: 'PageInfo'; - - endCursor?: EndCursor | null; + __typename?: 'PageInfoPaginated'; - hasNextPage?: boolean | null; - }; + activePage: number; - export type EndCursor = { - __typename?: 'CursorType'; + fakeTotalCount: number; - value?: string | null; + showMorePagesIndicator: boolean; }; export type Inspect = { @@ -5009,7 +4997,7 @@ export namespace GetTlsQuery { filterQuery?: string | null; flowTarget: FlowTarget; ip: string; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sort: TlsSortField; timerange: TimerangeInput; defaultIndex: string[]; @@ -5073,17 +5061,13 @@ export namespace GetTlsQuery { }; export type PageInfo = { - __typename?: 'PageInfo'; - - endCursor?: EndCursor | null; + __typename?: 'PageInfoPaginated'; - hasNextPage?: boolean | null; - }; + activePage: number; - export type EndCursor = { - __typename?: 'CursorType'; + fakeTotalCount: number; - value?: string | null; + showMorePagesIndicator: boolean; }; export type Inspect = { @@ -5099,7 +5083,7 @@ export namespace GetUncommonProcessesQuery { export type Variables = { sourceId: string; timerange: TimerangeInput; - pagination: PaginationInput; + pagination: PaginationInputPaginated; filterQuery?: string | null; defaultIndex: string[]; inspect: boolean; @@ -5182,17 +5166,13 @@ export namespace GetUncommonProcessesQuery { }; export type PageInfo = { - __typename?: 'PageInfo'; - - endCursor?: EndCursor | null; + __typename?: 'PageInfoPaginated'; - hasNextPage?: boolean | null; - }; + activePage: number; - export type EndCursor = { - __typename?: 'CursorType'; + fakeTotalCount: number; - value?: string | null; + showMorePagesIndicator: boolean; }; export type Inspect = { @@ -5210,7 +5190,7 @@ export namespace GetUsersQuery { filterQuery?: string | null; flowTarget: FlowTarget; ip: string; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sort: UsersSortField; timerange: TimerangeInput; defaultIndex: string[]; @@ -5278,17 +5258,13 @@ export namespace GetUsersQuery { }; export type PageInfo = { - __typename?: 'PageInfo'; - - endCursor?: EndCursor | null; + __typename?: 'PageInfoPaginated'; - hasNextPage?: boolean | null; - }; + activePage: number; - export type EndCursor = { - __typename?: 'CursorType'; + fakeTotalCount: number; - value?: string | null; + showMorePagesIndicator: boolean; }; export type Inspect = { diff --git a/x-pack/legacy/plugins/siem/public/lib/track_usage/index.ts b/x-pack/legacy/plugins/siem/public/lib/track_usage/index.ts index b2ebbaa2baea67..934e5c441e076a 100644 --- a/x-pack/legacy/plugins/siem/public/lib/track_usage/index.ts +++ b/x-pack/legacy/plugins/siem/public/lib/track_usage/index.ts @@ -5,7 +5,11 @@ */ // @ts-ignore -import { trackUiMetric } from '../../../../../../../src/legacy/core_plugins/ui_metric/public'; +import { + createUiStatsReporter, + METRIC_TYPE, +} from '../../../../../../../src/legacy/core_plugins/ui_metric/public'; import { APP_ID } from '../../../common/constants'; -export const trackUiAction = (metricType: string) => trackUiMetric(APP_ID, metricType); +export const trackUiAction = createUiStatsReporter(APP_ID); +export { METRIC_TYPE }; diff --git a/x-pack/legacy/plugins/siem/public/mock/global_state.ts b/x-pack/legacy/plugins/siem/public/mock/global_state.ts index e52d3d95dd1f05..dcdd1f312ede36 100644 --- a/x-pack/legacy/plugins/siem/public/mock/global_state.ts +++ b/x-pack/legacy/plugins/siem/public/mock/global_state.ts @@ -33,12 +33,13 @@ export const mockGlobalState: State = { queries: { authentications: { activePage: 0, limit: 10 }, hosts: { + activePage: 0, limit: 10, direction: Direction.desc, sortField: HostsFields.lastSeen, }, - events: { limit: 10 }, - uncommonProcesses: { limit: 10 }, + events: { activePage: 0, limit: 10 }, + uncommonProcesses: { activePage: 0, limit: 10 }, }, filterQuery: null, filterQueryDraft: null, @@ -47,12 +48,13 @@ export const mockGlobalState: State = { queries: { authentications: { activePage: 0, limit: 10 }, hosts: { + activePage: 0, limit: 10, direction: Direction.desc, sortField: HostsFields.lastSeen, }, - events: { limit: 10 }, - uncommonProcesses: { limit: 10 }, + events: { activePage: 0, limit: 10 }, + uncommonProcesses: { activePage: 0, limit: 10 }, }, filterQuery: null, filterQueryDraft: null, @@ -62,12 +64,14 @@ export const mockGlobalState: State = { page: { queries: { topNFlow: { + activePage: 0, limit: 10, flowTarget: FlowTarget.source, flowDirection: FlowDirection.uniDirectional, topNFlowSort: { field: NetworkTopNFlowFields.bytes, direction: Direction.desc }, }, dns: { + activePage: 0, limit: 10, dnsSortField: { field: NetworkDnsFields.queryCount, direction: Direction.desc }, isPtrIncluded: false, @@ -82,15 +86,18 @@ export const mockGlobalState: State = { flowTarget: FlowTarget.source, queries: { domains: { + activePage: 0, limit: 10, flowDirection: FlowDirection.uniDirectional, domainsSortField: { field: DomainsFields.bytes, direction: Direction.desc }, }, tls: { + activePage: 0, limit: 10, tlsSortField: { field: TlsFields._id, direction: Direction.desc }, }, users: { + activePage: 0, limit: 10, usersSortField: { field: UsersFields.name, direction: Direction.asc }, }, diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/host_details.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/host_details.tsx index e3c971436ed1bc..f6ad586a83b2b7 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/host_details.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/host_details.tsx @@ -206,26 +206,30 @@ const HostDetailsComponent = pure( type={type} > {({ - uncommonProcesses, - totalCount, - loading, - pageInfo, - loadMore, id, inspect, + loading, + loadPage, + pageInfo, refetch, + totalCount, + uncommonProcesses, }) => ( )} @@ -261,26 +265,29 @@ const HostDetailsComponent = pure( > {({ events, - loading, id, inspect, + loading, + loadPage, + pageInfo, refetch, totalCount, - pageInfo, - loadMore, }) => ( )} diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx index cee1b4bcaa7c28..5511f4cde21953 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx @@ -114,23 +114,23 @@ const HostsComponent = pure(({ filterQuery, setAbsoluteRang totalCount, loading, pageInfo, - loadMore, + loadPage, id, inspect, refetch, }) => ( )} @@ -187,22 +187,22 @@ const HostsComponent = pure(({ filterQuery, setAbsoluteRang totalCount, loading, pageInfo, - loadMore, + loadPage, id, inspect, refetch, }) => ( )} @@ -243,20 +243,19 @@ const HostsComponent = pure(({ filterQuery, setAbsoluteRang refetch, totalCount, pageInfo, - loadMore, + loadPage, }) => ( )} diff --git a/x-pack/legacy/plugins/siem/public/pages/network/ip_details.tsx b/x-pack/legacy/plugins/siem/public/pages/network/ip_details.tsx index 19e380be49d544..f290966e4318a0 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/ip_details.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/ip_details.tsx @@ -155,7 +155,7 @@ export const IPDetailsComponent = pure( totalCount, pageInfo, loading, - loadMore, + loadPage, refetch, }) => ( ( id={id} inspect={inspect} flowTarget={flowTarget} - hasNextPage={getOr(false, 'hasNextPage', pageInfo)!} + fakeTotalCount={getOr(50, 'fakeTotalCount', pageInfo)} ip={ip} loading={loading} - loadMore={loadMore} - nextCursor={getOr(null, 'endCursor.value', pageInfo)} + loadPage={loadPage} + showMorePagesIndicator={getOr( + false, + 'showMorePagesIndicator', + pageInfo + )} refetch={refetch} setQuery={setQuery} totalCount={totalCount} @@ -196,7 +200,7 @@ export const IPDetailsComponent = pure( totalCount, pageInfo, loading, - loadMore, + loadPage, refetch, }) => ( ( id={id} inspect={inspect} flowTarget={flowTarget} - hasNextPage={getOr(false, 'hasNextPage', pageInfo)!} + fakeTotalCount={getOr(50, 'fakeTotalCount', pageInfo)} loading={loading} - loadMore={loadMore} - nextCursor={getOr(null, 'endCursor.value', pageInfo)!} + loadPage={loadPage} + showMorePagesIndicator={getOr( + false, + 'showMorePagesIndicator', + pageInfo + )} refetch={refetch} setQuery={setQuery} totalCount={totalCount} @@ -235,17 +243,21 @@ export const IPDetailsComponent = pure( totalCount, pageInfo, loading, - loadMore, + loadPage, refetch, }) => ( ( loading, networkTopNFlow, pageInfo, - loadMore, + loadPage, id, inspect, refetch, }) => ( @@ -141,21 +145,25 @@ const NetworkComponent = pure( loading, networkDns, pageInfo, - loadMore, + loadPage, id, inspect, refetch, }) => ( diff --git a/x-pack/legacy/plugins/siem/public/store/hosts/actions.ts b/x-pack/legacy/plugins/siem/public/store/hosts/actions.ts index 2d1d5bd35e1b74..39240a15ba230b 100644 --- a/x-pack/legacy/plugins/siem/public/store/hosts/actions.ts +++ b/x-pack/legacy/plugins/siem/public/store/hosts/actions.ts @@ -24,27 +24,11 @@ export const updateTableLimit = actionCreator<{ tableType: HostsTableType; }>('UPDATE_HOST_TABLE_LIMIT'); -export const updateAuthenticationsLimit = actionCreator<{ limit: number; hostsType: HostsType }>( - 'UPDATE_AUTHENTICATIONS_LIMIT' -); - -export const updateHostsLimit = actionCreator<{ limit: number; hostsType: HostsType }>( - 'UPDATE_HOSTS_LIMIT' -); - export const updateHostsSort = actionCreator<{ sort: HostsSortField; hostsType: HostsType; }>('UPDATE_HOSTS_SORT'); -export const updateEventsLimit = actionCreator<{ limit: number; hostsType: HostsType }>( - 'UPDATE_EVENTS_LIMIT' -); - -export const updateUncommonProcessesLimit = actionCreator<{ limit: number; hostsType: HostsType }>( - 'UPDATE_UNCOMMONPROCESSES_LIMIT' -); - export const setHostsFilterQueryDraft = actionCreator<{ filterQueryDraft: KueryFilterQuery; hostsType: HostsType; diff --git a/x-pack/legacy/plugins/siem/public/store/hosts/model.ts b/x-pack/legacy/plugins/siem/public/store/hosts/model.ts index e9a587727d207c..0acacb506d49c6 100644 --- a/x-pack/legacy/plugins/siem/public/store/hosts/model.ts +++ b/x-pack/legacy/plugins/siem/public/store/hosts/model.ts @@ -19,16 +19,12 @@ export enum HostsTableType { uncommonProcesses = 'uncommonProcesses', } -export interface BasicQuery { - limit: number; -} - export interface BasicQueryPaginated { activePage: number; limit: number; } -export interface HostsQuery extends BasicQuery { +export interface HostsQuery extends BasicQueryPaginated { direction: Direction; sortField: HostsFields; } @@ -36,8 +32,8 @@ export interface HostsQuery extends BasicQuery { interface Queries { authentications: BasicQueryPaginated; hosts: HostsQuery; - events: BasicQuery; - uncommonProcesses: BasicQuery; + events: BasicQueryPaginated; + uncommonProcesses: BasicQueryPaginated; } export interface GenericHostsModel { diff --git a/x-pack/legacy/plugins/siem/public/store/hosts/reducer.ts b/x-pack/legacy/plugins/siem/public/store/hosts/reducer.ts index 99df07758a4a18..7c40bac28da0b7 100644 --- a/x-pack/legacy/plugins/siem/public/store/hosts/reducer.ts +++ b/x-pack/legacy/plugins/siem/public/store/hosts/reducer.ts @@ -12,43 +12,59 @@ import { DEFAULT_TABLE_ACTIVE_PAGE, DEFAULT_TABLE_LIMIT } from '../constants'; import { applyHostsFilterQuery, setHostsFilterQueryDraft, - updateAuthenticationsLimit, - updateEventsLimit, - updateHostsLimit, updateHostsSort, - updateUncommonProcessesLimit, updateTableActivePage, updateTableLimit, } from './actions'; -import { HostsModel } from './model'; +import { HostsModel, HostsTableType } from './model'; export type HostsState = HostsModel; export const initialHostsState: HostsState = { page: { queries: { - authentications: { limit: DEFAULT_TABLE_LIMIT, activePage: DEFAULT_TABLE_ACTIVE_PAGE }, - hosts: { + [HostsTableType.authentications]: { + activePage: DEFAULT_TABLE_ACTIVE_PAGE, limit: DEFAULT_TABLE_LIMIT, + }, + [HostsTableType.hosts]: { + activePage: DEFAULT_TABLE_ACTIVE_PAGE, direction: Direction.desc, + limit: DEFAULT_TABLE_LIMIT, sortField: HostsFields.lastSeen, }, - events: { limit: DEFAULT_TABLE_LIMIT }, - uncommonProcesses: { limit: DEFAULT_TABLE_LIMIT }, + [HostsTableType.events]: { + activePage: DEFAULT_TABLE_ACTIVE_PAGE, + limit: DEFAULT_TABLE_LIMIT, + }, + [HostsTableType.uncommonProcesses]: { + activePage: DEFAULT_TABLE_ACTIVE_PAGE, + limit: DEFAULT_TABLE_LIMIT, + }, }, filterQuery: null, filterQueryDraft: null, }, details: { queries: { - authentications: { limit: DEFAULT_TABLE_LIMIT, activePage: DEFAULT_TABLE_ACTIVE_PAGE }, - hosts: { + [HostsTableType.authentications]: { + activePage: DEFAULT_TABLE_ACTIVE_PAGE, limit: DEFAULT_TABLE_LIMIT, + }, + [HostsTableType.hosts]: { + activePage: DEFAULT_TABLE_ACTIVE_PAGE, direction: Direction.desc, + limit: DEFAULT_TABLE_LIMIT, sortField: HostsFields.lastSeen, }, - events: { limit: DEFAULT_TABLE_LIMIT }, - uncommonProcesses: { limit: DEFAULT_TABLE_LIMIT }, + [HostsTableType.events]: { + activePage: DEFAULT_TABLE_ACTIVE_PAGE, + limit: DEFAULT_TABLE_LIMIT, + }, + [HostsTableType.uncommonProcesses]: { + activePage: DEFAULT_TABLE_ACTIVE_PAGE, + limit: DEFAULT_TABLE_LIMIT, + }, }, filterQuery: null, filterQueryDraft: null, @@ -82,31 +98,6 @@ export const hostsReducer = reducerWithInitialState(initialHostsState) }, }, })) - .case(updateAuthenticationsLimit, (state, { limit, hostsType }) => ({ - ...state, - [hostsType]: { - ...state[hostsType], - queries: { - ...state[hostsType].queries, - authentications: { - limit, - }, - }, - }, - })) - .case(updateHostsLimit, (state, { limit, hostsType }) => ({ - ...state, - [hostsType]: { - ...state[hostsType], - queries: { - ...state[hostsType].queries, - hosts: { - ...state[hostsType].queries.hosts, - limit, - }, - }, - }, - })) .case(updateHostsSort, (state, { sort, hostsType }) => ({ ...state, [hostsType]: { @@ -121,30 +112,6 @@ export const hostsReducer = reducerWithInitialState(initialHostsState) }, }, })) - .case(updateEventsLimit, (state, { limit, hostsType }) => ({ - ...state, - [hostsType]: { - ...state[hostsType], - queries: { - ...state[hostsType].queries, - events: { - limit, - }, - }, - }, - })) - .case(updateUncommonProcessesLimit, (state, { limit, hostsType }) => ({ - ...state, - [hostsType]: { - ...state[hostsType], - queries: { - ...state[hostsType].queries, - uncommonProcesses: { - limit, - }, - }, - }, - })) .case(setHostsFilterQueryDraft, (state, { filterQueryDraft, hostsType }) => ({ ...state, [hostsType]: { diff --git a/x-pack/legacy/plugins/siem/public/store/network/actions.ts b/x-pack/legacy/plugins/siem/public/store/network/actions.ts index bb91db77170736..074aaefc8b4fc6 100644 --- a/x-pack/legacy/plugins/siem/public/store/network/actions.ts +++ b/x-pack/legacy/plugins/siem/public/store/network/actions.ts @@ -17,10 +17,20 @@ import { } from '../../graphql/types'; import { KueryFilterQuery, SerializedFilterQuery } from '../model'; -import { NetworkType } from './model'; +import { IpDetailsTableType, NetworkTableType, NetworkType } from './model'; const actionCreator = actionCreatorFactory('x-pack/siem/local/network'); +export const updateNetworkPageTableActivePage = actionCreator<{ + activePage: number; + tableType: NetworkTableType; +}>('UPDATE_NETWORK_PAGE_TABLE_ACTIVE_PAGE'); + +export const updateIpDetailsTableActivePage = actionCreator<{ + activePage: number; + tableType: IpDetailsTableType; +}>('UPDATE_NETWORK_DETAILS_TABLE_ACTIVE_PAGE'); + export const updateDnsLimit = actionCreator<{ limit: number; networkType: NetworkType; diff --git a/x-pack/legacy/plugins/siem/public/store/network/model.ts b/x-pack/legacy/plugins/siem/public/store/network/model.ts index dc49d2cbdd7530..c0dbc964f479e1 100644 --- a/x-pack/legacy/plugins/siem/public/store/network/model.ts +++ b/x-pack/legacy/plugins/siem/public/store/network/model.ts @@ -20,25 +20,37 @@ export enum NetworkType { details = 'details', } -export interface BasicQuery { +export enum NetworkTableType { + dns = 'dns', + topNFlow = 'topNFlow', +} + +export enum IpDetailsTableType { + domains = 'domains', + tls = 'tls', + users = 'users', +} + +export interface BasicQueryPaginated { + activePage: number; limit: number; } // Network Page Models -export interface TopNFlowQuery extends BasicQuery { +export interface TopNFlowQuery extends BasicQueryPaginated { flowTarget: FlowTarget; topNFlowSort: NetworkTopNFlowSortField; flowDirection: FlowDirection; } -export interface DnsQuery extends BasicQuery { +export interface DnsQuery extends BasicQueryPaginated { dnsSortField: NetworkDnsSortField; isPtrIncluded: boolean; } interface NetworkQueries { - topNFlow: TopNFlowQuery; - dns: DnsQuery; + [NetworkTableType.dns]: DnsQuery; + [NetworkTableType.topNFlow]: TopNFlowQuery; } export interface NetworkPageModel { @@ -48,23 +60,23 @@ export interface NetworkPageModel { } // IP Details Models -export interface DomainsQuery extends BasicQuery { +export interface DomainsQuery extends BasicQueryPaginated { flowDirection: FlowDirection; domainsSortField: DomainsSortField; } -export interface TlsQuery extends BasicQuery { +export interface TlsQuery extends BasicQueryPaginated { tlsSortField: TlsSortField; } -export interface UsersQuery extends BasicQuery { +export interface UsersQuery extends BasicQueryPaginated { usersSortField: UsersSortField; } interface IpOverviewQueries { - domains: DomainsQuery; - tls: TlsQuery; - users: UsersQuery; + [IpDetailsTableType.domains]: DomainsQuery; + [IpDetailsTableType.tls]: TlsQuery; + [IpDetailsTableType.users]: UsersQuery; } export interface NetworkDetailsModel { diff --git a/x-pack/legacy/plugins/siem/public/store/network/reducer.ts b/x-pack/legacy/plugins/siem/public/store/network/reducer.ts index 9bc4040eb277c0..6482182c65727a 100644 --- a/x-pack/legacy/plugins/siem/public/store/network/reducer.ts +++ b/x-pack/legacy/plugins/siem/public/store/network/reducer.ts @@ -16,7 +16,7 @@ import { TlsFields, UsersFields, } from '../../graphql/types'; -import { DEFAULT_TABLE_LIMIT } from '../constants'; +import { DEFAULT_TABLE_ACTIVE_PAGE, DEFAULT_TABLE_LIMIT } from '../constants'; import { applyNetworkFilterQuery, @@ -29,6 +29,8 @@ import { updateDomainsSort, updateIpDetailsFlowTarget, updateIsPtrIncluded, + updateIpDetailsTableActivePage, + updateNetworkPageTableActivePage, updateTopNFlowDirection, updateTopNFlowLimit, updateTopNFlowSort, @@ -38,14 +40,15 @@ import { updateUsersSort, } from './actions'; import { helperUpdateTopNFlowDirection } from './helper'; -import { NetworkModel, NetworkType } from './model'; +import { IpDetailsTableType, NetworkModel, NetworkTableType, NetworkType } from './model'; export type NetworkState = NetworkModel; export const initialNetworkState: NetworkState = { page: { queries: { - topNFlow: { + [NetworkTableType.topNFlow]: { + activePage: DEFAULT_TABLE_ACTIVE_PAGE, limit: DEFAULT_TABLE_LIMIT, topNFlowSort: { field: NetworkTopNFlowFields.bytes, @@ -54,7 +57,8 @@ export const initialNetworkState: NetworkState = { flowTarget: FlowTarget.source, flowDirection: FlowDirection.uniDirectional, }, - dns: { + [NetworkTableType.dns]: { + activePage: DEFAULT_TABLE_ACTIVE_PAGE, limit: DEFAULT_TABLE_LIMIT, dnsSortField: { field: NetworkDnsFields.uniqueDomains, @@ -68,7 +72,8 @@ export const initialNetworkState: NetworkState = { }, details: { queries: { - domains: { + [IpDetailsTableType.domains]: { + activePage: DEFAULT_TABLE_ACTIVE_PAGE, flowDirection: FlowDirection.uniDirectional, limit: DEFAULT_TABLE_LIMIT, domainsSortField: { @@ -76,14 +81,16 @@ export const initialNetworkState: NetworkState = { direction: Direction.desc, }, }, - tls: { + [IpDetailsTableType.tls]: { + activePage: DEFAULT_TABLE_ACTIVE_PAGE, limit: DEFAULT_TABLE_LIMIT, tlsSortField: { field: TlsFields._id, direction: Direction.desc, }, }, - users: { + [IpDetailsTableType.users]: { + activePage: DEFAULT_TABLE_ACTIVE_PAGE, limit: DEFAULT_TABLE_LIMIT, usersSortField: { field: UsersFields.name, @@ -98,13 +105,39 @@ export const initialNetworkState: NetworkState = { }; export const networkReducer = reducerWithInitialState(initialNetworkState) + .case(updateIpDetailsTableActivePage, (state, { activePage, tableType }) => ({ + ...state, + [NetworkType.details]: { + ...state[NetworkType.details], + queries: { + ...state[NetworkType.details].queries, + [tableType]: { + ...state[NetworkType.details].queries[tableType], + activePage, + }, + }, + }, + })) + .case(updateNetworkPageTableActivePage, (state, { activePage, tableType }) => ({ + ...state, + [NetworkType.page]: { + ...state[NetworkType.page], + queries: { + ...state[NetworkType.page].queries, + [tableType]: { + ...state[NetworkType.page].queries[tableType], + activePage, + }, + }, + }, + })) .case(updateDnsLimit, (state, { limit, networkType }) => ({ ...state, [networkType]: { ...state[networkType], queries: { ...state[networkType].queries, - dns: { + [NetworkTableType.dns]: { ...state[NetworkType.page].queries.dns, limit, }, @@ -117,7 +150,7 @@ export const networkReducer = reducerWithInitialState(initialNetworkState) ...state[networkType], queries: { ...state[networkType].queries, - dns: { + [NetworkTableType.dns]: { ...state[NetworkType.page].queries.dns, dnsSortField, }, @@ -130,7 +163,7 @@ export const networkReducer = reducerWithInitialState(initialNetworkState) ...state[networkType], queries: { ...state[networkType].queries, - dns: { + [NetworkTableType.dns]: { ...state[NetworkType.page].queries.dns, isPtrIncluded, }, @@ -143,7 +176,7 @@ export const networkReducer = reducerWithInitialState(initialNetworkState) ...state[networkType], queries: { ...state[networkType].queries, - topNFlow: { + [NetworkTableType.topNFlow]: { ...state[NetworkType.page].queries.topNFlow, limit, }, @@ -156,7 +189,7 @@ export const networkReducer = reducerWithInitialState(initialNetworkState) ...state[networkType], queries: { ...state[networkType].queries, - topNFlow: { + [NetworkTableType.topNFlow]: { ...state[NetworkType.page].queries.topNFlow, ...helperUpdateTopNFlowDirection( state[NetworkType.page].queries.topNFlow.flowTarget, @@ -172,7 +205,7 @@ export const networkReducer = reducerWithInitialState(initialNetworkState) ...state[networkType], queries: { ...state[networkType].queries, - topNFlow: { + [NetworkTableType.topNFlow]: { ...state[NetworkType.page].queries.topNFlow, topNFlowSort, }, @@ -185,7 +218,7 @@ export const networkReducer = reducerWithInitialState(initialNetworkState) ...state[NetworkType.page], queries: { ...state[NetworkType.page].queries, - topNFlow: { + [NetworkTableType.topNFlow]: { ...state[NetworkType.page].queries.topNFlow, flowTarget, topNFlowSort: { @@ -224,7 +257,7 @@ export const networkReducer = reducerWithInitialState(initialNetworkState) ...state[NetworkType.details], queries: { ...state[NetworkType.details].queries, - domains: { + [IpDetailsTableType.domains]: { ...state[NetworkType.details].queries.domains, limit, }, @@ -237,7 +270,7 @@ export const networkReducer = reducerWithInitialState(initialNetworkState) ...state[NetworkType.details], queries: { ...state[NetworkType.details].queries, - tls: { + [IpDetailsTableType.tls]: { ...state[NetworkType.details].queries.tls, limit, }, @@ -250,7 +283,7 @@ export const networkReducer = reducerWithInitialState(initialNetworkState) ...state[NetworkType.details], queries: { ...state[NetworkType.details].queries, - domains: { + [IpDetailsTableType.domains]: { ...state[NetworkType.details].queries.domains, flowDirection, }, @@ -263,7 +296,7 @@ export const networkReducer = reducerWithInitialState(initialNetworkState) ...state[NetworkType.details], queries: { ...state[NetworkType.details].queries, - domains: { + [IpDetailsTableType.domains]: { ...state[NetworkType.details].queries.domains, domainsSortField, }, @@ -276,7 +309,7 @@ export const networkReducer = reducerWithInitialState(initialNetworkState) ...state[NetworkType.details], queries: { ...state[NetworkType.details].queries, - tls: { + [IpDetailsTableType.tls]: { ...state[NetworkType.details].queries.tls, tlsSortField, }, @@ -289,7 +322,7 @@ export const networkReducer = reducerWithInitialState(initialNetworkState) ...state[NetworkType.details], queries: { ...state[NetworkType.details].queries, - users: { + [IpDetailsTableType.users]: { ...state[NetworkType.details].queries.users, limit, }, @@ -302,7 +335,7 @@ export const networkReducer = reducerWithInitialState(initialNetworkState) ...state[NetworkType.details], queries: { ...state[NetworkType.details].queries, - users: { + [IpDetailsTableType.users]: { ...state[NetworkType.details].queries.users, usersSortField, }, diff --git a/x-pack/legacy/plugins/siem/server/graphql/events/resolvers.ts b/x-pack/legacy/plugins/siem/server/graphql/events/resolvers.ts index 720bc9530be273..f2ee5e30285498 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/events/resolvers.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/events/resolvers.ts @@ -8,7 +8,7 @@ import { GraphQLScalarType, Kind } from 'graphql'; import { Events } from '../../lib/events'; import { AppResolverOf, ChildResolverOf } from '../../lib/framework'; -import { createOptions } from '../../utils/build_query/create_options'; +import { createOptions, createOptionsPaginated } from '../../utils/build_query/create_options'; import { QuerySourceResolver } from '../sources/resolvers'; import { SourceResolvers } from '../types'; import { LastEventTimeRequestOptions } from '../../lib/events/types'; @@ -49,7 +49,7 @@ export const createEventsResolvers = ( } => ({ Source: { async Events(source, args, { req }, info) { - const options = createOptions(source, args, info); + const options = createOptionsPaginated(source, args, info); return libs.events.getEvents(req, options); }, async Timeline(source, args, { req }, info) { diff --git a/x-pack/legacy/plugins/siem/server/graphql/events/schema.gql.ts b/x-pack/legacy/plugins/siem/server/graphql/events/schema.gql.ts index f934faffcf4f98..376f4b7e964267 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/events/schema.gql.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/events/schema.gql.ts @@ -10,6 +10,13 @@ export const eventsSchema = gql` scalar EsValue type EventsData { + edges: [EcsEdges!]! + totalCount: Float! + pageInfo: PageInfoPaginated! + inspect: Inspect + } + + type EventsTimelineData { edges: [EcsEdges!]! totalCount: Float! pageInfo: PageInfo! @@ -75,7 +82,7 @@ export const eventsSchema = gql` extend type Source { "Gets events based on timerange and specified criteria, or all events in the timerange if no criteria is specified" Events( - pagination: PaginationInput! + pagination: PaginationInputPaginated! sortField: SortField! timerange: TimerangeInput filterQuery: String diff --git a/x-pack/legacy/plugins/siem/server/graphql/hosts/resolvers.ts b/x-pack/legacy/plugins/siem/server/graphql/hosts/resolvers.ts index 22a744a3cf5b86..65403c4b312616 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/hosts/resolvers.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/hosts/resolvers.ts @@ -15,7 +15,7 @@ import { HostLastFirstSeenRequestOptions, } from '../../lib/hosts'; import { getFields } from '../../utils/build_query'; -import { createOptions } from '../../utils/build_query/create_options'; +import { createOptionsPaginated } from '../../utils/build_query/create_options'; import { QuerySourceResolver } from '../sources/resolvers'; type QueryHostsResolver = ChildResolverOf< @@ -49,7 +49,7 @@ export const createHostsResolvers = ( Source: { async Hosts(source, args, { req }, info) { const options: HostsRequestOptions = { - ...createOptions(source, args, info), + ...createOptionsPaginated(source, args, info), sort: args.sort, defaultIndex: args.defaultIndex, }; diff --git a/x-pack/legacy/plugins/siem/server/graphql/hosts/schema.gql.ts b/x-pack/legacy/plugins/siem/server/graphql/hosts/schema.gql.ts index db9ce410f23a09..d813a08cad6db2 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/hosts/schema.gql.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/hosts/schema.gql.ts @@ -57,7 +57,7 @@ export const hostsSchema = gql` type HostsData { edges: [HostsEdges!]! totalCount: Float! - pageInfo: PageInfo! + pageInfo: PageInfoPaginated! inspect: Inspect } @@ -82,7 +82,7 @@ export const hostsSchema = gql` Hosts( id: String timerange: TimerangeInput! - pagination: PaginationInput! + pagination: PaginationInputPaginated! sort: HostsSortField! filterQuery: String defaultIndex: [String!]! diff --git a/x-pack/legacy/plugins/siem/server/graphql/ip_details/resolvers.ts b/x-pack/legacy/plugins/siem/server/graphql/ip_details/resolvers.ts index 3e962f887b9627..102cc4d0ae08c8 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/ip_details/resolvers.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/ip_details/resolvers.ts @@ -12,7 +12,7 @@ import { TlsRequestOptions, UsersRequestOptions, } from '../../lib/ip_details'; -import { createOptions } from '../../utils/build_query/create_options'; +import { createOptions, createOptionsPaginated } from '../../utils/build_query/create_options'; import { QuerySourceResolver } from '../sources/resolvers'; export type QueryIpOverviewResolver = ChildResolverOf< @@ -56,7 +56,7 @@ export const createIpDetailsResolvers = ( }, async Domains(source, args, { req }, info) { const options: DomainsRequestOptions = { - ...createOptions(source, args, info), + ...createOptionsPaginated(source, args, info), ip: args.ip, domainsSortField: args.sort, flowTarget: args.flowTarget, @@ -66,7 +66,7 @@ export const createIpDetailsResolvers = ( }, async Tls(source, args, { req }, info) { const options: TlsRequestOptions = { - ...createOptions(source, args, info), + ...createOptionsPaginated(source, args, info), ip: args.ip, tlsSortField: args.sort, flowTarget: args.flowTarget, @@ -75,7 +75,7 @@ export const createIpDetailsResolvers = ( }, async Users(source, args, { req }, info) { const options: UsersRequestOptions = { - ...createOptions(source, args, info), + ...createOptionsPaginated(source, args, info), ip: args.ip, usersSortField: args.sort, flowTarget: args.flowTarget, diff --git a/x-pack/legacy/plugins/siem/server/graphql/ip_details/schema.gql.ts b/x-pack/legacy/plugins/siem/server/graphql/ip_details/schema.gql.ts index ee382eba345ec5..21cd330c6f4b72 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/ip_details/schema.gql.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/ip_details/schema.gql.ts @@ -85,7 +85,7 @@ const domainsSchema = gql` type DomainsData { edges: [DomainsEdges!]! totalCount: Float! - pageInfo: PageInfo! + pageInfo: PageInfoPaginated! inspect: Inspect } @@ -94,7 +94,7 @@ const domainsSchema = gql` filterQuery: String id: String ip: String! - pagination: PaginationInput! + pagination: PaginationInputPaginated! sort: DomainsSortField! flowDirection: FlowDirection! flowTarget: FlowTarget! @@ -128,7 +128,7 @@ const tlsSchema = gql` type TlsData { edges: [TlsEdges!]! totalCount: Float! - pageInfo: PageInfo! + pageInfo: PageInfoPaginated! inspect: Inspect } extend type Source { @@ -136,7 +136,7 @@ const tlsSchema = gql` filterQuery: String id: String ip: String! - pagination: PaginationInput! + pagination: PaginationInputPaginated! sort: TlsSortField! flowTarget: FlowTarget! timerange: TimerangeInput! @@ -178,7 +178,7 @@ const usersSchema = gql` type UsersData { edges: [UsersEdges!]! totalCount: Float! - pageInfo: PageInfo! + pageInfo: PageInfoPaginated! inspect: Inspect } @@ -187,7 +187,7 @@ const usersSchema = gql` filterQuery: String id: String ip: String! - pagination: PaginationInput! + pagination: PaginationInputPaginated! sort: UsersSortField! flowTarget: FlowTarget! timerange: TimerangeInput! diff --git a/x-pack/legacy/plugins/siem/server/graphql/network/resolvers.ts b/x-pack/legacy/plugins/siem/server/graphql/network/resolvers.ts index eb3b7473fbde33..6d29283b5fe1f3 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/network/resolvers.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/network/resolvers.ts @@ -7,7 +7,7 @@ import { SourceResolvers } from '../../graphql/types'; import { AppResolverOf, ChildResolverOf } from '../../lib/framework'; import { Network } from '../../lib/network'; -import { createOptions } from '../../utils/build_query/create_options'; +import { createOptionsPaginated } from '../../utils/build_query/create_options'; import { QuerySourceResolver } from '../sources/resolvers'; type QueryNetworkTopNFlowResolver = ChildResolverOf< @@ -35,7 +35,7 @@ export const createNetworkResolvers = ( Source: { async NetworkTopNFlow(source, args, { req }, info) { const options = { - ...createOptions(source, args, info), + ...createOptionsPaginated(source, args, info), flowTarget: args.flowTarget, networkTopNFlowSort: args.sort, flowDirection: args.flowDirection, @@ -44,7 +44,7 @@ export const createNetworkResolvers = ( }, async NetworkDns(source, args, { req }, info) { const options = { - ...createOptions(source, args, info), + ...createOptionsPaginated(source, args, info), networkDnsSortField: args.sort, isPtrIncluded: args.isPtrIncluded, }; diff --git a/x-pack/legacy/plugins/siem/server/graphql/network/schema.gql.ts b/x-pack/legacy/plugins/siem/server/graphql/network/schema.gql.ts index 70f7380c97f911..3d5f74e9ca46be 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/network/schema.gql.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/network/schema.gql.ts @@ -59,7 +59,7 @@ export const networkSchema = gql` type NetworkTopNFlowData { edges: [NetworkTopNFlowEdges!]! totalCount: Float! - pageInfo: PageInfo! + pageInfo: PageInfoPaginated! inspect: Inspect } @@ -93,7 +93,7 @@ export const networkSchema = gql` type NetworkDnsData { edges: [NetworkDnsEdges!]! totalCount: Float! - pageInfo: PageInfo! + pageInfo: PageInfoPaginated! inspect: Inspect } @@ -104,7 +104,7 @@ export const networkSchema = gql` filterQuery: String flowDirection: FlowDirection! flowTarget: FlowTarget! - pagination: PaginationInput! + pagination: PaginationInputPaginated! sort: NetworkTopNFlowSortField! timerange: TimerangeInput! defaultIndex: [String!]! @@ -113,7 +113,7 @@ export const networkSchema = gql` filterQuery: String id: String isPtrIncluded: Boolean! - pagination: PaginationInput! + pagination: PaginationInputPaginated! sort: NetworkDnsSortField! timerange: TimerangeInput! defaultIndex: [String!]! diff --git a/x-pack/legacy/plugins/siem/server/graphql/types.ts b/x-pack/legacy/plugins/siem/server/graphql/types.ts index 1fd83d65b2621c..6e14ba97f1c8ac 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/types.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/types.ts @@ -367,7 +367,7 @@ export interface EventsData { totalCount: number; - pageInfo: PageInfo; + pageInfo: PageInfoPaginated; inspect?: Inspect | null; } @@ -830,12 +830,6 @@ export interface SshEcsFields { signature?: ToStringArray | null; } -export interface PageInfo { - endCursor?: CursorType | null; - - hasNextPage?: boolean | null; -} - export interface TimelineData { edges: TimelineEdges[]; @@ -868,6 +862,12 @@ export interface TimelineNonEcsData { value?: ToStringArray | null; } +export interface PageInfo { + endCursor?: CursorType | null; + + hasNextPage?: boolean | null; +} + export interface TimelineDetailsData { data?: DetailItem[] | null; @@ -901,7 +901,7 @@ export interface HostsData { totalCount: number; - pageInfo: PageInfo; + pageInfo: PageInfoPaginated; inspect?: Inspect | null; } @@ -987,7 +987,7 @@ export interface DomainsData { totalCount: number; - pageInfo: PageInfo; + pageInfo: PageInfoPaginated; inspect?: Inspect | null; } @@ -1039,7 +1039,7 @@ export interface TlsData { totalCount: number; - pageInfo: PageInfo; + pageInfo: PageInfoPaginated; inspect?: Inspect | null; } @@ -1071,7 +1071,7 @@ export interface UsersData { totalCount: number; - pageInfo: PageInfo; + pageInfo: PageInfoPaginated; inspect?: Inspect | null; } @@ -1183,7 +1183,7 @@ export interface NetworkTopNFlowData { totalCount: number; - pageInfo: PageInfo; + pageInfo: PageInfoPaginated; inspect?: Inspect | null; } @@ -1231,7 +1231,7 @@ export interface NetworkDnsData { totalCount: number; - pageInfo: PageInfo; + pageInfo: PageInfoPaginated; inspect?: Inspect | null; } @@ -1303,7 +1303,7 @@ export interface UncommonProcessesData { totalCount: number; - pageInfo: PageInfo; + pageInfo: PageInfoPaginated; inspect?: Inspect | null; } @@ -1512,6 +1512,16 @@ export interface ResponseFavoriteTimeline { favorite?: FavoriteTimelineResult[] | null; } +export interface EventsTimelineData { + edges: EcsEdges[]; + + totalCount: number; + + pageInfo: PageInfo; + + inspect?: Inspect | null; +} + export interface OsFields { platform?: string | null; @@ -1578,6 +1588,12 @@ export interface PaginationInputPaginated { querySize: number; } +export interface SortField { + sortFieldId: string; + + direction: Direction; +} + export interface PaginationInput { /** The limit parameter allows you to configure the maximum amount of items to be returned */ limit: number; @@ -1587,12 +1603,6 @@ export interface PaginationInput { tiebreaker?: string | null; } -export interface SortField { - sortFieldId: string; - - direction: Direction; -} - export interface LastTimeDetails { hostName?: string | null; @@ -1810,7 +1820,7 @@ export interface AuthenticationsSourceArgs { defaultIndex: string[]; } export interface EventsSourceArgs { - pagination: PaginationInput; + pagination: PaginationInputPaginated; sortField: SortField; @@ -1854,7 +1864,7 @@ export interface HostsSourceArgs { timerange: TimerangeInput; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sort: HostsSortField; @@ -1894,7 +1904,7 @@ export interface DomainsSourceArgs { ip: string; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sort: DomainsSortField; @@ -1913,7 +1923,7 @@ export interface TlsSourceArgs { ip: string; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sort: TlsSortField; @@ -1930,7 +1940,7 @@ export interface UsersSourceArgs { ip: string; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sort: UsersSortField; @@ -1976,7 +1986,7 @@ export interface NetworkTopNFlowSourceArgs { flowTarget: FlowTarget; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sort: NetworkTopNFlowSortField; @@ -1991,7 +2001,7 @@ export interface NetworkDnsSourceArgs { isPtrIncluded: boolean; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sort: NetworkDnsSortField; @@ -2020,7 +2030,7 @@ export interface OverviewHostSourceArgs { export interface UncommonProcessesSourceArgs { timerange: TimerangeInput; - pagination: PaginationInput; + pagination: PaginationInputPaginated; filterQuery?: string | null; @@ -2536,7 +2546,7 @@ export namespace SourceResolvers { EventsArgs >; export interface EventsArgs { - pagination: PaginationInput; + pagination: PaginationInputPaginated; sortField: SortField; @@ -2606,7 +2616,7 @@ export namespace SourceResolvers { timerange: TimerangeInput; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sort: HostsSortField; @@ -2672,7 +2682,7 @@ export namespace SourceResolvers { ip: string; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sort: DomainsSortField; @@ -2698,7 +2708,7 @@ export namespace SourceResolvers { ip: string; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sort: TlsSortField; @@ -2722,7 +2732,7 @@ export namespace SourceResolvers { ip: string; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sort: UsersSortField; @@ -2793,7 +2803,7 @@ export namespace SourceResolvers { flowTarget: FlowTarget; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sort: NetworkTopNFlowSortField; @@ -2814,7 +2824,7 @@ export namespace SourceResolvers { isPtrIncluded: boolean; - pagination: PaginationInput; + pagination: PaginationInputPaginated; sort: NetworkDnsSortField; @@ -2861,7 +2871,7 @@ export namespace SourceResolvers { export interface UncommonProcessesArgs { timerange: TimerangeInput; - pagination: PaginationInput; + pagination: PaginationInputPaginated; filterQuery?: string | null; @@ -3488,7 +3498,7 @@ export namespace EventsDataResolvers { totalCount?: TotalCountResolver; - pageInfo?: PageInfoResolver; + pageInfo?: PageInfoResolver; inspect?: InspectResolver; } @@ -3503,11 +3513,11 @@ export namespace EventsDataResolvers { Parent, Context >; - export type PageInfoResolver = Resolver< - R, - Parent, - Context - >; + export type PageInfoResolver< + R = PageInfoPaginated, + Parent = EventsData, + Context = SiemContext + > = Resolver; export type InspectResolver< R = Inspect | null, Parent = EventsData, @@ -5050,25 +5060,6 @@ export namespace SshEcsFieldsResolvers { > = Resolver; } -export namespace PageInfoResolvers { - export interface Resolvers { - endCursor?: EndCursorResolver; - - hasNextPage?: HasNextPageResolver; - } - - export type EndCursorResolver< - R = CursorType | null, - Parent = PageInfo, - Context = SiemContext - > = Resolver; - export type HasNextPageResolver< - R = boolean | null, - Parent = PageInfo, - Context = SiemContext - > = Resolver; -} - export namespace TimelineDataResolvers { export interface Resolvers { edges?: EdgesResolver; @@ -5173,6 +5164,25 @@ export namespace TimelineNonEcsDataResolvers { > = Resolver; } +export namespace PageInfoResolvers { + export interface Resolvers { + endCursor?: EndCursorResolver; + + hasNextPage?: HasNextPageResolver; + } + + export type EndCursorResolver< + R = CursorType | null, + Parent = PageInfo, + Context = SiemContext + > = Resolver; + export type HasNextPageResolver< + R = boolean | null, + Parent = PageInfo, + Context = SiemContext + > = Resolver; +} + export namespace TimelineDetailsDataResolvers { export interface Resolvers { data?: DataResolver; @@ -5271,7 +5281,7 @@ export namespace HostsDataResolvers { totalCount?: TotalCountResolver; - pageInfo?: PageInfoResolver; + pageInfo?: PageInfoResolver; inspect?: InspectResolver; } @@ -5286,11 +5296,11 @@ export namespace HostsDataResolvers { Parent, Context >; - export type PageInfoResolver = Resolver< - R, - Parent, - Context - >; + export type PageInfoResolver< + R = PageInfoPaginated, + Parent = HostsData, + Context = SiemContext + > = Resolver; export type InspectResolver< R = Inspect | null, Parent = HostsData, @@ -5552,7 +5562,7 @@ export namespace DomainsDataResolvers { totalCount?: TotalCountResolver; - pageInfo?: PageInfoResolver; + pageInfo?: PageInfoResolver; inspect?: InspectResolver; } @@ -5568,7 +5578,7 @@ export namespace DomainsDataResolvers { Context = SiemContext > = Resolver; export type PageInfoResolver< - R = PageInfo, + R = PageInfoPaginated, Parent = DomainsData, Context = SiemContext > = Resolver; @@ -5724,7 +5734,7 @@ export namespace TlsDataResolvers { totalCount?: TotalCountResolver; - pageInfo?: PageInfoResolver; + pageInfo?: PageInfoResolver; inspect?: InspectResolver; } @@ -5739,11 +5749,11 @@ export namespace TlsDataResolvers { Parent, Context >; - export type PageInfoResolver = Resolver< - R, - Parent, - Context - >; + export type PageInfoResolver< + R = PageInfoPaginated, + Parent = TlsData, + Context = SiemContext + > = Resolver; export type InspectResolver< R = Inspect | null, Parent = TlsData, @@ -5830,7 +5840,7 @@ export namespace UsersDataResolvers { totalCount?: TotalCountResolver; - pageInfo?: PageInfoResolver; + pageInfo?: PageInfoResolver; inspect?: InspectResolver; } @@ -5845,11 +5855,11 @@ export namespace UsersDataResolvers { Parent, Context >; - export type PageInfoResolver = Resolver< - R, - Parent, - Context - >; + export type PageInfoResolver< + R = PageInfoPaginated, + Parent = UsersData, + Context = SiemContext + > = Resolver; export type InspectResolver< R = Inspect | null, Parent = UsersData, @@ -6248,7 +6258,7 @@ export namespace NetworkTopNFlowDataResolvers { totalCount?: TotalCountResolver; - pageInfo?: PageInfoResolver; + pageInfo?: PageInfoResolver; inspect?: InspectResolver; } @@ -6264,7 +6274,7 @@ export namespace NetworkTopNFlowDataResolvers { Context = SiemContext > = Resolver; export type PageInfoResolver< - R = PageInfo, + R = PageInfoPaginated, Parent = NetworkTopNFlowData, Context = SiemContext > = Resolver; @@ -6406,7 +6416,7 @@ export namespace NetworkDnsDataResolvers { totalCount?: TotalCountResolver; - pageInfo?: PageInfoResolver; + pageInfo?: PageInfoResolver; inspect?: InspectResolver; } @@ -6422,7 +6432,7 @@ export namespace NetworkDnsDataResolvers { Context = SiemContext > = Resolver; export type PageInfoResolver< - R = PageInfo, + R = PageInfoPaginated, Parent = NetworkDnsData, Context = SiemContext > = Resolver; @@ -6648,7 +6658,7 @@ export namespace UncommonProcessesDataResolvers { totalCount?: TotalCountResolver; - pageInfo?: PageInfoResolver; + pageInfo?: PageInfoResolver; inspect?: InspectResolver; } @@ -6664,7 +6674,7 @@ export namespace UncommonProcessesDataResolvers { Context = SiemContext > = Resolver; export type PageInfoResolver< - R = PageInfo, + R = PageInfoPaginated, Parent = UncommonProcessesData, Context = SiemContext > = Resolver; @@ -7410,6 +7420,39 @@ export namespace ResponseFavoriteTimelineResolvers { > = Resolver; } +export namespace EventsTimelineDataResolvers { + export interface Resolvers { + edges?: EdgesResolver; + + totalCount?: TotalCountResolver; + + pageInfo?: PageInfoResolver; + + inspect?: InspectResolver; + } + + export type EdgesResolver< + R = EcsEdges[], + Parent = EventsTimelineData, + Context = SiemContext + > = Resolver; + export type TotalCountResolver< + R = number, + Parent = EventsTimelineData, + Context = SiemContext + > = Resolver; + export type PageInfoResolver< + R = PageInfo, + Parent = EventsTimelineData, + Context = SiemContext + > = Resolver; + export type InspectResolver< + R = Inspect | null, + Parent = EventsTimelineData, + Context = SiemContext + > = Resolver; +} + export namespace OsFieldsResolvers { export interface Resolvers { platform?: PlatformResolver; diff --git a/x-pack/legacy/plugins/siem/server/graphql/uncommon_processes/resolvers.ts b/x-pack/legacy/plugins/siem/server/graphql/uncommon_processes/resolvers.ts index 9319d620ccf85d..03d3c3d1a1fe4f 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/uncommon_processes/resolvers.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/uncommon_processes/resolvers.ts @@ -7,7 +7,7 @@ import { SourceResolvers } from '../../graphql/types'; import { AppResolverOf, ChildResolverOf } from '../../lib/framework'; import { UncommonProcesses } from '../../lib/uncommon_processes'; -import { createOptions } from '../../utils/build_query/create_options'; +import { createOptionsPaginated } from '../../utils/build_query/create_options'; import { QuerySourceResolver } from '../sources/resolvers'; type QueryUncommonProcessesResolver = ChildResolverOf< @@ -28,7 +28,7 @@ export const createUncommonProcessesResolvers = ( } => ({ Source: { async UncommonProcesses(source, args, { req }, info) { - const options = createOptions(source, args, info); + const options = createOptionsPaginated(source, args, info); return libs.uncommonProcesses.getUncommonProcesses(req, options); }, }, diff --git a/x-pack/legacy/plugins/siem/server/graphql/uncommon_processes/schema.gql.ts b/x-pack/legacy/plugins/siem/server/graphql/uncommon_processes/schema.gql.ts index 2a6a0b342251f4..36a3da6779172b 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/uncommon_processes/schema.gql.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/uncommon_processes/schema.gql.ts @@ -23,7 +23,7 @@ export const uncommonProcessesSchema = gql` type UncommonProcessesData { edges: [UncommonProcessesEdges!]! totalCount: Float! - pageInfo: PageInfo! + pageInfo: PageInfoPaginated! inspect: Inspect } @@ -31,7 +31,7 @@ export const uncommonProcessesSchema = gql` "Gets UncommonProcesses based on a timerange, or all UncommonProcesses if no criteria is specified" UncommonProcesses( timerange: TimerangeInput! - pagination: PaginationInput! + pagination: PaginationInputPaginated! filterQuery: String defaultIndex: [String!]! ): UncommonProcessesData! diff --git a/x-pack/legacy/plugins/siem/server/lib/events/elasticsearch_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/events/elasticsearch_adapter.ts index 4a6bd18afa1eec..e8d3c36cb5a5dc 100644 --- a/x-pack/legacy/plugins/siem/server/lib/events/elasticsearch_adapter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/events/elasticsearch_adapter.ts @@ -36,25 +36,32 @@ import { FrameworkAdapter, FrameworkRequest, MappingProperties, - RequestOptions, + RequestOptionsPaginated, } from '../framework'; import { TermAggregation } from '../types'; -import { buildDetailsQuery, buildQuery } from './query.dsl'; +import { buildDetailsQuery, buildQuery, buildTimelineQuery } from './query.dsl'; import { buildLastEventTimeQuery } from './query.last_event_time.dsl'; import { EventHit, EventsAdapter, - EventsRequestOptions, LastEventTimeHit, LastEventTimeRequestOptions, RequestDetailsOptions, + TimelineRequestOptions, } from './types'; +import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../common/constants'; export class ElasticsearchEventsAdapter implements EventsAdapter { constructor(private readonly framework: FrameworkAdapter) {} - public async getEvents(request: FrameworkRequest, options: RequestOptions): Promise { + public async getEvents( + request: FrameworkRequest, + options: RequestOptionsPaginated + ): Promise { + if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { + throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); + } const queryOptions = cloneDeep(options); queryOptions.fields = reduceFields(options.fields, eventFieldsMap); @@ -65,31 +72,34 @@ export class ElasticsearchEventsAdapter implements EventsAdapter { dsl ); - const { limit } = options.pagination; + const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; const totalCount = getOr(0, 'hits.total.value', response); const hits = response.hits.hits; const eventsEdges: EcsEdges[] = hits.map(hit => formatEventsData(options.fields, hit, eventFieldsMap) ); - const hasNextPage = eventsEdges.length === limit + 1; - const edges = hasNextPage ? eventsEdges.splice(0, limit) : eventsEdges; - const lastCursor = get('cursor', last(edges)); + const fakeTotalCount = fakePossibleCount <= totalCount ? fakePossibleCount : totalCount; + const edges = eventsEdges.splice(cursorStart, querySize - cursorStart); const inspect = { dsl: [inspectStringifyObject(dsl)], response: [inspectStringifyObject(response)], }; - + const showMorePagesIndicator = totalCount > fakeTotalCount; return { inspect, edges, - pageInfo: { hasNextPage, endCursor: lastCursor }, + pageInfo: { + activePage: activePage ? activePage : 0, + fakeTotalCount, + showMorePagesIndicator, + }, totalCount, }; } public async getTimelineData( request: FrameworkRequest, - options: EventsRequestOptions + options: TimelineRequestOptions ): Promise { const queryOptions = cloneDeep(options); queryOptions.fields = uniq([ @@ -98,7 +108,7 @@ export class ElasticsearchEventsAdapter implements EventsAdapter { ]); delete queryOptions.fieldRequested; - const dsl = buildQuery(queryOptions); + const dsl = buildTimelineQuery(queryOptions); const response = await this.framework.callWithRequest( request, 'search', diff --git a/x-pack/legacy/plugins/siem/server/lib/events/index.ts b/x-pack/legacy/plugins/siem/server/lib/events/index.ts index c4fb63029bf498..c51b1ff203321e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/events/index.ts +++ b/x-pack/legacy/plugins/siem/server/lib/events/index.ts @@ -10,11 +10,11 @@ import { TimelineData, TimelineDetailsData, } from '../../graphql/types'; -import { FrameworkRequest, RequestOptions } from '../framework'; +import { FrameworkRequest, RequestOptionsPaginated } from '../framework'; export * from './elasticsearch_adapter'; import { EventsAdapter, - EventsRequestOptions, + TimelineRequestOptions, LastEventTimeRequestOptions, RequestDetailsOptions, } from './types'; @@ -22,13 +22,16 @@ import { export class Events { constructor(private readonly adapter: EventsAdapter) {} - public async getEvents(req: FrameworkRequest, options: RequestOptions): Promise { + public async getEvents( + req: FrameworkRequest, + options: RequestOptionsPaginated + ): Promise { return await this.adapter.getEvents(req, options); } public async getTimelineData( req: FrameworkRequest, - options: EventsRequestOptions + options: TimelineRequestOptions ): Promise { return await this.adapter.getTimelineData(req, options); } diff --git a/x-pack/legacy/plugins/siem/server/lib/events/query.dsl.ts b/x-pack/legacy/plugins/siem/server/lib/events/query.dsl.ts index 6755069d45be16..bc95fe56294495 100644 --- a/x-pack/legacy/plugins/siem/server/lib/events/query.dsl.ts +++ b/x-pack/legacy/plugins/siem/server/lib/events/query.dsl.ts @@ -6,12 +6,72 @@ import { SortField, TimerangeInput } from '../../graphql/types'; import { createQueryFilterClauses } from '../../utils/build_query'; -import { RequestOptions } from '../framework'; +import { RequestOptions, RequestOptionsPaginated } from '../framework'; import { SortRequest } from '../types'; import { TimerangeFilter } from './types'; -export const buildQuery = (options: RequestOptions) => { +export const buildQuery = (options: RequestOptionsPaginated) => { + const { querySize } = options.pagination; + const { fields, filterQuery } = options; + const filterClause = [...createQueryFilterClauses(filterQuery)]; + const defaultIndex = options.defaultIndex; + + const getTimerangeFilter = (timerange: TimerangeInput | undefined): TimerangeFilter[] => { + if (timerange) { + const { to, from } = timerange; + return [ + { + range: { + [options.sourceConfiguration.fields.timestamp]: { + gte: from, + lte: to, + }, + }, + }, + ]; + } + return []; + }; + + const filter = [...filterClause, ...getTimerangeFilter(options.timerange), { match_all: {} }]; + + const getSortField = (sortField: SortField) => { + if (sortField.sortFieldId) { + const field: string = + sortField.sortFieldId === 'timestamp' ? '@timestamp' : sortField.sortFieldId; + + return [ + { [field]: sortField.direction }, + { [options.sourceConfiguration.fields.tiebreaker]: sortField.direction }, + ]; + } + return []; + }; + + const sort: SortRequest = getSortField(options.sortField!); + + const dslQuery = { + allowNoIndices: true, + index: defaultIndex, + ignoreUnavailable: true, + body: { + query: { + bool: { + filter, + }, + }, + size: querySize, + track_total_hits: true, + sort, + _source: fields, + }, + }; + + return dslQuery; +}; + +export const buildTimelineQuery = (options: RequestOptions) => { const { limit, cursor, tiebreaker } = options.pagination; const { fields, filterQuery } = options; const filterClause = [...createQueryFilterClauses(filterQuery)]; diff --git a/x-pack/legacy/plugins/siem/server/lib/events/types.ts b/x-pack/legacy/plugins/siem/server/lib/events/types.ts index b88c26921b2f2d..bcd62c583cf6d8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/events/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/events/types.ts @@ -13,12 +13,12 @@ import { TimelineData, TimelineDetailsData, } from '../../graphql/types'; -import { FrameworkRequest, RequestOptions } from '../framework'; +import { FrameworkRequest, RequestOptions, RequestOptionsPaginated } from '../framework'; import { SearchHit } from '../types'; export interface EventsAdapter { - getEvents(req: FrameworkRequest, options: RequestOptions): Promise; - getTimelineData(req: FrameworkRequest, options: EventsRequestOptions): Promise; + getEvents(req: FrameworkRequest, options: RequestOptionsPaginated): Promise; + getTimelineData(req: FrameworkRequest, options: TimelineRequestOptions): Promise; getTimelineDetails( req: FrameworkRequest, options: RequestDetailsOptions @@ -29,7 +29,11 @@ export interface EventsAdapter { ): Promise; } -export interface EventsRequestOptions extends RequestOptions { +export interface TimelineRequestOptions extends RequestOptions { + fieldRequested: string[]; +} + +export interface EventsRequestOptions extends RequestOptionsPaginated { fieldRequested: string[]; } diff --git a/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.ts index e18fe7452ea84b..792a1a4be53074 100644 --- a/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.ts @@ -26,6 +26,7 @@ import { HostsRequestOptions, HostValue, } from './types'; +import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../common/constants'; export class ElasticsearchHostsAdapter implements HostsAdapter { constructor(private readonly framework: FrameworkAdapter) {} @@ -35,28 +36,35 @@ export class ElasticsearchHostsAdapter implements HostsAdapter { options: HostsRequestOptions ): Promise { const dsl = buildHostsQuery(options); + if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { + throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); + } const response = await this.framework.callWithRequest( request, 'search', dsl ); - const { cursor, limit } = options.pagination; + const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; const totalCount = getOr(0, 'aggregations.host_count.value', response); const buckets: HostAggEsItem[] = getOr([], 'aggregations.host_data.buckets', response); const hostsEdges = buckets.map(bucket => formatHostEdgesData(options.fields, bucket)); - const hasNextPage = hostsEdges.length === limit + 1; - const beginning = cursor != null ? parseInt(cursor, 10) : 0; - const edges = hostsEdges.splice(beginning, limit - beginning); + const fakeTotalCount = fakePossibleCount <= totalCount ? fakePossibleCount : totalCount; + const edges = hostsEdges.splice(cursorStart, querySize - cursorStart); const inspect = { dsl: [inspectStringifyObject(dsl)], response: [inspectStringifyObject(response)], }; + const showMorePagesIndicator = totalCount > fakeTotalCount; return { inspect, edges, totalCount, - pageInfo: { hasNextPage, endCursor: { value: String(limit) } }, + pageInfo: { + activePage: activePage ? activePage : 0, + fakeTotalCount, + showMorePagesIndicator, + }, }; } diff --git a/x-pack/legacy/plugins/siem/server/lib/hosts/mock.ts b/x-pack/legacy/plugins/siem/server/lib/hosts/mock.ts index 59c03560ac8103..e8c57be8e2ca51 100644 --- a/x-pack/legacy/plugins/siem/server/lib/hosts/mock.ts +++ b/x-pack/legacy/plugins/siem/server/lib/hosts/mock.ts @@ -26,7 +26,12 @@ export const mockGetHostsOptions: HostsRequestOptions = { }, timerange: { interval: '12h', to: 1554824274610, from: 1554737874610 }, sort: { field: HostsFields.lastSeen, direction: Direction.asc }, - pagination: { limit: 10, cursor: null, tiebreaker: null }, + pagination: { + activePage: 0, + cursorStart: 0, + fakePossibleCount: 10, + querySize: 2, + }, filterQuery: {}, fields: [ 'totalCount', @@ -36,8 +41,9 @@ export const mockGetHostsOptions: HostsRequestOptions = { 'host.os.name', 'host.os.version', 'edges.cursor.value', - 'pageInfo.endCursor.value', - 'pageInfo.hasNextPage', + 'pageInfo.activePage', + 'pageInfo.fakeTotalCount', + 'pageInfo.showMorePagesIndicator', ], }; @@ -48,7 +54,12 @@ export const mockGetHostsRequest = { variables: { sourceId: 'default', timerange: { interval: '12h', from: 1554737729201, to: 1554824129202 }, - pagination: { limit: 10, cursor: null, tiebreaker: null }, + pagination: { + activePage: 0, + cursorStart: 0, + fakePossibleCount: 10, + querySize: 2, + }, sort: { field: HostsFields.lastSeen, direction: Direction.asc }, filterQuery: '', }, @@ -269,10 +280,9 @@ export const mockGetHostsResult = { ], totalCount: 1627, pageInfo: { - hasNextPage: false, - endCursor: { - value: '10', - }, + activePage: 0, + fakeTotalCount: 10, + showMorePagesIndicator: true, }, }; diff --git a/x-pack/legacy/plugins/siem/server/lib/hosts/query.hosts.dsl.ts b/x-pack/legacy/plugins/siem/server/lib/hosts/query.hosts.dsl.ts index 35ed71b9d27b34..7c2e0df535344b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/hosts/query.hosts.dsl.ts +++ b/x-pack/legacy/plugins/siem/server/lib/hosts/query.hosts.dsl.ts @@ -13,11 +13,11 @@ import { HostsRequestOptions } from '.'; import { buildFieldsTermAggregation } from './helpers'; export const buildHostsQuery = ({ + defaultIndex, fields, filterQuery, - pagination: { limit, cursor }, + pagination: { querySize }, sort, - defaultIndex, sourceConfiguration: { fields: { timestamp }, }, @@ -47,7 +47,7 @@ export const buildHostsQuery = ({ aggregations: { ...agg, host_data: { - terms: { size: limit + 1, field: 'host.name', order: getQueryOrder(sort) }, + terms: { size: querySize, field: 'host.name', order: getQueryOrder(sort) }, aggs: { lastSeen: { max: { field: '@timestamp' } }, ...buildFieldsTermAggregation( diff --git a/x-pack/legacy/plugins/siem/server/lib/hosts/types.ts b/x-pack/legacy/plugins/siem/server/lib/hosts/types.ts index b63f4db2926e85..63360492c1cf32 100644 --- a/x-pack/legacy/plugins/siem/server/lib/hosts/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/hosts/types.ts @@ -13,7 +13,7 @@ import { SourceConfiguration, TimerangeInput, } from '../../graphql/types'; -import { FrameworkRequest, RequestOptions } from '../framework'; +import { FrameworkRequest, RequestOptionsPaginated } from '../framework'; import { Hit, Hits, SearchHit } from '../types'; export interface HostsAdapter { @@ -39,7 +39,7 @@ export interface HostHit extends Hit { export type HostHits = Hits; -export interface HostsRequestOptions extends RequestOptions { +export interface HostsRequestOptions extends RequestOptionsPaginated { sort: HostsSortField; defaultIndex: string[]; } diff --git a/x-pack/legacy/plugins/siem/server/lib/ip_details/elasticsearch_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/ip_details/elasticsearch_adapter.ts index c59145f5e25a26..6957b548dcb86f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/ip_details/elasticsearch_adapter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/ip_details/elasticsearch_adapter.ts @@ -22,7 +22,7 @@ import { import { inspectStringifyObject } from '../../utils/build_query'; import { DatabaseSearchResponse, FrameworkAdapter, FrameworkRequest } from '../framework'; import { TermAggregation } from '../types'; - +import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../common/constants'; import { DomainsRequestOptions, IpOverviewRequestOptions, @@ -76,6 +76,9 @@ export class ElasticsearchIpOverviewAdapter implements IpDetailsAdapter { request: FrameworkRequest, options: DomainsRequestOptions ): Promise { + if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { + throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); + } const dsl = buildDomainsQuery(options); const response = await this.framework.callWithRequest( request, @@ -83,32 +86,32 @@ export class ElasticsearchIpOverviewAdapter implements IpDetailsAdapter { dsl ); - const { cursor, limit } = options.pagination; + const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; const totalCount = getOr(0, 'aggregations.domain_count.value', response); const domainsEdges: DomainsEdges[] = getDomainsEdges(response, options); - const hasNextPage = domainsEdges.length > limit; - const beginning = cursor != null ? parseInt(cursor, 10) : 0; - const edges = domainsEdges.splice(beginning, limit - beginning); + const fakeTotalCount = fakePossibleCount <= totalCount ? fakePossibleCount : totalCount; + const edges = domainsEdges.splice(cursorStart, querySize - cursorStart); const inspect = { dsl: [inspectStringifyObject(dsl)], response: [inspectStringifyObject(response)], }; - + const showMorePagesIndicator = totalCount > fakeTotalCount; return { edges, inspect, pageInfo: { - hasNextPage, - endCursor: { - value: String(limit), - tiebreaker: null, - }, + activePage: activePage ? activePage : 0, + fakeTotalCount, + showMorePagesIndicator, }, totalCount, }; } public async getTls(request: FrameworkRequest, options: TlsRequestOptions): Promise { + if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { + throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); + } const dsl = buildTlsQuery(options); const response = await this.framework.callWithRequest( request, @@ -116,26 +119,23 @@ export class ElasticsearchIpOverviewAdapter implements IpDetailsAdapter { dsl ); - const { cursor, limit } = options.pagination; + const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; const totalCount = getOr(0, 'aggregations.count.value', response); const tlsEdges: TlsEdges[] = getTlsEdges(response, options); - const hasNextPage = tlsEdges.length > limit; - const beginning = cursor != null ? parseInt(cursor, 10) : 0; - const edges = tlsEdges.splice(beginning, limit - beginning); + const fakeTotalCount = fakePossibleCount <= totalCount ? fakePossibleCount : totalCount; + const edges = tlsEdges.splice(cursorStart, querySize - cursorStart); const inspect = { dsl: [inspectStringifyObject(dsl)], response: [inspectStringifyObject(response)], }; - + const showMorePagesIndicator = totalCount > fakeTotalCount; return { edges, inspect, pageInfo: { - hasNextPage, - endCursor: { - value: String(limit), - tiebreaker: null, - }, + activePage: activePage ? activePage : 0, + fakeTotalCount, + showMorePagesIndicator, }, totalCount, }; @@ -145,6 +145,9 @@ export class ElasticsearchIpOverviewAdapter implements IpDetailsAdapter { request: FrameworkRequest, options: UsersRequestOptions ): Promise { + if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { + throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); + } const dsl = buildUsersQuery(options); const response = await this.framework.callWithRequest( request, @@ -152,26 +155,23 @@ export class ElasticsearchIpOverviewAdapter implements IpDetailsAdapter { dsl ); - const { cursor, limit } = options.pagination; + const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; const totalCount = getOr(0, 'aggregations.user_count.value', response); const usersEdges = getUsersEdges(response); - const hasNextPage = usersEdges.length > limit; - const beginning = cursor != null ? parseInt(cursor, 10) : 0; - const edges = usersEdges.splice(beginning, limit - beginning); + const fakeTotalCount = fakePossibleCount <= totalCount ? fakePossibleCount : totalCount; + const edges = usersEdges.splice(cursorStart, querySize - cursorStart); const inspect = { dsl: [inspectStringifyObject(dsl)], response: [inspectStringifyObject(response)], }; - + const showMorePagesIndicator = totalCount > fakeTotalCount; return { edges, inspect, pageInfo: { - endCursor: { - value: String(limit), - tiebreaker: null, - }, - hasNextPage, + activePage: activePage ? activePage : 0, + fakeTotalCount, + showMorePagesIndicator, }, totalCount, }; diff --git a/x-pack/legacy/plugins/siem/server/lib/ip_details/index.ts b/x-pack/legacy/plugins/siem/server/lib/ip_details/index.ts index 83a58dc5d4e548..d1a70df4ffcf6a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/ip_details/index.ts +++ b/x-pack/legacy/plugins/siem/server/lib/ip_details/index.ts @@ -15,7 +15,7 @@ import { UsersData, UsersSortField, } from '../../graphql/types'; -import { FrameworkRequest, RequestOptions } from '../framework'; +import { FrameworkRequest, RequestOptions, RequestOptionsPaginated } from '../framework'; import { IpDetailsAdapter } from './types'; @@ -25,19 +25,19 @@ export interface IpOverviewRequestOptions extends RequestOptions { ip: string; } -export interface DomainsRequestOptions extends RequestOptions { +export interface DomainsRequestOptions extends RequestOptionsPaginated { ip: string; domainsSortField: DomainsSortField; flowTarget: FlowTarget; flowDirection: FlowDirection; } -export interface TlsRequestOptions extends RequestOptions { +export interface TlsRequestOptions extends RequestOptionsPaginated { ip: string; tlsSortField: TlsSortField; flowTarget: FlowTarget; } -export interface UsersRequestOptions extends RequestOptions { +export interface UsersRequestOptions extends RequestOptionsPaginated { ip: string; usersSortField: UsersSortField; flowTarget: FlowTarget; diff --git a/x-pack/legacy/plugins/siem/server/lib/ip_details/query_domains.dsl.ts b/x-pack/legacy/plugins/siem/server/lib/ip_details/query_domains.dsl.ts index fb6f02bb866e57..e81a337458203a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/ip_details/query_domains.dsl.ts +++ b/x-pack/legacy/plugins/siem/server/lib/ip_details/query_domains.dsl.ts @@ -20,7 +20,7 @@ const getAggs = ( flowTarget: FlowTarget, flowDirection: FlowDirection, domainsSortField: DomainsSortField, - limit: number + querySize: number ) => { return { domain_count: { @@ -31,7 +31,7 @@ const getAggs = ( [`${flowTarget}_domains`]: { terms: { field: `${flowTarget}.domain`, - size: limit + 1, + size: querySize, order: { ...getQueryOrder(domainsSortField), }, @@ -129,7 +129,7 @@ export const buildDomainsQuery = ({ filterQuery, flowDirection, flowTarget, - pagination: { limit }, + pagination: { querySize }, defaultIndex, sourceConfiguration: { fields: { timestamp }, @@ -149,7 +149,7 @@ export const buildDomainsQuery = ({ ignoreUnavailable: true, body: { aggs: { - ...getAggs(ip, flowTarget, flowDirection, domainsSortField, limit), + ...getAggs(ip, flowTarget, flowDirection, domainsSortField, querySize), }, query: { bool: { diff --git a/x-pack/legacy/plugins/siem/server/lib/ip_details/query_tls.dsl.ts b/x-pack/legacy/plugins/siem/server/lib/ip_details/query_tls.dsl.ts index 22292cb1194e33..562746bb9645c6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/ip_details/query_tls.dsl.ts +++ b/x-pack/legacy/plugins/siem/server/lib/ip_details/query_tls.dsl.ts @@ -9,7 +9,7 @@ import { createQueryFilterClauses, assertUnreachable } from '../../utils/build_q import { TlsRequestOptions } from './index'; import { TlsSortField, Direction, TlsFields } from '../../graphql/types'; -const getAggs = (limit: number, tlsSortField: TlsSortField) => ({ +const getAggs = (querySize: number, tlsSortField: TlsSortField) => ({ count: { cardinality: { field: 'tls.server_certificate.fingerprint.sha1', @@ -18,7 +18,7 @@ const getAggs = (limit: number, tlsSortField: TlsSortField) => ({ sha1: { terms: { field: 'tls.server_certificate.fingerprint.sha1', - size: limit + 1, + size: querySize, order: { ...getQueryOrder(tlsSortField), }, @@ -58,7 +58,7 @@ export const buildTlsQuery = ({ tlsSortField, filterQuery, flowTarget, - pagination: { limit }, + pagination: { querySize }, defaultIndex, sourceConfiguration: { fields: { timestamp }, @@ -77,7 +77,7 @@ export const buildTlsQuery = ({ ignoreUnavailable: true, body: { aggs: { - ...getAggs(limit, tlsSortField), + ...getAggs(querySize, tlsSortField), }, query: { bool: { diff --git a/x-pack/legacy/plugins/siem/server/lib/ip_details/query_users.dsl.ts b/x-pack/legacy/plugins/siem/server/lib/ip_details/query_users.dsl.ts index 222b69a76e660a..0133b2e3732a11 100644 --- a/x-pack/legacy/plugins/siem/server/lib/ip_details/query_users.dsl.ts +++ b/x-pack/legacy/plugins/siem/server/lib/ip_details/query_users.dsl.ts @@ -14,7 +14,7 @@ export const buildUsersQuery = ({ usersSortField, filterQuery, flowTarget, - pagination: { limit }, + pagination: { querySize }, defaultIndex, sourceConfiguration: { fields: { timestamp }, @@ -41,7 +41,7 @@ export const buildUsersQuery = ({ users: { terms: { field: 'user.name', - size: limit + 1, + size: querySize, order: { ...getQueryOrder(usersSortField), }, diff --git a/x-pack/legacy/plugins/siem/server/lib/network/elastic_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/network/elastic_adapter.test.ts index 1f3b59a4cc278c..7ebb3427e700a5 100644 --- a/x-pack/legacy/plugins/siem/server/lib/network/elastic_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/network/elastic_adapter.test.ts @@ -74,7 +74,11 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source and Fl response: [JSON.stringify(mockNoDataResponse, null, 2)], }, edges: [], - pageInfo: { endCursor: { tiebreaker: null, value: '10' }, hasNextPage: false }, + pageInfo: { + activePage: 0, + fakeTotalCount: 0, + showMorePagesIndicator: false, + }, totalCount: 0, }); }); @@ -107,7 +111,7 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source and Fl mockRequest as FrameworkRequest, mockOptions ); - expect(data.pageInfo.hasNextPage).toBeFalsy(); + expect(data.pageInfo.showMorePagesIndicator).toBeFalsy(); }); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/network/elasticsearch_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/network/elasticsearch_adapter.ts index 96dafa0ee91389..44b70f81ed3a40 100644 --- a/x-pack/legacy/plugins/siem/server/lib/network/elasticsearch_adapter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/network/elasticsearch_adapter.ts @@ -9,6 +9,7 @@ import { get, getOr } from 'lodash/fp'; import { FlowDirection, FlowTarget, + NetworkDnsData, NetworkDnsEdges, NetworkTopNFlowData, NetworkTopNFlowEdges, @@ -16,6 +17,7 @@ import { import { inspectStringifyObject } from '../../utils/build_query'; import { DatabaseSearchResponse, FrameworkAdapter, FrameworkRequest } from '../framework'; import { TermAggregation } from '../types'; +import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../common/constants'; import { NetworkDnsRequestOptions, NetworkTopNFlowRequestOptions } from './index'; import { buildDnsQuery } from './query_dns.dsl'; @@ -35,26 +37,24 @@ export class ElasticsearchNetworkAdapter implements NetworkAdapter { 'search', dsl ); - const { cursor, limit } = options.pagination; + const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; const totalCount = getOr(0, 'aggregations.top_n_flow_count.value', response); const networkTopNFlowEdges: NetworkTopNFlowEdges[] = getTopNFlowEdges(response, options); - const hasNextPage = networkTopNFlowEdges.length > limit; - const beginning = cursor != null ? parseInt(cursor, 10) : 0; - const edges = networkTopNFlowEdges.splice(beginning, limit - beginning); + const fakeTotalCount = fakePossibleCount <= totalCount ? fakePossibleCount : totalCount; + const edges = networkTopNFlowEdges.splice(cursorStart, querySize - cursorStart); const inspect = { dsl: [inspectStringifyObject(dsl)], response: [inspectStringifyObject(response)], }; + const showMorePagesIndicator = totalCount > fakeTotalCount; return { edges, inspect, pageInfo: { - hasNextPage, - endCursor: { - value: String(limit), - tiebreaker: null, - }, + activePage: activePage ? activePage : 0, + fakeTotalCount, + showMorePagesIndicator, }, totalCount, }; @@ -63,35 +63,32 @@ export class ElasticsearchNetworkAdapter implements NetworkAdapter { public async getNetworkDns( request: FrameworkRequest, options: NetworkDnsRequestOptions - ): Promise { + ): Promise { const dsl = buildDnsQuery(options); - const response = await this.framework.callWithRequest( + const response = await this.framework.callWithRequest( request, 'search', dsl ); - const { cursor, limit } = options.pagination; + const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; const totalCount = getOr(0, 'aggregations.dns_count.value', response); const networkDnsEdges: NetworkDnsEdges[] = formatDnsEdges( getOr([], 'aggregations.dns_name_query_count.buckets', response) ); - const hasNextPage = networkDnsEdges.length > limit; - const beginning = cursor != null ? parseInt(cursor, 10) : 0; - const edges = networkDnsEdges.splice(beginning, limit - beginning); + const fakeTotalCount = fakePossibleCount <= totalCount ? fakePossibleCount : totalCount; + const edges = networkDnsEdges.splice(cursorStart, querySize - cursorStart); const inspect = { dsl: [inspectStringifyObject(dsl)], response: [inspectStringifyObject(response)], }; - + const showMorePagesIndicator = totalCount > fakeTotalCount; return { edges, inspect, pageInfo: { - hasNextPage, - endCursor: { - value: String(limit), - tiebreaker: null, - }, + activePage: activePage ? activePage : 0, + fakeTotalCount, + showMorePagesIndicator, }, totalCount, }; @@ -102,6 +99,9 @@ const getTopNFlowEdges = ( response: DatabaseSearchResponse, options: NetworkTopNFlowRequestOptions ): NetworkTopNFlowEdges[] => { + if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { + throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); + } if (options.flowDirection === FlowDirection.uniDirectional) { return formatTopNFlowEdges( getOr([], 'aggregations.top_uni_flow.buckets', response), diff --git a/x-pack/legacy/plugins/siem/server/lib/network/index.ts b/x-pack/legacy/plugins/siem/server/lib/network/index.ts index dd3f7032ce99f6..9bec909814afce 100644 --- a/x-pack/legacy/plugins/siem/server/lib/network/index.ts +++ b/x-pack/legacy/plugins/siem/server/lib/network/index.ts @@ -11,19 +11,19 @@ import { NetworkTopNFlowData, NetworkTopNFlowSortField, } from '../../graphql/types'; -import { FrameworkRequest, RequestOptions } from '../framework'; +import { FrameworkRequest, RequestOptionsPaginated } from '../framework'; export * from './elasticsearch_adapter'; import { NetworkAdapter } from './types'; export * from './types'; -export interface NetworkTopNFlowRequestOptions extends RequestOptions { +export interface NetworkTopNFlowRequestOptions extends RequestOptionsPaginated { networkTopNFlowSort: NetworkTopNFlowSortField; flowTarget: FlowTarget; flowDirection: FlowDirection; } -export interface NetworkDnsRequestOptions extends RequestOptions { +export interface NetworkDnsRequestOptions extends RequestOptionsPaginated { isPtrIncluded: boolean; networkDnsSortField: NetworkDnsSortField; } diff --git a/x-pack/legacy/plugins/siem/server/lib/network/mock.ts b/x-pack/legacy/plugins/siem/server/lib/network/mock.ts index afbbb1dfa86bbe..d1de95f34c372a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/network/mock.ts +++ b/x-pack/legacy/plugins/siem/server/lib/network/mock.ts @@ -21,7 +21,12 @@ export const mockOptions: NetworkTopNFlowRequestOptions = { }, }, timerange: { interval: '12h', to: 1549852006071, from: 1549765606071 }, - pagination: { limit: 10, cursor: null, tiebreaker: null }, + pagination: { + activePage: 0, + cursorStart: 0, + fakePossibleCount: 50, + querySize: 10, + }, filterQuery: {}, fields: [ 'totalCount', @@ -40,9 +45,11 @@ export const mockOptions: NetworkTopNFlowRequestOptions = { 'edges.cursor.value', 'edges.cursor.__typename', 'edges.__typename', - 'pageInfo.endCursor.value', - 'pageInfo.endCursor.__typename', - 'pageInfo.hasNextPage', + 'pageInfo.activePage', + 'pageInfo.__typename', + 'pageInfo.fakeTotalCount', + 'pageInfo.__typename', + 'pageInfo.showMorePagesIndicator', 'pageInfo.__typename', '__typename', ], @@ -59,7 +66,12 @@ export const mockRequest = { filterQuery: '', flowDirection: FlowDirection.uniDirectional, flowType: FlowTarget.source, - pagination: { limit: 10, cursor: null, tiebreaker: null }, + pagination: { + activePage: 0, + cursorStart: 0, + fakePossibleCount: 50, + querySize: 10, + }, sourceId: 'default', timerange: { interval: '12h', from: 1549765830772, to: 1549852230772 }, }, @@ -97,11 +109,11 @@ export const mockRequest = { __typename } pageInfo { - endCursor { - value - __typename - } - hasNextPage + activePage + __typename + fakeTotalCount + __typename + showMorePagesIndicator __typename } __typename @@ -634,11 +646,9 @@ export const mockResult = { }, ], pageInfo: { - endCursor: { - tiebreaker: null, - value: '10', - }, - hasNextPage: true, + activePage: 0, + fakeTotalCount: 50, + showMorePagesIndicator: true, }, totalCount: 545, }; diff --git a/x-pack/legacy/plugins/siem/server/lib/network/query_dns.dsl.ts b/x-pack/legacy/plugins/siem/server/lib/network/query_dns.dsl.ts index 7c08aab1e20b5e..c72020198ac93f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/network/query_dns.dsl.ts +++ b/x-pack/legacy/plugins/siem/server/lib/network/query_dns.dsl.ts @@ -56,15 +56,15 @@ const createIncludePTRFilter = (isPtrIncluded: boolean) => }; export const buildDnsQuery = ({ + defaultIndex, filterQuery, isPtrIncluded, networkDnsSortField, - timerange: { from, to }, - pagination: { limit }, - defaultIndex, + pagination: { querySize }, sourceConfiguration: { fields: { timestamp }, }, + timerange: { from, to }, }: NetworkDnsRequestOptions) => { const filter = [ ...createQueryFilterClauses(filterQuery), @@ -88,7 +88,7 @@ export const buildDnsQuery = ({ dns_name_query_count: { terms: { field: 'dns.question.etld_plus_one', - size: limit + 1, + size: querySize, order: { ...getQueryOrder(networkDnsSortField), }, diff --git a/x-pack/legacy/plugins/siem/server/lib/network/query_top_n_flow.dsl.ts b/x-pack/legacy/plugins/siem/server/lib/network/query_top_n_flow.dsl.ts index 4afb3c6607ff17..efbce55a3e968f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/network/query_top_n_flow.dsl.ts +++ b/x-pack/legacy/plugins/siem/server/lib/network/query_top_n_flow.dsl.ts @@ -74,16 +74,16 @@ const getCountAgg = (flowTarget: FlowTarget) => ({ }); export const buildTopNFlowQuery = ({ + defaultIndex, filterQuery, flowDirection, - networkTopNFlowSort, flowTarget, - timerange: { from, to }, - pagination: { limit }, - defaultIndex, + networkTopNFlowSort, + pagination: { querySize }, sourceConfiguration: { fields: { timestamp }, }, + timerange: { from, to }, }: NetworkTopNFlowRequestOptions) => { const filter = [ ...createQueryFilterClauses(filterQuery), @@ -98,8 +98,8 @@ export const buildTopNFlowQuery = ({ body: { aggregations: { ...getCountAgg(flowTarget), - ...getUniDirectionAggs(flowDirection, networkTopNFlowSort, flowTarget, limit), - ...getBiDirectionAggs(flowDirection, networkTopNFlowSort, flowTarget, limit), + ...getUniDirectionAggs(flowDirection, networkTopNFlowSort, flowTarget, querySize), + ...getBiDirectionAggs(flowDirection, networkTopNFlowSort, flowTarget, querySize), }, query: { bool: { @@ -118,14 +118,14 @@ const getUniDirectionAggs = ( flowDirection: FlowDirection, networkTopNFlowSortField: NetworkTopNFlowSortField, flowTarget: FlowTarget, - limit: number + querySize: number ) => flowDirection === FlowDirection.uniDirectional ? { top_uni_flow: { terms: { field: `${flowTarget}.ip`, - size: limit + 1, + size: querySize, order: { ...getQueryOrder(networkTopNFlowSortField), }, @@ -177,14 +177,14 @@ const getBiDirectionAggs = ( flowDirection: FlowDirection, networkTopNFlowSortField: NetworkTopNFlowSortField, flowTarget: FlowTarget, - limit: number + querySize: number ) => flowDirection === FlowDirection.biDirectional ? { top_bi_flow: { terms: { field: `${flowTarget}.ip`, - size: limit + 1, + size: querySize, order: { ...getQueryOrder(networkTopNFlowSortField), }, diff --git a/x-pack/legacy/plugins/siem/server/lib/network/types.ts b/x-pack/legacy/plugins/siem/server/lib/network/types.ts index ca2800a58f5764..40690a442f6554 100644 --- a/x-pack/legacy/plugins/siem/server/lib/network/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/network/types.ts @@ -5,12 +5,15 @@ */ import { NetworkDirectionEcs, NetworkDnsData, NetworkTopNFlowData } from '../../graphql/types'; -import { FrameworkRequest, RequestOptions } from '../framework'; +import { FrameworkRequest, RequestOptionsPaginated } from '../framework'; import { SearchHit } from '../types'; export interface NetworkAdapter { - getNetworkTopNFlow(req: FrameworkRequest, options: RequestOptions): Promise; - getNetworkDns(req: FrameworkRequest, options: RequestOptions): Promise; + getNetworkTopNFlow( + req: FrameworkRequest, + options: RequestOptionsPaginated + ): Promise; + getNetworkDns(req: FrameworkRequest, options: RequestOptionsPaginated): Promise; } export interface GenericBuckets { diff --git a/x-pack/legacy/plugins/siem/server/lib/uncommon_processes/elasticsearch_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/uncommon_processes/elasticsearch_adapter.ts index ce331b0c30ed73..111df08b89e2f9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/uncommon_processes/elasticsearch_adapter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/uncommon_processes/elasticsearch_adapter.ts @@ -9,9 +9,9 @@ import { get, getOr } from 'lodash/fp'; import { UncommonProcessesData, UncommonProcessesEdges } from '../../graphql/types'; import { mergeFieldsWithHit, inspectStringifyObject } from '../../utils/build_query'; import { processFieldsMap, userFieldsMap } from '../ecs_fields'; -import { FrameworkAdapter, FrameworkRequest, RequestOptions } from '../framework'; +import { FrameworkAdapter, FrameworkRequest, RequestOptionsPaginated } from '../framework'; import { HostHits, TermAggregation } from '../types'; - +import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../common/constants'; import { buildQuery } from './query.dsl'; import { UncommonProcessBucket, @@ -25,15 +25,18 @@ export class ElasticsearchUncommonProcessesAdapter implements UncommonProcessesA public async getUncommonProcesses( request: FrameworkRequest, - options: RequestOptions + options: RequestOptionsPaginated ): Promise { + if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { + throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); + } const dsl = buildQuery(options); const response = await this.framework.callWithRequest( request, 'search', dsl ); - const { cursor, limit } = options.pagination; + const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; const totalCount = getOr(0, 'aggregations.process_count.value', response); const buckets = getOr([], 'aggregations.group_by_process.buckets', response); const hits = getHits(buckets); @@ -41,23 +44,22 @@ export class ElasticsearchUncommonProcessesAdapter implements UncommonProcessesA const uncommonProcessesEdges = hits.map(hit => formatUncommonProcessesData(options.fields, hit, { ...processFieldsMap, ...userFieldsMap }) ); - const hasNextPage = uncommonProcessesEdges.length === limit + 1; - const beginning = cursor != null ? parseInt(cursor!, 10) : 0; - const edges = uncommonProcessesEdges.splice(beginning, limit - beginning); + + const fakeTotalCount = fakePossibleCount <= totalCount ? fakePossibleCount : totalCount; + const edges = uncommonProcessesEdges.splice(cursorStart, querySize - cursorStart); const inspect = { dsl: [inspectStringifyObject(dsl)], response: [inspectStringifyObject(response)], }; + const showMorePagesIndicator = totalCount > fakeTotalCount; return { edges, inspect, pageInfo: { - hasNextPage, - endCursor: { - value: String(limit), - tiebreaker: null, - }, + activePage: activePage ? activePage : 0, + fakeTotalCount, + showMorePagesIndicator, }, totalCount, }; diff --git a/x-pack/legacy/plugins/siem/server/lib/uncommon_processes/index.ts b/x-pack/legacy/plugins/siem/server/lib/uncommon_processes/index.ts index 17083414d245c1..d9f6a5966836ff 100644 --- a/x-pack/legacy/plugins/siem/server/lib/uncommon_processes/index.ts +++ b/x-pack/legacy/plugins/siem/server/lib/uncommon_processes/index.ts @@ -5,7 +5,7 @@ */ import { UncommonProcessesData } from '../../graphql/types'; -import { FrameworkRequest, RequestOptions } from '../framework'; +import { FrameworkRequest, RequestOptionsPaginated } from '../framework'; export * from './elasticsearch_adapter'; import { UncommonProcessesAdapter } from './types'; @@ -14,7 +14,7 @@ export class UncommonProcesses { public async getUncommonProcesses( req: FrameworkRequest, - options: RequestOptions + options: RequestOptionsPaginated ): Promise { return await this.adapter.getUncommonProcesses(req, options); } diff --git a/x-pack/legacy/plugins/siem/server/lib/uncommon_processes/query.dsl.ts b/x-pack/legacy/plugins/siem/server/lib/uncommon_processes/query.dsl.ts index 2f83c839d80cb2..e4b6bb8c6596bc 100644 --- a/x-pack/legacy/plugins/siem/server/lib/uncommon_processes/query.dsl.ts +++ b/x-pack/legacy/plugins/siem/server/lib/uncommon_processes/query.dsl.ts @@ -7,18 +7,18 @@ import { createQueryFilterClauses } from '../../utils/build_query'; import { reduceFields } from '../../utils/build_query/reduce_fields'; import { hostFieldsMap, processFieldsMap, userFieldsMap } from '../ecs_fields'; -import { RequestOptions } from '../framework'; +import { RequestOptionsPaginated } from '../framework'; export const buildQuery = ({ + defaultIndex, fields, filterQuery, - timerange: { from, to }, - defaultIndex, - pagination: { limit }, + pagination: { querySize }, sourceConfiguration: { fields: { timestamp }, }, -}: RequestOptions) => { + timerange: { from, to }, +}: RequestOptionsPaginated) => { const processUserFields = reduceFields(fields, { ...processFieldsMap, ...userFieldsMap }); const hostFields = reduceFields(fields, hostFieldsMap); const filter = [ @@ -50,7 +50,7 @@ export const buildQuery = ({ ...agg, group_by_process: { terms: { - size: limit + 1, + size: querySize, field: 'process.name', order: [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/uncommon_processes/types.ts b/x-pack/legacy/plugins/siem/server/lib/uncommon_processes/types.ts index 2ebaadc17b1f57..dc60de5963a187 100644 --- a/x-pack/legacy/plugins/siem/server/lib/uncommon_processes/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/uncommon_processes/types.ts @@ -5,13 +5,13 @@ */ import { ProcessEcsFields, UncommonProcessesData } from '../../graphql/types'; -import { FrameworkRequest, RequestOptions } from '../framework'; +import { FrameworkRequest, RequestOptionsPaginated } from '../framework'; import { Hit, Hits, HostHits, SearchHit, TotalHit } from '../types'; export interface UncommonProcessesAdapter { getUncommonProcesses( req: FrameworkRequest, - options: RequestOptions + options: RequestOptionsPaginated ): Promise; } diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/services/ui_metric/ui_metric.ts b/x-pack/legacy/plugins/snapshot_restore/public/app/services/ui_metric/ui_metric.ts index 1f06ff83e4ee04..a2f0a6e1a54821 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/services/ui_metric/ui_metric.ts +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/services/ui_metric/ui_metric.ts @@ -4,16 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ import { UIM_APP_NAME } from '../../constants'; +import { + createUiStatsReporter, + METRIC_TYPE, +} from '../../../../../../../../src/legacy/core_plugins/ui_metric/public'; class UiMetricService { - public track: any = () => {}; + track?: ReturnType; - public init = (track: any): void => { - this.track = track; + public init = (getReporter: typeof createUiStatsReporter): void => { + this.track = getReporter(UIM_APP_NAME); }; - public trackUiMetric = (actionType: string): any => { - return this.track(UIM_APP_NAME, actionType); + public trackUiMetric = (eventName: string): void => { + if (!this.track) throw Error('UiMetricService not initialized.'); + return this.track(METRIC_TYPE.COUNT, eventName); }; } diff --git a/x-pack/legacy/plugins/snapshot_restore/public/plugin.ts b/x-pack/legacy/plugins/snapshot_restore/public/plugin.ts index b3fce61d3876a0..f590237bec737d 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/plugin.ts +++ b/x-pack/legacy/plugins/snapshot_restore/public/plugin.ts @@ -39,7 +39,7 @@ export class Plugin { textService.init(i18n); breadcrumbService.init(chrome, management.constants.BREADCRUMB); documentationLinksService.init(documentation.esDocBasePath, documentation.esPluginDocBasePath); - uiMetricService.init(uiMetric.track); + uiMetricService.init(uiMetric.createUiStatsReporter); const unmountReactApp = (): void => { const elem = document.getElementById(REACT_ROOT_ID); diff --git a/x-pack/legacy/plugins/snapshot_restore/public/shim.ts b/x-pack/legacy/plugins/snapshot_restore/public/shim.ts index 45083d997fdb60..77604f90fd570c 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/shim.ts +++ b/x-pack/legacy/plugins/snapshot_restore/public/shim.ts @@ -16,7 +16,7 @@ import routes from 'ui/routes'; import { HashRouter } from 'react-router-dom'; // @ts-ignore: allow traversal to fail on x-pack build -import { trackUiMetric as track } from '../../../../../src/legacy/core_plugins/ui_metric/public'; +import { createUiStatsReporter } from '../../../../../src/legacy/core_plugins/ui_metric/public'; export interface AppCore { i18n: { @@ -63,7 +63,7 @@ export interface Plugins extends AppPlugins { }; }; uiMetric: { - track: typeof track; + createUiStatsReporter: typeof createUiStatsReporter; }; } @@ -118,7 +118,7 @@ export function createShim(): { core: Core; plugins: Plugins } { }, }, uiMetric: { - track, + createUiStatsReporter, }, }, }; diff --git a/x-pack/legacy/plugins/telemetry/common/constants.ts b/x-pack/legacy/plugins/telemetry/common/constants.ts index 8c6cfb6e558da1..c50f36ac94497a 100644 --- a/x-pack/legacy/plugins/telemetry/common/constants.ts +++ b/x-pack/legacy/plugins/telemetry/common/constants.ts @@ -81,3 +81,9 @@ export const KIBANA_LOCALIZATION_STATS_TYPE = 'localization'; * @type {string} */ export const TELEMETRY_QUERY_SOURCE = 'TELEMETRY'; + +/** + * UI metric usage type + * @type {string} + */ +export const UI_METRIC_USAGE_TYPE = 'ui_metric'; diff --git a/x-pack/legacy/plugins/telemetry/index.ts b/x-pack/legacy/plugins/telemetry/index.ts index 4125dae82f9fa7..6d4e9be67fb98a 100644 --- a/x-pack/legacy/plugins/telemetry/index.ts +++ b/x-pack/legacy/plugins/telemetry/index.ts @@ -17,6 +17,7 @@ import { telemetryPlugin } from './server'; import { createLocalizationUsageCollector, createTelemetryUsageCollector, + createUiMetricUsageCollector, } from './server/collectors'; const ENDPOINT_VERSION = 'v2'; @@ -72,10 +73,7 @@ export const telemetry = (kibana: any) => { activeSpace: null, }; }, - hacks: [ - 'plugins/telemetry/hacks/telemetry_opt_in', - 'plugins/telemetry/hacks/telemetry_trigger', - ], + hacks: ['plugins/telemetry/hacks/telemetry_init', 'plugins/telemetry/hacks/telemetry_opt_in'], mappings, }, init(server: Server) { @@ -89,6 +87,7 @@ export const telemetry = (kibana: any) => { // register collectors server.usage.collectorSet.register(createLocalizationUsageCollector(server)); server.usage.collectorSet.register(createTelemetryUsageCollector(server)); + server.usage.collectorSet.register(createUiMetricUsageCollector(server)); // expose server.expose('telemetryCollectionInterval', REPORT_INTERVAL_MS); diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/telemetry_trigger.js b/x-pack/legacy/plugins/telemetry/public/hacks/telemetry_init.ts similarity index 74% rename from x-pack/legacy/plugins/telemetry/public/hacks/telemetry_trigger.js rename to x-pack/legacy/plugins/telemetry/public/hacks/telemetry_init.ts index efee9e07dc7bd8..c44da2b36bf1e2 100644 --- a/x-pack/legacy/plugins/telemetry/public/hacks/telemetry_trigger.js +++ b/x-pack/legacy/plugins/telemetry/public/hacks/telemetry_init.ts @@ -4,24 +4,31 @@ * you may not use this file except in compliance with the Elastic License. */ +// @ts-ignore import { uiModules } from 'ui/modules'; +// @ts-ignore import { Path } from 'plugins/xpack_main/services/path'; +// @ts-ignore +import { npStart } from 'ui/new_platform'; +// @ts-ignore import { Telemetry } from './telemetry'; +// @ts-ignore import { fetchTelemetry } from './fetch_telemetry'; -import { npStart } from 'ui/new_platform'; -function telemetryStart($injector) { +function telemetryInit($injector: any) { + const $http = $injector.get('$http'); + const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled'); if (telemetryEnabled) { // no telemetry for non-logged in users - if (Path.isUnauthenticated()) { return; } + if (Path.isUnauthenticated()) { + return; + } - const $http = $injector.get('$http'); const sender = new Telemetry($injector, () => fetchTelemetry($http)); - sender.start(); } } -uiModules.get('telemetry/hacks').run(telemetryStart); +uiModules.get('telemetry/hacks').run(telemetryInit); diff --git a/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.js b/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.js index a1b45b3fa91da0..2fcd2012a15285 100644 --- a/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.js +++ b/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.js @@ -17,7 +17,6 @@ export function TelemetryOptInProvider($injector, chrome) { getOptIn: () => currentOptInStatus, setOptIn: async (enabled) => { setCanTrackUiMetrics(enabled); - const $http = $injector.get('$http'); try { diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/index.ts b/x-pack/legacy/plugins/telemetry/server/collectors/index.ts index 2123d5a9251a70..c9b94a8ea5d5eb 100644 --- a/x-pack/legacy/plugins/telemetry/server/collectors/index.ts +++ b/x-pack/legacy/plugins/telemetry/server/collectors/index.ts @@ -11,4 +11,5 @@ export { getLocalStats } from './local'; export { getStats } from './get_stats'; export { encryptTelemetry } from './encryption'; export { createTelemetryUsageCollector } from './usage'; +export { createUiMetricUsageCollector } from './ui_metric'; export { createLocalizationUsageCollector } from './localization'; diff --git a/x-pack/legacy/plugins/notifications/server/routes/api/v1/notifications/index.js b/x-pack/legacy/plugins/telemetry/server/collectors/ui_metric/index.ts similarity index 75% rename from x-pack/legacy/plugins/notifications/server/routes/api/v1/notifications/index.js rename to x-pack/legacy/plugins/telemetry/server/collectors/ui_metric/index.ts index dd754c454ee254..f5a49587d49c8c 100644 --- a/x-pack/legacy/plugins/notifications/server/routes/api/v1/notifications/index.js +++ b/x-pack/legacy/plugins/telemetry/server/collectors/ui_metric/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { notificationServiceSendRoute } from './notify'; +export { createUiMetricUsageCollector } from './telemetry_ui_metric_collector'; diff --git a/x-pack/legacy/plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts b/x-pack/legacy/plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts new file mode 100644 index 00000000000000..a931400399b443 --- /dev/null +++ b/x-pack/legacy/plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { UI_METRIC_USAGE_TYPE } from '../../../common/constants'; + +export function createUiMetricUsageCollector(server: any) { + const { collectorSet } = server.usage; + return collectorSet.makeUsageCollector({ + type: UI_METRIC_USAGE_TYPE, + fetch: async () => { + const { SavedObjectsClient, getSavedObjectsRepository } = server.savedObjects; + const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); + const internalRepository = getSavedObjectsRepository(callWithInternalUser); + const savedObjectsClient = new SavedObjectsClient(internalRepository); + + const { saved_objects: rawUiMetrics } = await savedObjectsClient.find({ + type: 'ui-metric', + fields: ['count'], + }); + + const uiMetricsByAppName = rawUiMetrics.reduce((accum: any, rawUiMetric: any) => { + const { + id, + attributes: { count }, + } = rawUiMetric; + + const [appName, metricType] = id.split(':'); + + if (!accum[appName]) { + accum[appName] = []; + } + + const pair = { key: metricType, value: count }; + accum[appName].push(pair); + return accum; + }, {}); + + return uiMetricsByAppName; + }, + isReady: () => true, + }); +} diff --git a/x-pack/package.json b/x-pack/package.json index e1955f582a7314..4db0b33a7f324b 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -172,13 +172,14 @@ "@babel/runtime": "7.4.5", "@elastic/ctags-langserver": "^0.1.2", "@elastic/datemath": "5.0.2", - "@elastic/eui": "12.3.1", + "@elastic/eui": "12.4.0", "@elastic/javascript-typescript-langserver": "^0.2.1", "@elastic/lsp-extension": "^0.1.2", "@elastic/node-crypto": "^1.0.0", "@elastic/nodegit": "0.25.0-alpha.22", "@elastic/numeral": "2.3.3", "@elastic/request-crypto": "^1.0.2", + "@kbn/analytics": "1.0.0", "@kbn/babel-preset": "1.0.0", "@kbn/config-schema": "1.0.0", "@kbn/elastic-idx": "1.0.0", diff --git a/x-pack/plugins/security/server/authentication/index.ts b/x-pack/plugins/security/server/authentication/index.ts index 5e4568c485d590..11e226c1342a52 100644 --- a/x-pack/plugins/security/server/authentication/index.ts +++ b/x-pack/plugins/security/server/authentication/index.ts @@ -92,7 +92,7 @@ export async function setupAuthentication({ if (authenticationResult.succeeded()) { return t.authenticated({ - state: (authenticationResult.user as unknown) as Record, + state: authenticationResult.user, headers: authenticationResult.authHeaders, }); } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 06b199444bf499..06c7dcb6cdb888 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -298,8 +298,6 @@ "common.ui.directives.fieldNameIcons.stringFieldAriaLabel": "文字列フィールド", "common.ui.directives.fieldNameIcons.unknownFieldAriaLabel": "不明なフィールド", "common.ui.directives.paginate.size.allDropDownOptionLabel": "すべて", - "common.ui.directives.truncated.showLessLinkText": "隠す", - "common.ui.directives.truncated.showMoreLinkText": "もっと", "common.ui.dualRangeControl.mustSetBothErrorMessage": "下と上の値の両方を設定する必要があります", "common.ui.dualRangeControl.outsideOfRangeErrorMessage": "値は {min} と {max} の間でなければなりません", "common.ui.dualRangeControl.upperValidErrorMessage": "上の値は下の値以上でなければなりません", @@ -504,13 +502,6 @@ "common.ui.notify.fatalError.unavailableServerErrorMessage": "HTTP リクエストが接続に失敗しました。Kibana サーバーが実行されていて、ご使用のブラウザの接続が正常に動作していることを確認するか、システム管理者にお問い合わせください。", "common.ui.notify.toaster.errorMessage": "エラー: {errorMessage}\n {errorStack}", "common.ui.notify.toaster.errorStatusMessage": "エラー {errStatus} {errStatusText}: {errMessage}", - "common.ui.notify.toaster.errorTitle": "エラー", - "common.ui.notify.toaster.fixItButtonLabel": "修正", - "common.ui.notify.toaster.lessInfoButtonLabel": "情報を縮小", - "common.ui.notify.toaster.moreInfoButtonLabel": "情報を拡張", - "common.ui.notify.toaster.okButtonLabel": "OK", - "common.ui.notify.toaster.stopCountdownButtonTooltip": "停止", - "common.ui.notify.toaster.timeRemainingInSecondsLabel": "{timeRemaining}s", "common.ui.notify.toaster.unavailableServerErrorMessage": "HTTP リクエストが接続に失敗しました。Kibana サーバーが実行されていて、ご使用のブラウザの接続が正常に動作していることを確認するか、システム管理者にお問い合わせください。", "common.ui.paginateControls.pageSizeLabel": "ページサイズ", "common.ui.paginateControls.scrollTopButtonLabel": "最上部に移動", @@ -565,20 +556,6 @@ "common.ui.stateManagement.unableToParseUrlErrorMessage": "URL をパースできません", "common.ui.stateManagement.unableToRestoreUrlErrorMessage": "URL を完全に復元できません。共有機能を使用していることを確認してください。", "common.ui.stateManagement.unableToStoreHistoryInSessionErrorMessage": "セッションがいっぱいで安全に削除できるアイテムが見つからないため、Kibana は履歴アイテムを保存できません。\n\nこれは大抵新規タブに移動することで解決されますが、より大きな問題が原因である可能性もあります。このメッセージが定期的に表示される場合は、{gitHubIssuesUrl} で問題を報告してください。", - "common.ui.timepicker.relOpts.daysAgo": "日前", - "common.ui.timepicker.relOpts.daysFromNow": "現在からの日数", - "common.ui.timepicker.relOpts.hoursAgo": "時間前", - "common.ui.timepicker.relOpts.hoursFromNow": "現在からの時間数", - "common.ui.timepicker.relOpts.minutesAgo": "分前", - "common.ui.timepicker.relOpts.minutesFromNow": "現在からの分数", - "common.ui.timepicker.relOpts.monthsAgo": "か月前", - "common.ui.timepicker.relOpts.monthsFromNow": "現在からの月数", - "common.ui.timepicker.relOpts.secondsAgo": "秒前", - "common.ui.timepicker.relOpts.secondsFromNow": "現在からの秒数", - "common.ui.timepicker.relOpts.weeksAgo": "週間前", - "common.ui.timepicker.relOpts.weeksFromNow": "現在からの週数", - "common.ui.timepicker.relOpts.yearsAgo": "年前", - "common.ui.timepicker.relOpts.yearsFromNow": "現在からの年数", "common.ui.topNav.closeAriaLabel": "閉じる", "common.ui.topNav.toggleViewAriaLabel": "{optLabel} ビューを切り替える", "common.ui.url.replacementFailedErrorMessage": "置換に失敗、未解決の表現式: {expr}", @@ -826,9 +803,9 @@ "data.query.queryBar.syntaxOptionsDescription": "{docsLink} (KQL) は、シンプルなクエリ構文とスクリプトフィールドのサポートを提供します。また、KQL はベーシックライセンス以上をご利用の場合、自動入力も提供します。KQL をオフにすると、Kibana は Lucene を使用します。", "data.query.queryBar.syntaxOptionsDescription.docsLinkText": "こちら", "data.query.queryBar.syntaxOptionsTitle": "構文オプション", - "data.search.searchBar.filtersButtonClickToHideTitle": "選択して表示", - "data.search.searchBar.filtersButtonClickToShowTitle": "選択して非表示", - "data.search.searchBar.filtersButtonFiltersAppliedTitle": "フィルターが適用されました。", + "kibana_react.search.searchBar.filtersButtonClickToHideTitle": "選択して表示", + "kibana_react.search.searchBar.filtersButtonClickToShowTitle": "選択して非表示", + "kibana_react.search.searchBar.filtersButtonFiltersAppliedTitle": "フィルターが適用されました。", "embeddableApi.actionPanel.title": "オプション", "embeddableApi.actions.applyFilterActionTitle": "現在のビューにフィルターを適用", "embeddableApi.addPanel.createNew": "新規 {factoryName} を作成", @@ -10712,4 +10689,4 @@ "xpack.watcher.watchActions.logging.logTextIsRequiredValidationMessage": "ログテキストが必要です。", "xpack.watcher.watcherDescription": "アラートの作成、管理、監視によりデータへの変更を検知します。" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 6d0c506670c968..0bbd70178a68d6 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -298,8 +298,6 @@ "common.ui.directives.fieldNameIcons.stringFieldAriaLabel": "字符串字段", "common.ui.directives.fieldNameIcons.unknownFieldAriaLabel": "未知字段", "common.ui.directives.paginate.size.allDropDownOptionLabel": "全部", - "common.ui.directives.truncated.showLessLinkText": "更少", - "common.ui.directives.truncated.showMoreLinkText": "更多", "common.ui.dualRangeControl.mustSetBothErrorMessage": "下限值和上限值都须设置", "common.ui.dualRangeControl.outsideOfRangeErrorMessage": "值必须是在 {min} 到 {max} 的范围内", "common.ui.dualRangeControl.upperValidErrorMessage": "上限值必须大于或等于下限值", @@ -504,13 +502,6 @@ "common.ui.notify.fatalError.unavailableServerErrorMessage": "HTTP 请求无法连接。请检查 Kibana 服务器是否正在运行以及您的浏览器是否具有有效的连接,或请联系您的系统管理员。", "common.ui.notify.toaster.errorMessage": "错误:{errorMessage}\n {errorStack}", "common.ui.notify.toaster.errorStatusMessage": "错误 {errStatus} {errStatusText}:{errMessage}", - "common.ui.notify.toaster.errorTitle": "错误", - "common.ui.notify.toaster.fixItButtonLabel": "解决", - "common.ui.notify.toaster.lessInfoButtonLabel": "更少信息", - "common.ui.notify.toaster.moreInfoButtonLabel": "更多信息", - "common.ui.notify.toaster.okButtonLabel": "确定", - "common.ui.notify.toaster.stopCountdownButtonTooltip": "停止", - "common.ui.notify.toaster.timeRemainingInSecondsLabel": "{timeRemaining} 秒", "common.ui.notify.toaster.unavailableServerErrorMessage": "HTTP 请求无法连接。请检查 Kibana 服务器是否正在运行以及您的浏览器是否具有有效的连接,或请联系您的系统管理员。", "common.ui.paginateControls.pageSizeLabel": "页面大小", "common.ui.paginateControls.scrollTopButtonLabel": "滚动至顶部", @@ -565,20 +556,6 @@ "common.ui.stateManagement.unableToParseUrlErrorMessage": "无法解析 URL", "common.ui.stateManagement.unableToRestoreUrlErrorMessage": "无法完整还原 URL,确保使用共享功能。", "common.ui.stateManagement.unableToStoreHistoryInSessionErrorMessage": "Kibana 无法将历史记录项存储在您的会话中,因为其已满,并且似乎没有任何可安全删除的项。\n\n通常可通过移至新的标签页来解决此问题,但这会导致更大的问题。如果您有规律地看到此消息,请在 {gitHubIssuesUrl} 提交问题。", - "common.ui.timepicker.relOpts.daysAgo": "天前", - "common.ui.timepicker.relOpts.daysFromNow": "自现在开始的天数", - "common.ui.timepicker.relOpts.hoursAgo": "小时前", - "common.ui.timepicker.relOpts.hoursFromNow": "自现在开始的小时数", - "common.ui.timepicker.relOpts.minutesAgo": "分钟前", - "common.ui.timepicker.relOpts.minutesFromNow": "自现在开始的分钟数", - "common.ui.timepicker.relOpts.monthsAgo": "个月前", - "common.ui.timepicker.relOpts.monthsFromNow": "自现在开始的月份数", - "common.ui.timepicker.relOpts.secondsAgo": "秒前", - "common.ui.timepicker.relOpts.secondsFromNow": "自现在开始的秒数", - "common.ui.timepicker.relOpts.weeksAgo": "周前", - "common.ui.timepicker.relOpts.weeksFromNow": "自现在开始的周数", - "common.ui.timepicker.relOpts.yearsAgo": "年前", - "common.ui.timepicker.relOpts.yearsFromNow": "自现在开始的年数", "common.ui.topNav.closeAriaLabel": "关闭", "common.ui.topNav.toggleViewAriaLabel": "切换 {optLabel} 视图", "common.ui.url.replacementFailedErrorMessage": "替换失败,未解析的表达式:{expr}", @@ -826,9 +803,9 @@ "data.query.queryBar.syntaxOptionsDescription": "{docsLink} (KQL) 提供简化查询语法并支持脚本字段。如果您具有基本许可或更高级别的许可,KQL 还提供自动填充功能。如果关闭 KQL,Kibana 将使用 Lucene。", "data.query.queryBar.syntaxOptionsDescription.docsLinkText": "此处", "data.query.queryBar.syntaxOptionsTitle": "语法选项", - "data.search.searchBar.filtersButtonClickToHideTitle": "选择以显示", - "data.search.searchBar.filtersButtonClickToShowTitle": "选择以隐藏", - "data.search.searchBar.filtersButtonFiltersAppliedTitle": "个筛选已应用。", + "kibana_react.search.searchBar.filtersButtonClickToHideTitle": "选择以显示", + "kibana_react.search.searchBar.filtersButtonClickToShowTitle": "选择以隐藏", + "kibana_react.search.searchBar.filtersButtonFiltersAppliedTitle": "个筛选已应用。", "embeddableApi.actionPanel.title": "选项", "embeddableApi.actions.applyFilterActionTitle": "将筛选应用于当前视图", "embeddableApi.addPanel.createNew": "创建新的{factoryName}", @@ -10711,4 +10688,4 @@ "xpack.watcher.watchActions.logging.logTextIsRequiredValidationMessage": "“日志文本”必填。", "xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。" } -} +} \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/siem/domains.ts b/x-pack/test/api_integration/apis/siem/domains.ts index 959d95e16188c3..571f2685cf38de 100644 --- a/x-pack/test/api_integration/apis/siem/domains.ts +++ b/x-pack/test/api_integration/apis/siem/domains.ts @@ -43,8 +43,10 @@ const domainsTests: KbnTestProvider = ({ getService }) => { flowTarget: FlowTarget.source, sort: { field: DomainsFields.bytes, direction: Direction.desc }, pagination: { - limit: 10, - cursor: null, + activePage: 0, + cursorStart: 0, + fakePossibleCount: 30, + querySize: 10, }, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], inspect: false, @@ -78,8 +80,10 @@ const domainsTests: KbnTestProvider = ({ getService }) => { flowTarget: FlowTarget.source, sort: { field: DomainsFields.bytes, direction: Direction.desc }, pagination: { - limit: 10, - cursor: null, + activePage: 0, + cursorStart: 0, + fakePossibleCount: 30, + querySize: 10, }, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], inspect: false, @@ -113,8 +117,10 @@ const domainsTests: KbnTestProvider = ({ getService }) => { flowTarget: FlowTarget.destination, sort: { field: DomainsFields.bytes, direction: Direction.desc }, pagination: { - limit: 10, - cursor: null, + activePage: 0, + cursorStart: 0, + fakePossibleCount: 30, + querySize: 10, }, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], inspect: false, @@ -127,7 +133,7 @@ const domainsTests: KbnTestProvider = ({ getService }) => { expect(domains.edges.map(i => i.node.destination!.domainName).join(',')).to.be( 'samsungtv-kitchen.iot.sr.local.crowbird.com,12s3.lvlt.dash.row.aiv-cdn.net,151.205.0.17,151.205.0.19,151.205.0.21,151.205.0.23,15s3.lvlt.dash.row.aiv-cdn.net,api-global.netflix.com,d25xi40x97liuc.cloudfront.net,d2lkq7nlcrdi7q.cloudfront.net' ); - expect(domains.pageInfo.endCursor!.value).to.equal('10'); + expect(domains.pageInfo.fakeTotalCount).to.equal(12); }); }); }); diff --git a/x-pack/test/api_integration/apis/siem/events.ts b/x-pack/test/api_integration/apis/siem/events.ts index f3d7aecfacf732..9a4084a66d1c6b 100644 --- a/x-pack/test/api_integration/apis/siem/events.ts +++ b/x-pack/test/api_integration/apis/siem/events.ts @@ -23,7 +23,6 @@ const TO = new Date('3000-01-01T00:00:00.000Z').valueOf(); const HOST_NAME = 'suricata-sensor-amsterdam'; const TOTAL_COUNT = 1751; const EDGE_LENGTH = 2; -const CURSOR_ID = '1550608953561'; const eventsTests: KbnTestProvider = ({ getService }) => { const esArchiver = getService('esArchiver'); @@ -45,9 +44,10 @@ const eventsTests: KbnTestProvider = ({ getService }) => { from: FROM, }, pagination: { - limit: 2, - cursor: null, - tiebreaker: null, + activePage: 0, + cursorStart: 0, + fakePossibleCount: 3, + querySize: 2, }, sortField: { sortFieldId: 'timestamp', @@ -61,7 +61,7 @@ const eventsTests: KbnTestProvider = ({ getService }) => { const events = resp.data.source.Events; expect(events.edges.length).to.be(EDGE_LENGTH); expect(events.totalCount).to.be(TOTAL_COUNT); - expect(events.pageInfo.endCursor!.value).to.equal(CURSOR_ID); + expect(events.pageInfo.fakeTotalCount).to.equal(3); }); }); @@ -77,9 +77,10 @@ const eventsTests: KbnTestProvider = ({ getService }) => { from: FROM, }, pagination: { - limit: 2, - cursor: CURSOR_ID, - tiebreaker: '193', + activePage: 1, + cursorStart: 2, + fakePossibleCount: 10, + querySize: 4, }, sortField: { sortFieldId: 'timestamp', @@ -109,9 +110,10 @@ const eventsTests: KbnTestProvider = ({ getService }) => { from: FROM, }, pagination: { - limit: 2, - cursor: CURSOR_ID, - tiebreaker: '193', + activePage: 1, + cursorStart: 2, + fakePossibleCount: 10, + querySize: 4, }, sortField: { sortFieldId: 'timestamp', diff --git a/x-pack/test/api_integration/apis/siem/hosts.ts b/x-pack/test/api_integration/apis/siem/hosts.ts index 42a5fc5bab43f4..95f05516f0aa04 100644 --- a/x-pack/test/api_integration/apis/siem/hosts.ts +++ b/x-pack/test/api_integration/apis/siem/hosts.ts @@ -52,8 +52,10 @@ const hostsTests: KbnTestProvider = ({ getService }) => { direction: Direction.asc, }, pagination: { - limit: 1, - cursor: null, + activePage: 0, + cursorStart: 0, + fakePossibleCount: 3, + querySize: 1, }, inspect: false, }, @@ -62,7 +64,7 @@ const hostsTests: KbnTestProvider = ({ getService }) => { const hosts = resp.data.source.Hosts; expect(hosts.edges.length).to.be(EDGE_LENGTH); expect(hosts.totalCount).to.be(TOTAL_COUNT); - expect(hosts.pageInfo.endCursor!.value).to.equal('1'); + expect(hosts.pageInfo.fakeTotalCount).to.equal(3); }); }); @@ -83,8 +85,10 @@ const hostsTests: KbnTestProvider = ({ getService }) => { }, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], pagination: { - limit: 2, - cursor: '1', + activePage: 2, + cursorStart: 1, + fakePossibleCount: 5, + querySize: 2, }, inspect: false, }, diff --git a/x-pack/test/api_integration/apis/siem/network_dns.ts b/x-pack/test/api_integration/apis/siem/network_dns.ts index 6055ee652dd634..1851c23aa3492e 100644 --- a/x-pack/test/api_integration/apis/siem/network_dns.ts +++ b/x-pack/test/api_integration/apis/siem/network_dns.ts @@ -38,8 +38,10 @@ const networkDnsTests: KbnTestProvider = ({ getService }) => { isPtrIncluded: false, sort: { field: NetworkDnsFields.uniqueDomains, direction: Direction.asc }, pagination: { - limit: 10, - cursor: null, + activePage: 0, + cursorStart: 0, + fakePossibleCount: 30, + querySize: 10, }, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], inspect: false, @@ -50,9 +52,9 @@ const networkDnsTests: KbnTestProvider = ({ getService }) => { expect(networkDns.edges.length).to.be(10); expect(networkDns.totalCount).to.be(44); expect(networkDns.edges.map(i => i.node.dnsName).join(',')).to.be( - 'aaplimg.com,adgrx.com,akadns.net,akamaiedge.net,amazonaws.com,cbsistatic.com,cdn-apple.com,connman.net,d1oxlq5h9kq8q5.cloudfront.net,d3epxf4t8a32oh.cloudfront.net' + 'aaplimg.com,adgrx.com,akadns.net,akamaiedge.net,amazonaws.com,cbsistatic.com,cdn-apple.com,connman.net,crowbird.com,d1oxlq5h9kq8q5.cloudfront.net' ); - expect(networkDns.pageInfo.endCursor!.value).to.equal('10'); + expect(networkDns.pageInfo.fakeTotalCount).to.equal(30); }); }); @@ -70,8 +72,10 @@ const networkDnsTests: KbnTestProvider = ({ getService }) => { isPtrIncluded: false, sort: { field: NetworkDnsFields.uniqueDomains, direction: Direction.desc }, pagination: { - limit: 10, - cursor: null, + activePage: 0, + cursorStart: 0, + fakePossibleCount: 30, + querySize: 10, }, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], inspect: false, @@ -84,7 +88,7 @@ const networkDnsTests: KbnTestProvider = ({ getService }) => { expect(networkDns.edges.map(i => i.node.dnsName).join(',')).to.be( 'nflxvideo.net,apple.com,netflix.com,samsungcloudsolution.com,samsungqbe.com,samsungelectronics.com,internetat.tv,samsungcloudsolution.net,samsungosp.com,cbsnews.com' ); - expect(networkDns.pageInfo.endCursor!.value).to.equal('10'); + expect(networkDns.pageInfo.fakeTotalCount).to.equal(30); }); }); }); diff --git a/x-pack/test/api_integration/apis/siem/network_top_n_flow.ts b/x-pack/test/api_integration/apis/siem/network_top_n_flow.ts index cd0c8323c1a6d8..eedcd986b08997 100644 --- a/x-pack/test/api_integration/apis/siem/network_top_n_flow.ts +++ b/x-pack/test/api_integration/apis/siem/network_top_n_flow.ts @@ -43,8 +43,10 @@ const networkTopNFlowTests: KbnTestProvider = ({ getService }) => { sort: { field: NetworkTopNFlowFields.bytes, direction: Direction.desc }, flowDirection: FlowDirection.uniDirectional, pagination: { - limit: 10, - cursor: null, + activePage: 0, + cursorStart: 0, + fakePossibleCount: 50, + querySize: 10, }, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], inspect: false, @@ -58,7 +60,7 @@ const networkTopNFlowTests: KbnTestProvider = ({ getService }) => { '8.250.107.245,10.100.7.198,8.248.211.247,8.253.157.240,151.205.0.21,8.254.254.117,54.239.220.40,151.205.0.23,8.248.223.246,151.205.0.17' ); expect(networkTopNFlow.edges[0].node.destination).to.be(null); - expect(networkTopNFlow.pageInfo.endCursor!.value).to.equal('10'); + expect(networkTopNFlow.pageInfo.fakeTotalCount).to.equal(50); }); }); @@ -77,8 +79,10 @@ const networkTopNFlowTests: KbnTestProvider = ({ getService }) => { sort: { field: NetworkTopNFlowFields.bytes, direction: Direction.asc }, flowDirection: FlowDirection.uniDirectional, pagination: { - limit: 10, - cursor: null, + activePage: 0, + cursorStart: 0, + fakePossibleCount: 50, + querySize: 10, }, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], inspect: false, @@ -92,7 +96,7 @@ const networkTopNFlowTests: KbnTestProvider = ({ getService }) => { '10.100.4.1,54.239.219.220,54.239.219.228,54.239.220.94,54.239.220.138,54.239.220.184,54.239.220.186,54.239.221.253,35.167.45.163,52.5.171.20' ); expect(networkTopNFlow.edges[0].node.destination).to.be(null); - expect(networkTopNFlow.pageInfo.endCursor!.value).to.equal('10'); + expect(networkTopNFlow.pageInfo.fakeTotalCount).to.equal(50); }); }); @@ -111,8 +115,10 @@ const networkTopNFlowTests: KbnTestProvider = ({ getService }) => { flowTarget: FlowTarget.source, flowDirection: FlowDirection.biDirectional, pagination: { - limit: 10, - cursor: null, + activePage: 0, + cursorStart: 0, + fakePossibleCount: 10, + querySize: 10, }, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], inspect: false, @@ -123,7 +129,7 @@ const networkTopNFlowTests: KbnTestProvider = ({ getService }) => { expect(networkTopNFlow.edges.length).to.be(EDGE_LENGTH); expect(networkTopNFlow.totalCount).to.be(10); expect(networkTopNFlow.edges[0].node.destination).to.be(null); - expect(networkTopNFlow.pageInfo.endCursor!.value).to.equal('10'); + expect(networkTopNFlow.pageInfo.fakeTotalCount).to.equal(10); }); }); @@ -142,8 +148,10 @@ const networkTopNFlowTests: KbnTestProvider = ({ getService }) => { flowTarget: FlowTarget.destination, flowDirection: FlowDirection.uniDirectional, pagination: { - limit: 10, - cursor: null, + activePage: 0, + cursorStart: 0, + fakePossibleCount: 50, + querySize: 10, }, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], inspect: false, @@ -154,7 +162,7 @@ const networkTopNFlowTests: KbnTestProvider = ({ getService }) => { expect(networkTopNFlow.edges.length).to.be(EDGE_LENGTH); expect(networkTopNFlow.totalCount).to.be(144); expect(networkTopNFlow.edges[0].node.source).to.be(null); - expect(networkTopNFlow.pageInfo.endCursor!.value).to.equal('10'); + expect(networkTopNFlow.pageInfo.fakeTotalCount).to.equal(50); }); }); @@ -173,8 +181,10 @@ const networkTopNFlowTests: KbnTestProvider = ({ getService }) => { flowTarget: FlowTarget.destination, flowDirection: FlowDirection.biDirectional, pagination: { - limit: 10, - cursor: null, + activePage: 0, + cursorStart: 0, + fakePossibleCount: 50, + querySize: 10, }, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], inspect: false, @@ -185,7 +195,7 @@ const networkTopNFlowTests: KbnTestProvider = ({ getService }) => { expect(networkTopNFlow.edges.length).to.be(EDGE_LENGTH); expect(networkTopNFlow.totalCount).to.be(89); expect(networkTopNFlow.edges[0].node.source).to.be(null); - expect(networkTopNFlow.pageInfo.endCursor!.value).to.equal('10'); + expect(networkTopNFlow.pageInfo.fakeTotalCount).to.equal(50); }); }); @@ -204,8 +214,10 @@ const networkTopNFlowTests: KbnTestProvider = ({ getService }) => { flowTarget: FlowTarget.source, flowDirection: FlowDirection.uniDirectional, pagination: { - limit: 20, - cursor: 10, + activePage: 1, + cursorStart: 10, + fakePossibleCount: 50, + querySize: 20, }, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], inspect: false, @@ -243,8 +255,10 @@ const networkTopNFlowTests: KbnTestProvider = ({ getService }) => { flowTarget: FlowTarget.client, flowDirection: FlowDirection.biDirectional, pagination: { - limit: 10, - cursor: null, + activePage: 0, + cursorStart: 0, + fakePossibleCount: 50, + querySize: 10, }, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], inspect: false, @@ -255,7 +269,7 @@ const networkTopNFlowTests: KbnTestProvider = ({ getService }) => { expect(networkTopNFlow.edges.length).to.be(1); expect(networkTopNFlow.totalCount).to.be(1); expect(networkTopNFlow.edges[0].node.server).to.be(null); - expect(networkTopNFlow.pageInfo.endCursor!.value).to.equal('10'); + expect(networkTopNFlow.pageInfo.fakeTotalCount).to.equal(1); }); }); @@ -274,8 +288,10 @@ const networkTopNFlowTests: KbnTestProvider = ({ getService }) => { flowTarget: FlowTarget.server, flowDirection: FlowDirection.biDirectional, pagination: { - limit: 10, - cursor: null, + activePage: 0, + cursorStart: 0, + fakePossibleCount: 50, + querySize: 10, }, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], inspect: false, @@ -286,7 +302,7 @@ const networkTopNFlowTests: KbnTestProvider = ({ getService }) => { expect(networkTopNFlow.edges.length).to.be(1); expect(networkTopNFlow.totalCount).to.be(1); expect(networkTopNFlow.edges[0].node.client).to.be(null); - expect(networkTopNFlow.pageInfo.endCursor!.value).to.equal('10'); + expect(networkTopNFlow.pageInfo.fakeTotalCount).to.equal(1); }); }); }); diff --git a/x-pack/test/api_integration/apis/siem/tls.ts b/x-pack/test/api_integration/apis/siem/tls.ts index 5f8a1f5d3734ad..bd7a9dfd28bae2 100644 --- a/x-pack/test/api_integration/apis/siem/tls.ts +++ b/x-pack/test/api_integration/apis/siem/tls.ts @@ -52,8 +52,10 @@ const tlsTests: KbnTestProvider = ({ getService }) => { flowTarget: FlowTarget.source, sort: { field: TlsFields._id, direction: Direction.desc }, pagination: { - limit: 10, - cursor: null, + activePage: 0, + cursorStart: 0, + fakePossibleCount: 30, + querySize: 10, }, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], inspect: false, @@ -82,8 +84,10 @@ const tlsTests: KbnTestProvider = ({ getService }) => { flowTarget: FlowTarget.destination, sort: { field: TlsFields._id, direction: Direction.desc }, pagination: { - limit: 10, - cursor: null, + activePage: 0, + cursorStart: 0, + fakePossibleCount: 30, + querySize: 10, }, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], inspect: false, diff --git a/x-pack/test/api_integration/apis/siem/uncommon_processes.ts b/x-pack/test/api_integration/apis/siem/uncommon_processes.ts index 648974b331de23..0e1ac306348952 100644 --- a/x-pack/test/api_integration/apis/siem/uncommon_processes.ts +++ b/x-pack/test/api_integration/apis/siem/uncommon_processes.ts @@ -39,7 +39,10 @@ const uncommonProcessesTests: KbnTestProvider = ({ getService }) => { from: FROM, }, pagination: { - limit: 1, + activePage: 0, + cursorStart: 0, + fakePossibleCount: 3, + querySize: 1, }, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], inspect: false, @@ -63,7 +66,10 @@ const uncommonProcessesTests: KbnTestProvider = ({ getService }) => { from: FROM, }, pagination: { - limit: 2, + activePage: 0, + cursorStart: 0, + fakePossibleCount: 3, + querySize: 2, }, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], inspect: false, @@ -87,7 +93,10 @@ const uncommonProcessesTests: KbnTestProvider = ({ getService }) => { from: FROM, }, pagination: { - limit: 1, + activePage: 0, + cursorStart: 0, + fakePossibleCount: 3, + querySize: 1, }, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], inspect: false, @@ -111,7 +120,10 @@ const uncommonProcessesTests: KbnTestProvider = ({ getService }) => { from: FROM, }, pagination: { - limit: 1, + activePage: 0, + cursorStart: 0, + fakePossibleCount: 3, + querySize: 1, }, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], inspect: false, diff --git a/x-pack/test/api_integration/apis/siem/users.ts b/x-pack/test/api_integration/apis/siem/users.ts index 7b26152e8e4d2d..25ff30c5803a93 100644 --- a/x-pack/test/api_integration/apis/siem/users.ts +++ b/x-pack/test/api_integration/apis/siem/users.ts @@ -42,8 +42,10 @@ const usersTests: KbnTestProvider = ({ getService }) => { flowTarget: FlowTarget.destination, sort: { field: UsersFields.name, direction: Direction.asc }, pagination: { - limit: 10, - cursor: null, + activePage: 0, + cursorStart: 0, + fakePossibleCount: 30, + querySize: 10, }, inspect: false, }, diff --git a/x-pack/test/functional/apps/infra/logs_source_configuration.ts b/x-pack/test/functional/apps/infra/logs_source_configuration.ts index f9b4bcca1debad..235dd729072a4b 100644 --- a/x-pack/test/functional/apps/infra/logs_source_configuration.ts +++ b/x-pack/test/functional/apps/infra/logs_source_configuration.ts @@ -98,6 +98,9 @@ export default ({ getPageObjects, getService }: KibanaFunctionalTestDefaultProvi await infraSourceConfigurationFlyout.addTimestampLogColumn(); await infraSourceConfigurationFlyout.addFieldLogColumn('host.name'); + // TODO: make test more robust + // await infraSourceConfigurationFlyout.moveLogColumn(0, 1); + await infraSourceConfigurationFlyout.saveConfiguration(); await infraSourceConfigurationFlyout.closeFlyout(); }); @@ -105,6 +108,8 @@ export default ({ getPageObjects, getService }: KibanaFunctionalTestDefaultProvi it('renders the changed log columns with their headers', async () => { const columnHeaderLabels = await infraLogStream.getColumnHeaderLabels(); + // TODO: make test more robust + // expect(columnHeaderLabels).to.eql(['host.name', 'Timestamp', '']); expect(columnHeaderLabels).to.eql(['Timestamp', 'host.name', '']); const logStreamEntries = await infraLogStream.getStreamEntries(); diff --git a/x-pack/test/functional/apps/maps/full_screen_mode.js b/x-pack/test/functional/apps/maps/full_screen_mode.js new file mode 100644 index 00000000000000..36db159704d211 --- /dev/null +++ b/x-pack/test/functional/apps/maps/full_screen_mode.js @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +export default function ({ getService, getPageObjects }) { + const PageObjects = getPageObjects(['maps', 'common']); + const browser = getService('browser'); + const retry = getService('retry'); + + describe('full screen mode', () => { + before(async () => { + await PageObjects.maps.openNewMap(); + }); + + it('full screen button should exist', async () => { + const exists = await PageObjects.maps.fullScreenModeMenuItemExists(); + expect(exists).to.be(true); + }); + + it('hides the chrome', async () => { + const isChromeVisible = await PageObjects.common.isChromeVisible(); + expect(isChromeVisible).to.be(true); + + await PageObjects.maps.clickFullScreenMode(); + + await retry.try(async () => { + const isChromeHidden = await PageObjects.common.isChromeHidden(); + expect(isChromeHidden).to.be(true); + }); + }); + + it('displays exit full screen logo button', async () => { + const exists = await PageObjects.maps.exitFullScreenLogoButtonExists(); + expect(exists).to.be(true); + }); + + it('exits when the text button is clicked on', async () => { + const logoButton = await PageObjects.maps.getExitFullScreenLogoButton(); + await browser.moveMouseTo(logoButton); + await PageObjects.maps.clickExitFullScreenTextButton(); + + await retry.try(async () => { + const isChromeVisible = await PageObjects.common.isChromeVisible(); + expect(isChromeVisible).to.be(true); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/maps/index.js b/x-pack/test/functional/apps/maps/index.js index c0acdf0019d143..5a0fa0e5d856d9 100644 --- a/x-pack/test/functional/apps/maps/index.js +++ b/x-pack/test/functional/apps/maps/index.js @@ -35,6 +35,7 @@ export default function ({ loadTestFile, getService }) { loadTestFile(require.resolve('./sample_data')); loadTestFile(require.resolve('./feature_controls/maps_security')); loadTestFile(require.resolve('./feature_controls/maps_spaces')); + loadTestFile(require.resolve('./full_screen_mode')); }); describe('', function () { diff --git a/x-pack/test/functional/page_objects/gis_page.js b/x-pack/test/functional/page_objects/gis_page.js index 042d92ab81c9ea..bd16883f5955c8 100644 --- a/x-pack/test/functional/page_objects/gis_page.js +++ b/x-pack/test/functional/page_objects/gis_page.js @@ -402,6 +402,27 @@ export function GisPageProvider({ getService, getPageObjects }) { return await testSubjects.getVisibleText(`layerErrorMessage`); } + async fullScreenModeMenuItemExists() { + return await testSubjects.exists('mapsFullScreenMode'); + } + + async clickFullScreenMode() { + log.debug(`clickFullScreenMode`); + await testSubjects.click('mapsFullScreenMode'); + } + + async exitFullScreenLogoButtonExists() { + return await testSubjects.exists('exitFullScreenModeLogo'); + } + + async getExitFullScreenLogoButton() { + return await testSubjects.find('exitFullScreenModeLogo'); + } + + async clickExitFullScreenTextButton() { + await testSubjects.click('exitFullScreenModeText'); + } + async openInspectorMapView() { await inspector.openInspectorView('inspectorViewChooserMap'); } diff --git a/x-pack/test/functional/services/infra_source_configuration_flyout.ts b/x-pack/test/functional/services/infra_source_configuration_flyout.ts index e3b9d29352eae5..4ba87c018c4124 100644 --- a/x-pack/test/functional/services/infra_source_configuration_flyout.ts +++ b/x-pack/test/functional/services/infra_source_configuration_flyout.ts @@ -13,6 +13,7 @@ export function InfraSourceConfigurationFlyoutProvider({ const find = getService('find'); const retry = getService('retry'); const testSubjects = getService('testSubjects'); + const browser = getService('browser'); return { /** @@ -81,6 +82,25 @@ export function InfraSourceConfigurationFlyoutProvider({ await this.removeLogColumn(0); } }, + async moveLogColumn(sourceIndex: number, destinationIndex: number) { + const logColumnPanel = (await this.getLogColumnPanels())[sourceIndex]; + const moveLogColumnHandle = await testSubjects.findDescendant( + 'moveLogColumnHandle', + logColumnPanel + ); + await moveLogColumnHandle.focus(); + const movementDifference = destinationIndex - sourceIndex; + await moveLogColumnHandle.pressKeys(browser.keys.SPACE); + for (let i = 0; i < Math.abs(movementDifference); i++) { + await new Promise(res => setTimeout(res, 100)); + if (movementDifference > 0) { + await moveLogColumnHandle.pressKeys(browser.keys.ARROW_DOWN); + } else { + await moveLogColumnHandle.pressKeys(browser.keys.ARROW_UP); + } + } + await moveLogColumnHandle.pressKeys(browser.keys.SPACE); + }, /** * Form and flyout diff --git a/yarn.lock b/yarn.lock index 2fd21ed8628026..f6d391088d6396 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1659,10 +1659,10 @@ tabbable "^1.1.0" uuid "^3.1.0" -"@elastic/eui@12.3.1": - version "12.3.1" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-12.3.1.tgz#89ced2cf43d5363f0cbc9cca76f6fcdfb3466276" - integrity sha512-7XQSSsyJTPyJQR+tmo14z/ek7/9PDz7jI/t1sPa14hG8CLYKhOgtYjVFeLM4hHz4F4MLwfwK1t3gMHqFxnvGeQ== +"@elastic/eui@12.4.0": + version "12.4.0" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-12.4.0.tgz#a679c498e5890fea34dab35346212022538fef5d" + integrity sha512-b3bY5cRqvbyRa8Uain1drZc6djvxxcZFfx0uQbkHOe9iK+AppHfkqQ5KeXtH3CUF55PZI56DRkVzsBbcOMMtOQ== dependencies: "@types/lodash" "^4.14.116" "@types/numeral" "^0.0.25" @@ -6711,10 +6711,10 @@ backo2@1.0.2: resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" integrity sha1-MasayLEpNjRj41s+u2n038+6eUc= -backport@4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/backport/-/backport-4.6.0.tgz#3592a3c4ad6f3b251435cc21494086f2df975ba6" - integrity sha512-7rqzXZOEWPYUebfWQDqaLPm4Oe3vCa2OJoYx4nlT18cy911TUKI/Ci+jt1c4JuhRAKX03vCNpncjEHXzNKHoyw== +backport@4.6.1: + version "4.6.1" + resolved "https://registry.yarnpkg.com/backport/-/backport-4.6.1.tgz#9e48d53447fa026157f2dc395442960470d963a6" + integrity sha512-doReAW1xAHmpv98z+Zh4wDQX0T5/i2TGfkPsHbfmA9E9XGUYcMy2YljPBiCntHvE+/ncBtw4shZu8QJqJIww/w== dependencies: axios "^0.19.0" del "^5.0.0" @@ -27964,7 +27964,7 @@ typescript-fsa@^2.0.0, typescript-fsa@^2.5.0: resolved "https://registry.yarnpkg.com/typescript-fsa/-/typescript-fsa-2.5.0.tgz#1baec01b5e8f5f34c322679d1327016e9e294faf" integrity sha1-G67AG16PXzTDImedEycBbp4pT68= -typescript@3.5.3, typescript@^3.0.3, typescript@^3.3.3333, typescript@^3.4.5, typescript@~3.0.3, typescript@~3.3.3333, typescript@~3.4.3: +typescript@3.5.1, typescript@3.5.3, typescript@^3.0.3, typescript@^3.3.3333, typescript@^3.4.5, typescript@~3.0.3, typescript@~3.3.3333, typescript@~3.4.3: version "3.5.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==