diff --git a/docs/maps/heatmap-layer.asciidoc b/docs/maps/heatmap-layer.asciidoc index 77b6d929a931c..7149bc5623169 100644 --- a/docs/maps/heatmap-layer.asciidoc +++ b/docs/maps/heatmap-layer.asciidoc @@ -2,15 +2,12 @@ [[heatmap-layer]] == Heat map layer -In the heat map layer, point data is clustered to show locations with higher densities. +Heat map layers cluster point data to show locations with higher densities. [role="screenshot"] image::maps/images/heatmap_layer.png[] -You can create a heat map layer from the following data source: - -*Grid aggregation*:: Geospatial data grouped in grids with metrics for each gridded cell. -Set *Show as* to *heat map*. +To add a heat map layer to your map, click *Add layer*, then select the *Heat map* layer. The index must contain at least one field mapped as {ref}/geo-point.html[geo_point]. NOTE: Only count, sum, unique count metric aggregations are available with the grid aggregation source and heat map layers. diff --git a/docs/maps/images/heatmap_layer.png b/docs/maps/images/heatmap_layer.png index 8d59de38beccd..87a45146f95a5 100644 Binary files a/docs/maps/images/heatmap_layer.png and b/docs/maps/images/heatmap_layer.png differ diff --git a/docs/maps/images/spatial_filters.png b/docs/maps/images/spatial_filters.png new file mode 100644 index 0000000000000..991e7f62962d0 Binary files /dev/null and b/docs/maps/images/spatial_filters.png differ diff --git a/docs/maps/images/tile_layer.png b/docs/maps/images/tile_layer.png index 60cb90ac5b90b..fc1d571b3e9b0 100644 Binary files a/docs/maps/images/tile_layer.png and b/docs/maps/images/tile_layer.png differ diff --git a/docs/maps/images/vector_layer.png b/docs/maps/images/vector_layer.png index a30f6c1d6acfd..6bc9701759ce7 100644 Binary files a/docs/maps/images/vector_layer.png and b/docs/maps/images/vector_layer.png differ diff --git a/docs/maps/index.asciidoc b/docs/maps/index.asciidoc index 56826c5209034..de90d7adb29c0 100644 --- a/docs/maps/index.asciidoc +++ b/docs/maps/index.asciidoc @@ -30,6 +30,7 @@ include::tile-layer.asciidoc[] include::vector-layer.asciidoc[] include::maps-aggregations.asciidoc[] include::search.asciidoc[] +include::map-settings.asciidoc[] include::connect-to-ems.asciidoc[] include::geojson-upload.asciidoc[] include::indexing-geojson-data-tutorial.asciidoc[] diff --git a/docs/maps/indexing-geojson-data-tutorial.asciidoc b/docs/maps/indexing-geojson-data-tutorial.asciidoc index bf846a2b80e03..c1ca9d0925c9a 100644 --- a/docs/maps/indexing-geojson-data-tutorial.asciidoc +++ b/docs/maps/indexing-geojson-data-tutorial.asciidoc @@ -87,14 +87,13 @@ hot spots are. An advantage of having indexed lightning strikes is that you can perform aggregations on the data. . Click *Add layer*. -. From the list of layer types, click *Grid aggregation*. +. From the list of layer types, click *Heat map*. + Because you indexed `lightning_detected.geojson` using the index name and pattern `lightning_detected`, that data is available as a {ref}/geo-point.html[geo_point] aggregation. . Select `lightning_detected`. -. Click *Show as* and select `heat map`. . Click *Add layer* to add the heat map layer "Lightning intensity". + diff --git a/docs/maps/map-settings.asciidoc b/docs/maps/map-settings.asciidoc new file mode 100644 index 0000000000000..4e290b6da2e71 --- /dev/null +++ b/docs/maps/map-settings.asciidoc @@ -0,0 +1,39 @@ +[role="xpack"] +[[maps-settings]] +== Map settings + +Elastic Maps offers settings that let you configure how a map is displayed. +To access these settings, click *Map settings* in the application toolbar. + +[float] +[[maps-settings-navigation]] +=== Navigation + +*Zoom range*:: +Constrain the map to the defined zoom range. + +*Initial map location*:: +Configure the initial map center and zoom. +* *Map location at save*: Use the map center and zoom from the map position at the time of the latest save. +* *Fixed location*: Lock the map center and zoom to fixed values. +* *Browser location*: Set the initial map center to the browser location. + +[float] +[[maps-settings-spatial-filters]] +=== Spatial filters + +Use spatial filter settings to configure how <> are displayed. + +image::maps/images/spatial_filters.png[] + +*Show spatial filters on map*:: +Clear the checkbox so <> do not appear on the map. + +*Opacity*:: +Set the opacity of spatial filters. + +*Fill color*:: +Set the fill color of spatial filters. + +*Border color*:: +Set the border color of spatial filters. diff --git a/docs/maps/maps-aggregations.asciidoc b/docs/maps/maps-aggregations.asciidoc index 2b65ae99a381b..6b03614ab9d6a 100644 --- a/docs/maps/maps-aggregations.asciidoc +++ b/docs/maps/maps-aggregations.asciidoc @@ -37,7 +37,7 @@ image::maps/images/grid_to_docs.gif[] [[maps-grid-aggregation]] === Grid aggregation -The *Grid aggregation* source uses {ref}/search-aggregations-bucket-geotilegrid-aggregation.html[GeoTile grid aggregation] to group your documents into grids. You can calculate metrics for each gridded cell. +*Grid aggregation* layers use {ref}/search-aggregations-bucket-geotilegrid-aggregation.html[GeoTile grid aggregation] to group your documents into grids. You can calculate metrics for each gridded cell. Symbolize grid aggregation metrics as: @@ -48,13 +48,13 @@ The cluster location is the weighted centroid for all geo-points in the gridded *Heat map*:: Creates a <> that clusters the weighted centroids for each gridded cell. -To enable grid aggregation: +To enable a grid aggregation layer: -. Click *Add layer*, then select the *Grid aggregation* source. +. Click *Add layer*, then select the *Clusters and grids* or *Heat map* layer. To enable a blended layer that dynamically shows clusters or documents: -. Click *Add layer*, then select the *Documents* source. +. Click *Add layer*, then select the *Documents* layer. . Configure *Index pattern* and the *Geospatial field*. To enable clustering, the *Geospatial field* must be set to a field mapped as {ref}/geo-point.html[geo_point]. . In *Scaling*, select *Show clusters when results exceed 10000*. @@ -69,7 +69,7 @@ then accumulates the most relevant documents based on sort order for each entry To enable top hits: -. Click *Add layer* button and select *Documents* source. +. Click *Add layer*, then select the *Documents* layer. . Configure *Index pattern* and *Geospatial field*. . In *Scaling*, select *Show top hits per entity*. . Set *Entity* to the field that identifies entities in your documents. @@ -99,7 +99,7 @@ image::maps/images/point_to_point.png[] Use term joins to augment vector features with properties for <> and richer tooltip content. -Term joins are available for <> with the following sources: +Term joins are available for the following <>: * Configured GeoJSON * Documents diff --git a/docs/maps/maps-getting-started.asciidoc b/docs/maps/maps-getting-started.asciidoc index 6495b8a057cf6..a74d442d6ffa2 100644 --- a/docs/maps/maps-getting-started.asciidoc +++ b/docs/maps/maps-getting-started.asciidoc @@ -62,10 +62,10 @@ The first layer you'll add is a choropleth layer to shade world countries by web log traffic. Darker shades symbolize countries with more web log traffic, and lighter shades symbolize countries with less traffic. -==== Add a vector layer from the Elastic Maps Service source +==== Add a vector layer to display world country boundaries -. In the map legend, click *Add layer*. -. Click the *EMS Boundaries* data source. +. Click *Add layer*. +. Select the *EMS Boundaries* layer. . From the *Layer* dropdown menu, select *World Countries*. . Click the *Add layer* button. . Set *Name* to `Total Requests by Country`. @@ -112,16 +112,16 @@ To avoid overwhelming the user with too much data at once, you'll add two layers * The first layer will display individual documents. The layer will appear when the user zooms in the map to show smaller regions. -* The second layer will show aggregated data that represents many documents. +* The second layer will display aggregated data that represents many documents. The layer will appear when the user zooms out the map to show larger amounts of the globe. -==== Add a vector layer from the document source +==== Add a vector layer to display individual documents This layer displays web log documents as points. The layer is only visible when users zoom in the map past zoom level 9. -. In the map legend, click *Add layer*. -. Click the *Documents* data source. +. Click *Add layer*. +. Click the *Documents* layer. . Set *Index pattern* to *kibana_sample_data_logs*. . Click the *Add layer* button. . Set *Name* to `Actual Requests`. @@ -137,7 +137,7 @@ Your map now looks like this between zoom levels 9 and 24: [role="screenshot"] image::maps/images/gs_add_es_document_layer.png[] -==== Add a vector layer from the grid aggregation source +==== Add a vector layer to display aggregated data Aggregations group {es} documents into grids. You can calculate metrics for each gridded cell. @@ -154,10 +154,9 @@ image::maps/images/grid_metrics_both.png[] ===== Add the layer -. In the map legend, click *Add layer*. -. Click the *Grid aggregation* data source. +. Click *Add layer*. +. Click the *Clusters and grids* layer. . Set *Index pattern* to *kibana_sample_data_logs*. -. Set *Show as* to *clusters*. . Click the *Add layer* button. . Set *Name* to `Total Requests and Bytes`. . Set *Visibility* to the range [0, 9]. diff --git a/docs/maps/search.asciidoc b/docs/maps/search.asciidoc index a461ab6fbb3a6..124a976c009d4 100644 --- a/docs/maps/search.asciidoc +++ b/docs/maps/search.asciidoc @@ -10,13 +10,13 @@ You can create a layer that requests data from {es} from the following: * <> with: -** Documents source +** Documents -** Grid aggregation source +** Clusters and grid ** <> -* <> with Grid aggregation source +* <> [role="screenshot"] image::maps/images/global_search_bar.png[] diff --git a/docs/maps/tile-layer.asciidoc b/docs/maps/tile-layer.asciidoc index 059dd527f4810..6da8dbad0a66d 100644 --- a/docs/maps/tile-layer.asciidoc +++ b/docs/maps/tile-layer.asciidoc @@ -2,12 +2,12 @@ [[tile-layer]] == Tile layer -The tile layer displays image tiles served from a tile server. +Tile layers display image tiles served from a tile server. [role="screenshot"] image::maps/images/tile_layer.png[] -You can create a tile layer from the following data sources: +To add a tile layer to your map, click *Add layer*, then select one of the following layers: *Configured Tile Map Service*:: Tile map service configured in kibana.yml. See map.tilemap.url in <> for details. diff --git a/docs/maps/vector-layer.asciidoc b/docs/maps/vector-layer.asciidoc index 17c57c82b0f17..d6a5931659a40 100644 --- a/docs/maps/vector-layer.asciidoc +++ b/docs/maps/vector-layer.asciidoc @@ -2,12 +2,15 @@ [[vector-layer]] == Vector layer -The vector layer displays points, lines, and polygons. +Vector layers display points, lines, and polygons. [role="screenshot"] image::maps/images/vector_layer.png[] -You can create a vector layer from the following sources: +To add a vector layer to your map, click *Add layer*, then select one of the following layers: + +*Clusters and grids*:: Geospatial data grouped in grids with metrics for each gridded cell. +The index must contain at least one field mapped as {ref}/geo-point.html[geo_point]. *Configured GeoJSON*:: Vector data from hosted GeoJSON configured in kibana.yml. See map.regionmap.* in <> for details. @@ -18,15 +21,13 @@ The index must contain at least one field mapped as {ref}/geo-point.html[geo_poi NOTE: Document results are limited to the `index.max_result_window` index setting, which defaults to 10000. Use <> to plot large data sets. -*Grid aggregation*:: Geospatial data grouped in grids with metrics for each gridded cell. -Set *Show as* to *grid rectangles* or *clusters*. -The index must contain at least one field mapped as {ref}/geo-point.html[geo_point]. - *EMS Boundaries*:: Administrative boundaries from https://www.elastic.co/elastic-maps-service[Elastic Maps Service]. *Point to point*:: Aggregated data paths between the source and destination. The index must contain at least 2 fields mapped as {ref}/geo-point.html[geo_point], source and destination. +*Upload Geojson*:: Index GeoJSON data in Elasticsearch. + include::vector-style.asciidoc[] include::vector-style-properties.asciidoc[] include::vector-tooltips.asciidoc[] diff --git a/docs/maps/vector-style.asciidoc b/docs/maps/vector-style.asciidoc index 7bc8a909d1ec6..5f5b3a1b2aecd 100644 --- a/docs/maps/vector-style.asciidoc +++ b/docs/maps/vector-style.asciidoc @@ -86,7 +86,7 @@ Qualitative data driven styling is available for the following styling propertie * *Label color* * *Label border color* -To ensure symbols are consistent as you pan, zoom, and filter the map, qualitative data driven styling uses a {ref}/search-aggregations-bucket-terms-aggregation.html[terms aggregation]. The term aggregation retrieves the top nine categories for the property. Feature values within the top categories are assigned a unique style. Feature values outside of the top categories are grouped into the *Other* category. A feature is assigned the *Other* category when the property value is undefined. +To ensure symbols are consistent as you pan, zoom, and filter the map, qualitative data driven styling uses a {ref}/search-aggregations-bucket-terms-aggregation.html[terms aggregation]. The term aggregation retrieves the top categories for the property. Feature values within the top categories are assigned a unique style. Feature values outside of the top categories are grouped into the *Other* category. A feature is assigned the *Other* category when the property value is undefined. To configure the terms aggregation, click the gear icon image:maps/images/gear_icon.png[]. Clear the *Get categories from indice* checkbox to turn off the terms aggregation request. diff --git a/src/dev/ci_setup/setup_env.sh b/src/dev/ci_setup/setup_env.sh index 05840926d35de..d9d0528748dc0 100644 --- a/src/dev/ci_setup/setup_env.sh +++ b/src/dev/ci_setup/setup_env.sh @@ -126,6 +126,7 @@ export PATH="$PATH:$yarnGlobalDir" # use a proxy to fetch chromedriver/geckodriver asset export GECKODRIVER_CDNURL="https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache" export CHROMEDRIVER_CDNURL="https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache" +export CYPRESS_DOWNLOAD_MIRROR="https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/cypress" export CHECKS_REPORTER_ACTIVE=false diff --git a/x-pack/plugins/infra/common/http_api/metrics_explorer.ts b/x-pack/plugins/infra/common/http_api/metrics_explorer.ts index 07d5fe59a9718..d3a978c9963cf 100644 --- a/x-pack/plugins/infra/common/http_api/metrics_explorer.ts +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer.ts @@ -58,6 +58,7 @@ export const metricsExplorerRequestBodyOptionalFieldsRT = rt.partial({ limit: rt.union([rt.number, rt.null, rt.undefined]), filterQuery: rt.union([rt.string, rt.null, rt.undefined]), forceInterval: rt.boolean, + dropLastBucket: rt.boolean, }); export const metricsExplorerRequestBodyRT = rt.intersection([ diff --git a/x-pack/plugins/infra/common/inventory_models/container/index.ts b/x-pack/plugins/infra/common/inventory_models/container/index.ts index c142f600d1d56..8f2336d11e42b 100644 --- a/x-pack/plugins/infra/common/inventory_models/container/index.ts +++ b/x-pack/plugins/infra/common/inventory_models/container/index.ts @@ -26,7 +26,7 @@ export const container: InventoryModel = { fields: { id: 'container.id', name: 'container.name', - ip: 'continaer.ip_address', + ip: 'container.ip_address', }, metrics, requiredMetrics: [ diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx index 77147d1b3b2b7..0e1195965448c 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx @@ -35,6 +35,7 @@ import { getChartTheme } from '../../../pages/metrics/metrics_explorer/component import { createFormatterForMetric } from '../../../pages/metrics/metrics_explorer/components/helpers/create_formatter_for_metric'; import { calculateDomain } from '../../../pages/metrics/metrics_explorer/components/helpers/calculate_domain'; import { useMetricsExplorerChartData } from '../hooks/use_metrics_explorer_chart_data'; +import { getMetricId } from '../../../pages/metrics/metrics_explorer/components/helpers/get_metric_id'; interface Props { context: AlertsContextValue; @@ -120,7 +121,7 @@ export const ExpressionChart: React.FC = ({ rows: firstSeries.rows.map(row => { const newRow: MetricsExplorerRow = { ...row }; thresholds.forEach((thresholdValue, index) => { - newRow[`metric_threshold_${index}`] = thresholdValue; + newRow[getMetricId(metric, `threshold_${index}`)] = thresholdValue; }); return newRow; }), @@ -140,7 +141,8 @@ export const ExpressionChart: React.FC = ({ const isAbove = [Comparator.GT, Comparator.GT_OR_EQ].includes(expression.comparator); const opacity = 0.3; - const timeLabel = TIME_LABELS[expression.timeUnit]; + const { timeSize, timeUnit } = expression; + const timeLabel = TIME_LABELS[timeUnit]; return ( <> @@ -255,8 +257,8 @@ export const ExpressionChart: React.FC = ({ ) : ( diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/hooks/use_metrics_explorer_chart_data.ts b/x-pack/plugins/infra/public/alerting/metric_threshold/hooks/use_metrics_explorer_chart_data.ts index 67f66bf742f43..185895062cfe2 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/hooks/use_metrics_explorer_chart_data.ts +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/hooks/use_metrics_explorer_chart_data.ts @@ -26,6 +26,7 @@ export const useMetricsExplorerChartData = ( () => ({ limit: 1, forceInterval: true, + dropLastBucket: false, groupBy, filterQuery, metrics: [ diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts index 414e204f3df50..da6d77ef4b478 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts @@ -60,6 +60,7 @@ export function useMetricsExplorerData( method: 'POST', body: JSON.stringify({ forceInterval: options.forceInterval, + dropLastBucket: options.dropLastBucket != null ? options.dropLastBucket : true, metrics: options.aggregation === 'count' ? [{ aggregation: 'count' }] diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.ts index 1b3e809fde61f..f79c7aa0d4d67 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.ts @@ -41,6 +41,7 @@ export interface MetricsExplorerOptions { filterQuery?: string; aggregation: MetricsExplorerAggregation; forceInterval?: boolean; + dropLastBucket?: boolean; } export interface MetricsExplorerTimeOptions { diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/create_metrics_model.ts b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/create_metrics_model.ts index a7f393261a096..3a9abf525a9f0 100644 --- a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/create_metrics_model.ts +++ b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/create_metrics_model.ts @@ -15,12 +15,15 @@ const percentileToVaue = (agg: 'p95' | 'p99') => { }; export const createMetricModel = (options: MetricsExplorerRequestBody): TSVBMetricModel => { + // if dropLastBucket is set use the value otherwise default to true. + const dropLastBucket: boolean = options.dropLastBucket != null ? options.dropLastBucket : true; return { id: 'custom', requires: [], index_pattern: options.indexPattern, interval: options.timerange.interval, time_field: options.timerange.field, + drop_last_bucket: dropLastBucket, type: 'timeseries', // Create one series per metric requested. The series.id will be used to identify the metric // when the responses are processed and combined with the grouping request. diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx index ab0af94cbc2b4..d069a76f214d7 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx @@ -236,17 +236,9 @@ export function XYChart({ // check all the tables to see if all of the rows have the same timestamp // that would mean that chart will draw a single bar const isSingleTimestampInXDomain = () => { - const nonEmptyLayers = layers.filter( - layer => data.tables[layer.layerId].rows.length && layer.xAccessor - ); - - if (!nonEmptyLayers.length) { - return; - } - const firstRowValue = - data.tables[nonEmptyLayers[0].layerId].rows[0][nonEmptyLayers[0].xAccessor!]; - for (const layer of nonEmptyLayers) { + data.tables[filteredLayers[0].layerId].rows[0][filteredLayers[0].xAccessor!]; + for (const layer of filteredLayers) { if ( layer.xAccessor && data.tables[layer.layerId].rows.some(row => row[layer.xAccessor!] !== firstRowValue) @@ -270,7 +262,7 @@ export function XYChart({ return undefined; } - const isTimeViz = data.dateRange && layers.every(l => l.xScaleType === 'time'); + const isTimeViz = data.dateRange && filteredLayers.every(l => l.xScaleType === 'time'); const xDomain = isTimeViz ? { @@ -299,12 +291,10 @@ export function XYChart({ return; } - const firstLayerWithData = - layers[layers.findIndex(layer => data.tables[layer.layerId].rows.length)]; - const table = data.tables[firstLayerWithData.layerId]; + const table = data.tables[filteredLayers[0].layerId]; const xAxisColumnIndex = table.columns.findIndex( - el => el.id === firstLayerWithData.xAccessor + el => el.id === filteredLayers[0].xAccessor ); const timeFieldName = table.columns[xAxisColumnIndex]?.meta?.aggConfigParams?.field; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx index 58bc75bd7309b..01cce153ce494 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx @@ -17,9 +17,14 @@ import { EuiSpacer, } from '@elastic/eui'; -import { DataFrameAnalyticsId, useRefreshAnalyticsList } from '../../../../common'; +import { + getAnalysisType, + DataFrameAnalyticsId, + useRefreshAnalyticsList, + ANALYSIS_CONFIG_TYPE, +} from '../../../../common'; import { checkPermission } from '../../../../../capabilities/check_capabilities'; -import { getTaskStateBadge } from './columns'; +import { getTaskStateBadge, getJobTypeBadge } from './columns'; import { DataFrameAnalyticsListColumn, @@ -154,7 +159,7 @@ export const DataFrameAnalyticsList: FC = ({ clauses.forEach(c => { // the search term could be negated with a minus, e.g. -bananas const bool = c.match === 'must'; - let ts = []; + let ts: DataFrameAnalyticsListRow[]; if (c.type === 'term') { // filter term based clauses, e.g. bananas @@ -174,8 +179,14 @@ export const DataFrameAnalyticsList: FC = ({ } else { // filter other clauses, i.e. the mode and status filters if (Array.isArray(c.value)) { - // the status value is an array of string(s) e.g. ['failed', 'stopped'] - ts = analytics.filter(d => (c.value as string).includes(d.stats.state)); + if (c.field === 'job_type') { + ts = analytics.filter(d => + (c.value as string).includes(getAnalysisType(d.config.analysis)) + ); + } else { + // the status value is an array of string(s) e.g. ['failed', 'stopped'] + ts = analytics.filter(d => (c.value as string).includes(d.stats.state)); + } } else { ts = analytics.filter(d => d.mode === c.value); } @@ -291,6 +302,19 @@ export const DataFrameAnalyticsList: FC = ({ incremental: true, }, filters: [ + { + type: 'field_value_selection', + field: 'job_type', + name: i18n.translate('xpack.ml.dataframe.analyticsList.typeFilter', { + defaultMessage: 'Type', + }), + multiSelect: 'or', + options: Object.values(ANALYSIS_CONFIG_TYPE).map(val => ({ + value: val, + name: val, + view: getJobTypeBadge(val), + })), + }, { type: 'field_value_selection', field: 'state.state', diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx index 907297cf69bfc..194d59faccf3f 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx @@ -64,6 +64,12 @@ export const getTaskStateBadge = ( ); }; +export const getJobTypeBadge = (jobType: string) => ( + + {jobType} + +); + export const progressColumn = { name: i18n.translate('xpack.ml.dataframe.analyticsList.progress', { defaultMessage: 'Progress per Step', @@ -230,7 +236,7 @@ export const getColumns = ( sortable: (item: DataFrameAnalyticsListRow) => getAnalysisType(item.config.analysis), truncateText: true, render(item: DataFrameAnalyticsListRow) { - return {getAnalysisType(item.config.analysis)}; + return getJobTypeBadge(getAnalysisType(item.config.analysis)); }, width: '150px', 'data-test-subj': 'mlAnalyticsTableColumnType', diff --git a/x-pack/plugins/triggers_actions_ui/README.md b/x-pack/plugins/triggers_actions_ui/README.md index c5f02863ba8a1..0ed6917854dc4 100644 --- a/x-pack/plugins/triggers_actions_ui/README.md +++ b/x-pack/plugins/triggers_actions_ui/README.md @@ -69,13 +69,13 @@ export function getAlertType(): AlertTypeModel { id: '.index-threshold', name: 'Index threshold', iconClass: 'alert', - alertParamsExpression: IndexThresholdAlertTypeExpression, + alertParamsExpression: lazy(() => import('./index_threshold_expression')), validate: validateAlertType, }; } ``` -alertParamsExpression form represented as an expression using `EuiExpression` components: +alertParamsExpression should be a lazy loaded React component extending an expression using `EuiExpression` components: ![Index Threshold Alert expression form](https://i.imgur.com/Ysk1ljY.png) ``` @@ -171,6 +171,7 @@ export const alertReducer = (state: any, action: AlertReducerAction) => { ``` +The Expression component should be lazy loaded which means it'll have to be the default export in `index_threshold_expression.ts`: ``` export const IndexThresholdAlertTypeExpression: React.FunctionComponent = ({ @@ -224,6 +225,9 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent ); }; + +// Export as default in order to support lazy loading +export {IndexThresholdAlertTypeExpression as default}; ``` Index Threshold Alert form with validation: @@ -237,7 +241,9 @@ Each alert type should be defined as `AlertTypeModel` object with the these prop name: string; iconClass: string; validate: (alertParams: any) => ValidationResult; - alertParamsExpression: React.FunctionComponent; + alertParamsExpression: React.LazyExoticComponent< + ComponentType> + >; defaultActionMessage?: string; ``` |Property|Description| @@ -246,7 +252,7 @@ Each alert type should be defined as `AlertTypeModel` object with the these prop |name|Name of the alert type that will be displayed on the select card in the UI.| |iconClass|Icon of the alert type that will be displayed on the select card in the UI.| |validate|Validation function for the alert params.| -|alertParamsExpression|React functional component for building UI of the current alert type params.| +|alertParamsExpression| A lazy loaded React component for building UI of the current alert type params.| |defaultActionMessage|Optional property for providing default message for all added actions with `message` property.| IMPORTANT: The current UI supports a single action group only. @@ -295,8 +301,8 @@ Below is a list of steps that should be done to build and register a new alert t 1. At any suitable place in Kibana, create a file, which will expose an object implementing interface [AlertTypeModel](https://github.com/elastic/kibana/blob/55b7905fb5265b73806006e7265739545d7521d0/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/types.ts#L83). Example: ``` +import { lazy } from 'react'; import { AlertTypeModel } from '../../../../types'; -import { ExampleExpression } from './expression'; import { validateExampleAlertType } from './validation'; export function getAlertType(): AlertTypeModel { @@ -304,7 +310,7 @@ export function getAlertType(): AlertTypeModel { id: 'example', name: 'Example Alert Type', iconClass: 'bell', - alertParamsExpression: ExampleExpression, + alertParamsExpression: lazy(() => import('./expression')), validate: validateExampleAlertType, defaultActionMessage: 'Alert [{{ctx.metadata.name}}] has exceeded the threshold', }; @@ -361,6 +367,9 @@ export const ExampleExpression: React.FunctionComponent = ({ ); }; +// Export as default in order to support lazy loading +export {ExampleExpression as default}; + ``` This alert type form becomes available, when the card of `Example Alert Type` is selected. Each expression word here is `EuiExpression` component and implements the basic aggregation, grouping and comparison methods. @@ -1017,7 +1026,7 @@ Below is a list of steps that should be done to build and register a new action 1. At any suitable place in Kibana, create a file, which will expose an object implementing interface [ActionTypeModel]: ``` -import React, { Fragment } from 'react'; +import React, { Fragment, lazy } from 'react'; import { i18n } from '@kbn/i18n'; import { ActionTypeModel, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx index 63860e062c8da..ebd9294ce1e6d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx @@ -3,8 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { lazy, Suspense } from 'react'; -import { Switch, Route, Redirect, HashRouter, RouteComponentProps } from 'react-router-dom'; +import React, { lazy } from 'react'; +import { Switch, Route, Redirect, HashRouter } from 'react-router-dom'; import { ChromeStart, DocLinksStart, @@ -15,7 +15,6 @@ import { ChromeBreadcrumb, CoreStart, } from 'kibana/public'; -import { EuiLoadingSpinner, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { BASE_PATH, Section, routeToAlertDetails } from './constants'; import { AppContextProvider, useAppDependencies } from './app_context'; import { hasShowAlertsCapability } from './lib/capabilities'; @@ -24,6 +23,7 @@ import { TypeRegistry } from './type_registry'; import { ChartsPluginStart } from '../../../../../src/plugins/charts/public'; import { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; import { PluginStartContract as AlertingStart } from '../../../alerting/public'; +import { suspendedComponentWithProps } from './lib/suspended_component_with_props'; const TriggersActionsUIHome = lazy(async () => import('./home')); const AlertDetailsRoute = lazy(() => @@ -68,30 +68,15 @@ export const AppWithoutRouter = ({ sectionsRegex }: { sectionsRegex: string }) = {canShowAlerts && ( - + )} ); }; - -function suspendedRouteComponent( - RouteComponent: React.ComponentType> -) { - return (props: RouteComponentProps) => ( - - - - - - } - > - - - ); -} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx index 43955db97f295..7803ed1ac3a7f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx @@ -42,6 +42,7 @@ import { } from '../../../../common'; import { builtInAggregationTypes } from '../../../../common/constants'; import { IndexThresholdAlertParams } from './types'; +import { AlertTypeParamsExpressionProps } from '../../../../types'; import { AlertsContextValue } from '../../../context/alerts_context'; import './expression.scss'; @@ -66,23 +67,10 @@ const expressionFieldsWithValidation = [ 'timeWindowSize', ]; -interface IndexThresholdProps { - alertParams: IndexThresholdAlertParams; - alertInterval: string; - setAlertParams: (property: string, value: any) => void; - setAlertProperty: (key: string, value: any) => void; - errors: { [key: string]: string[] }; - alertsContext: AlertsContextValue; -} - -export const IndexThresholdAlertTypeExpression: React.FunctionComponent = ({ - alertParams, - alertInterval, - setAlertParams, - setAlertProperty, - errors, - alertsContext, -}) => { +export const IndexThresholdAlertTypeExpression: React.FunctionComponent> = ({ alertParams, alertInterval, setAlertParams, setAlertProperty, errors, alertsContext }) => { const { index, timeField, @@ -476,3 +464,6 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent ); }; + +// eslint-disable-next-line import/no-default-export +export { IndexThresholdAlertTypeExpression as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/index.ts index 983f759214b6b..42747b9e85e2e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/index.ts @@ -3,16 +3,19 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { lazy } from 'react'; + import { AlertTypeModel } from '../../../../types'; -import { IndexThresholdAlertTypeExpression } from './expression'; import { validateExpression } from './validation'; +import { IndexThresholdAlertParams } from './types'; +import { AlertsContextValue } from '../../../context/alerts_context'; -export function getAlertType(): AlertTypeModel { +export function getAlertType(): AlertTypeModel { return { id: '.index-threshold', name: 'Index threshold', iconClass: 'alert', - alertParamsExpression: IndexThresholdAlertTypeExpression, + alertParamsExpression: lazy(() => import('./expression')), validate: validateExpression, }; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/suspended_component_with_props.tsx b/x-pack/plugins/triggers_actions_ui/public/application/lib/suspended_component_with_props.tsx new file mode 100644 index 0000000000000..563353793f991 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/suspended_component_with_props.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { Suspense } from 'react'; +import { EuiLoadingSpinner, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiLoadingSpinnerSize } from '@elastic/eui/src/components/loading/loading_spinner'; + +export function suspendedComponentWithProps( + ComponentToSuspend: React.ComponentType, + size?: EuiLoadingSpinnerSize +) { + return (props: T) => ( + + + + + + } + > + + + ); +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx index cdc187bc6f3ba..931fde430c601 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx @@ -10,7 +10,7 @@ import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { ValidationResult, Alert, AlertAction } from '../../../types'; -import { ActionForm } from './action_form'; +import ActionForm from './action_form'; jest.mock('../../lib/action_connector_api', () => ({ loadAllActions: jest.fn(), loadActionTypes: jest.fn(), diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index f3e955c973309..5af56f410ad50 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -713,3 +713,6 @@ export const ActionForm = ({ ); }; + +// eslint-disable-next-line import/no-default-export +export { ActionForm as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx index ac6c0e2749776..4f5007949f8b1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx @@ -6,7 +6,7 @@ import * as React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { coreMock } from '../../../../../../../src/core/public/mocks'; -import { ConnectorAddFlyout } from './connector_add_flyout'; +import ConnectorAddFlyout from './connector_add_flyout'; import { ActionsConnectorsContextProvider } from '../../context/actions_connectors_context'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { ValidationResult } from '../../../types'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx index c9844f4e10864..adee2e09a56fd 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx @@ -319,3 +319,6 @@ const UpgradeYourLicenseCallOut = ({ http }: { http: HttpSetup }) => ( ); + +// eslint-disable-next-line import/no-default-export +export { ConnectorAddFlyout as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx index 976ec146181c2..e4a9e6e74173e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx @@ -9,7 +9,7 @@ import { coreMock } from '../../../../../../../src/core/public/mocks'; import { ActionsConnectorsContextProvider } from '../../context/actions_connectors_context'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { ValidationResult } from '../../../types'; -import { ConnectorEditFlyout } from './connector_edit_flyout'; +import ConnectorEditFlyout from './connector_edit_flyout'; import { AppContextProvider } from '../../app_context'; const actionTypeRegistry = actionTypeRegistryMock.create(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx index 4a0effcbd6825..6ea78f60c52ea 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx @@ -254,3 +254,6 @@ export const ConnectorEditFlyout = ({ ); }; + +// eslint-disable-next-line import/no-default-export +export { ConnectorEditFlyout as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/index.ts index 52ee1efbdaf9f..e0065c143a1a2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/index.ts @@ -4,6 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -export { ConnectorAddFlyout } from './connector_add_flyout'; -export { ConnectorEditFlyout } from './connector_edit_flyout'; -export { ActionForm } from './action_form'; +import { lazy } from 'react'; +import { suspendedComponentWithProps } from '../../lib/suspended_component_with_props'; + +export const ConnectorAddFlyout = suspendedComponentWithProps( + lazy(() => import('./connector_add_flyout')) +); +export const ConnectorEditFlyout = suspendedComponentWithProps( + lazy(() => import('./connector_edit_flyout')) +); +export const ActionForm = suspendedComponentWithProps(lazy(() => import('./action_form'))); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx index 01d21e954bbf3..12b6f99319596 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx @@ -202,11 +202,12 @@ describe('actions_connectors_list component with items', () => { expect(wrapper.find('[data-test-subj="preConfiguredTitleMessage"]')).toHaveLength(2); }); - test('if select item for edit should render ConnectorEditFlyout', () => { - wrapper + test('if select item for edit should render ConnectorEditFlyout', async () => { + await wrapper .find('[data-test-subj="edit1"]') .first() .simulate('click'); + expect(wrapper.find('ConnectorEditFlyout')).toHaveLength(1); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index 9267a154efaa0..64a7aa9ffa8b9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -22,7 +22,9 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { useAppDependencies } from '../../../app_context'; import { loadAllActions, loadActionTypes, deleteActions } from '../../../lib/action_connector_api'; -import { ConnectorAddFlyout, ConnectorEditFlyout } from '../../action_connector_form'; +import ConnectorAddFlyout from '../../action_connector_form/connector_add_flyout'; +import ConnectorEditFlyout from '../../action_connector_form/connector_edit_flyout'; + import { hasDeleteActionsCapability, hasSaveActionsCapability } from '../../../lib/capabilities'; import { DeleteModalConfirmation } from '../../../components/delete_modal_confirmation'; import { ActionsConnectorsContextProvider } from '../../../context/actions_connectors_context'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx index 3d6493a5131e5..bebbcdda10a00 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx @@ -9,7 +9,7 @@ import { act } from 'react-dom/test-utils'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFormLabel } from '@elastic/eui'; import { coreMock } from '../../../../../../../src/core/public/mocks'; -import { AlertAdd } from './alert_add'; +import AlertAdd from './alert_add'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { ValidationResult } from '../../../types'; import { AlertsContextProvider, useAlertsContext } from '../../context/alerts_context'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx index 651f2cdba34af..004ad97083fe4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx @@ -219,3 +219,6 @@ const parseErrors: (errors: IErrorObject) => boolean = errors => if (isObject(errorList)) return parseErrors(errorList as IErrorObject); return errorList.length >= 1; }); + +// eslint-disable-next-line import/no-default-export +export { AlertAdd as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx index 4d8801d8b7484..39112a1509580 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx @@ -12,7 +12,7 @@ import { ValidationResult } from '../../../types'; import { AlertsContextProvider } from '../../context/alerts_context'; import { alertTypeRegistryMock } from '../../alert_type_registry.mock'; import { ReactWrapper } from 'enzyme'; -import { AlertEdit } from './alert_edit'; +import AlertEdit from './alert_edit'; import { AppContextProvider } from '../../app_context'; const actionTypeRegistry = actionTypeRegistryMock.create(); const alertTypeRegistry = alertTypeRegistryMock.create(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx index 747464d2212f4..fc1a3778bc5b8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx @@ -201,3 +201,6 @@ export const AlertEdit = ({ initialAlert, onClose }: AlertEditProps) => { ); }; + +// eslint-disable-next-line import/no-default-export +export { AlertEdit as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index 3b7283e69e019..e956c8ecc4f3b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, useState, useEffect } from 'react'; +import React, { Fragment, useState, useEffect, Suspense } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -23,6 +23,7 @@ import { EuiIconTip, EuiButtonIcon, EuiHorizontalRule, + EuiLoadingSpinner, } from '@elastic/eui'; import { some, filter, map, fold } from 'fp-ts/lib/Option'; import { pipe } from 'fp-ts/lib/pipeable'; @@ -36,7 +37,7 @@ import { AlertReducerAction } from './alert_reducer'; import { AlertTypeModel, Alert, IErrorObject, AlertAction, AlertTypeIndex } from '../../../types'; import { getTimeOptions } from '../../../common/lib/get_time_options'; import { useAlertsContext } from '../../context/alerts_context'; -import { ActionForm } from '../action_connector_form/action_form'; +import { ActionForm } from '../action_connector_form'; export function validateBaseProperties(alertObject: Alert) { const validationResult = { errors: {} }; @@ -222,14 +223,24 @@ export const AlertForm = ({ ) : null} {AlertParamsExpressionComponent ? ( - + + + + + + } + > + + ) : null} {defaultActionGroupId ? ( import('./alert_add'))); +export const AlertEdit = suspendedComponentWithProps(lazy(() => import('./alert_edit'))); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/index.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/index.tsx new file mode 100644 index 0000000000000..677ee139271c0 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/index.tsx @@ -0,0 +1,21 @@ +/* + * 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 { lazy } from 'react'; +import { suspendedComponentWithProps } from '../lib/suspended_component_with_props'; + +export const AlertAdd = suspendedComponentWithProps(lazy(() => import('./alert_form/alert_add'))); +export const AlertEdit = suspendedComponentWithProps(lazy(() => import('./alert_form/alert_edit'))); + +export const ConnectorAddFlyout = suspendedComponentWithProps( + lazy(() => import('./action_connector_form/connector_add_flyout')) +); +export const ConnectorEditFlyout = suspendedComponentWithProps( + lazy(() => import('./action_connector_form/connector_edit_flyout')) +); +export const ActionForm = suspendedComponentWithProps( + lazy(() => import('./action_connector_form/action_form')) +); diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/for_the_last.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/for_the_last.tsx index defad2b801718..5405d96bb1dce 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/for_the_last.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/for_the_last.tsx @@ -20,11 +20,12 @@ import { getTimeUnitLabel } from '../lib/get_time_unit_label'; import { TIME_UNITS } from '../../application/constants'; import { getTimeOptions } from '../lib/get_time_options'; import { ClosablePopoverTitle } from './components'; +import { IErrorObject } from '../../types'; interface ForLastExpressionProps { timeWindowSize?: number; timeWindowUnit?: string; - errors: { [key: string]: string[] }; + errors: IErrorObject; onChangeWindowSize: (selectedWindowSize: number | undefined) => void; onChangeWindowUnit: (selectedWindowUnit: string) => void; popupPosition?: diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx index 619d85d99719b..33ca98de4c08b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx @@ -19,10 +19,11 @@ import { import { builtInGroupByTypes } from '../constants'; import { GroupByType } from '../types'; import { ClosablePopoverTitle } from './components'; +import { IErrorObject } from '../../types'; interface GroupByExpressionProps { groupBy: string; - errors: { [key: string]: string[] }; + errors: IErrorObject; onChangeSelectedTermSize: (selectedTermSize?: number) => void; onChangeSelectedTermField: (selectedTermField?: string) => void; onChangeSelectedGroupBy: (selectedGroupBy?: string) => void; diff --git a/x-pack/plugins/triggers_actions_ui/public/index.ts b/x-pack/plugins/triggers_actions_ui/public/index.ts index 96645e856e418..a72d8815c95b4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/index.ts @@ -11,7 +11,13 @@ export { AlertsContextProvider } from './application/context/alerts_context'; export { ActionsConnectorsContextProvider } from './application/context/actions_connectors_context'; export { AlertAdd } from './application/sections/alert_form'; export { ActionForm } from './application/sections/action_connector_form'; -export { AlertAction, Alert, AlertTypeModel, ActionType } from './types'; +export { + AlertAction, + Alert, + AlertTypeModel, + AlertTypeParamsExpressionProps, + ActionType, +} from './types'; export { ConnectorAddFlyout, ConnectorEditFlyout, diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index cc511434267cc..e9cfd5b33db23 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -110,12 +110,28 @@ export interface AlertTableItem extends Alert { tagsText: string; } -export interface AlertTypeModel { +export interface AlertTypeParamsExpressionProps< + AlertParamsType = unknown, + AlertsContextValue = unknown +> { + alertParams: AlertParamsType; + alertInterval: string; + setAlertParams: (property: string, value: any) => void; + setAlertProperty: (key: string, value: any) => void; + errors: IErrorObject; + alertsContext: AlertsContextValue; +} + +export interface AlertTypeModel { id: string; name: string | JSX.Element; iconClass: string; - validate: (alertParams: any) => ValidationResult; - alertParamsExpression: React.FunctionComponent; + validate: (alertParams: AlertParamsType) => ValidationResult; + alertParamsExpression: + | React.FunctionComponent + | React.LazyExoticComponent< + ComponentType> + >; defaultActionMessage?: string; } diff --git a/x-pack/plugins/uptime/public/apps/plugin.ts b/x-pack/plugins/uptime/public/apps/plugin.ts index c6a7eb261d8fd..b589bd64591fc 100644 --- a/x-pack/plugins/uptime/public/apps/plugin.ts +++ b/x-pack/plugins/uptime/public/apps/plugin.ts @@ -4,11 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'kibana/public'; -import { - LegacyCoreStart, - AppMountParameters, - DEFAULT_APP_CATEGORIES, -} from '../../../../../src/core/public'; +import { AppMountParameters, DEFAULT_APP_CATEGORIES } from '../../../../../src/core/public'; import { UMFrontendLibs } from '../lib/lib'; import { PLUGIN } from '../../common/constants'; import { FeatureCatalogueCategory } from '../../../../../src/plugins/home/public'; @@ -17,11 +13,6 @@ import { EmbeddableStart } from '../../../../../src/plugins/embeddable/public'; import { TriggersAndActionsUIPublicPluginSetup } from '../../../triggers_actions_ui/public'; import { DataPublicPluginSetup } from '../../../../../src/plugins/data/public'; -export interface StartObject { - core: LegacyCoreStart; - plugins: any; -} - export interface ClientPluginsSetup { data: DataPublicPluginSetup; home: HomePublicPluginSetup; diff --git a/x-pack/plugins/uptime/public/components/overview/overview_container.tsx b/x-pack/plugins/uptime/public/components/overview/overview_container.tsx index 320536bc63b3c..7fd71f3ac89be 100644 --- a/x-pack/plugins/uptime/public/components/overview/overview_container.tsx +++ b/x-pack/plugins/uptime/public/components/overview/overview_container.tsx @@ -6,16 +6,11 @@ import { useDispatch, useSelector } from 'react-redux'; import React, { useCallback } from 'react'; -import { DataPublicPluginSetup } from 'src/plugins/data/public'; import { OverviewPageComponent } from '../../pages/overview'; import { selectIndexPattern } from '../../state/selectors'; import { setEsKueryString } from '../../state/actions'; -export interface OverviewPageProps { - autocomplete: DataPublicPluginSetup['autocomplete']; -} - -export const OverviewPage: React.FC = props => { +export const OverviewPage: React.FC = props => { const dispatch = useDispatch(); const setEsKueryFilters = useCallback( (esFilters: string) => dispatch(setEsKueryString(esFilters)), diff --git a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx index 66e61fbf73b64..65827867da5ee 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx +++ b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx @@ -63,7 +63,9 @@ export const initMonitorStatusAlertType: AlertTypeInitializer = ({ id: CLIENT_ALERT_TYPES.MONITOR_STATUS, name: , iconClass: 'uptimeApp', - alertParamsExpression: params => , + alertParamsExpression: (params: any) => ( + + ), validate, defaultActionMessage, }); diff --git a/x-pack/plugins/uptime/public/pages/__tests__/__snapshots__/overview.test.tsx.snap b/x-pack/plugins/uptime/public/pages/__tests__/__snapshots__/overview.test.tsx.snap index 71b3fb5c7146a..791bb4a57ae52 100644 --- a/x-pack/plugins/uptime/public/pages/__tests__/__snapshots__/overview.test.tsx.snap +++ b/x-pack/plugins/uptime/public/pages/__tests__/__snapshots__/overview.test.tsx.snap @@ -51,15 +51,7 @@ exports[`MonitorPage shallow renders expected elements for valid props 1`] = ` } } > - { title: 'heartbeat-8*', }; - const autocomplete = { - getQuerySuggestions: jest.fn(), - hasQuerySuggestions: () => true, - getValueSuggestions: jest.fn(), - addQuerySuggestionProvider: jest.fn(), - }; - it('shallow renders expected elements for valid props', () => { expect( shallowWithRouter( - + ) ).toMatchSnapshot(); }); diff --git a/x-pack/plugins/uptime/public/pages/certificates.tsx b/x-pack/plugins/uptime/public/pages/certificates.tsx index a2b37657cf3fe..517252dcd1969 100644 --- a/x-pack/plugins/uptime/public/pages/certificates.tsx +++ b/x-pack/plugins/uptime/public/pages/certificates.tsx @@ -23,7 +23,6 @@ import { OVERVIEW_ROUTE, SETTINGS_ROUTE, CLIENT_ALERT_TYPES } from '../../common import { getDynamicSettings } from '../state/actions/dynamic_settings'; import { UptimeRefreshContext } from '../contexts'; import * as labels from './translations'; -import { UptimePage, useUptimeTelemetry } from '../hooks'; import { certificatesSelector, getCertificatesAction } from '../state/certificates/certificates'; import { CertificateList, CertificateSearch, CertSort } from '../components/certificates'; import { ToggleAlertFlyoutButton } from '../components/overview/alerts/alerts_containers'; @@ -39,8 +38,6 @@ const getPageSizeValue = () => { }; export const CertificatesPage: React.FC = () => { - useUptimeTelemetry(UptimePage.Certificates); - useTrackPageview({ app: 'uptime', path: 'certificates' }); useTrackPageview({ app: 'uptime', path: 'certificates', delay: 15000 }); diff --git a/x-pack/plugins/uptime/public/pages/monitor.tsx b/x-pack/plugins/uptime/public/pages/monitor.tsx index fc796e679a2f6..129b673f9e102 100644 --- a/x-pack/plugins/uptime/public/pages/monitor.tsx +++ b/x-pack/plugins/uptime/public/pages/monitor.tsx @@ -11,7 +11,7 @@ import { monitorStatusSelector } from '../state/selectors'; import { PageHeader } from './page_header'; import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; import { useTrackPageview } from '../../../observability/public'; -import { useMonitorId, useUptimeTelemetry, UptimePage } from '../hooks'; +import { useMonitorId } from '../hooks'; import { MonitorCharts } from '../components/monitor'; import { MonitorStatusDetails, PingList } from '../components/monitor'; import { getDynamicSettings } from '../state/actions/dynamic_settings'; @@ -27,8 +27,6 @@ export const MonitorPage: React.FC = () => { const selectedMonitor = useSelector(monitorStatusSelector); - useUptimeTelemetry(UptimePage.Monitor); - useTrackPageview({ app: 'uptime', path: 'monitor' }); useTrackPageview({ app: 'uptime', path: 'monitor', delay: 15000 }); diff --git a/x-pack/plugins/uptime/public/pages/overview.tsx b/x-pack/plugins/uptime/public/pages/overview.tsx index 65f64aa7352a9..639f363e6f9b1 100644 --- a/x-pack/plugins/uptime/public/pages/overview.tsx +++ b/x-pack/plugins/uptime/public/pages/overview.tsx @@ -8,7 +8,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import React, { useEffect } from 'react'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; -import { useUptimeTelemetry, UptimePage, useGetUrlParams } from '../hooks'; +import { useGetUrlParams } from '../hooks'; import { stringifyUrlParams } from '../lib/helper/stringify_url_params'; import { PageHeader } from './page_header'; import { IIndexPattern } from '../../../../../src/plugins/data/public'; @@ -18,9 +18,9 @@ import { useTrackPageview } from '../../../observability/public'; import { MonitorList } from '../components/overview/monitor_list/monitor_list_container'; import { EmptyState, FilterGroup, KueryBar, ParsingErrorCallout } from '../components/overview'; import { StatusPanel } from '../components/overview/status_panel'; -import { OverviewPageProps } from '../components/overview/overview_container'; +import { useKibana } from '../../../../../src/plugins/kibana_react/public'; -interface Props extends OverviewPageProps { +interface Props { indexPattern: IIndexPattern | null; setEsKueryFilters: (esFilters: string) => void; } @@ -34,11 +34,15 @@ const EuiFlexItemStyled = styled(EuiFlexItem)` } `; -export const OverviewPageComponent = ({ autocomplete, indexPattern, setEsKueryFilters }: Props) => { +export const OverviewPageComponent = React.memo(({ indexPattern, setEsKueryFilters }: Props) => { const { absoluteDateRangeStart, absoluteDateRangeEnd, ...params } = useGetUrlParams(); const { search, filters: urlFilters } = params; - useUptimeTelemetry(UptimePage.Overview); + const { + services: { + data: { autocomplete }, + }, + } = useKibana(); useTrackPageview({ app: 'uptime', path: 'overview' }); useTrackPageview({ app: 'uptime', path: 'overview', delay: 15000 }); @@ -57,6 +61,7 @@ export const OverviewPageComponent = ({ autocomplete, indexPattern, setEsKueryFi }); useBreadcrumbs([]); // No extra breadcrumbs on overview + return ( <> @@ -83,4 +88,4 @@ export const OverviewPageComponent = ({ autocomplete, indexPattern, setEsKueryFi ); -}; +}); diff --git a/x-pack/plugins/uptime/public/pages/settings.tsx b/x-pack/plugins/uptime/public/pages/settings.tsx index d018567ae1104..b617e81bad88a 100644 --- a/x-pack/plugins/uptime/public/pages/settings.tsx +++ b/x-pack/plugins/uptime/public/pages/settings.tsx @@ -25,7 +25,6 @@ import { DynamicSettings } from '../../common/runtime_types'; import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; import { OVERVIEW_ROUTE } from '../../common/constants'; import { useKibana } from '../../../../../src/plugins/kibana_react/public'; -import { UptimePage, useUptimeTelemetry } from '../hooks'; import { IndicesForm } from '../components/settings/indices_form'; import { CertificateExpirationForm, @@ -75,13 +74,11 @@ const getFieldErrors = (formFields: DynamicSettings | null): SettingsPageFieldEr return null; }; -export const SettingsPage = () => { +export const SettingsPage: React.FC = () => { const dss = useSelector(selectDynamicSettings); useBreadcrumbs([{ text: Translations.settings.breadcrumbText }]); - useUptimeTelemetry(UptimePage.Settings); - const dispatch = useDispatch(); useEffect(() => { diff --git a/x-pack/plugins/uptime/public/routes.tsx b/x-pack/plugins/uptime/public/routes.tsx index ca97858998df7..455d5070128f5 100644 --- a/x-pack/plugins/uptime/public/routes.tsx +++ b/x-pack/plugins/uptime/public/routes.tsx @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC } from 'react'; +import React, { FC, useEffect } from 'react'; import { Route, Switch } from 'react-router-dom'; -import { DataPublicPluginSetup } from '../../../../src/plugins/data/public'; import { OverviewPage } from './components/overview/overview_container'; import { CERTIFICATES_ROUTE, @@ -16,33 +15,73 @@ import { } from '../common/constants'; import { MonitorPage, NotFoundPage, SettingsPage } from './pages'; import { CertificatesPage } from './pages/certificates'; +import { UptimePage, useUptimeTelemetry } from './hooks'; -interface RouterProps { - autocomplete: DataPublicPluginSetup['autocomplete']; +interface RouteProps { + path: string; + component: React.FC; + dataTestSubj: string; + title: string; + telemetryId: UptimePage; } -export const PageRouter: FC = ({ autocomplete }) => ( - - -
- -
-
- -
- -
-
- -
- -
-
- -
- -
-
- -
-); +const baseTitle = 'Uptime - Kibana'; + +const Routes: RouteProps[] = [ + { + title: `Monitor | ${baseTitle}`, + path: MONITOR_ROUTE, + component: MonitorPage, + dataTestSubj: 'uptimeMonitorPage', + telemetryId: UptimePage.Monitor, + }, + { + title: `Settings | ${baseTitle}`, + path: SETTINGS_ROUTE, + component: SettingsPage, + dataTestSubj: 'uptimeSettingsPage', + telemetryId: UptimePage.Settings, + }, + { + title: `Certificates | ${baseTitle}`, + path: CERTIFICATES_ROUTE, + component: CertificatesPage, + dataTestSubj: 'uptimeCertificatesPage', + telemetryId: UptimePage.Certificates, + }, + { + title: baseTitle, + path: OVERVIEW_ROUTE, + component: OverviewPage, + dataTestSubj: 'uptimeOverviewPage', + telemetryId: UptimePage.Overview, + }, +]; + +const RouteInit: React.FC> = ({ + path, + title, + telemetryId, +}) => { + useUptimeTelemetry(telemetryId); + useEffect(() => { + document.title = title; + }, [path, title]); + return null; +}; + +export const PageRouter: FC = () => { + return ( + + {Routes.map(({ title, path, component: RouteComponent, dataTestSubj, telemetryId }) => ( + +
+ + +
+
+ ))} + +
+ ); +}; diff --git a/x-pack/plugins/uptime/public/uptime_app.tsx b/x-pack/plugins/uptime/public/uptime_app.tsx index 836d942d92165..2891a15510f31 100644 --- a/x-pack/plugins/uptime/public/uptime_app.tsx +++ b/x-pack/plugins/uptime/public/uptime_app.tsx @@ -106,7 +106,7 @@ const Application = (props: UptimeAppProps) => {
- +