Merge remote-tracking branch 'origin/master' into feature/merge-code

This commit is contained in:
Fuyao Zhao 2019-03-22 11:57:15 -07:00
commit 3c1028a749
95 changed files with 1049 additions and 446 deletions

View file

@ -50,6 +50,8 @@ include::logs/index.asciidoc[]
include::apm/index.asciidoc[]
include::uptime/index.asciidoc[]
include::graph/index.asciidoc[]
include::dev-tools.asciidoc[]

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 748 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View file

@ -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.
--

View file

@ -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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 748 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View file

@ -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[]

View file

@ -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.

View file

@ -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-*`.

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View 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[]

View 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.

View 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.

View 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

View file

@ -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",

View file

@ -26,6 +26,7 @@ exports[`rendering render matches snapshot 1`] = `
fill={true}
iconSide="left"
onClick={[Function]}
size="m"
type="button"
>
<FormattedMessage

View file

@ -126,6 +126,7 @@ exports[`renders ControlsTab 1`] = `
iconSide="left"
iconType="plusInCircle"
onClick={[Function]}
size="m"
type="button"
>
<FormattedMessage

View file

@ -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

View file

@ -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

View file

@ -47,6 +47,7 @@ exports[`render 1`] = `
fill={true}
iconSide="left"
onClick={[Function]}
size="m"
type="button"
>
<FormattedMessage

View file

@ -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

View file

@ -60,6 +60,7 @@ exports[`render 1`] = `
href="#/management/kibana/objects?_a=(tab:search)"
iconSide="left"
onClick={[Function]}
size="m"
type="button"
>
<FormattedMessage

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -31,6 +31,7 @@ exports[`render 1`] = `
fill={true}
href="/app/myapp"
iconSide="left"
size="m"
type="button"
>
launch myapp

View file

@ -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

View file

@ -32,6 +32,7 @@ exports[`props exportedFieldsUrl 1`] = `
href="exported_fields_url"
iconSide="left"
rel="noopener"
size="m"
target="_blank"
type="button"
>

View file

@ -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

View file

@ -62,6 +62,7 @@ exports[`EmptyState should render normally 1`] = `
iconSide="left"
iconType="refresh"
onClick={[Function]}
size="m"
type="button"
>
<FormattedMessage

View file

@ -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

View file

@ -39,6 +39,7 @@ exports[`Header should render normally 1`] = `
fill={false}
href=""
iconSide="left"
size="m"
type="button"
>
<FormattedMessage

View file

@ -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

View file

@ -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

View file

@ -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;
}
}
}

View file

@ -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

View file

@ -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,
},
];

View file

@ -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;
}

View file

@ -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();

View file

@ -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 };

View file

@ -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",

View file

@ -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`

View file

@ -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)
};
}

View file

@ -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}

View file

@ -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>
);
}}
/>
);
}
}

View file

@ -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>
&nbsp;
{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>
);
};

View file

@ -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%'
}
];

View file

@ -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>

View file

@ -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)}
/>
);

View file

@ -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)
);

View file

@ -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();

View file

@ -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) => {

View file

@ -15,6 +15,7 @@ interface Props extends KibanaHrefArgs {
disabled?: boolean;
to?: StringMap;
className?: string;
[prop: string]: any;
}
/**

View file

@ -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: '' },

View file

@ -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' });
});
});

View 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'
});
});
});

View file

@ -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;

View file

@ -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);
}

View file

@ -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

View file

@ -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

View file

@ -118,6 +118,7 @@ exports[`PipelinesTable component renders component as expected 1`] = `
iconSide="left"
isDisabled={false}
onClick={[MockFunction]}
size="m"
type="button"
>
<FormattedMessage

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -8,6 +8,7 @@ exports[`ExplorerNoInfluencersFound snapshot 1`] = `
fill={true}
href="ml#/jobs"
iconSide="left"
size="m"
type="button"
>
<FormattedMessage

View file

@ -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();
});
});
}

View file

@ -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

View file

@ -58,6 +58,7 @@ exports[`ImportModal Renders import modal 1`] = `
fill={true}
iconSide="left"
onClick={[Function]}
size="m"
type="button"
>
<FormattedMessage

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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();

View file

@ -269,6 +269,7 @@ exports[`ExplainCollectionEnabled should explain about xpack.monitoring.collecti
fill={true}
iconSide="left"
onClick={[Function]}
size="m"
type="button"
>
<button

View file

@ -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

View file

@ -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);
});
});
});

View file

@ -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);

View file

@ -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);

View file

@ -73,6 +73,7 @@ exports[`BasicLoginForm renders as expected 1`] = `
iconSide="left"
isLoading={false}
onClick={[Function]}
size="m"
type="submit"
>
<FormattedMessage

View file

@ -112,6 +112,7 @@ exports[`ConfirmDeleteModal renders as expected 1`] = `
iconSide="left"
isLoading={false}
onClick={[Function]}
size="m"
type="button"
>
<FormattedMessage

View file

@ -8,6 +8,7 @@ exports[`DeleteSpacesButton renders as expected 1`] = `
fill={false}
iconSide="left"
onClick={[Function]}
size="m"
type="button"
>
<FormattedMessage

View file

@ -44,6 +44,7 @@ exports[`SpacesGridPage renders as expected 1`] = `
fill={true}
iconSide="left"
onClick={[Function]}
size="m"
type="button"
>
<FormattedMessage

View file

@ -94,6 +94,7 @@ exports[`ChecklistFlyout renders 1`] = `
iconSide="left"
isLoading={false}
onClick={[MockFunction]}
size="m"
type="button"
>
<FormattedMessage

View file

@ -146,6 +146,7 @@ exports[`WarningsFlyoutStep renders 1`] = `
fill={true}
iconSide="left"
onClick={[MockFunction]}
size="m"
type="button"
>
<FormattedMessage

View file

@ -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"