Merge remote-tracking branch 'origin/master' into feature/merge-code
|
@ -50,6 +50,8 @@ include::logs/index.asciidoc[]
|
|||
|
||||
include::apm/index.asciidoc[]
|
||||
|
||||
include::uptime/index.asciidoc[]
|
||||
|
||||
include::graph/index.asciidoc[]
|
||||
|
||||
include::dev-tools.asciidoc[]
|
||||
|
|
After Width: | Height: | Size: 127 KiB |
After Width: | Height: | Size: 748 B |
BIN
docs/infrastructure/images/infrastructure-configure-source.png
Normal file
After Width: | Height: | Size: 38 KiB |
|
@ -4,8 +4,6 @@
|
|||
|
||||
[partintro]
|
||||
--
|
||||
beta[]
|
||||
|
||||
Use the interactive Infrastructure UI to monitor your infrastructure and
|
||||
identify problems in real time. You can explore metrics and logs for common
|
||||
servers, containers, and services.
|
||||
|
@ -15,15 +13,59 @@ image::infrastructure/images/infra-sysmon.jpg[Infrastructure Overview in Kibana]
|
|||
|
||||
|
||||
[float]
|
||||
== Add data sources
|
||||
Kibana provides step-by-step instructions to help you add your data sources.
|
||||
The {infra-guide}[Infrastructure Monitoring Guide] is a good source for more detailed
|
||||
instructions and information.
|
||||
== Add data
|
||||
|
||||
Kibana provides step-by-step instructions to help you add log data. The
|
||||
{infra-guide}[Infrastructure Monitoring Guide] is a good source for more
|
||||
detailed information and instructions.
|
||||
|
||||
[float]
|
||||
== Configure data sources
|
||||
|
||||
By default the Infrastructure UI uses the `metricbeat-*` index pattern to query the data. If you configured Metricbeat to export data to a different set of indices, you will need to set `xpack.infra.sources.default.metricAlias` in `config/kibana.yml` to match your index pattern. You can also configure the timestamp field by overriding `xpack.infra.sources.default.fields.timestamp`. See <<infrastructure-ui-settings-kb>> for a complete list.
|
||||
The `metricbeat-*` index pattern is used to query the data by default.
|
||||
If your metrics are located in a different set of indices, or use a
|
||||
different timestamp field, you can adjust the source configuration via the user
|
||||
interface or the {kib} configuration file.
|
||||
|
||||
NOTE: Logs and Infrastructure share a common data source definition in
|
||||
each space. Changes in one of them can influence the data displayed in the
|
||||
other.
|
||||
|
||||
[float]
|
||||
=== Configure source
|
||||
|
||||
Configure source can be accessed via the corresponding
|
||||
image:logs/images/logs-configure-source-gear-icon.png[Configure source icon]
|
||||
button in the toolbar:
|
||||
|
||||
[role="screenshot"]
|
||||
image::infrastructure/images/infrastructure-configure-source.png[Configure Infrastructure UI source button in Kibana]
|
||||
|
||||
This opens the source configuration fly-out dialog, in which the following
|
||||
configuration items can be inspected and adjusted:
|
||||
|
||||
* *Name*: The name of the source configuration.
|
||||
* *Indices*: The patterns of the elasticsearch indices to read metrics and logs
|
||||
from.
|
||||
* *Fields*: The names of particular fields in the indices that need to be known
|
||||
to the Infrastructure and Logs UIs in order to query and interpret the data
|
||||
correctly.
|
||||
|
||||
[role="screenshot"]
|
||||
image::infrastructure/images/infrastructure-configure-source-dialog.png[Configure Infrastructure UI source dialog in Kibana]
|
||||
|
||||
TIP: If <<xpack-spaces>> are enabled in your Kibana instance, any configuration
|
||||
changes performed via Configure source are specific to that space. You can
|
||||
therefore easily make different subsets of the data available by creating
|
||||
multiple spaces with different data source configurations.
|
||||
|
||||
[float]
|
||||
=== Configuration file
|
||||
|
||||
The settings in the configuration file are used as a fallback when no other
|
||||
configuration for that space has been defined. They are located in the
|
||||
configuration namespace `xpack.infra.sources.default`. See
|
||||
<<infrastructure-ui-settings-kb>> for a complete list of the possible entries.
|
||||
|
||||
--
|
||||
|
||||
|
|
|
@ -59,4 +59,10 @@ Use the time selector to focus on a specific timeframe.
|
|||
Set auto-refresh to keep up-to-date information coming in, or stop
|
||||
refreshing to focus on historical data without new distractions.
|
||||
|
||||
[float]
|
||||
[[infra-configure-source]]
|
||||
=== Adapt to your metric source
|
||||
|
||||
Using a custom index pattern to store the metrics, or want to limit the entries
|
||||
presented in a space? Use configure source to change the index pattern and
|
||||
other settings.
|
||||
|
|
BIN
docs/logs/images/logs-configure-source-dialog.png
Normal file
After Width: | Height: | Size: 279 KiB |
BIN
docs/logs/images/logs-configure-source-gear-icon.png
Normal file
After Width: | Height: | Size: 748 B |
BIN
docs/logs/images/logs-configure-source.png
Normal file
After Width: | Height: | Size: 45 KiB |
|
@ -5,7 +5,6 @@
|
|||
[partintro]
|
||||
--
|
||||
|
||||
beta[]
|
||||
Use the Logs UI to explore logs for common servers, containers, and services.
|
||||
{kib} provides a compact, console-like display that you can customize.
|
||||
|
||||
|
@ -14,17 +13,60 @@ image::logs/images/logs-console.png[Log Console in Kibana]
|
|||
|
||||
|
||||
[float]
|
||||
== Add data sources
|
||||
== Add data
|
||||
|
||||
Kibana provides step-by-step instructions to help you add your data sources.
|
||||
The {infra-guide}[Infrastructure Monitoring Guide] is a good source for more detailed information and
|
||||
instructions.
|
||||
Kibana provides step-by-step instructions to help you add log data. The
|
||||
{infra-guide}[Infrastructure Monitoring Guide] is a good source for more
|
||||
detailed information and instructions.
|
||||
|
||||
[float]
|
||||
== Configure data sources
|
||||
|
||||
By default the Logs UI uses the `filebeat-*` index pattern to query the data. If your logs are located in a different set of indices, you will need to set `xpack.infra.sources.default.logAlias` in `config/kibana.yml` to match your log's index pattern. You can also configure the timestamp field by overriding `xpack.infra.sources.default.fields.timestamp`, by default it is set to `@timestamp`. See <<logs-ui-settings-kb>> for a complete list.
|
||||
The `filebeat-*` index pattern is used to query data by default.
|
||||
If your logs are located in a different set of indices, or use a different
|
||||
timestamp field, you can adjust the source configuration via the user interface
|
||||
or the {kib} configuration file.
|
||||
|
||||
NOTE: Logs and Infrastructure share a common data source definition in
|
||||
each space. Changes in one of them can influence the data displayed in the
|
||||
other.
|
||||
|
||||
[float]
|
||||
=== Configure source
|
||||
|
||||
Configure source can be accessed via the corresponding
|
||||
image:logs/images/logs-configure-source-gear-icon.png[Configure source icon]
|
||||
button in the toolbar.
|
||||
|
||||
[role="screenshot"]
|
||||
image::logs/images/logs-configure-source.png[Configure Logs UI source button in Kibana]
|
||||
|
||||
This opens the source configuration fly-out dialog, in which the following
|
||||
configuration items can be inspected and adjusted:
|
||||
|
||||
* *Name*: The name of the source configuration.
|
||||
* *Indices*: The patterns of the elasticsearch indices to read metrics and logs
|
||||
from.
|
||||
* *Fields*: The names of particular fields in the indices that need to be known
|
||||
to the Infrastructure and Logs UIs in order to query and interpret the data
|
||||
correctly.
|
||||
|
||||
[role="screenshot"]
|
||||
image::logs/images/logs-configure-source-dialog.png[Configure logs UI source dialog in Kibana]
|
||||
|
||||
TIP: If <<xpack-spaces>> are enabled in your Kibana instance, any configuration
|
||||
changes performed via Configure source are specific to that space. You can
|
||||
therefore easily make different subsets of the data available by creating
|
||||
multiple spaces with different data source configurations.
|
||||
|
||||
[float]
|
||||
=== Configuration file
|
||||
|
||||
The settings in the configuration file are used as a fallback when no other
|
||||
configuration for that space has been defined. They are located in the
|
||||
configuration namespace `xpack.infra.sources.default`. See
|
||||
<<logs-ui-settings-kb>> for a complete list of the possible entries.
|
||||
|
||||
--
|
||||
|
||||
include::logs-ui.asciidoc[]
|
||||
include::logs-ui.asciidoc[]
|
||||
|
|
|
@ -12,6 +12,13 @@ image::logs/images/logs-console.png[Log Console in Kibana]
|
|||
=== Use the power of Search
|
||||
The Search bar is always available. Use it to perform adhoc and structured searches.
|
||||
|
||||
[float]
|
||||
[[logs-configure-source]]
|
||||
=== Adapt to your log source
|
||||
Using a custom index pattern to store the log entries, or want to limit the
|
||||
entries presented in a space? Use configure source to change the index pattern
|
||||
and other settings.
|
||||
|
||||
[float]
|
||||
[[logs-time]]
|
||||
=== Jump to a specific time period
|
||||
|
@ -31,7 +38,4 @@ Use *Customize* to adjust your console view and to set the time scale of the log
|
|||
=== Stream or pause logs
|
||||
You can stream data for live log tailing, or pause streaming to focus on historical log data.
|
||||
When you are streaming logs, the most recent log appears at the bottom on the console.
|
||||
Historical data offers infinite scrolling.
|
||||
|
||||
|
||||
|
||||
Historical data offers infinite scrolling.
|
|
@ -1,6 +1,6 @@
|
|||
`xpack.infra.enabled`:: Set to `false` to disable the Logs and Infrastructure UI plugin {kib}. Defaults to `true`.
|
||||
|
||||
`xpack.infra.sources.default.logAlias`:: Index pattern for matching indices that contain log data. Defaults to `filebeat-*`.
|
||||
`xpack.infra.sources.default.logAlias`:: Index pattern for matching indices that contain log data. Defaults to `filebeat-*,kibana_sample_data_logs*`.
|
||||
|
||||
`xpack.infra.sources.default.metricAlias`:: Index pattern for matching indices that contain Metricbeat data. Defaults to `metricbeat-*`.
|
||||
|
||||
|
|
BIN
docs/uptime/images/check-history.png
Normal file
After Width: | Height: | Size: 295 KiB |
BIN
docs/uptime/images/crosshair-example.png
Normal file
After Width: | Height: | Size: 97 KiB |
BIN
docs/uptime/images/error-list.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
docs/uptime/images/filter-bar.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
docs/uptime/images/monitor-charts.png
Normal file
After Width: | Height: | Size: 118 KiB |
BIN
docs/uptime/images/monitor-list.png
Normal file
After Width: | Height: | Size: 208 KiB |
BIN
docs/uptime/images/snapshot-view.png
Normal file
After Width: | Height: | Size: 82 KiB |
BIN
docs/uptime/images/status-bar.png
Normal file
After Width: | Height: | Size: 33 KiB |
28
docs/uptime/index.asciidoc
Normal file
|
@ -0,0 +1,28 @@
|
|||
[role="xpack"]
|
||||
[[xpack-uptime]]
|
||||
= Uptime
|
||||
|
||||
[partintro]
|
||||
--
|
||||
Use the Uptime UI to monitor the status of network endpoints via HTTP/S, TCP,
|
||||
and ICMP. You will be able to explore status over time, drill into specific monitors,
|
||||
and view a high-level snapshot of your environment at a selected point in time.
|
||||
|
||||
[float]
|
||||
== Add monitors
|
||||
To get started with Uptime monitoring, you'll need to define some monitors and run Heartbeat.
|
||||
These monitors will provide the data we will be visualizing in the Uptime UI.
|
||||
See {heartbeat-ref}/heartbeat-configuration.html[Configure Heartbeat] for instructions
|
||||
on configuring monitors to begin storing Uptime information in your cluster.
|
||||
|
||||
[float]
|
||||
== Uptime, Heartbeat, and Kibana
|
||||
For Uptime to work, it is important you use the same major versions of Heartbeat and Kibana.
|
||||
For example, version 6.7 of Kibana will expect an index of `heartbeat-6*`,
|
||||
while Kibana 7.0 requires an index of `heartbeat-7*` (containing documents from Heartbeat 7.0).
|
||||
|
||||
--
|
||||
|
||||
include::overview.asciidoc[]
|
||||
include::monitor.asciidoc[]
|
||||
include::security.asciidoc[]
|
54
docs/uptime/monitor.asciidoc
Normal file
|
@ -0,0 +1,54 @@
|
|||
[role="xpack"]
|
||||
[[uptime-monitor]]
|
||||
== Monitor
|
||||
|
||||
The Monitor page will help you get further insight into the performance
|
||||
of a specific network endpoint. You'll see a detailed visualization of
|
||||
the monitor's request duration over time, as well as the `up`/`down`
|
||||
status over time.
|
||||
|
||||
[float]
|
||||
=== Status bar
|
||||
|
||||
[role="screenshot"]
|
||||
image::uptime/images/status-bar.png[Status bar]
|
||||
|
||||
The Status bar displays a quick summary of the latest information
|
||||
regarding your monitor. You can view its latest status, click a link to
|
||||
visit the targeted URL, see its most recent request duration, and determine the
|
||||
amount of time that has elapsed since the last check.
|
||||
|
||||
You can use the Status bar to get a quick summary of current performance,
|
||||
beyond simply knowing if the monitor is `up` or `down`.
|
||||
|
||||
[float]
|
||||
=== Monitor charts
|
||||
|
||||
[role="screenshot"]
|
||||
image::uptime/images/monitor-charts.png[Monitor charts]
|
||||
|
||||
The Monitor charts visualize information over the time specified in the
|
||||
date range. These charts can help you gain insight into how quickly requests are being resolved
|
||||
by the targeted endpoint, and give you a sense of how frequently a host or endpoint
|
||||
was down in your selected timespan.
|
||||
|
||||
The first chart displays request duration information for your monitor.
|
||||
The area surrounding the line is the range of request time for the corresponding
|
||||
bucket. The line is the average time.
|
||||
|
||||
Next, is a graphical representation of the check statuses over time. Hover over
|
||||
the charts to display crosshairs with more specific numeric data.
|
||||
|
||||
[role="screenshot"]
|
||||
image::uptime/images/crosshair-example.png[Chart crosshair]
|
||||
|
||||
[float]
|
||||
=== Check history
|
||||
|
||||
[role="screenshot"]
|
||||
image::uptime/images/check-history.png[Check history view]
|
||||
|
||||
The Check history displays the total count of this monitor's checks for the selected
|
||||
date range. You can additionally filter the checks by `status` to help find recent problems
|
||||
on a per-check basis. This table can help you gain some insight into more granular details
|
||||
about recent individual data points Heartbeat is logging about your host or endpoint.
|
61
docs/uptime/overview.asciidoc
Normal file
|
@ -0,0 +1,61 @@
|
|||
[role="xpack"]
|
||||
[[uptime-overview]]
|
||||
|
||||
== Overview
|
||||
|
||||
The Uptime overview is intended to help you quickly identify and diagnose outages and
|
||||
other connectivity issues within your network or environment. There is a date range
|
||||
selection that is global to the Uptime UI; you can use this selection to highlight
|
||||
an absolute date range, or a relative one, similar to other areas of Kibana.
|
||||
|
||||
[float]
|
||||
=== Filter bar
|
||||
|
||||
[role="screenshot"]
|
||||
image::uptime/images/filter-bar.png[Filter bar]
|
||||
|
||||
The filter bar is designed to let you quickly view specific groups of monitors, or even
|
||||
an individual monitor, if you have defined many.
|
||||
|
||||
This control allows you to use automated filter options, as well as input custom filter
|
||||
text to select specific monitors by field, URL, ID, and other attributes.
|
||||
|
||||
[float]
|
||||
=== Snapshot view
|
||||
|
||||
[role="screenshot"]
|
||||
image::uptime/images/snapshot-view.png[Snapshot view]
|
||||
|
||||
This view is intended to quickly give you a sense of the overall
|
||||
status of the environment you're monitoring, or a subset of those monitors.
|
||||
Here, you can see the total number of detected monitors within the selected
|
||||
Uptime date range. In addition to the total, the counts for the number of monitors
|
||||
in an `up` or `down` state are displayed, based on the last check reported by Heartbeat
|
||||
for each monitor.
|
||||
|
||||
Next to the counts, there is a histogram displaying the change over time throughout the
|
||||
selected date range.
|
||||
|
||||
[float]
|
||||
=== Monitor list
|
||||
|
||||
[role="screenshot"]
|
||||
image::uptime/images/monitor-list.png[Monitor list]
|
||||
|
||||
The Monitor list displays information at the level of individual monitors.
|
||||
The data shown here will flesh out your individual monitors, and provide a quick
|
||||
way to navigate to a more in-depth visualization for interesting hosts or endpoints.
|
||||
|
||||
This table includes information like the most recent status, when the monitor was last checked, its
|
||||
ID and URL, its IP address, and a dedicated sparkline showing its check status over time.
|
||||
|
||||
[float]
|
||||
=== Error list
|
||||
|
||||
[role="screenshot"]
|
||||
image::uptime/images/error-list.png[Error list]
|
||||
|
||||
The Error list displays aggregations of errors that Heartbeat has logged. Errors are
|
||||
displayed by Error type, monitor ID, and message. Clicking a monitor's ID will take you
|
||||
to the corresponding Monitor view, which can provide you richer information about the individual
|
||||
data points that are resulting in the displayed errors.
|
73
docs/uptime/security.asciidoc
Normal file
|
@ -0,0 +1,73 @@
|
|||
[role="xpack"]
|
||||
[[uptime-security]]
|
||||
|
||||
== Use with Elasticsearch Security
|
||||
|
||||
If you use Elasticsearch security, you'll need to enable certain privileges for users
|
||||
that would like to access the Uptime app. Below is an example of creating
|
||||
a user and support role to implement those privileges.
|
||||
|
||||
[float]
|
||||
=== Create a role
|
||||
|
||||
You'll need a role that lets you access the Heartbeat indices, which by default are `heartbeat-*`.
|
||||
You can create this with the following request:
|
||||
|
||||
["source","sh",subs="attributes,callouts"]
|
||||
---------------------------------------------------------------
|
||||
PUT /_security/role/uptime
|
||||
{ "indices" : [
|
||||
{
|
||||
"names" : [
|
||||
"heartbeat-*"
|
||||
],
|
||||
"privileges" : [
|
||||
"read",
|
||||
"view_index_metadata"
|
||||
],
|
||||
"field_security" : {
|
||||
"grant" : [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
"allow_restricted_indices" : false
|
||||
}
|
||||
],
|
||||
"applications" : [
|
||||
{
|
||||
"application" : "kibana-.kibana",
|
||||
"privileges" : [
|
||||
"all"
|
||||
],
|
||||
"resources" : [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"transient_metadata" : {
|
||||
"enabled" : true
|
||||
}
|
||||
}
|
||||
---------------------------------------------------------------
|
||||
// CONSOLE
|
||||
|
||||
[float]
|
||||
=== Assign the role to a user
|
||||
|
||||
Next, you'll need to create a user with both the `kibana_user`, and `uptime` roles.
|
||||
You can do this with the following request:
|
||||
|
||||
["source","sh",subs="attributes,callouts"]
|
||||
---------------------------------------------------------------
|
||||
PUT /_security/user/jacknich
|
||||
{
|
||||
"password" : "j@rV1s",
|
||||
"roles" : [ "uptime", "kibana_user" ],
|
||||
"full_name" : "Jack Nicholson",
|
||||
"email" : "jacknich@example.com",
|
||||
"metadata" : {
|
||||
"intelligence" : 7
|
||||
}
|
||||
}
|
||||
---------------------------------------------------------------
|
||||
// CONSOLE
|
|
@ -96,7 +96,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@elastic/datemath": "5.0.2",
|
||||
"@elastic/eui": "9.4.2",
|
||||
"@elastic/eui": "9.5.0",
|
||||
"@elastic/filesaver": "1.1.2",
|
||||
"@elastic/good": "8.1.1-kibana2",
|
||||
"@elastic/numeral": "2.3.2",
|
||||
|
|
|
@ -26,6 +26,7 @@ exports[`rendering render matches snapshot 1`] = `
|
|||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -126,6 +126,7 @@ exports[`renders ControlsTab 1`] = `
|
|||
iconSide="left"
|
||||
iconType="plusInCircle"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -48,6 +48,7 @@ exports[`Apply and Cancel change btns enabled when there are changes 1`] = `
|
|||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -145,6 +146,7 @@ exports[`Clear btns enabled when there are values 1`] = `
|
|||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -242,6 +244,7 @@ exports[`Renders list control 1`] = `
|
|||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -345,6 +348,7 @@ exports[`Renders range control 1`] = `
|
|||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -70,6 +70,7 @@ exports[`after fetch initialFilter 1`] = `
|
|||
iconSide="left"
|
||||
iconType="plusInCircle"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -167,6 +168,7 @@ exports[`after fetch renders call to action when no dashboards exist 1`] = `
|
|||
iconSide="left"
|
||||
iconType="plusInCircle"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -264,6 +266,7 @@ exports[`after fetch renders table rows 1`] = `
|
|||
iconSide="left"
|
||||
iconType="plusInCircle"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -361,6 +364,7 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = `
|
|||
iconSide="left"
|
||||
iconType="plusInCircle"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -458,6 +462,7 @@ exports[`renders empty page in before initial fetch to avoid flickering 1`] = `
|
|||
iconSide="left"
|
||||
iconType="plusInCircle"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -47,6 +47,7 @@ exports[`render 1`] = `
|
|||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -49,6 +49,7 @@ exports[`renders DashboardCloneModal 1`] = `
|
|||
fill={false}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -64,6 +65,7 @@ exports[`renders DashboardCloneModal 1`] = `
|
|||
iconSide="left"
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -60,6 +60,7 @@ exports[`render 1`] = `
|
|||
href="#/management/kibana/objects?_a=(tab:search)"
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -55,6 +55,7 @@ exports[`apmUiEnabled 1`] = `
|
|||
fill={false}
|
||||
href="#/home/tutorial/apm"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -89,6 +90,7 @@ exports[`apmUiEnabled 1`] = `
|
|||
fill={false}
|
||||
href="#/home/tutorial_directory/logging"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -123,6 +125,7 @@ exports[`apmUiEnabled 1`] = `
|
|||
fill={false}
|
||||
href="#/home/tutorial_directory/metrics"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -157,6 +160,7 @@ exports[`apmUiEnabled 1`] = `
|
|||
fill={false}
|
||||
href="#/home/tutorial_directory/security"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -320,6 +324,7 @@ exports[`isNewKibanaInstance 1`] = `
|
|||
fill={false}
|
||||
href="#/home/tutorial_directory/logging"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -354,6 +359,7 @@ exports[`isNewKibanaInstance 1`] = `
|
|||
fill={false}
|
||||
href="#/home/tutorial_directory/metrics"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -388,6 +394,7 @@ exports[`isNewKibanaInstance 1`] = `
|
|||
fill={false}
|
||||
href="#/home/tutorial_directory/security"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -551,6 +558,7 @@ exports[`mlEnabled 1`] = `
|
|||
fill={false}
|
||||
href="#/home/tutorial/apm"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -585,6 +593,7 @@ exports[`mlEnabled 1`] = `
|
|||
fill={false}
|
||||
href="#/home/tutorial_directory/logging"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -619,6 +628,7 @@ exports[`mlEnabled 1`] = `
|
|||
fill={false}
|
||||
href="#/home/tutorial_directory/metrics"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -653,6 +663,7 @@ exports[`mlEnabled 1`] = `
|
|||
fill={false}
|
||||
href="#/home/tutorial_directory/security"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -855,6 +866,7 @@ exports[`render 1`] = `
|
|||
fill={false}
|
||||
href="#/home/tutorial_directory/logging"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -889,6 +901,7 @@ exports[`render 1`] = `
|
|||
fill={false}
|
||||
href="#/home/tutorial_directory/metrics"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -923,6 +936,7 @@ exports[`render 1`] = `
|
|||
fill={false}
|
||||
href="#/home/tutorial_directory/security"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -102,6 +102,7 @@ exports[`home directories should not render directory entry when showOnHomePage
|
|||
fill={false}
|
||||
href="#/home/feature_directory"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -231,6 +232,7 @@ exports[`home directories should render ADMIN directory entry in "Manage" panel
|
|||
fill={false}
|
||||
href="#/home/feature_directory"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -360,6 +362,7 @@ exports[`home directories should render DATA directory entry in "Explore Data" p
|
|||
fill={false}
|
||||
href="#/home/feature_directory"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -476,6 +479,7 @@ exports[`home isNewKibanaInstance should safely handle execeptions 1`] = `
|
|||
fill={false}
|
||||
href="#/home/feature_directory"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -592,6 +596,7 @@ exports[`home isNewKibanaInstance should set isNewKibanaInstance to false when t
|
|||
fill={false}
|
||||
href="#/home/feature_directory"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -708,6 +713,7 @@ exports[`home isNewKibanaInstance should set isNewKibanaInstance to true when th
|
|||
fill={false}
|
||||
href="#/home/feature_directory"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -824,6 +830,7 @@ exports[`home should render home component 1`] = `
|
|||
fill={false}
|
||||
href="#/home/feature_directory"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -940,6 +947,7 @@ exports[`home welcome should show the normal home page if loading fails 1`] = `
|
|||
fill={false}
|
||||
href="#/home/feature_directory"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -1056,6 +1064,7 @@ exports[`home welcome should show the normal home page if welcome screen is disa
|
|||
fill={false}
|
||||
href="#/home/feature_directory"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -1179,6 +1188,7 @@ exports[`home welcome stores skip welcome setting if skipped 1`] = `
|
|||
fill={false}
|
||||
href="#/home/feature_directory"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -11,6 +11,7 @@ exports[`should render popover when appLinks is not empty 1`] = `
|
|||
iconSide="right"
|
||||
iconType="arrowDown"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
View data
|
||||
|
@ -62,6 +63,7 @@ exports[`should render simple button when appLinks is empty 1`] = `
|
|||
fill={false}
|
||||
href="root/app/kibana#/dashboard/722b74f0-b882-11e8-a6d9-e546fe2bba5f"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
View data
|
||||
|
|
|
@ -31,6 +31,7 @@ exports[`render 1`] = `
|
|||
fill={true}
|
||||
href="/app/myapp"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
launch myapp
|
||||
|
|
|
@ -182,6 +182,7 @@ exports[`statusCheckState checking status 1`] = `
|
|||
iconSide="left"
|
||||
isLoading={true}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
custom btn label
|
||||
|
@ -300,6 +301,7 @@ exports[`statusCheckState failed status check - error 1`] = `
|
|||
iconSide="left"
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
custom btn label
|
||||
|
@ -423,6 +425,7 @@ exports[`statusCheckState failed status check - no data 1`] = `
|
|||
iconSide="left"
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
custom btn label
|
||||
|
@ -546,6 +549,7 @@ exports[`statusCheckState initial state - no check has been attempted 1`] = `
|
|||
iconSide="left"
|
||||
isLoading={true}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
custom btn label
|
||||
|
@ -664,6 +668,7 @@ exports[`statusCheckState successful status check 1`] = `
|
|||
iconSide="left"
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
custom btn label
|
||||
|
|
|
@ -32,6 +32,7 @@ exports[`props exportedFieldsUrl 1`] = `
|
|||
href="exported_fields_url"
|
||||
iconSide="left"
|
||||
rel="noopener"
|
||||
size="m"
|
||||
target="_blank"
|
||||
type="button"
|
||||
>
|
||||
|
|
|
@ -146,6 +146,7 @@ exports[`bulkCreate should display error message when bulkCreate request fails 1
|
|||
iconSide="left"
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
Load Kibana objects
|
||||
|
@ -257,6 +258,7 @@ exports[`bulkCreate should display error message when bulkCreate request fails 1
|
|||
iconSide="left"
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<button
|
||||
|
@ -462,6 +464,7 @@ exports[`bulkCreate should display success message when bulkCreate is successful
|
|||
iconSide="left"
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
Load Kibana objects
|
||||
|
@ -611,6 +614,7 @@ exports[`bulkCreate should display success message when bulkCreate is successful
|
|||
iconSide="left"
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<button
|
||||
|
@ -702,6 +706,7 @@ exports[`renders 1`] = `
|
|||
iconSide="left"
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
Load Kibana objects
|
||||
|
|
|
@ -62,6 +62,7 @@ exports[`EmptyState should render normally 1`] = `
|
|||
iconSide="left"
|
||||
iconType="refresh"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -101,6 +101,7 @@ exports[`Header should mark the input as invalid 1`] = `
|
|||
iconType="arrowRight"
|
||||
isDisabled={true}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -211,6 +212,7 @@ exports[`Header should render normally 1`] = `
|
|||
iconType="arrowRight"
|
||||
isDisabled={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -39,6 +39,7 @@ exports[`Header should render normally 1`] = `
|
|||
fill={false}
|
||||
href=""
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -21,6 +21,7 @@ exports[`AddFilter should ignore strings with just spaces 1`] = `
|
|||
iconSide="left"
|
||||
isDisabled={true}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -54,6 +55,7 @@ exports[`AddFilter should render normally 1`] = `
|
|||
iconSide="left"
|
||||
isDisabled={true}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -33,6 +33,7 @@ exports[`Table should render normally 1`] = `
|
|||
iconType="trash"
|
||||
isDisabled={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -48,6 +49,7 @@ exports[`Table should render normally 1`] = `
|
|||
iconType="exportAction"
|
||||
isDisabled={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -106,6 +106,8 @@
|
|||
// Adapted from https://github.com/vega/vega-tooltip
|
||||
|
||||
.vgaVis__tooltip {
|
||||
max-width: 100%;
|
||||
|
||||
h2 {
|
||||
margin-bottom: $euiSizeS;
|
||||
}
|
||||
|
@ -126,4 +128,13 @@
|
|||
max-width: $euiSizeL * 10;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: map-get($euiBreakpoints, 'm')){
|
||||
td.key {
|
||||
max-width: $euiSize * 6;
|
||||
}
|
||||
td.value {
|
||||
max-width: $euiSize * 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ exports[`SavedObjectSaveModal should render matching snapshot 1`] = `
|
|||
fill={false}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -72,6 +73,7 @@ exports[`SavedObjectSaveModal should render matching snapshot 1`] = `
|
|||
isDisabled={false}
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -37,6 +37,7 @@ import {
|
|||
EuiPagination,
|
||||
EuiTablePagination,
|
||||
} from '@elastic/eui';
|
||||
import { IconType } from '@elastic/eui';
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import * as sinon from 'sinon';
|
||||
|
@ -63,7 +64,7 @@ describe('SavedObjectsFinder', () => {
|
|||
{
|
||||
type: 'search',
|
||||
name: 'Search',
|
||||
getIconForSavedObject: () => 'search',
|
||||
getIconForSavedObject: () => 'search' as IconType,
|
||||
showSavedObject: () => true,
|
||||
},
|
||||
];
|
||||
|
@ -224,7 +225,7 @@ describe('SavedObjectsFinder', () => {
|
|||
{
|
||||
type: 'vis',
|
||||
name: 'Vis',
|
||||
getIconForSavedObject: () => 'visualization',
|
||||
getIconForSavedObject: () => 'visLine',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
@ -249,12 +250,12 @@ describe('SavedObjectsFinder', () => {
|
|||
{
|
||||
type: 'search',
|
||||
name: 'Search',
|
||||
getIconForSavedObject: () => 'search',
|
||||
getIconForSavedObject: () => 'search' as IconType,
|
||||
},
|
||||
{
|
||||
type: 'vis',
|
||||
name: 'Vis',
|
||||
getIconForSavedObject: () => 'document',
|
||||
getIconForSavedObject: () => 'document' as IconType,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ import {
|
|||
EuiPopover,
|
||||
EuiSpacer,
|
||||
EuiTablePagination,
|
||||
IconType,
|
||||
} from '@elastic/eui';
|
||||
import { Direction } from '@elastic/eui/src/services/sort/sort_direction';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -60,7 +61,7 @@ const FixedEuiContextMenuPanel = (EuiContextMenuPanel as any) as React.FunctionC
|
|||
export interface SavedObjectMetaData<T extends SavedObjectAttributes> {
|
||||
type: string;
|
||||
name: string;
|
||||
getIconForSavedObject(savedObject: SimpleSavedObject<T>): string | undefined;
|
||||
getIconForSavedObject(savedObject: SimpleSavedObject<T>): IconType;
|
||||
getTooltipForSavedObject?(savedObject: SimpleSavedObject<T>): string;
|
||||
showSavedObject?(savedObject: SimpleSavedObject<T>): boolean;
|
||||
}
|
||||
|
|
|
@ -91,7 +91,8 @@ export default function ({ getService, getPageObjects }) {
|
|||
await dashboardExpect.vegaTextsDoNotExist(['5,000']);
|
||||
};
|
||||
|
||||
describe('dashboard embeddable rendering', function describeIndexTests() {
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/33504
|
||||
describe.skip('dashboard embeddable rendering', function describeIndexTests() {
|
||||
before(async () => {
|
||||
await PageObjects.dashboard.clickNewDashboard();
|
||||
|
||||
|
|
1
typings/@elastic/eui/index.d.ts
vendored
|
@ -25,7 +25,6 @@ declare module '@elastic/eui' {
|
|||
export const EuiCopy: React.SFC<any>;
|
||||
export const EuiOutsideClickDetector: React.SFC<any>;
|
||||
export const EuiSideNav: React.SFC<any>;
|
||||
export const EuiListGroupItem: React.FunctionComponent<any>;
|
||||
|
||||
export interface EuiTableCriteria {
|
||||
page: { index: number; size: number };
|
||||
|
|
|
@ -154,7 +154,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@elastic/datemath": "5.0.2",
|
||||
"@elastic/eui": "9.4.2",
|
||||
"@elastic/eui": "9.5.0",
|
||||
"@elastic/javascript-typescript-langserver": "^0.1.19",
|
||||
"@elastic/lsp-extension": "^0.1.1",
|
||||
"@elastic/node-crypto": "0.1.2",
|
||||
|
|
|
@ -54,10 +54,17 @@ export class MachineLearningFlyout extends Component<FlyoutProps, FlyoutState> {
|
|||
hasMLJob: false,
|
||||
selectedTransactionType: this.props.urlParams.transactionType
|
||||
};
|
||||
public willUnmount = false;
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.willUnmount = true;
|
||||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
const indexPattern = await getAPMIndexPattern();
|
||||
this.setState({ hasIndexPattern: !!indexPattern });
|
||||
if (!this.willUnmount) {
|
||||
this.setState({ hasIndexPattern: !!indexPattern });
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This should use `getDerivedStateFromProps`
|
||||
|
|
|
@ -5,15 +5,13 @@
|
|||
*/
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { getServiceDetails } from 'x-pack/plugins/apm/public/store/reactReduxRequest/serviceDetails';
|
||||
import { IReduxState } from 'x-pack/plugins/apm/public/store/rootReducer';
|
||||
import { selectIsMLAvailable } from 'x-pack/plugins/apm/public/store/selectors/license';
|
||||
import { ServiceIntegrationsView } from './view';
|
||||
|
||||
function mapStateToProps(state = {} as IReduxState) {
|
||||
return {
|
||||
mlAvailable: selectIsMLAvailable(state),
|
||||
serviceDetails: getServiceDetails(state).data
|
||||
mlAvailable: selectIsMLAvailable(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -22,9 +22,7 @@ import { WatcherFlyout } from './WatcherFlyout';
|
|||
export interface ServiceIntegrationProps {
|
||||
mlAvailable: boolean;
|
||||
location: Location;
|
||||
serviceDetails: {
|
||||
types: string[];
|
||||
};
|
||||
transactionTypes: string[];
|
||||
urlParams: IUrlParams;
|
||||
}
|
||||
interface ServiceIntegrationState {
|
||||
|
@ -171,7 +169,7 @@ export class ServiceIntegrationsView extends React.Component<
|
|||
isOpen={this.state.activeFlyout === 'ML'}
|
||||
onClose={this.closeFlyouts}
|
||||
urlParams={this.props.urlParams}
|
||||
serviceTransactionTypes={this.props.serviceDetails.types}
|
||||
serviceTransactionTypes={this.props.transactionTypes}
|
||||
/>
|
||||
<WatcherFlyout
|
||||
location={this.props.location}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui';
|
||||
import { Location } from 'history';
|
||||
import React from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import { ServiceDetailsRequest } from 'x-pack/plugins/apm/public/store/reactReduxRequest/serviceDetails';
|
||||
import { IUrlParams } from 'x-pack/plugins/apm/public/store/urlParams';
|
||||
// @ts-ignore
|
||||
|
@ -23,36 +23,39 @@ export class ServiceDetailsView extends React.Component<ServiceDetailsProps> {
|
|||
public render() {
|
||||
const { urlParams, location } = this.props;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="l">
|
||||
<h1>{urlParams.serviceName}</h1>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ServiceIntegrations
|
||||
location={this.props.location}
|
||||
urlParams={urlParams}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<ServiceDetailsRequest
|
||||
urlParams={urlParams}
|
||||
render={({ data }) => {
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="l">
|
||||
<h1>{urlParams.serviceName}</h1>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ServiceIntegrations
|
||||
transactionTypes={data.types}
|
||||
location={this.props.location}
|
||||
urlParams={urlParams}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer />
|
||||
<EuiSpacer />
|
||||
|
||||
<FilterBar />
|
||||
<FilterBar />
|
||||
|
||||
<ServiceDetailsRequest
|
||||
urlParams={urlParams}
|
||||
render={({ data }) => (
|
||||
<ServiceDetailTabs
|
||||
location={location}
|
||||
urlParams={urlParams}
|
||||
transactionTypes={data.types}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</React.Fragment>
|
||||
<ServiceDetailTabs
|
||||
location={location}
|
||||
urlParams={urlParams}
|
||||
transactionTypes={data.types}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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 { EuiBadge, EuiToolTip } from '@elastic/eui';
|
||||
import euiThemeLight from '@elastic/eui/dist/eui_theme_light.json';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { Fragment } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { idx } from 'x-pack/plugins/apm/common/idx';
|
||||
import { KibanaLink } from 'x-pack/plugins/apm/public/components/shared/Links/KibanaLink';
|
||||
import { legacyEncodeURIComponent } from 'x-pack/plugins/apm/public/components/shared/Links/url_helpers';
|
||||
import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction';
|
||||
import { fontSize } from '../../../../style/variables';
|
||||
|
||||
const LinkLabel = styled.span`
|
||||
font-size: ${fontSize};
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
errorCount: number;
|
||||
transaction: Transaction;
|
||||
verbose?: boolean;
|
||||
}
|
||||
|
||||
export const ErrorCountBadge: React.SFC<Props> = ({
|
||||
errorCount = 0,
|
||||
transaction,
|
||||
verbose
|
||||
}) => {
|
||||
const toolTipContent = i18n.translate(
|
||||
'xpack.apm.transactionDetails.errorsOverviewLinkTooltip',
|
||||
{
|
||||
values: { errorCount },
|
||||
defaultMessage:
|
||||
'{errorCount, plural, one {View 1 related error} other {View # related errors}}'
|
||||
}
|
||||
);
|
||||
|
||||
const errorCountBadge = (
|
||||
<EuiBadge
|
||||
color={euiThemeLight.euiColorDanger}
|
||||
onClick={(event: any) => {
|
||||
(event as MouseEvent).stopPropagation();
|
||||
}}
|
||||
onClickAriaLabel={toolTipContent}
|
||||
>
|
||||
{errorCount}
|
||||
</EuiBadge>
|
||||
);
|
||||
|
||||
return (
|
||||
<KibanaLink
|
||||
pathname={'/app/apm'}
|
||||
hash={`/${idx(transaction, _ => _.service.name)}/errors`}
|
||||
query={{
|
||||
kuery: legacyEncodeURIComponent(
|
||||
`trace.id : "${transaction.trace.id}" and transaction.id : "${
|
||||
transaction.transaction.id
|
||||
}"`
|
||||
)
|
||||
}}
|
||||
color="danger"
|
||||
style={{ textDecoration: 'none' }}
|
||||
>
|
||||
{verbose ? (
|
||||
<Fragment>
|
||||
{errorCountBadge}
|
||||
<LinkLabel>
|
||||
|
||||
{i18n.translate('xpack.apm.transactionDetails.errorsOverviewLink', {
|
||||
values: { errorCount },
|
||||
defaultMessage:
|
||||
'{errorCount, plural, one {Related error} other {Related errors}}'
|
||||
})}
|
||||
</LinkLabel>
|
||||
</Fragment>
|
||||
) : (
|
||||
<EuiToolTip content={toolTipContent}>{errorCountBadge}</EuiToolTip>
|
||||
)}
|
||||
</KibanaLink>
|
||||
);
|
||||
};
|
|
@ -8,8 +8,6 @@ import { i18n } from '@kbn/i18n';
|
|||
import React from 'react';
|
||||
import { NOT_AVAILABLE_LABEL } from 'x-pack/plugins/apm/common/i18n';
|
||||
import { idx } from 'x-pack/plugins/apm/common/idx';
|
||||
import { KibanaLink } from 'x-pack/plugins/apm/public/components/shared/Links/KibanaLink';
|
||||
import { legacyEncodeURIComponent } from 'x-pack/plugins/apm/public/components/shared/Links/url_helpers';
|
||||
import {
|
||||
TRANSACTION_DURATION,
|
||||
TRANSACTION_RESULT,
|
||||
|
@ -22,6 +20,7 @@ import {
|
|||
IStickyProperty,
|
||||
StickyProperties
|
||||
} from '../../../shared/StickyProperties';
|
||||
import { ErrorCountBadge } from './ErrorCountBadge';
|
||||
|
||||
interface Props {
|
||||
transaction: Transaction;
|
||||
|
@ -41,26 +40,6 @@ export function StickyTransactionProperties({
|
|||
NOT_AVAILABLE_LABEL;
|
||||
const duration = transaction.transaction.duration.us;
|
||||
|
||||
const errorsOverviewLink = (
|
||||
<KibanaLink
|
||||
pathname={'/app/apm'}
|
||||
hash={`/${idx(transaction, _ => _.service.name)}/errors`}
|
||||
query={{
|
||||
kuery: legacyEncodeURIComponent(
|
||||
`trace.id : "${transaction.trace.id}" and transaction.id : "${
|
||||
transaction.transaction.id
|
||||
}"`
|
||||
)
|
||||
}}
|
||||
>
|
||||
{i18n.translate('xpack.apm.transactionDetails.errorsOverviewLink', {
|
||||
values: { errorCount: errorCount || 0 },
|
||||
defaultMessage:
|
||||
'{errorCount, plural, one {View 1 error} other {View # errors}}'
|
||||
})}
|
||||
</KibanaLink>
|
||||
);
|
||||
|
||||
const noErrorsText = i18n.translate(
|
||||
'xpack.apm.transactionDetails.errorsNone',
|
||||
{
|
||||
|
@ -109,16 +88,7 @@ export function StickyTransactionProperties({
|
|||
}),
|
||||
fieldName: TRANSACTION_RESULT,
|
||||
val: idx(transaction, _ => _.transaction.result) || NOT_AVAILABLE_LABEL,
|
||||
width: '25%'
|
||||
},
|
||||
{
|
||||
label: i18n.translate('xpack.apm.transactionDetails.userIdLabel', {
|
||||
defaultMessage: 'User ID'
|
||||
}),
|
||||
fieldName: USER_ID,
|
||||
val: idx(transaction, _ => _.user.id) || NOT_AVAILABLE_LABEL,
|
||||
truncated: true,
|
||||
width: '25%'
|
||||
width: '14%'
|
||||
},
|
||||
{
|
||||
label: i18n.translate(
|
||||
|
@ -127,8 +97,25 @@ export function StickyTransactionProperties({
|
|||
defaultMessage: 'Errors'
|
||||
}
|
||||
),
|
||||
val: errorCount ? errorsOverviewLink : noErrorsText,
|
||||
width: '25%'
|
||||
val: errorCount ? (
|
||||
<ErrorCountBadge
|
||||
errorCount={errorCount}
|
||||
transaction={transaction}
|
||||
verbose
|
||||
/>
|
||||
) : (
|
||||
noErrorsText
|
||||
),
|
||||
width: '18%'
|
||||
},
|
||||
{
|
||||
label: i18n.translate('xpack.apm.transactionDetails.userIdLabel', {
|
||||
defaultMessage: 'User ID'
|
||||
}),
|
||||
fieldName: USER_ID,
|
||||
val: idx(transaction, _ => _.user.id) || NOT_AVAILABLE_LABEL,
|
||||
truncated: true,
|
||||
width: '18%'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import theme from '@elastic/eui/dist/eui_theme_light.json';
|
|||
import { asTime } from 'x-pack/plugins/apm/public/utils/formatters';
|
||||
import { isRumAgentName } from '../../../../../../../common/agent_name';
|
||||
import { px, unit, units } from '../../../../../../style/variables';
|
||||
import { ErrorCountBadge } from '../../ErrorCountBadge';
|
||||
import { IWaterfallItem } from './waterfall_helpers/waterfall_helpers';
|
||||
|
||||
type ItemType = 'transaction' | 'span';
|
||||
|
@ -41,6 +42,7 @@ const Container = styled<IContainerStyleProps, 'div'>('div')`
|
|||
background-color: ${props =>
|
||||
props.isSelected ? theme.euiColorLightestShade : 'initial'};
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: ${theme.euiColorLightestShade};
|
||||
}
|
||||
|
@ -81,6 +83,7 @@ interface IWaterfallItemProps {
|
|||
item: IWaterfallItem;
|
||||
color: string;
|
||||
isSelected: boolean;
|
||||
errorCount: number;
|
||||
onClick: () => unknown;
|
||||
}
|
||||
|
||||
|
@ -145,6 +148,7 @@ export function WaterfallItem({
|
|||
item,
|
||||
color,
|
||||
isSelected,
|
||||
errorCount,
|
||||
onClick
|
||||
}: IWaterfallItemProps) {
|
||||
if (!totalDuration) {
|
||||
|
@ -172,6 +176,12 @@ export function WaterfallItem({
|
|||
<PrefixIcon item={item} />
|
||||
<HttpStatusCode item={item} />
|
||||
<NameLabel item={item} />
|
||||
{errorCount > 0 && item.docType === 'transaction' ? (
|
||||
<ErrorCountBadge
|
||||
errorCount={errorCount}
|
||||
transaction={item.transaction}
|
||||
/>
|
||||
) : null}
|
||||
<Duration item={item} />
|
||||
</ItemText>
|
||||
</Container>
|
||||
|
|
|
@ -67,6 +67,11 @@ export class Waterfall extends Component<Props> {
|
|||
public renderWaterfallItem = (item: IWaterfallItem) => {
|
||||
const { serviceColors, waterfall, urlParams }: Props = this.props;
|
||||
|
||||
const errorCount =
|
||||
item.docType === 'transaction'
|
||||
? waterfall.errorCountByTransactionId[item.transaction.transaction.id]
|
||||
: 0;
|
||||
|
||||
return (
|
||||
<WaterfallItem
|
||||
key={item.id}
|
||||
|
@ -75,6 +80,7 @@ export class Waterfall extends Component<Props> {
|
|||
item={item}
|
||||
totalDuration={waterfall.duration}
|
||||
isSelected={item.id === urlParams.waterfallItemId}
|
||||
errorCount={errorCount}
|
||||
onClick={() => this.onOpenFlyout(item)}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -4,94 +4,53 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import datemath from '@elastic/datemath';
|
||||
import { EuiSuperDatePicker, EuiSuperDatePickerProps } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
import { IReduxState } from '../../../store/rootReducer';
|
||||
import {
|
||||
TIMEPICKER_DEFAULTS,
|
||||
toBoolean,
|
||||
toNumber,
|
||||
updateTimePicker
|
||||
getUrlParams,
|
||||
IUrlParams,
|
||||
refreshTimeRange
|
||||
} from '../../../store/urlParams';
|
||||
import { fromQuery, toQuery } from '../Links/url_helpers';
|
||||
|
||||
export interface DatePickerProps extends RouteComponentProps {
|
||||
dispatchUpdateTimePicker: typeof updateTimePicker;
|
||||
dispatchRefreshTimeRange: typeof refreshTimeRange;
|
||||
urlParams: IUrlParams;
|
||||
}
|
||||
|
||||
export class DatePickerComponent extends React.Component<DatePickerProps> {
|
||||
public refreshTimeoutId = 0;
|
||||
|
||||
public getParamsFromSearch = (search: string) => {
|
||||
const { rangeFrom, rangeTo, refreshPaused, refreshInterval } = {
|
||||
...TIMEPICKER_DEFAULTS,
|
||||
...toQuery(search)
|
||||
};
|
||||
return {
|
||||
rangeFrom,
|
||||
rangeTo,
|
||||
refreshPaused: toBoolean(refreshPaused),
|
||||
refreshInterval: toNumber(refreshInterval)
|
||||
};
|
||||
};
|
||||
|
||||
public componentDidMount() {
|
||||
this.dispatchTimeRangeUpdate();
|
||||
}
|
||||
|
||||
public componentDidUpdate() {
|
||||
this.dispatchTimeRangeUpdate();
|
||||
}
|
||||
|
||||
public dispatchTimeRangeUpdate() {
|
||||
const { rangeFrom, rangeTo } = this.getParamsFromSearch(
|
||||
this.props.location.search
|
||||
);
|
||||
const parsed = {
|
||||
from: datemath.parse(rangeFrom),
|
||||
// roundUp: true is required for the quick select relative date values to work properly
|
||||
to: datemath.parse(rangeTo, { roundUp: true })
|
||||
};
|
||||
if (!parsed.from || !parsed.to) {
|
||||
return;
|
||||
}
|
||||
const min = parsed.from.toISOString();
|
||||
const max = parsed.to.toISOString();
|
||||
this.props.dispatchUpdateTimePicker({ min, max });
|
||||
}
|
||||
|
||||
public updateUrl(nextQuery: {
|
||||
rangeFrom?: string;
|
||||
rangeTo?: string;
|
||||
refreshPaused?: boolean;
|
||||
refreshInterval?: number;
|
||||
}) {
|
||||
const currentQuery = toQuery(this.props.location.search);
|
||||
const nextSearch = fromQuery({ ...currentQuery, ...nextQuery });
|
||||
|
||||
// Compare the encoded versions of current and next search string, and if they're the same,
|
||||
// use replace instead of push to prevent an unnecessary stack entry which breaks the back button.
|
||||
const currentSearch = fromQuery(currentQuery);
|
||||
const { push, replace } = this.props.history;
|
||||
const update = currentSearch === nextSearch ? replace : push;
|
||||
|
||||
update({ ...this.props.location, search: nextSearch });
|
||||
const { history, location } = this.props;
|
||||
history.push({
|
||||
...location,
|
||||
search: fromQuery({ ...toQuery(location.search), ...nextQuery })
|
||||
});
|
||||
}
|
||||
|
||||
public handleRefreshChange: EuiSuperDatePickerProps['onRefreshChange'] = ({
|
||||
public onRefreshChange: EuiSuperDatePickerProps['onRefreshChange'] = ({
|
||||
isPaused,
|
||||
refreshInterval
|
||||
}) => {
|
||||
this.updateUrl({
|
||||
refreshPaused: isPaused,
|
||||
refreshInterval
|
||||
});
|
||||
this.updateUrl({ refreshPaused: isPaused, refreshInterval });
|
||||
};
|
||||
|
||||
public handleTimeChange = (options: { start: string; end: string }) => {
|
||||
this.updateUrl({ rangeFrom: options.start, rangeTo: options.end });
|
||||
public onTimeChange: EuiSuperDatePickerProps['onTimeChange'] = ({
|
||||
start,
|
||||
end
|
||||
}) => {
|
||||
this.updateUrl({ rangeFrom: start, rangeTo: end });
|
||||
};
|
||||
|
||||
public onRefresh: EuiSuperDatePickerProps['onRefresh'] = ({ start, end }) => {
|
||||
this.props.dispatchRefreshTimeRange({ rangeFrom: start, rangeTo: end });
|
||||
};
|
||||
|
||||
public render() {
|
||||
|
@ -100,26 +59,31 @@ export class DatePickerComponent extends React.Component<DatePickerProps> {
|
|||
rangeTo,
|
||||
refreshPaused,
|
||||
refreshInterval
|
||||
} = this.getParamsFromSearch(this.props.location.search);
|
||||
} = this.props.urlParams;
|
||||
return (
|
||||
<EuiSuperDatePicker
|
||||
start={rangeFrom}
|
||||
end={rangeTo}
|
||||
isPaused={refreshPaused}
|
||||
refreshInterval={refreshInterval}
|
||||
onTimeChange={this.handleTimeChange}
|
||||
onRefresh={this.handleTimeChange}
|
||||
onRefreshChange={this.handleRefreshChange}
|
||||
onTimeChange={this.onTimeChange}
|
||||
onRefresh={this.onRefresh}
|
||||
onRefreshChange={this.onRefreshChange}
|
||||
showUpdateButton={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: IReduxState) => ({
|
||||
urlParams: getUrlParams(state)
|
||||
});
|
||||
const mapDispatchToProps = { dispatchRefreshTimeRange: refreshTimeRange };
|
||||
|
||||
const DatePicker = withRouter(
|
||||
connect(
|
||||
null,
|
||||
{ dispatchUpdateTimePicker: updateTimePicker }
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(DatePickerComponent)
|
||||
);
|
||||
|
||||
|
|
|
@ -5,144 +5,120 @@
|
|||
*/
|
||||
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import { History, Location } from 'history';
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { Store } from 'redux';
|
||||
// @ts-ignore
|
||||
import configureStore from 'x-pack/plugins/apm/public/store/config/configureStore';
|
||||
import { mockNow } from 'x-pack/plugins/apm/public/utils/testHelpers';
|
||||
import { DatePicker, DatePickerComponent } from '../DatePicker';
|
||||
|
||||
function mountPicker(search?: string) {
|
||||
const store = configureStore();
|
||||
let path = '/whatever';
|
||||
if (search) {
|
||||
path += `?${search}`;
|
||||
}
|
||||
const mounted = mount(
|
||||
function mountPicker(initialState = {}) {
|
||||
const store = configureStore(initialState);
|
||||
const wrapper = mount(
|
||||
<Provider store={store}>
|
||||
<MemoryRouter initialEntries={[path]}>
|
||||
<MemoryRouter>
|
||||
<DatePicker />
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
);
|
||||
return { mounted, store };
|
||||
return { wrapper, store };
|
||||
}
|
||||
|
||||
describe('DatePicker', () => {
|
||||
describe('date calculations', () => {
|
||||
let restoreNow: () => void;
|
||||
|
||||
beforeAll(() => {
|
||||
restoreNow = mockNow('2019-02-15T12:00:00.000Z');
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
restoreNow();
|
||||
});
|
||||
|
||||
it('should initialize with APM default date range', () => {
|
||||
const { store } = mountPicker();
|
||||
expect(store.getState().urlParams).toEqual({
|
||||
start: '2019-02-14T12:00:00.000Z',
|
||||
end: '2019-02-15T12:00:00.000Z'
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse "last 15 minutes" from URL params', () => {
|
||||
const { store } = mountPicker('rangeFrom=now-15m&rangeTo=now');
|
||||
expect(store.getState().urlParams).toEqual({
|
||||
start: '2019-02-15T11:45:00.000Z',
|
||||
end: '2019-02-15T12:00:00.000Z'
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse "last 7 days" from URL params', () => {
|
||||
const { store } = mountPicker('rangeFrom=now-7d&rangeTo=now');
|
||||
expect(store.getState().urlParams).toEqual({
|
||||
start: '2019-02-08T12:00:00.000Z',
|
||||
end: '2019-02-15T12:00:00.000Z'
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse absolute dates from URL params', () => {
|
||||
const { store } = mountPicker(
|
||||
`rangeFrom=2019-02-03T10:00:00.000Z&rangeTo=2019-02-10T16:30:00.000Z`
|
||||
);
|
||||
expect(store.getState().urlParams).toEqual({
|
||||
start: '2019-02-03T10:00:00.000Z',
|
||||
end: '2019-02-10T16:30:00.000Z'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('url updates', () => {
|
||||
function setupTest() {
|
||||
const location = { search: '?rangeFrom=now-15m&rangeTo=now' } as Location;
|
||||
const history = {
|
||||
push: jest.fn() as History['push'],
|
||||
replace: jest.fn() as History['replace']
|
||||
} as History;
|
||||
const routerProps = { location, history } as RouteComponentProps;
|
||||
const actionMock = jest.fn();
|
||||
const props = { ...routerProps, dispatchUpdateTimePicker: actionMock };
|
||||
const routerProps = {
|
||||
location: { search: '' },
|
||||
history: { push: jest.fn() }
|
||||
} as any;
|
||||
|
||||
const wrapper = shallow<DatePickerComponent>(
|
||||
<DatePickerComponent {...props} />
|
||||
<DatePickerComponent
|
||||
{...routerProps}
|
||||
dispatchUpdateTimePicker={jest.fn()}
|
||||
urlParams={{}}
|
||||
/>
|
||||
);
|
||||
|
||||
return { history, wrapper };
|
||||
return { history: routerProps.history, wrapper };
|
||||
}
|
||||
|
||||
it('should push an entry to the stack for each change', () => {
|
||||
const { history, wrapper } = setupTest();
|
||||
|
||||
wrapper.instance().updateUrl({ rangeFrom: 'now-20m', rangeTo: 'now' });
|
||||
|
||||
expect(history.push).toHaveBeenCalledTimes(1);
|
||||
expect(history.replace).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should replace the last entry in the stack if the URL is the same', () => {
|
||||
const { history, wrapper } = setupTest();
|
||||
|
||||
wrapper.instance().updateUrl({ rangeFrom: 'now-15m', rangeTo: 'now' });
|
||||
|
||||
expect(history.replace).toHaveBeenCalledTimes(1);
|
||||
expect(history.push).not.toHaveBeenCalled();
|
||||
expect(history.push).toHaveBeenCalledWith({
|
||||
search: 'rangeFrom=now-20m&rangeTo=now'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const tick = () => new Promise(resolve => setImmediate(resolve, 0));
|
||||
|
||||
describe('refresh cycle', () => {
|
||||
let nowSpy: jest.Mock;
|
||||
beforeEach(() => {
|
||||
nowSpy = mockNow('2010');
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
nowSpy.mockRestore();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should refresh the store once per refresh interval', async () => {
|
||||
const { store } = mountPicker(
|
||||
'rangeFrom=now-15m&rangeTo=now&refreshPaused=false&refreshInterval=200'
|
||||
);
|
||||
const listener = jest.fn();
|
||||
store.subscribe(listener);
|
||||
describe('when refresh is not paused', () => {
|
||||
let listener: jest.Mock;
|
||||
let store: Store;
|
||||
beforeEach(async () => {
|
||||
const obj = mountPicker({
|
||||
urlParams: {
|
||||
rangeFrom: 'now-15m',
|
||||
rangeTo: 'now',
|
||||
refreshPaused: false,
|
||||
refreshInterval: 200
|
||||
}
|
||||
});
|
||||
store = obj.store;
|
||||
|
||||
jest.advanceTimersByTime(200);
|
||||
await new Promise(resolve => setImmediate(resolve, 0));
|
||||
jest.advanceTimersByTime(200);
|
||||
await new Promise(resolve => setImmediate(resolve, 0));
|
||||
jest.advanceTimersByTime(200);
|
||||
await new Promise(resolve => setImmediate(resolve, 0));
|
||||
listener = jest.fn();
|
||||
store.subscribe(listener);
|
||||
|
||||
expect(listener).toHaveBeenCalledTimes(3);
|
||||
jest.advanceTimersByTime(200);
|
||||
await tick();
|
||||
jest.advanceTimersByTime(200);
|
||||
await tick();
|
||||
jest.advanceTimersByTime(200);
|
||||
await tick();
|
||||
});
|
||||
|
||||
it('should dispatch every refresh interval', async () => {
|
||||
expect(listener).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('should update the store with the new date range', () => {
|
||||
expect(store.getState().urlParams).toEqual({
|
||||
end: '2010-01-01T00:00:00.000Z',
|
||||
rangeFrom: 'now-15m',
|
||||
rangeTo: 'now',
|
||||
refreshInterval: 200,
|
||||
refreshPaused: false,
|
||||
start: '2009-12-31T23:45:00.000Z'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not refresh when paused', () => {
|
||||
const { store } = mountPicker(
|
||||
'rangeFrom=now-15m&rangeTo=now&refreshPaused=true&refreshInterval=200'
|
||||
);
|
||||
const { store } = mountPicker({
|
||||
urlParams: {
|
||||
rangeFrom: 'now-15m',
|
||||
rangeTo: 'now',
|
||||
refreshPaused: true,
|
||||
refreshInterval: 200
|
||||
}
|
||||
});
|
||||
|
||||
const listener = jest.fn();
|
||||
store.subscribe(listener);
|
||||
jest.advanceTimersByTime(1100);
|
||||
|
@ -151,9 +127,14 @@ describe('DatePicker', () => {
|
|||
});
|
||||
|
||||
it('should be paused by default', () => {
|
||||
const { store } = mountPicker(
|
||||
'rangeFrom=now-15m&rangeTo=now&refreshInterval=200'
|
||||
);
|
||||
const { store } = mountPicker({
|
||||
urlParams: {
|
||||
rangeFrom: 'now-15m',
|
||||
rangeTo: 'now',
|
||||
refreshInterval: 200
|
||||
}
|
||||
});
|
||||
|
||||
const listener = jest.fn();
|
||||
store.subscribe(listener);
|
||||
jest.advanceTimersByTime(1100);
|
||||
|
@ -162,12 +143,18 @@ describe('DatePicker', () => {
|
|||
});
|
||||
|
||||
it('should not attempt refreshes after unmounting', () => {
|
||||
const { store, mounted } = mountPicker(
|
||||
'rangeFrom=now-15m&rangeTo=now&refreshPaused=false&refreshInterval=200'
|
||||
);
|
||||
const { store, wrapper } = mountPicker({
|
||||
urlParams: {
|
||||
rangeFrom: 'now-15m',
|
||||
rangeTo: 'now',
|
||||
refreshPaused: false,
|
||||
refreshInterval: 200
|
||||
}
|
||||
});
|
||||
|
||||
const listener = jest.fn();
|
||||
store.subscribe(listener);
|
||||
mounted.unmount();
|
||||
wrapper.unmount();
|
||||
jest.advanceTimersByTime(1100);
|
||||
|
||||
expect(listener).not.toHaveBeenCalled();
|
||||
|
|
|
@ -39,9 +39,17 @@ class KueryBarView extends Component {
|
|||
isLoadingSuggestions: false
|
||||
};
|
||||
|
||||
willUnmount = false;
|
||||
|
||||
componentWillUnmount() {
|
||||
this.willUnmount = true;
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const indexPattern = await getAPMIndexPatternForKuery();
|
||||
this.setState({ indexPattern, isLoadingIndexPattern: false });
|
||||
if (!this.willUnmount) {
|
||||
this.setState({ indexPattern, isLoadingIndexPattern: false });
|
||||
}
|
||||
}
|
||||
|
||||
onChange = async (inputValue, selectionStart) => {
|
||||
|
|
|
@ -15,6 +15,7 @@ interface Props extends KibanaHrefArgs {
|
|||
disabled?: boolean;
|
||||
to?: StringMap;
|
||||
className?: string;
|
||||
[prop: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,17 +6,9 @@
|
|||
|
||||
import { rootReducer } from '../rootReducer';
|
||||
|
||||
const ISO_DATE_PATTERN = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/;
|
||||
|
||||
describe('root reducer', () => {
|
||||
it('should return the initial state', () => {
|
||||
const state = rootReducer(undefined, {});
|
||||
|
||||
expect(state.urlParams.start).toMatch(ISO_DATE_PATTERN);
|
||||
expect(state.urlParams.end).toMatch(ISO_DATE_PATTERN);
|
||||
|
||||
delete state.urlParams.start;
|
||||
delete state.urlParams.end;
|
||||
const state = rootReducer(undefined, {} as any);
|
||||
|
||||
expect(state).toEqual({
|
||||
location: { hash: '', pathname: '', search: '' },
|
|
@ -1,69 +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 { urlParamsReducer, updateTimePicker } from '../urlParams';
|
||||
import { LOCATION_UPDATE } from '../location';
|
||||
|
||||
describe('urlParams', () => {
|
||||
it('should handle LOCATION_UPDATE for transactions section', () => {
|
||||
const state = urlParamsReducer(
|
||||
{},
|
||||
{
|
||||
type: LOCATION_UPDATE,
|
||||
location: {
|
||||
pathname:
|
||||
'myServiceName/transactions/myTransactionType/myTransactionName/b/c',
|
||||
search: '?transactionId=myTransactionId&detailTab=request&spanId=10'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
expect(state).toEqual({
|
||||
page: 0,
|
||||
serviceName: 'myServiceName',
|
||||
spanId: 10,
|
||||
processorEvent: 'transaction',
|
||||
transactionId: 'myTransactionId',
|
||||
transactionName: 'myTransactionName',
|
||||
detailTab: 'request',
|
||||
transactionType: 'myTransactionType'
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle LOCATION_UPDATE for error section', () => {
|
||||
const state = urlParamsReducer(
|
||||
{},
|
||||
{
|
||||
type: LOCATION_UPDATE,
|
||||
location: {
|
||||
pathname: 'myServiceName/errors/myErrorGroupId',
|
||||
search: '?detailTab=request&transactionId=myTransactionId'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
expect(state).toEqual(
|
||||
expect.objectContaining({
|
||||
serviceName: 'myServiceName',
|
||||
errorGroupId: 'myErrorGroupId',
|
||||
detailTab: 'request',
|
||||
transactionId: 'myTransactionId'
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle TIMEPICKER_UPDATE', () => {
|
||||
const state = urlParamsReducer(
|
||||
{},
|
||||
updateTimePicker({
|
||||
min: 'minTime',
|
||||
max: 'maxTime'
|
||||
})
|
||||
);
|
||||
|
||||
expect(state).toEqual({ end: 'maxTime', start: 'minTime' });
|
||||
});
|
||||
});
|
113
x-pack/plugins/apm/public/store/__jest__/urlParams.test.tsx
Normal file
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* 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 { Location } from 'history';
|
||||
import { mockNow } from '../../utils/testHelpers';
|
||||
import { updateLocation } from '../location';
|
||||
import { APMAction, refreshTimeRange, urlParamsReducer } from '../urlParams';
|
||||
|
||||
describe('urlParams', () => {
|
||||
beforeEach(() => {
|
||||
mockNow('2010');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should parse "last 15 minutes"', () => {
|
||||
const action = updateLocation({
|
||||
pathname: '',
|
||||
search: '?rangeFrom=now-15m&rangeTo=now'
|
||||
} as Location) as APMAction;
|
||||
const { start, end } = urlParamsReducer({}, action);
|
||||
|
||||
expect({ start, end }).toEqual({
|
||||
start: '2009-12-31T23:45:00.000Z',
|
||||
end: '2010-01-01T00:00:00.000Z'
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse "last 7 days"', () => {
|
||||
const action = updateLocation({
|
||||
pathname: '',
|
||||
search: '?rangeFrom=now-7d&rangeTo=now'
|
||||
} as Location) as APMAction;
|
||||
const { start, end } = urlParamsReducer({}, action);
|
||||
|
||||
expect({ start, end }).toEqual({
|
||||
start: '2009-12-25T00:00:00.000Z',
|
||||
end: '2010-01-01T00:00:00.000Z'
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse absolute dates', () => {
|
||||
const action = updateLocation({
|
||||
pathname: '',
|
||||
search:
|
||||
'?rangeFrom=2019-02-03T10:00:00.000Z&rangeTo=2019-02-10T16:30:00.000Z'
|
||||
} as Location) as APMAction;
|
||||
const { start, end } = urlParamsReducer({}, action);
|
||||
|
||||
expect({ start, end }).toEqual({
|
||||
start: '2019-02-03T10:00:00.000Z',
|
||||
end: '2019-02-10T16:30:00.000Z'
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle LOCATION_UPDATE for transactions section', () => {
|
||||
const action = updateLocation({
|
||||
pathname:
|
||||
'myServiceName/transactions/myTransactionType/myTransactionName/b/c',
|
||||
search: '?transactionId=myTransactionId&detailTab=request&spanId=10'
|
||||
} as Location) as APMAction;
|
||||
const state = urlParamsReducer({}, action);
|
||||
|
||||
expect(state).toEqual({
|
||||
detailTab: 'request',
|
||||
end: '2010-01-01T00:00:00.000Z',
|
||||
page: 0,
|
||||
processorEvent: 'transaction',
|
||||
rangeFrom: 'now-24h',
|
||||
rangeTo: 'now',
|
||||
refreshInterval: 0,
|
||||
refreshPaused: true,
|
||||
serviceName: 'myServiceName',
|
||||
spanId: 10,
|
||||
start: '2009-12-31T00:00:00.000Z',
|
||||
transactionId: 'myTransactionId',
|
||||
transactionName: 'myTransactionName',
|
||||
transactionType: 'myTransactionType'
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle LOCATION_UPDATE for error section', () => {
|
||||
const action = updateLocation({
|
||||
pathname: 'myServiceName/errors/myErrorGroupId',
|
||||
search: '?detailTab=request&transactionId=myTransactionId'
|
||||
} as Location) as APMAction;
|
||||
const state = urlParamsReducer({}, action);
|
||||
|
||||
expect(state).toEqual(
|
||||
expect.objectContaining({
|
||||
serviceName: 'myServiceName',
|
||||
errorGroupId: 'myErrorGroupId',
|
||||
detailTab: 'request',
|
||||
transactionId: 'myTransactionId'
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle refreshTimeRange action', () => {
|
||||
const action = refreshTimeRange({ rangeFrom: 'now', rangeTo: 'now-15m' });
|
||||
const state = urlParamsReducer({}, action);
|
||||
|
||||
expect(state).toEqual({
|
||||
start: '2010-01-01T00:00:00.000Z',
|
||||
end: '2009-12-31T23:45:00.000Z'
|
||||
});
|
||||
});
|
||||
});
|
|
@ -18,7 +18,7 @@ import { getDefaultDistributionSample } from './reactReduxRequest/transactionDis
|
|||
import { IReduxState } from './rootReducer';
|
||||
|
||||
// ACTION TYPES
|
||||
export const TIMEPICKER_UPDATE = 'TIMEPICKER_UPDATE';
|
||||
export const TIME_RANGE_REFRESH = 'TIME_RANGE_REFRESH';
|
||||
export const TIMEPICKER_DEFAULTS = {
|
||||
rangeFrom: 'now-24h',
|
||||
rangeTo: 'now',
|
||||
|
@ -26,34 +26,43 @@ export const TIMEPICKER_DEFAULTS = {
|
|||
refreshInterval: '0'
|
||||
};
|
||||
|
||||
function calculateTimePickerDefaults() {
|
||||
const parsed = {
|
||||
from: datemath.parse(TIMEPICKER_DEFAULTS.rangeFrom),
|
||||
// roundUp: true is required for the quick select relative date values to work properly
|
||||
to: datemath.parse(TIMEPICKER_DEFAULTS.rangeTo, { roundUp: true })
|
||||
};
|
||||
|
||||
const result: IUrlParams = {};
|
||||
if (parsed.from) {
|
||||
result.start = parsed.from.toISOString();
|
||||
}
|
||||
if (parsed.to) {
|
||||
result.end = parsed.to.toISOString();
|
||||
}
|
||||
return result;
|
||||
interface TimeRange {
|
||||
rangeFrom: string;
|
||||
rangeTo: string;
|
||||
}
|
||||
|
||||
const INITIAL_STATE: IUrlParams = calculateTimePickerDefaults();
|
||||
|
||||
interface LocationAction {
|
||||
type: typeof LOCATION_UPDATE;
|
||||
location: Location;
|
||||
}
|
||||
interface TimepickerAction {
|
||||
type: typeof TIMEPICKER_UPDATE;
|
||||
time: { min: string; max: string };
|
||||
interface TimeRangeRefreshAction {
|
||||
type: typeof TIME_RANGE_REFRESH;
|
||||
time: TimeRange;
|
||||
}
|
||||
export type APMAction = LocationAction | TimeRangeRefreshAction;
|
||||
|
||||
function getParsedDate(rawDate?: string, opts = {}) {
|
||||
if (rawDate) {
|
||||
const parsed = datemath.parse(rawDate, opts);
|
||||
if (parsed) {
|
||||
return parsed.toISOString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getStart(prevState: IUrlParams, rangeFrom?: string) {
|
||||
if (prevState.rangeFrom !== rangeFrom) {
|
||||
return getParsedDate(rangeFrom);
|
||||
}
|
||||
return prevState.start;
|
||||
}
|
||||
|
||||
function getEnd(prevState: IUrlParams, rangeTo?: string) {
|
||||
if (prevState.rangeTo !== rangeTo) {
|
||||
return getParsedDate(rangeTo, { roundUp: true });
|
||||
}
|
||||
return prevState.end;
|
||||
}
|
||||
export type APMAction = LocationAction | TimepickerAction;
|
||||
|
||||
// "urlParams" contains path and query parameters from the url, that can be easily consumed from
|
||||
// any (container) component with access to the store
|
||||
|
@ -63,7 +72,10 @@ export type APMAction = LocationAction | TimepickerAction;
|
|||
// serviceName: opbeans-backend (path param)
|
||||
// transactionType: Brewing%20Bot (path param)
|
||||
// transactionId: 1321 (query param)
|
||||
export function urlParamsReducer(state = INITIAL_STATE, action: APMAction) {
|
||||
export function urlParamsReducer(
|
||||
state: IUrlParams = {},
|
||||
action: APMAction
|
||||
): IUrlParams {
|
||||
switch (action.type) {
|
||||
case LOCATION_UPDATE: {
|
||||
const {
|
||||
|
@ -84,12 +96,24 @@ export function urlParamsReducer(state = INITIAL_STATE, action: APMAction) {
|
|||
page,
|
||||
sortDirection,
|
||||
sortField,
|
||||
kuery
|
||||
kuery,
|
||||
refreshPaused = TIMEPICKER_DEFAULTS.refreshPaused,
|
||||
refreshInterval = TIMEPICKER_DEFAULTS.refreshInterval,
|
||||
rangeFrom = TIMEPICKER_DEFAULTS.rangeFrom,
|
||||
rangeTo = TIMEPICKER_DEFAULTS.rangeTo
|
||||
} = toQuery(action.location.search);
|
||||
|
||||
return removeUndefinedProps({
|
||||
...state,
|
||||
|
||||
// date params
|
||||
start: getStart(state, rangeFrom),
|
||||
end: getEnd(state, rangeTo),
|
||||
rangeFrom,
|
||||
rangeTo,
|
||||
refreshPaused: toBoolean(refreshPaused),
|
||||
refreshInterval: toNumber(refreshInterval),
|
||||
|
||||
// query params
|
||||
sortDirection,
|
||||
sortField,
|
||||
|
@ -111,11 +135,11 @@ export function urlParamsReducer(state = INITIAL_STATE, action: APMAction) {
|
|||
});
|
||||
}
|
||||
|
||||
case TIMEPICKER_UPDATE:
|
||||
case TIME_RANGE_REFRESH:
|
||||
return {
|
||||
...state,
|
||||
start: action.time.min,
|
||||
end: action.time.max
|
||||
start: getParsedDate(action.time.rangeFrom),
|
||||
end: getParsedDate(action.time.rangeTo)
|
||||
};
|
||||
|
||||
default:
|
||||
|
@ -181,14 +205,9 @@ function getPathParams(pathname: string) {
|
|||
}
|
||||
}
|
||||
|
||||
interface TimeUpdate {
|
||||
min: string;
|
||||
max: string;
|
||||
}
|
||||
|
||||
// ACTION CREATORS
|
||||
export function updateTimePicker(time: TimeUpdate) {
|
||||
return { type: TIMEPICKER_UPDATE, time };
|
||||
export function refreshTimeRange(time: TimeRange): TimeRangeRefreshAction {
|
||||
return { type: TIME_RANGE_REFRESH, time };
|
||||
}
|
||||
|
||||
// Selectors
|
||||
|
@ -216,9 +235,13 @@ export interface IUrlParams {
|
|||
errorGroupId?: string;
|
||||
flyoutDetailTab?: string;
|
||||
kuery?: string;
|
||||
rangeFrom?: string;
|
||||
rangeTo?: string;
|
||||
refreshInterval?: number;
|
||||
refreshPaused?: boolean;
|
||||
serviceName?: string;
|
||||
sortField?: string;
|
||||
sortDirection?: string;
|
||||
sortField?: string;
|
||||
start?: string;
|
||||
traceId?: string;
|
||||
transactionId?: string;
|
||||
|
|
|
@ -116,11 +116,5 @@ export async function getRenderedHref(
|
|||
|
||||
export function mockNow(date: string) {
|
||||
const fakeNow = new Date(date).getTime();
|
||||
const realDateNow = global.Date.now.bind(global.Date);
|
||||
|
||||
global.Date.now = jest.fn(() => fakeNow);
|
||||
|
||||
return () => {
|
||||
global.Date.now = realDateNow;
|
||||
};
|
||||
return jest.spyOn(Date, 'now').mockReturnValue(fakeNow);
|
||||
}
|
||||
|
|
|
@ -771,6 +771,7 @@ exports[`UploadLicense should display a modal when license requires acknowledgem
|
|||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<button
|
||||
|
@ -1039,6 +1040,7 @@ exports[`UploadLicense should display a modal when license requires acknowledgem
|
|||
iconSide="left"
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<button
|
||||
|
@ -1533,6 +1535,7 @@ exports[`UploadLicense should display an error when ES says license is expired 1
|
|||
iconSide="left"
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<button
|
||||
|
@ -2027,6 +2030,7 @@ exports[`UploadLicense should display an error when ES says license is invalid 1
|
|||
iconSide="left"
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<button
|
||||
|
@ -2521,6 +2525,7 @@ exports[`UploadLicense should display an error when submitting invalid JSON 1`]
|
|||
iconSide="left"
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<button
|
||||
|
@ -3015,6 +3020,7 @@ exports[`UploadLicense should display error when ES returns error 1`] = `
|
|||
iconSide="left"
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<button
|
||||
|
|
|
@ -291,6 +291,7 @@ Default value: 1024"
|
|||
iconSide="left"
|
||||
isDisabled={true}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -309,6 +310,7 @@ Default value: 1024"
|
|||
fill={false}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -613,6 +615,7 @@ Default value: 1024"
|
|||
iconSide="left"
|
||||
isDisabled={true}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -631,6 +634,7 @@ Default value: 1024"
|
|||
fill={false}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -935,6 +939,7 @@ Default value: 1024"
|
|||
iconSide="left"
|
||||
isDisabled={true}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -953,6 +958,7 @@ Default value: 1024"
|
|||
fill={false}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -1228,6 +1234,7 @@ Default value: 1024"
|
|||
iconSide="left"
|
||||
isDisabled={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -1246,6 +1253,7 @@ Default value: 1024"
|
|||
fill={false}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -1563,6 +1571,7 @@ Default value: 1024"
|
|||
iconSide="left"
|
||||
isDisabled={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -1581,6 +1590,7 @@ Default value: 1024"
|
|||
fill={false}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -1856,6 +1866,7 @@ Default value: 1024"
|
|||
iconSide="left"
|
||||
isDisabled={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -1874,6 +1885,7 @@ Default value: 1024"
|
|||
fill={false}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -118,6 +118,7 @@ exports[`PipelinesTable component renders component as expected 1`] = `
|
|||
iconSide="left"
|
||||
isDisabled={false}
|
||||
onClick={[MockFunction]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -299,6 +299,7 @@ exports[`UpgradeFailure component passes expected text for new pipeline 1`] = `
|
|||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[MockFunction]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<button
|
||||
|
@ -666,6 +667,7 @@ exports[`UpgradeFailure component passes expected text for not manual upgrade 1`
|
|||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[MockFunction]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<button
|
||||
|
@ -1033,6 +1035,7 @@ exports[`UpgradeFailure component passes expected text for not new pipeline 1`]
|
|||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[MockFunction]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<button
|
||||
|
|
|
@ -12,6 +12,7 @@ exports[`UpgradeFailureActions component renders component as expected 1`] = `
|
|||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[MockFunction]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
upgrade button text
|
||||
|
|
|
@ -227,9 +227,9 @@ export class VectorStyle extends AbstractStyle {
|
|||
|
||||
//scale to [0,1] domain
|
||||
for (let i = 0; i < features.length; i++) {
|
||||
const unscaledValue = features[i].properties[fieldName];
|
||||
const unscaledValue = parseFloat(features[i].properties[fieldName]);
|
||||
let scaledValue;
|
||||
if (typeof unscaledValue !== 'number' || isNaN(unscaledValue)) {//cannot scale
|
||||
if (isNaN(unscaledValue)) {//cannot scale
|
||||
scaledValue = -1;//put outside range
|
||||
} else if (diff === 0) {//values are identical
|
||||
scaledValue = 1;//snap to end of color range
|
||||
|
|
|
@ -258,6 +258,7 @@ exports[`RuleEditorFlyout renders the flyout after adding a condition to a rule
|
|||
iconSide="left"
|
||||
isDisabled={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -541,6 +542,7 @@ exports[`RuleEditorFlyout renders the flyout after setting the rule to edit 1`]
|
|||
iconSide="left"
|
||||
isDisabled={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -802,6 +804,7 @@ exports[`RuleEditorFlyout renders the flyout for creating a rule with conditions
|
|||
iconSide="left"
|
||||
isDisabled={true}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -8,6 +8,7 @@ exports[`ExplorerNoInfluencersFound snapshot 1`] = `
|
|||
fill={true}
|
||||
href="ml#/jobs"
|
||||
iconSide="left"
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -25,6 +25,7 @@ import { CreateRecognizerJobsServiceProvider } from './create_job_service';
|
|||
import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service';
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
import template from './create_job.html';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
import { timefilter } from 'ui/timefilter';
|
||||
|
||||
uiRoutes
|
||||
|
@ -358,6 +359,23 @@ module
|
|||
});
|
||||
}
|
||||
resolve();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('Error setting up module', err);
|
||||
toastNotifications.addWarning({
|
||||
title: i18n('xpack.ml.newJob.simple.recognize.moduleSetupFailedWarningTitle', {
|
||||
defaultMessage: 'Error setting up module {moduleId}',
|
||||
values: { moduleId }
|
||||
}),
|
||||
text: i18n('xpack.ml.newJob.simple.recognize.moduleSetupFailedWarningDescription', {
|
||||
defaultMessage: 'An error occurred trying to create the {count, plural, one {job} other {jobs}} in the module.',
|
||||
values: {
|
||||
count: $scope.formConfig.jobs.length
|
||||
}
|
||||
})
|
||||
});
|
||||
$scope.overallState = SAVE_STATE.FAILED;
|
||||
$scope.$applyAsync();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -183,6 +183,7 @@ exports[`CalendarForm Renders calendar form 1`] = `
|
|||
iconSide="left"
|
||||
isDisabled={true}
|
||||
onClick={[MockFunction]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -201,6 +202,7 @@ exports[`CalendarForm Renders calendar form 1`] = `
|
|||
href="undefined/app/ml#/settings/calendars_list"
|
||||
iconSide="left"
|
||||
isDisabled={false}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -58,6 +58,7 @@ exports[`ImportModal Renders import modal 1`] = `
|
|||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -77,6 +77,7 @@ exports[`AddItemPopover calls addItems with multiple items on clicking Add butto
|
|||
fill={false}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -168,6 +169,7 @@ exports[`AddItemPopover opens the popover onButtonClick 1`] = `
|
|||
fill={false}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -259,6 +261,7 @@ exports[`AddItemPopover renders the popover 1`] = `
|
|||
fill={false}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -10,6 +10,7 @@ exports[`DeleteFilterListModal false canDeleteFilter privilege renders as disabl
|
|||
isDisabled={false}
|
||||
key="delete_filter_list"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -31,6 +32,7 @@ exports[`DeleteFilterListModal renders as delete button after opening and closin
|
|||
isDisabled={false}
|
||||
key="delete_filter_list"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -52,6 +54,7 @@ exports[`DeleteFilterListModal renders as disabled delete button when no lists s
|
|||
isDisabled={false}
|
||||
key="delete_filter_list"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -73,6 +76,7 @@ exports[`DeleteFilterListModal renders as enabled delete button when a list is s
|
|||
isDisabled={false}
|
||||
key="delete_filter_list"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -94,6 +98,7 @@ exports[`DeleteFilterListModal renders modal after clicking delete button 1`] =
|
|||
isDisabled={false}
|
||||
key="delete_filter_list"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -97,6 +97,7 @@ exports[`EditFilterList adds new items to filter list 1`] = `
|
|||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -210,6 +211,7 @@ exports[`EditFilterList renders after selecting an item and deleting it 1`] = `
|
|||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -318,6 +320,7 @@ exports[`EditFilterList renders after selecting an item and deleting it 2`] = `
|
|||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -410,6 +413,7 @@ exports[`EditFilterList renders the edit page for a new filter list and updates
|
|||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -502,6 +506,7 @@ exports[`EditFilterList renders the edit page for a new filter list and updates
|
|||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -611,6 +616,7 @@ exports[`EditFilterList renders the edit page for an existing filter list and up
|
|||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -720,6 +726,7 @@ exports[`EditFilterList renders the edit page for an existing filter list and up
|
|||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -829,6 +836,7 @@ exports[`EditFilterList updates the items per page 1`] = `
|
|||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -63,15 +63,24 @@ export class DataRecognizer {
|
|||
const configs = [];
|
||||
const dirs = await this.listDirs(this.modulesDir);
|
||||
await Promise.all(dirs.map(async (dir) => {
|
||||
const file = await this.readFile(`${this.modulesDir}/${dir}/manifest.json`);
|
||||
let file;
|
||||
try {
|
||||
configs.push({
|
||||
dirName: dir,
|
||||
json: JSON.parse(file)
|
||||
});
|
||||
file = await this.readFile(`${this.modulesDir}/${dir}/manifest.json`);
|
||||
} catch (error) {
|
||||
mlLog('warning', `Error parsing ${dir}/manifest.json`);
|
||||
mlLog('warning', `Data recognizer skipping folder ${dir} as manifest.json cannot be read`);
|
||||
}
|
||||
|
||||
if (file !== undefined) {
|
||||
try {
|
||||
configs.push({
|
||||
dirName: dir,
|
||||
json: JSON.parse(file)
|
||||
});
|
||||
} catch (error) {
|
||||
mlLog('warning', `Data recognizer error parsing ${dir}/manifest.json. ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
}));
|
||||
|
||||
return configs;
|
||||
|
@ -90,8 +99,14 @@ export class DataRecognizer {
|
|||
|
||||
await Promise.all(manifestFiles.map(async (i) => {
|
||||
const moduleConfig = i.json;
|
||||
const match = await this.searchForFields(moduleConfig, indexPattern);
|
||||
if (match) {
|
||||
let match = false;
|
||||
try {
|
||||
match = await this.searchForFields(moduleConfig, indexPattern);
|
||||
} catch (error) {
|
||||
mlLog('warning', `Data recognizer error running query defined for module ${moduleConfig.id}. ${error}`);
|
||||
}
|
||||
|
||||
if (match === true) {
|
||||
let logo = null;
|
||||
if (moduleConfig.logoFile) {
|
||||
try {
|
||||
|
@ -131,6 +146,7 @@ export class DataRecognizer {
|
|||
size,
|
||||
body
|
||||
});
|
||||
|
||||
return (resp.hits.total !== 0);
|
||||
}
|
||||
|
||||
|
@ -155,25 +171,33 @@ export class DataRecognizer {
|
|||
const kibana = {};
|
||||
// load all of the job configs
|
||||
await Promise.all(manifestJSON.jobs.map(async (job) => {
|
||||
const jobConfig = await this.readFile(`${this.modulesDir}/${dirName}/${ML_DIR}/${job.file}`);
|
||||
// use the file name for the id
|
||||
jobs.push({
|
||||
id: `${prefix}${job.id}`,
|
||||
config: JSON.parse(jobConfig)
|
||||
});
|
||||
try {
|
||||
const jobConfig = await this.readFile(`${this.modulesDir}/${dirName}/${ML_DIR}/${job.file}`);
|
||||
// use the file name for the id
|
||||
jobs.push({
|
||||
id: `${prefix}${job.id}`,
|
||||
config: JSON.parse(jobConfig)
|
||||
});
|
||||
} catch (error) {
|
||||
mlLog('warning', `Data recognizer error loading config for job ${job.id} for module ${id}. ${error}`);
|
||||
}
|
||||
}));
|
||||
|
||||
// load all of the datafeed configs
|
||||
await Promise.all(manifestJSON.datafeeds.map(async (datafeed) => {
|
||||
const datafeedConfig = await this.readFile(`${this.modulesDir}/${dirName}/${ML_DIR}/${datafeed.file}`);
|
||||
const config = JSON.parse(datafeedConfig);
|
||||
// use the job id from the manifestFile
|
||||
config.job_id = `${prefix}${datafeed.job_id}`;
|
||||
try {
|
||||
const datafeedConfig = await this.readFile(`${this.modulesDir}/${dirName}/${ML_DIR}/${datafeed.file}`);
|
||||
const config = JSON.parse(datafeedConfig);
|
||||
// use the job id from the manifestFile
|
||||
config.job_id = `${prefix}${datafeed.job_id}`;
|
||||
|
||||
datafeeds.push({
|
||||
id: prefixDatafeedId(datafeed.id, prefix),
|
||||
config
|
||||
});
|
||||
datafeeds.push({
|
||||
id: prefixDatafeedId(datafeed.id, prefix),
|
||||
config
|
||||
});
|
||||
} catch (error) {
|
||||
mlLog('warning', `Data recognizer error loading config for datafeed ${datafeed.id} for module ${id}. ${error}`);
|
||||
}
|
||||
}));
|
||||
|
||||
// load all of the kibana saved objects
|
||||
|
@ -182,15 +206,19 @@ export class DataRecognizer {
|
|||
await Promise.all(kKeys.map(async (key) => {
|
||||
kibana[key] = [];
|
||||
await Promise.all(manifestJSON.kibana[key].map(async (obj) => {
|
||||
const kConfig = await this.readFile(`${this.modulesDir}/${dirName}/${KIBANA_DIR}/${key}/${obj.file}`);
|
||||
// use the file name for the id
|
||||
const kId = obj.file.replace('.json', '');
|
||||
const config = JSON.parse(kConfig);
|
||||
kibana[key].push({
|
||||
id: kId,
|
||||
title: config.title,
|
||||
config
|
||||
});
|
||||
try {
|
||||
const kConfig = await this.readFile(`${this.modulesDir}/${dirName}/${KIBANA_DIR}/${key}/${obj.file}`);
|
||||
// use the file name for the id
|
||||
const kId = obj.file.replace('.json', '');
|
||||
const config = JSON.parse(kConfig);
|
||||
kibana[key].push({
|
||||
id: kId,
|
||||
title: config.title,
|
||||
config
|
||||
});
|
||||
} catch (error) {
|
||||
mlLog('warning', `Data recognizer error loading config for ${key} ${obj.id} for module ${id}. ${error}`);
|
||||
}
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
@ -218,6 +246,7 @@ export class DataRecognizer {
|
|||
end,
|
||||
request
|
||||
) {
|
||||
|
||||
this.savedObjectsClient = request.getSavedObjectsClient();
|
||||
this.indexPatterns = await this.loadIndexPatterns();
|
||||
|
||||
|
|
|
@ -269,6 +269,7 @@ exports[`ExplainCollectionEnabled should explain about xpack.monitoring.collecti
|
|||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<button
|
||||
|
|
|
@ -452,6 +452,7 @@ exports[`ExplainCollectionInterval collection interval setting updates should sh
|
|||
iconSide="left"
|
||||
isLoading={true}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<button
|
||||
|
@ -774,6 +775,7 @@ exports[`ExplainCollectionInterval should explain about xpack.monitoring.collect
|
|||
iconSide="left"
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<button
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import expect from 'expect.js';
|
||||
import sinon from 'sinon';
|
||||
import { get, noop } from 'lodash';
|
||||
import { exposeClient } from '../instantiate_client';
|
||||
import { exposeClient, hasMonitoringCluster } from '../instantiate_client';
|
||||
|
||||
function getMockServerFromConnectionUrl(monitoringClusterUrl) {
|
||||
const server = {
|
||||
|
@ -136,4 +136,16 @@ describe('Instantiate Client', () => {
|
|||
expect(createClientOptions.password).to.eql('monitoring-p@ssw0rd!-internal-test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasMonitoringCluster', () => {
|
||||
it('returns true if monitoring is configured', () => {
|
||||
const server = getMockServerFromConnectionUrl('http://monitoring-cluster.test:9200'); // pass null for URL to create the client using prod config
|
||||
expect(hasMonitoringCluster(server)).to.be(true);
|
||||
});
|
||||
|
||||
it('returns false if monitoring is not configured', () => {
|
||||
const server = getMockServerFromConnectionUrl(null);
|
||||
expect(hasMonitoringCluster(server)).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,28 +15,22 @@ import { LOGGING_TAG } from '../../common/constants';
|
|||
*/
|
||||
|
||||
export function exposeClient(server) {
|
||||
const monitoringEsConfig = server.config().get('xpack.monitoring.elasticsearch');
|
||||
|
||||
let config;
|
||||
let configSource;
|
||||
if (!Boolean(monitoringEsConfig.hosts && monitoringEsConfig.hosts.length)) {
|
||||
config = {};
|
||||
configSource = 'production';
|
||||
} else {
|
||||
config = { ...monitoringEsConfig };
|
||||
configSource = 'monitoring';
|
||||
}
|
||||
|
||||
const config = hasMonitoringCluster(server) ? server.config().get('xpack.monitoring.elasticsearch') : {};
|
||||
const cluster = server.plugins.elasticsearch.createCluster('monitoring', {
|
||||
...config,
|
||||
plugins: [monitoringBulk],
|
||||
logQueries: Boolean(monitoringEsConfig.logQueries),
|
||||
logQueries: Boolean(config.logQueries),
|
||||
});
|
||||
|
||||
server.events.on('stop', bindKey(cluster, 'close'));
|
||||
|
||||
const configSource = hasMonitoringCluster(server) ? 'monitoring' : 'production';
|
||||
server.log([LOGGING_TAG, 'es-client'], `config sourced from: ${configSource} cluster`);
|
||||
}
|
||||
|
||||
export function hasMonitoringCluster(server) {
|
||||
const hosts = server.config().get('xpack.monitoring.elasticsearch.hosts');
|
||||
return Boolean(hosts && hosts.length);
|
||||
}
|
||||
|
||||
|
||||
export const instantiateClient = once(exposeClient);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { checkLicenseGenerator } from './cluster_alerts/check_license';
|
||||
import { hasMonitoringCluster } from './es_client/instantiate_client';
|
||||
import { LOGGING_TAG } from '../common/constants';
|
||||
|
||||
/*
|
||||
|
@ -12,11 +13,10 @@ import { LOGGING_TAG } from '../common/constants';
|
|||
*/
|
||||
export const initMonitoringXpackInfo = async server => {
|
||||
const config = server.config();
|
||||
const xpackInfoOptions = {
|
||||
const xpackInfo = hasMonitoringCluster(server) ? server.plugins.xpack_main.createXPackInfo({
|
||||
clusterSource: 'monitoring',
|
||||
pollFrequencyInMillis: config.get('xpack.monitoring.xpack_api_polling_frequency_millis')
|
||||
};
|
||||
const xpackInfo = server.plugins.xpack_main.createXPackInfo(xpackInfoOptions);
|
||||
}) : server.plugins.xpack_main.info;
|
||||
|
||||
xpackInfo.feature('monitoring').registerLicenseCheckResultsGenerator(checkLicenseGenerator);
|
||||
server.expose('info', xpackInfo);
|
||||
|
|
|
@ -73,6 +73,7 @@ exports[`BasicLoginForm renders as expected 1`] = `
|
|||
iconSide="left"
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="submit"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -112,6 +112,7 @@ exports[`ConfirmDeleteModal renders as expected 1`] = `
|
|||
iconSide="left"
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -8,6 +8,7 @@ exports[`DeleteSpacesButton renders as expected 1`] = `
|
|||
fill={false}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -44,6 +44,7 @@ exports[`SpacesGridPage renders as expected 1`] = `
|
|||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -94,6 +94,7 @@ exports[`ChecklistFlyout renders 1`] = `
|
|||
iconSide="left"
|
||||
isLoading={false}
|
||||
onClick={[MockFunction]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -146,6 +146,7 @@ exports[`WarningsFlyoutStep renders 1`] = `
|
|||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[MockFunction]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -822,10 +822,10 @@
|
|||
tabbable "^1.1.0"
|
||||
uuid "^3.1.0"
|
||||
|
||||
"@elastic/eui@9.4.2":
|
||||
version "9.4.2"
|
||||
resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-9.4.2.tgz#530ec18f9b1cb3972824a707cc0cb3005c6c5028"
|
||||
integrity sha512-2fH+WiYYq1tPn5mEvg5NR5b9TjVYY0cM26HPERMRMZyAenGFQtjhansO8qFlTl/MAYz5GsQ7ejgdtzu095opdw==
|
||||
"@elastic/eui@9.5.0":
|
||||
version "9.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-9.5.0.tgz#233b4722b0cd2506793472ec1c6b97640a365229"
|
||||
integrity sha512-LXtCNJxdbH6+ajb1x0/AvEAnpdNMQzlDubj+yqP8XShNiNBPMa3M7Hzdb5iSPJ4TL/EPuGrUQbMPx4aZNKLc1g==
|
||||
dependencies:
|
||||
"@types/lodash" "^4.14.116"
|
||||
"@types/numeral" "^0.0.25"
|
||||
|
|