diff --git a/.buildkite/scripts/common/env.sh b/.buildkite/scripts/common/env.sh index 0715b07fd58e8..b5acfe140df24 100755 --- a/.buildkite/scripts/common/env.sh +++ b/.buildkite/scripts/common/env.sh @@ -38,8 +38,10 @@ export ELASTIC_APM_TRANSACTION_SAMPLE_RATE=0.1 if is_pr; then if [[ "${GITHUB_PR_LABELS:-}" == *"ci:collect-apm"* ]]; then export ELASTIC_APM_ACTIVE=true + export ELASTIC_APM_CONTEXT_PROPAGATION_ONLY=false else - export ELASTIC_APM_ACTIVE=false + export ELASTIC_APM_ACTIVE=true + export ELASTIC_APM_CONTEXT_PROPAGATION_ONLY=true fi if [[ "${GITHUB_STEP_COMMIT_STATUS_ENABLED:-}" != "true" ]]; then @@ -61,6 +63,7 @@ if is_pr; then export PR_TARGET_BRANCH="$GITHUB_PR_TARGET_BRANCH" else export ELASTIC_APM_ACTIVE=true + export ELASTIC_APM_CONTEXT_PROPAGATION_ONLY=false export CHECKS_REPORTER_ACTIVE=false fi diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d532d4b681c77..d5c569ac9d552 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -59,6 +59,7 @@ /examples/url_generators_explorer/ @elastic/kibana-app-services /examples/field_formats_example/ @elastic/kibana-app-services /examples/partial_results_example/ @elastic/kibana-app-services +/examples/search_examples/ @elastic/kibana-app-services /packages/elastic-datemath/ @elastic/kibana-app-services /packages/kbn-interpreter/ @elastic/kibana-app-services /packages/kbn-react-field/ @elastic/kibana-app-services @@ -78,24 +79,33 @@ /src/plugins/ui_actions/ @elastic/kibana-app-services /src/plugins/index_pattern_field_editor @elastic/kibana-app-services /src/plugins/screenshot_mode @elastic/kibana-app-services +/src/plugins/bfetch/ @elastic/kibana-app-services +/src/plugins/index_pattern_management/ @elastic/kibana-app-services +/src/plugins/inspector/ @elastic/kibana-app-services /x-pack/examples/ui_actions_enhanced_examples/ @elastic/kibana-app-services /x-pack/plugins/data_enhanced/ @elastic/kibana-app-services /x-pack/plugins/embeddable_enhanced/ @elastic/kibana-app-services /x-pack/plugins/ui_actions_enhanced/ @elastic/kibana-app-services /x-pack/plugins/runtime_fields @elastic/kibana-app-services /x-pack/test/search_sessions_integration/ @elastic/kibana-app-services -#CC# /src/plugins/bfetch/ @elastic/kibana-app-services -#CC# /src/plugins/index_pattern_management/ @elastic/kibana-app-services -#CC# /src/plugins/inspector/ @elastic/kibana-app-services -#CC# /src/plugins/share/ @elastic/kibana-app-services -#CC# /x-pack/plugins/drilldowns/ @elastic/kibana-app-services -#CC# /packages/kbn-interpreter/ @elastic/kibana-app-services ### Observability Plugins # Observability Shared /x-pack/plugins/observability/ @elastic/observability-ui +# Unified Observability +/x-pack/plugins/observability/public/pages/overview @elastic/unified-observability +/x-pack/plugins/observability/public/pages/home @elastic/unified-observability +/x-pack/plugins/observability/public/pages/landing @elastic/unified-observability +/x-pack/plugins/observability/public/context @elastic/unified-observability + +# Actionable Observability +/x-pack/plugins/observability/common/rules @elastic/actionable-observability +/x-pack/plugins/observability/public/rules @elastic/actionable-observability +/x-pack/plugins/observability/public/pages/alerts @elastic/actionable-observability +/x-pack/plugins/observability/public/pages/cases @elastic/actionable-observability + # Infra Monitoring /x-pack/plugins/infra/ @elastic/infra-monitoring-ui /x-pack/test/functional/apps/infra @elastic/infra-monitoring-ui diff --git a/api_docs/apm.json b/api_docs/apm.json index a97e5428b2b8e..2fd290d8b9c35 100644 --- a/api_docs/apm.json +++ b/api_docs/apm.json @@ -737,7 +737,7 @@ "label": "APIEndpoint", "description": [], "signature": [ - "\"POST /internal/apm/data_view/static\" | \"GET /internal/apm/data_view/dynamic\" | \"GET /internal/apm/environments\" | \"GET /internal/apm/services/{serviceName}/errors\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}\" | \"GET /internal/apm/services/{serviceName}/errors/distribution\" | \"POST /internal/apm/latency/overall_distribution\" | \"GET /internal/apm/services/{serviceName}/metrics/charts\" | \"GET /internal/apm/observability_overview\" | \"GET /internal/apm/observability_overview/has_data\" | \"GET /api/apm/rum/client-metrics\" | \"GET /api/apm/rum-client/page-load-distribution\" | \"GET /api/apm/rum-client/page-load-distribution/breakdown\" | \"GET /api/apm/rum-client/page-view-trends\" | \"GET /api/apm/rum-client/services\" | \"GET /api/apm/rum-client/visitor-breakdown\" | \"GET /api/apm/rum-client/web-core-vitals\" | \"GET /api/apm/rum-client/long-task-metrics\" | \"GET /api/apm/rum-client/url-search\" | \"GET /api/apm/rum-client/js-errors\" | \"GET /api/apm/observability_overview/has_rum_data\" | \"GET /internal/apm/service-map\" | \"GET /internal/apm/service-map/service/{serviceName}\" | \"GET /internal/apm/service-map/backend\" | \"GET /internal/apm/services/{serviceName}/serviceNodes\" | \"GET /internal/apm/services\" | \"GET /internal/apm/services/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/metadata/details\" | \"GET /internal/apm/services/{serviceName}/metadata/icons\" | \"GET /internal/apm/services/{serviceName}/agent\" | \"GET /internal/apm/services/{serviceName}/transaction_types\" | \"GET /internal/apm/services/{serviceName}/node/{serviceNodeName}/metadata\" | \"GET /api/apm/services/{serviceName}/annotation/search\" | \"POST /api/apm/services/{serviceName}/annotation\" | \"GET /internal/apm/services/{serviceName}/error_groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/error_groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}\" | \"GET /internal/apm/services/{serviceName}/throughput\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/dependencies\" | \"GET /internal/apm/services/{serviceName}/dependencies/breakdown\" | \"GET /internal/apm/services/{serviceName}/profiling/timeline\" | \"GET /internal/apm/services/{serviceName}/profiling/statistics\" | \"GET /internal/apm/services/{serviceName}/alerts\" | \"GET /internal/apm/services/{serviceName}/infrastructure\" | \"GET /internal/apm/suggestions\" | \"GET /internal/apm/traces/{traceId}\" | \"GET /internal/apm/traces\" | \"GET /internal/apm/traces/{traceId}/root_transaction\" | \"GET /internal/apm/transactions/{transactionId}\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/latency\" | \"GET /internal/apm/services/{serviceName}/transactions/traces/samples\" | \"GET /internal/apm/services/{serviceName}/transaction/charts/breakdown\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/error_rate\" | \"GET /internal/apm/alerts/chart_preview/transaction_error_rate\" | \"GET /internal/apm/alerts/chart_preview/transaction_duration\" | \"GET /internal/apm/alerts/chart_preview/transaction_error_count\" | \"GET /api/apm/settings/agent-configuration\" | \"GET /api/apm/settings/agent-configuration/view\" | \"DELETE /api/apm/settings/agent-configuration\" | \"PUT /api/apm/settings/agent-configuration\" | \"POST /api/apm/settings/agent-configuration/search\" | \"GET /api/apm/settings/agent-configuration/services\" | \"GET /api/apm/settings/agent-configuration/environments\" | \"GET /api/apm/settings/agent-configuration/agent_name\" | \"GET /internal/apm/settings/anomaly-detection/jobs\" | \"POST /internal/apm/settings/anomaly-detection/jobs\" | \"GET /internal/apm/settings/anomaly-detection/environments\" | \"GET /internal/apm/settings/apm-index-settings\" | \"GET /internal/apm/settings/apm-indices\" | \"POST /internal/apm/settings/apm-indices/save\" | \"GET /internal/apm/settings/custom_links/transaction\" | \"GET /internal/apm/settings/custom_links\" | \"POST /internal/apm/settings/custom_links\" | \"PUT /internal/apm/settings/custom_links/{id}\" | \"DELETE /internal/apm/settings/custom_links/{id}\" | \"GET /api/apm/sourcemaps\" | \"POST /api/apm/sourcemaps\" | \"DELETE /api/apm/sourcemaps/{id}\" | \"GET /internal/apm/fleet/has_data\" | \"GET /internal/apm/fleet/agents\" | \"POST /api/apm/fleet/apm_server_schema\" | \"GET /internal/apm/fleet/apm_server_schema/unsupported\" | \"GET /internal/apm/fleet/migration_check\" | \"POST /internal/apm/fleet/cloud_apm_package_policy\" | \"GET /internal/apm/backends/top_backends\" | \"GET /internal/apm/backends/upstream_services\" | \"GET /internal/apm/backends/metadata\" | \"GET /internal/apm/backends/charts/latency\" | \"GET /internal/apm/backends/charts/throughput\" | \"GET /internal/apm/backends/charts/error_rate\" | \"GET /internal/apm/fallback_to_transactions\" | \"GET /internal/apm/has_data\" | \"GET /internal/apm/event_metadata/{processorEvent}/{id}\"" + "\"POST /internal/apm/data_view/static\" | \"GET /internal/apm/data_view/dynamic\" | \"GET /internal/apm/environments\" | \"GET /internal/apm/services/{serviceName}/errors\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}\" | \"GET /internal/apm/services/{serviceName}/errors/distribution\" | \"POST /internal/apm/latency/overall_distribution\" | \"GET /internal/apm/services/{serviceName}/metrics/charts\" | \"GET /internal/apm/observability_overview\" | \"GET /internal/apm/observability_overview/has_data\" | \"GET /internal/apm/ux/client-metrics\" | \"GET /internal/apm/ux/page-load-distribution\" | \"GET /internal/apm/ux/page-load-distribution/breakdown\" | \"GET /internal/apm/ux/page-view-trends\" | \"GET /internal/apm/ux/services\" | \"GET /internal/apm/ux/visitor-breakdown\" | \"GET /internal/apm/ux/web-core-vitals\" | \"GET /internal/apm/ux/long-task-metrics\" | \"GET /internal/apm/ux/url-search\" | \"GET /internal/apm/ux/js-errors\" | \"GET /api/apm/observability_overview/has_rum_data\" | \"GET /internal/apm/service-map\" | \"GET /internal/apm/service-map/service/{serviceName}\" | \"GET /internal/apm/service-map/backend\" | \"GET /internal/apm/services/{serviceName}/serviceNodes\" | \"GET /internal/apm/services\" | \"GET /internal/apm/services/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/metadata/details\" | \"GET /internal/apm/services/{serviceName}/metadata/icons\" | \"GET /internal/apm/services/{serviceName}/agent\" | \"GET /internal/apm/services/{serviceName}/transaction_types\" | \"GET /internal/apm/services/{serviceName}/node/{serviceNodeName}/metadata\" | \"GET /api/apm/services/{serviceName}/annotation/search\" | \"POST /api/apm/services/{serviceName}/annotation\" | \"GET /internal/apm/services/{serviceName}/error_groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/error_groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}\" | \"GET /internal/apm/services/{serviceName}/throughput\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/dependencies\" | \"GET /internal/apm/services/{serviceName}/dependencies/breakdown\" | \"GET /internal/apm/services/{serviceName}/profiling/timeline\" | \"GET /internal/apm/services/{serviceName}/profiling/statistics\" | \"GET /internal/apm/services/{serviceName}/alerts\" | \"GET /internal/apm/services/{serviceName}/infrastructure\" | \"GET /internal/apm/suggestions\" | \"GET /internal/apm/traces/{traceId}\" | \"GET /internal/apm/traces\" | \"GET /internal/apm/traces/{traceId}/root_transaction\" | \"GET /internal/apm/transactions/{transactionId}\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/latency\" | \"GET /internal/apm/services/{serviceName}/transactions/traces/samples\" | \"GET /internal/apm/services/{serviceName}/transaction/charts/breakdown\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/error_rate\" | \"GET /internal/apm/alerts/chart_preview/transaction_error_rate\" | \"GET /internal/apm/alerts/chart_preview/transaction_duration\" | \"GET /internal/apm/alerts/chart_preview/transaction_error_count\" | \"GET /api/apm/settings/agent-configuration\" | \"GET /api/apm/settings/agent-configuration/view\" | \"DELETE /api/apm/settings/agent-configuration\" | \"PUT /api/apm/settings/agent-configuration\" | \"POST /api/apm/settings/agent-configuration/search\" | \"GET /api/apm/settings/agent-configuration/services\" | \"GET /api/apm/settings/agent-configuration/environments\" | \"GET /api/apm/settings/agent-configuration/agent_name\" | \"GET /internal/apm/settings/anomaly-detection/jobs\" | \"POST /internal/apm/settings/anomaly-detection/jobs\" | \"GET /internal/apm/settings/anomaly-detection/environments\" | \"GET /internal/apm/settings/apm-index-settings\" | \"GET /internal/apm/settings/apm-indices\" | \"POST /internal/apm/settings/apm-indices/save\" | \"GET /internal/apm/settings/custom_links/transaction\" | \"GET /internal/apm/settings/custom_links\" | \"POST /internal/apm/settings/custom_links\" | \"PUT /internal/apm/settings/custom_links/{id}\" | \"DELETE /internal/apm/settings/custom_links/{id}\" | \"GET /api/apm/sourcemaps\" | \"POST /api/apm/sourcemaps\" | \"DELETE /api/apm/sourcemaps/{id}\" | \"GET /internal/apm/fleet/has_data\" | \"GET /internal/apm/fleet/agents\" | \"POST /api/apm/fleet/apm_server_schema\" | \"GET /internal/apm/fleet/apm_server_schema/unsupported\" | \"GET /internal/apm/fleet/migration_check\" | \"POST /internal/apm/fleet/cloud_apm_package_policy\" | \"GET /internal/apm/backends/top_backends\" | \"GET /internal/apm/backends/upstream_services\" | \"GET /internal/apm/backends/metadata\" | \"GET /internal/apm/backends/charts/latency\" | \"GET /internal/apm/backends/charts/throughput\" | \"GET /internal/apm/backends/charts/error_rate\" | \"GET /internal/apm/fallback_to_transactions\" | \"GET /internal/apm/has_data\" | \"GET /internal/apm/event_metadata/{processorEvent}/{id}\"" ], "path": "x-pack/plugins/apm/server/routes/get_global_apm_server_route_repository.ts", "deprecated": false, @@ -1222,7 +1222,7 @@ "section": "def-server.APMRouteHandlerResources", "text": "APMRouteHandlerResources" }, - ", { serviceCount: number; transactionPerMinute: { value: undefined; timeseries: never[]; } | { value: number; timeseries: { x: number; y: number; }[]; }; }, ", + ", { serviceCount: number; transactionPerMinute: { value: undefined; timeseries: never[]; } | { value: number; timeseries: { x: number; y: number | null; }[]; }; }, ", "APMRouteCreateOptions", ">; } & { \"GET /internal/apm/observability_overview/has_data\": ", { @@ -1244,7 +1244,7 @@ "ApmIndicesConfig", "; }, ", "APMRouteCreateOptions", - ">; } & { \"GET /api/apm/rum/client-metrics\": ", + ">; } & { \"GET /internal/apm/ux/client-metrics\": ", { "pluginId": "@kbn/server-route-repository", "scope": "server", @@ -1252,7 +1252,7 @@ "section": "def-server.ServerRoute", "text": "ServerRoute" }, - "<\"GET /api/apm/rum/client-metrics\", ", + "<\"GET /internal/apm/ux/client-metrics\", ", "TypeC", "<{ query: ", "IntersectionC", @@ -1282,7 +1282,7 @@ }, ", { pageViews: { value: number; }; totalPageLoadDuration: { value: number; }; backEnd: { value: number; }; frontEnd: { value: number; }; }, ", "APMRouteCreateOptions", - ">; } & { \"GET /api/apm/rum-client/page-load-distribution\": ", + ">; } & { \"GET /internal/apm/ux/page-load-distribution\": ", { "pluginId": "@kbn/server-route-repository", "scope": "server", @@ -1290,7 +1290,7 @@ "section": "def-server.ServerRoute", "text": "ServerRoute" }, - "<\"GET /api/apm/rum-client/page-load-distribution\", ", + "<\"GET /internal/apm/ux/page-load-distribution\", ", "TypeC", "<{ query: ", "IntersectionC", @@ -1328,7 +1328,7 @@ }, ", { pageLoadDistribution: { pageLoadDistribution: { x: number; y: number; }[]; percentiles: Record | undefined; minDuration: number; maxDuration: number; } | null; }, ", "APMRouteCreateOptions", - ">; } & { \"GET /api/apm/rum-client/page-load-distribution/breakdown\": ", + ">; } & { \"GET /internal/apm/ux/page-load-distribution/breakdown\": ", { "pluginId": "@kbn/server-route-repository", "scope": "server", @@ -1336,7 +1336,7 @@ "section": "def-server.ServerRoute", "text": "ServerRoute" }, - "<\"GET /api/apm/rum-client/page-load-distribution/breakdown\", ", + "<\"GET /internal/apm/ux/page-load-distribution/breakdown\", ", "TypeC", "<{ query: ", "IntersectionC", @@ -1378,7 +1378,7 @@ }, ", { pageLoadDistBreakdown: { name: string; data: { x: number; y: number; }[]; }[] | undefined; }, ", "APMRouteCreateOptions", - ">; } & { \"GET /api/apm/rum-client/page-view-trends\": ", + ">; } & { \"GET /internal/apm/ux/page-view-trends\": ", { "pluginId": "@kbn/server-route-repository", "scope": "server", @@ -1386,7 +1386,7 @@ "section": "def-server.ServerRoute", "text": "ServerRoute" }, - "<\"GET /api/apm/rum-client/page-view-trends\", ", + "<\"GET /internal/apm/ux/page-view-trends\", ", "TypeC", "<{ query: ", "IntersectionC", @@ -1422,7 +1422,7 @@ }, ", { topItems: string[]; items: Record[]; }, ", "APMRouteCreateOptions", - ">; } & { \"GET /api/apm/rum-client/services\": ", + ">; } & { \"GET /internal/apm/ux/services\": ", { "pluginId": "@kbn/server-route-repository", "scope": "server", @@ -1430,7 +1430,7 @@ "section": "def-server.ServerRoute", "text": "ServerRoute" }, - "<\"GET /api/apm/rum-client/services\", ", + "<\"GET /internal/apm/ux/services\", ", "TypeC", "<{ query: ", "IntersectionC", @@ -1454,7 +1454,7 @@ }, ", { rumServices: string[]; }, ", "APMRouteCreateOptions", - ">; } & { \"GET /api/apm/rum-client/visitor-breakdown\": ", + ">; } & { \"GET /internal/apm/ux/visitor-breakdown\": ", { "pluginId": "@kbn/server-route-repository", "scope": "server", @@ -1462,7 +1462,7 @@ "section": "def-server.ServerRoute", "text": "ServerRoute" }, - "<\"GET /api/apm/rum-client/visitor-breakdown\", ", + "<\"GET /internal/apm/ux/visitor-breakdown\", ", "TypeC", "<{ query: ", "IntersectionC", @@ -1492,7 +1492,7 @@ }, ", { os: { count: number; name: string; }[]; browsers: { count: number; name: string; }[]; }, ", "APMRouteCreateOptions", - ">; } & { \"GET /api/apm/rum-client/web-core-vitals\": ", + ">; } & { \"GET /internal/apm/ux/web-core-vitals\": ", { "pluginId": "@kbn/server-route-repository", "scope": "server", @@ -1500,7 +1500,7 @@ "section": "def-server.ServerRoute", "text": "ServerRoute" }, - "<\"GET /api/apm/rum-client/web-core-vitals\", ", + "<\"GET /internal/apm/ux/web-core-vitals\", ", "TypeC", "<{ query: ", "IntersectionC", @@ -1530,7 +1530,7 @@ }, ", { coreVitalPages: number; cls: number | null; fid: number | null | undefined; lcp: number | null | undefined; tbt: number; fcp: number | null | undefined; lcpRanks: number[]; fidRanks: number[]; clsRanks: number[]; }, ", "APMRouteCreateOptions", - ">; } & { \"GET /api/apm/rum-client/long-task-metrics\": ", + ">; } & { \"GET /internal/apm/ux/long-task-metrics\": ", { "pluginId": "@kbn/server-route-repository", "scope": "server", @@ -1538,7 +1538,7 @@ "section": "def-server.ServerRoute", "text": "ServerRoute" }, - "<\"GET /api/apm/rum-client/long-task-metrics\", ", + "<\"GET /internal/apm/ux/long-task-metrics\", ", "TypeC", "<{ query: ", "IntersectionC", @@ -1568,7 +1568,7 @@ }, ", { noOfLongTasks: number; sumOfLongTasks: number; longestLongTask: number; }, ", "APMRouteCreateOptions", - ">; } & { \"GET /api/apm/rum-client/url-search\": ", + ">; } & { \"GET /internal/apm/ux/url-search\": ", { "pluginId": "@kbn/server-route-repository", "scope": "server", @@ -1576,7 +1576,7 @@ "section": "def-server.ServerRoute", "text": "ServerRoute" }, - "<\"GET /api/apm/rum-client/url-search\", ", + "<\"GET /internal/apm/ux/url-search\", ", "TypeC", "<{ query: ", "IntersectionC", @@ -1606,7 +1606,7 @@ }, ", { total: number; items: { url: string; count: number; pld: number; }[]; }, ", "APMRouteCreateOptions", - ">; } & { \"GET /api/apm/rum-client/js-errors\": ", + ">; } & { \"GET /internal/apm/ux/js-errors\": ", { "pluginId": "@kbn/server-route-repository", "scope": "server", @@ -1614,7 +1614,7 @@ "section": "def-server.ServerRoute", "text": "ServerRoute" }, - "<\"GET /api/apm/rum-client/js-errors\", ", + "<\"GET /internal/apm/ux/js-errors\", ", "TypeC", "<{ query: ", "IntersectionC", @@ -2478,7 +2478,7 @@ "section": "def-server.APMRouteHandlerResources", "text": "APMRouteHandlerResources" }, - ", { currentPeriod: { x: number; y: number; }[]; previousPeriod: { x: number; y: number | null | undefined; }[]; }, ", + ", { currentPeriod: { x: number; y: number | null; }[]; previousPeriod: { x: number; y: number | null | undefined; }[]; }, ", "APMRouteCreateOptions", ">; } & { \"GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics\": ", { @@ -3456,7 +3456,7 @@ "section": "def-server.APMRouteHandlerResources", "text": "APMRouteHandlerResources" }, - ", { noHits: boolean; traceSamples: { transactionId: string; traceId: string; }[]; }, ", + ", { traceSamples: { transactionId: string; traceId: string; }[]; }, ", "APMRouteCreateOptions", ">; } & { \"GET /internal/apm/services/{serviceName}/transaction/charts/breakdown\": ", { @@ -3580,9 +3580,9 @@ "section": "def-server.APMRouteHandlerResources", "text": "APMRouteHandlerResources" }, - ", { currentPeriod: { noHits: boolean; transactionErrorRate: ", + ", { currentPeriod: { timeseries: ", "Coordinate", - "[]; average: number | null; }; previousPeriod: { transactionErrorRate: { x: number; y: number | null | undefined; }[]; noHits: boolean; average: number | null; }; }, ", + "[]; average: number | null; }; previousPeriod: { timeseries: { x: number; y: number | null | undefined; }[]; average: number | null; }; }, ", "APMRouteCreateOptions", ">; } & { \"GET /internal/apm/alerts/chart_preview/transaction_error_rate\": ", { @@ -4832,7 +4832,7 @@ "section": "def-server.APMRouteHandlerResources", "text": "APMRouteHandlerResources" }, - ", { currentTimeseries: { x: number; y: number; }[]; comparisonTimeseries: { x: number; y: number; }[] | null; }, ", + ", { currentTimeseries: { x: number; y: number | null; }[]; comparisonTimeseries: { x: number; y: number | null; }[] | null; }, ", "APMRouteCreateOptions", ">; } & { \"GET /internal/apm/backends/charts/error_rate\": ", { diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index 59d1f13ed89fb..37524daa39c51 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md @@ -89,6 +89,7 @@ readonly links: { readonly range: string; readonly significant_terms: string; readonly terms: string; + readonly terms_doc_count_error: string; readonly avg: string; readonly avg_bucket: string; readonly max_bucket: string; diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md index 788f0b9de8218..d0df23f35ab9e 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md @@ -17,5 +17,5 @@ export interface DocLinksStart | --- | --- | --- | | [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string | | | [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string | | -| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly elasticStackGetStarted: string;
readonly apm: {
readonly kibanaSettings: string;
readonly supportedServiceMaps: string;
readonly customLinks: string;
readonly droppedTransactionSpans: string;
readonly upgrading: string;
readonly metaData: string;
};
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly privileges: string;
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
readonly troubleshootGaps: string;
};
readonly securitySolution: {
readonly trustedApps: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
readonly autocompleteChanges: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Readonly<{
guide: string;
infrastructureThreshold: string;
logsThreshold: string;
metricsThreshold: string;
monitorStatus: string;
monitorUptime: string;
tlsCertificate: string;
uptimeDurationAnomaly: string;
}>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
elasticsearchEnableApiKeys: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly spaces: Readonly<{
kibanaLegacyUrlAliases: string;
kibanaDisableLegacyUrlAliasesApi: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
datastreamsILM: string;
beatsAgentComparison: string;
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
installElasticAgent: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
learnMoreBlog: string;
apiKeysLearnMore: string;
}>;
readonly ecs: {
readonly guide: string;
};
readonly clients: {
readonly guide: string;
readonly goOverview: string;
readonly javaIndex: string;
readonly jsIntro: string;
readonly netGuide: string;
readonly perlGuide: string;
readonly phpGuide: string;
readonly pythonGuide: string;
readonly rubyOverview: string;
readonly rustGuide: string;
};
readonly endpoints: {
readonly troubleshooting: string;
};
} | | +| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly elasticStackGetStarted: string;
readonly apm: {
readonly kibanaSettings: string;
readonly supportedServiceMaps: string;
readonly customLinks: string;
readonly droppedTransactionSpans: string;
readonly upgrading: string;
readonly metaData: string;
};
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly terms_doc_count_error: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly privileges: string;
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
readonly troubleshootGaps: string;
};
readonly securitySolution: {
readonly trustedApps: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
readonly autocompleteChanges: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Readonly<{
guide: string;
infrastructureThreshold: string;
logsThreshold: string;
metricsThreshold: string;
monitorStatus: string;
monitorUptime: string;
tlsCertificate: string;
uptimeDurationAnomaly: string;
}>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
elasticsearchEnableApiKeys: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly spaces: Readonly<{
kibanaLegacyUrlAliases: string;
kibanaDisableLegacyUrlAliasesApi: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
datastreamsILM: string;
beatsAgentComparison: string;
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
installElasticAgent: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
learnMoreBlog: string;
apiKeysLearnMore: string;
}>;
readonly ecs: {
readonly guide: string;
};
readonly clients: {
readonly guide: string;
readonly goOverview: string;
readonly javaIndex: string;
readonly jsIntro: string;
readonly netGuide: string;
readonly perlGuide: string;
readonly phpGuide: string;
readonly pythonGuide: string;
readonly rubyOverview: string;
readonly rustGuide: string;
};
readonly endpoints: {
readonly troubleshooting: string;
};
} | | diff --git a/docs/user/alerting/rule-types/es-query.asciidoc b/docs/user/alerting/rule-types/es-query.asciidoc index 86367a6a2e2c0..e3ce35687260f 100644 --- a/docs/user/alerting/rule-types/es-query.asciidoc +++ b/docs/user/alerting/rule-types/es-query.asciidoc @@ -17,7 +17,7 @@ Define properties to detect the condition. [role="screenshot"] image::user/alerting/images/rule-types-es-query-conditions.png[Five clauses define the condition to detect] -Index:: This clause requires an *index or index pattern* and a *time field* that will be used for the *time window*. +Index:: This clause requires an *index or data view* and a *time field* that will be used for the *time window*. Size:: This clause specifies the number of documents to pass to the configured actions when the the threshold condition is met. {es} query:: This clause specifies the ES DSL query to execute. The number of documents that match this query will be evaluated against the threshold condition. Aggregations are not supported at this time. diff --git a/docs/user/alerting/rule-types/geo-rule-types.asciidoc b/docs/user/alerting/rule-types/geo-rule-types.asciidoc index 244cf90c855a7..454c51ad69860 100644 --- a/docs/user/alerting/rule-types/geo-rule-types.asciidoc +++ b/docs/user/alerting/rule-types/geo-rule-types.asciidoc @@ -10,17 +10,17 @@ In the event that an entity is contained within a boundary, an alert may be gene ==== Requirements To create a Tracking containment rule, the following requirements must be present: -- *Tracks index or index pattern*: An index containing a `geo_point` field, `date` field, +- *Tracks index or data view*: An index containing a `geo_point` field, `date` field, and some form of entity identifier. An entity identifier is a `keyword` or `number` field that consistently identifies the entity to be tracked. The data in this index should be dynamically updating so that there are entity movements to alert upon. -- *Boundaries index or index pattern*: An index containing `geo_shape` data, such as boundary data and bounding box data. +- *Boundaries index or data view*: An index containing `geo_shape` data, such as boundary data and bounding box data. This data is presumed to be static (not updating). Shape data matching the query is harvested once when the rule is created and anytime after when the rule is re-enabled after disablement. By design, current interval entity locations (_current_ is determined by `date` in -the *Tracked index or index pattern*) are queried to determine if they are contained +the *Tracked index or data view*) are queried to determine if they are contained within any monitored boundaries. Entity data should be somewhat "real time", meaning the dates of new documents aren’t older than the current time minus the amount of the interval. If data older than @@ -39,13 +39,13 @@ as well as 2 Kuery bars used to provide additional filtering context for each of [role="screenshot"] image::user/alerting/images/alert-types-tracking-containment-conditions.png[Five clauses define the condition to detect] -Index (entity):: This clause requires an *index or index pattern*, a *time field* that will be used for the *time window*, and a *`geo_point` field* for tracking. +Index (entity):: This clause requires an *index or data view*, a *time field* that will be used for the *time window*, and a *`geo_point` field* for tracking. When entity:: This clause specifies which crossing option to track. The values *Entered*, *Exited*, and *Crossed* can be selected to indicate which crossing conditions should trigger a rule. *Entered* alerts on entry into a boundary, *Exited* alerts on exit from a boundary, and *Crossed* alerts on all boundary crossings whether they be entrances or exits. -Index (Boundary):: This clause requires an *index or index pattern*, a *`geo_shape` field* +Index (Boundary):: This clause requires an *index or data view*, a *`geo_shape` field* identifying boundaries, and an optional *Human-readable boundary name* for better alerting messages. diff --git a/docs/user/alerting/rule-types/index-threshold.asciidoc b/docs/user/alerting/rule-types/index-threshold.asciidoc index 8c45c158414f4..c65b0f66b1b63 100644 --- a/docs/user/alerting/rule-types/index-threshold.asciidoc +++ b/docs/user/alerting/rule-types/index-threshold.asciidoc @@ -17,7 +17,7 @@ Define properties to detect the condition. [role="screenshot"] image::user/alerting/images/rule-types-index-threshold-conditions.png[Five clauses define the condition to detect] -Index:: This clause requires an *index or index pattern* and a *time field* that will be used for the *time window*. +Index:: This clause requires an *index or data view* and a *time field* that will be used for the *time window*. When:: This clause specifies how the value to be compared to the threshold is calculated. The value is calculated by aggregating a numeric field a the *time window*. The aggregation options are: `count`, `average`, `sum`, `min`, and `max`. When using `count` the document count is used, and an aggregation field is not necessary. Over/Grouped Over:: This clause lets you configure whether the aggregation is applied over all documents, or should be split into groups using a grouping field. If grouping is used, an <> will be created for each group when it exceeds the threshold. To limit the number of alerts on high cardinality fields, you must specify the number of groups to check against the threshold. Only the *top* groups are checked. Threshold:: This clause defines a threshold value and a comparison operator (one of `is above`, `is above or equals`, `is below`, `is below or equals`, or `is between`). The result of the aggregation is compared to this threshold. diff --git a/package.json b/package.json index 39bfc891e65b3..677e817cf11c9 100644 --- a/package.json +++ b/package.json @@ -193,7 +193,6 @@ "archiver": "^5.2.0", "axios": "^0.21.1", "base64-js": "^1.3.1", - "bluebird": "3.5.5", "brace": "0.11.1", "broadcast-channel": "^4.2.0", "chalk": "^4.1.0", @@ -223,7 +222,7 @@ "deep-freeze-strict": "^1.1.1", "deepmerge": "^4.2.2", "del": "^5.1.0", - "elastic-apm-node": "^3.23.0", + "elastic-apm-node": "3.24.0", "execa": "^4.0.2", "exit-hook": "^2.2.0", "expiry-js": "0.1.7", @@ -494,7 +493,6 @@ "@types/archiver": "^5.1.0", "@types/babel__core": "^7.1.16", "@types/base64-js": "^1.2.5", - "@types/bluebird": "^3.1.1", "@types/chance": "^1.0.0", "@types/chroma-js": "^1.4.2", "@types/chromedriver": "^81.0.0", diff --git a/packages/elastic-eslint-config-kibana/.eslintrc.js b/packages/elastic-eslint-config-kibana/.eslintrc.js index 6bfefc8e118d4..99377540d38f7 100644 --- a/packages/elastic-eslint-config-kibana/.eslintrc.js +++ b/packages/elastic-eslint-config-kibana/.eslintrc.js @@ -88,6 +88,16 @@ module.exports = { exclude: USES_STYLED_COMPONENTS, disallowedMessage: `Prefer using @emotion/react instead. To use styled-components, ensure you plugin is enabled in @kbn/dev-utils/src/babel.ts.` }, + ...[ + '@elastic/eui/dist/eui_theme_light.json', + '@elastic/eui/dist/eui_theme_dark.json', + '@elastic/eui/dist/eui_theme_amsterdam_light.json', + '@elastic/eui/dist/eui_theme_amsterdam_dark.json', + ].map(from => ({ + from, + to: false, + disallowedMessage: `Use "@kbn/ui-shared-deps-src/theme" to access theme vars.` + })), ], ], diff --git a/packages/kbn-apm-config-loader/src/config.test.ts b/packages/kbn-apm-config-loader/src/config.test.ts index 60d773e3a420b..b0beebfefd6bd 100644 --- a/packages/kbn-apm-config-loader/src/config.test.ts +++ b/packages/kbn-apm-config-loader/src/config.test.ts @@ -21,7 +21,7 @@ describe('ApmConfiguration', () => { beforeEach(() => { // start with an empty env to avoid CI from spoiling snapshots, env is unique for each jest file process.env = {}; - + devConfigMock.raw = {}; packageMock.raw = { version: '8.0.0', build: { @@ -86,10 +86,11 @@ describe('ApmConfiguration', () => { let config = new ApmConfiguration(mockedRootDir, {}, false); expect(config.getConfig('serviceName')).toMatchInlineSnapshot(` Object { - "active": false, + "active": true, "breakdownMetrics": true, "captureSpanStackTraces": false, "centralConfig": false, + "contextPropagationOnly": true, "environment": "development", "globalLabels": Object {}, "logUncaughtExceptions": true, @@ -105,12 +106,13 @@ describe('ApmConfiguration', () => { config = new ApmConfiguration(mockedRootDir, {}, true); expect(config.getConfig('serviceName')).toMatchInlineSnapshot(` Object { - "active": false, + "active": true, "breakdownMetrics": false, "captureBody": "off", "captureHeaders": false, "captureSpanStackTraces": false, "centralConfig": false, + "contextPropagationOnly": true, "environment": "development", "globalLabels": Object { "git_rev": "sha", @@ -162,13 +164,12 @@ describe('ApmConfiguration', () => { it('does not load the configuration from the dev config in distributable', () => { devConfigMock.raw = { - active: true, - serverUrl: 'https://dev-url.co', + active: false, }; const config = new ApmConfiguration(mockedRootDir, {}, true); expect(config.getConfig('serviceName')).toEqual( expect.objectContaining({ - active: false, + active: true, }) ); }); @@ -224,4 +225,130 @@ describe('ApmConfiguration', () => { }) ); }); + + describe('contextPropagationOnly', () => { + it('sets "active: true" and "contextPropagationOnly: true" by default', () => { + expect(new ApmConfiguration(mockedRootDir, {}, false).getConfig('serviceName')).toEqual( + expect.objectContaining({ + active: true, + contextPropagationOnly: true, + }) + ); + + expect(new ApmConfiguration(mockedRootDir, {}, true).getConfig('serviceName')).toEqual( + expect.objectContaining({ + active: true, + contextPropagationOnly: true, + }) + ); + }); + + it('value from config overrides the default', () => { + const kibanaConfig = { + elastic: { + apm: { + active: false, + contextPropagationOnly: false, + }, + }, + }; + + expect( + new ApmConfiguration(mockedRootDir, kibanaConfig, false).getConfig('serviceName') + ).toEqual( + expect.objectContaining({ + active: false, + contextPropagationOnly: false, + }) + ); + + expect( + new ApmConfiguration(mockedRootDir, kibanaConfig, true).getConfig('serviceName') + ).toEqual( + expect.objectContaining({ + active: false, + contextPropagationOnly: false, + }) + ); + }); + + it('is "false" if "active: true" configured and "contextPropagationOnly" is not specified', () => { + const kibanaConfig = { + elastic: { + apm: { + active: true, + }, + }, + }; + + expect( + new ApmConfiguration(mockedRootDir, kibanaConfig, false).getConfig('serviceName') + ).toEqual( + expect.objectContaining({ + active: true, + contextPropagationOnly: false, + }) + ); + + expect( + new ApmConfiguration(mockedRootDir, kibanaConfig, true).getConfig('serviceName') + ).toEqual( + expect.objectContaining({ + active: true, + contextPropagationOnly: false, + }) + ); + }); + + it('throws if "active: false" set without configuring "contextPropagationOnly: false"', () => { + const kibanaConfig = { + elastic: { + apm: { + active: false, + }, + }, + }; + + expect(() => + new ApmConfiguration(mockedRootDir, kibanaConfig, false).getConfig('serviceName') + ).toThrowErrorMatchingInlineSnapshot( + `"APM is disabled, but context propagation is enabled. Please disable context propagation with contextPropagationOnly:false"` + ); + + expect(() => + new ApmConfiguration(mockedRootDir, kibanaConfig, true).getConfig('serviceName') + ).toThrowErrorMatchingInlineSnapshot( + `"APM is disabled, but context propagation is enabled. Please disable context propagation with contextPropagationOnly:false"` + ); + }); + + it('does not throw if "active: false" and "contextPropagationOnly: false" configured', () => { + const kibanaConfig = { + elastic: { + apm: { + active: false, + contextPropagationOnly: false, + }, + }, + }; + + expect( + new ApmConfiguration(mockedRootDir, kibanaConfig, false).getConfig('serviceName') + ).toEqual( + expect.objectContaining({ + active: false, + contextPropagationOnly: false, + }) + ); + + expect( + new ApmConfiguration(mockedRootDir, kibanaConfig, true).getConfig('serviceName') + ).toEqual( + expect.objectContaining({ + active: false, + contextPropagationOnly: false, + }) + ); + }); + }); }); diff --git a/packages/kbn-apm-config-loader/src/config.ts b/packages/kbn-apm-config-loader/src/config.ts index 999e4ce3a6805..ecafcbd7e3261 100644 --- a/packages/kbn-apm-config-loader/src/config.ts +++ b/packages/kbn-apm-config-loader/src/config.ts @@ -16,7 +16,8 @@ import type { AgentConfigOptions } from 'elastic-apm-node'; // https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html const DEFAULT_CONFIG: AgentConfigOptions = { - active: false, + active: true, + contextPropagationOnly: true, environment: 'development', logUncaughtExceptions: true, globalLabels: {}, @@ -71,6 +72,8 @@ export class ApmConfiguration { private getBaseConfig() { if (!this.baseConfig) { + const configFromSources = this.getConfigFromAllSources(); + this.baseConfig = merge( { serviceVersion: this.kibanaVersion, @@ -79,9 +82,7 @@ export class ApmConfiguration { this.getUuidConfig(), this.getGitConfig(), this.getCiConfig(), - this.getConfigFromKibanaConfig(), - this.getDevConfig(), - this.getConfigFromEnv() + configFromSources ); /** @@ -114,6 +115,12 @@ export class ApmConfiguration { config.active = true; } + if (process.env.ELASTIC_APM_CONTEXT_PROPAGATION_ONLY === 'true') { + config.contextPropagationOnly = true; + } else if (process.env.ELASTIC_APM_CONTEXT_PROPAGATION_ONLY === 'false') { + config.contextPropagationOnly = false; + } + if (process.env.ELASTIC_APM_ENVIRONMENT || process.env.NODE_ENV) { config.environment = process.env.ELASTIC_APM_ENVIRONMENT || process.env.NODE_ENV; } @@ -249,4 +256,28 @@ export class ApmConfiguration { return {}; } } + + /** + * Reads APM configuration from different sources and merges them together. + */ + private getConfigFromAllSources(): AgentConfigOptions { + const config = merge( + {}, + this.getConfigFromKibanaConfig(), + this.getDevConfig(), + this.getConfigFromEnv() + ); + + if (config.active === false && config.contextPropagationOnly !== false) { + throw new Error( + 'APM is disabled, but context propagation is enabled. Please disable context propagation with contextPropagationOnly:false' + ); + } + + if (config.active === true) { + config.contextPropagationOnly = config.contextPropagationOnly ?? false; + } + + return config; + } } diff --git a/packages/kbn-es-archiver/BUILD.bazel b/packages/kbn-es-archiver/BUILD.bazel index 90c63f82b72fa..2dc311ed74406 100644 --- a/packages/kbn-es-archiver/BUILD.bazel +++ b/packages/kbn-es-archiver/BUILD.bazel @@ -34,7 +34,6 @@ RUNTIME_DEPS = [ "//packages/kbn-utils", "@npm//@elastic/elasticsearch", "@npm//aggregate-error", - "@npm//bluebird", "@npm//chance", "@npm//globby", "@npm//json-stable-stringify", @@ -51,7 +50,6 @@ TYPES_DEPS = [ "@npm//aggregate-error", "@npm//globby", "@npm//zlib", - "@npm//@types/bluebird", "@npm//@types/chance", "@npm//@types/jest", "@npm//@types/json-stable-stringify", diff --git a/packages/kbn-es-archiver/src/actions/load.ts b/packages/kbn-es-archiver/src/actions/load.ts index 619c946f0c988..0a7235c566b52 100644 --- a/packages/kbn-es-archiver/src/actions/load.ts +++ b/packages/kbn-es-archiver/src/actions/load.ts @@ -40,6 +40,7 @@ export async function loadAction({ inputDir, skipExisting, useCreate, + docsOnly, client, log, kbnClient, @@ -47,6 +48,7 @@ export async function loadAction({ inputDir: string; skipExisting: boolean; useCreate: boolean; + docsOnly?: boolean; client: Client; log: ToolingLog; kbnClient: KbnClient; @@ -76,7 +78,7 @@ export async function loadAction({ await createPromiseFromStreams([ recordStream, - createCreateIndexStream({ client, stats, skipExisting, log }), + createCreateIndexStream({ client, stats, skipExisting, docsOnly, log }), createIndexDocRecordsStream(client, stats, progress, useCreate), ]); diff --git a/packages/kbn-es-archiver/src/actions/rebuild_all.ts b/packages/kbn-es-archiver/src/actions/rebuild_all.ts index f286f9719bdf1..360fdb438f2db 100644 --- a/packages/kbn-es-archiver/src/actions/rebuild_all.ts +++ b/packages/kbn-es-archiver/src/actions/rebuild_all.ts @@ -7,9 +7,9 @@ */ import { resolve, relative } from 'path'; -import { stat, Stats, rename, createReadStream, createWriteStream } from 'fs'; +import { Stats, createReadStream, createWriteStream } from 'fs'; +import { stat, rename } from 'fs/promises'; import { Readable, Writable } from 'stream'; -import { fromNode } from 'bluebird'; import { ToolingLog, REPO_ROOT } from '@kbn/dev-utils'; import { createPromiseFromStreams } from '@kbn/utils'; import { @@ -21,7 +21,7 @@ import { } from '../lib'; async function isDirectory(path: string): Promise { - const stats: Stats = await fromNode((cb) => stat(path, cb)); + const stats: Stats = await stat(path); return stats.isDirectory(); } @@ -50,7 +50,7 @@ export async function rebuildAllAction({ dataDir, log }: { dataDir: string; log: createWriteStream(tempFile), ] as [Readable, ...Writable[]]); - await fromNode((cb) => rename(tempFile, childPath, cb)); + await rename(tempFile, childPath); log.info('[%s] Rebuilt %j', archiveName, childName); } } diff --git a/packages/kbn-es-archiver/src/actions/save.ts b/packages/kbn-es-archiver/src/actions/save.ts index 07ed2b206c1dd..9cb5be05ac060 100644 --- a/packages/kbn-es-archiver/src/actions/save.ts +++ b/packages/kbn-es-archiver/src/actions/save.ts @@ -27,6 +27,7 @@ export async function saveAction({ client, log, raw, + keepIndexNames, query, }: { outputDir: string; @@ -34,6 +35,7 @@ export async function saveAction({ client: Client; log: ToolingLog; raw: boolean; + keepIndexNames?: boolean; query?: Record; }) { const name = relative(REPO_ROOT, outputDir); @@ -50,7 +52,7 @@ export async function saveAction({ // export and save the matching indices to mappings.json createPromiseFromStreams([ createListStream(indices), - createGenerateIndexRecordsStream(client, stats), + createGenerateIndexRecordsStream({ client, stats, keepIndexNames }), ...createFormatArchiveStreams(), createWriteStream(resolve(outputDir, 'mappings.json')), ] as [Readable, ...Writable[]]), @@ -58,7 +60,7 @@ export async function saveAction({ // export all documents from matching indexes into data.json.gz createPromiseFromStreams([ createListStream(indices), - createGenerateDocRecordsStream({ client, stats, progress, query }), + createGenerateDocRecordsStream({ client, stats, progress, keepIndexNames, query }), ...createFormatArchiveStreams({ gzip: !raw }), createWriteStream(resolve(outputDir, `data.json${raw ? '' : '.gz'}`)), ] as [Readable, ...Writable[]]), diff --git a/packages/kbn-es-archiver/src/cli.ts b/packages/kbn-es-archiver/src/cli.ts index db54a3bade74b..e54b4d5fbdb52 100644 --- a/packages/kbn-es-archiver/src/cli.ts +++ b/packages/kbn-es-archiver/src/cli.ts @@ -143,11 +143,12 @@ export function runCli() { $ node scripts/es_archiver save test/functional/es_archives/my_test_data logstash-* `, flags: { - boolean: ['raw'], + boolean: ['raw', 'keep-index-names'], string: ['query'], help: ` - --raw don't gzip the archives - --query query object to limit the documents being archived, needs to be properly escaped JSON + --raw don't gzip the archives + --keep-index-names don't change the names of Kibana indices to .kibana_1 + --query query object to limit the documents being archived, needs to be properly escaped JSON `, }, async run({ flags, esArchiver, statsMeta }) { @@ -168,6 +169,11 @@ export function runCli() { throw createFlagError('--raw does not take a value'); } + const keepIndexNames = flags['keep-index-names']; + if (typeof keepIndexNames !== 'boolean') { + throw createFlagError('--keep-index-names does not take a value'); + } + const query = flags.query; let parsedQuery; if (typeof query === 'string' && query.length > 0) { @@ -178,7 +184,7 @@ export function runCli() { } } - await esArchiver.save(path, indices, { raw, query: parsedQuery }); + await esArchiver.save(path, indices, { raw, keepIndexNames, query: parsedQuery }); }, }) .command({ @@ -196,9 +202,10 @@ export function runCli() { $ node scripts/es_archiver load my_test_data --config ../config.js `, flags: { - boolean: ['use-create'], + boolean: ['use-create', 'docs-only'], help: ` --use-create use create instead of index for loading documents + --docs-only load only documents, not indices `, }, async run({ flags, esArchiver, statsMeta }) { @@ -217,7 +224,12 @@ export function runCli() { throw createFlagError('--use-create does not take a value'); } - await esArchiver.load(path, { useCreate }); + const docsOnly = flags['docs-only']; + if (typeof docsOnly !== 'boolean') { + throw createFlagError('--docs-only does not take a value'); + } + + await esArchiver.load(path, { useCreate, docsOnly }); }, }) .command({ diff --git a/packages/kbn-es-archiver/src/es_archiver.ts b/packages/kbn-es-archiver/src/es_archiver.ts index ed27bc0afcf34..354197a98fa46 100644 --- a/packages/kbn-es-archiver/src/es_archiver.ts +++ b/packages/kbn-es-archiver/src/es_archiver.ts @@ -50,16 +50,22 @@ export class EsArchiver { * @param {String|Array} indices - the indices to archive * @param {Object} options * @property {Boolean} options.raw - should the archive be raw (unzipped) or not + * @property {Boolean} options.keepIndexNames - should the Kibana index name be kept as-is or renamed */ async save( path: string, indices: string | string[], - { raw = false, query }: { raw?: boolean; query?: Record } = {} + { + raw = false, + keepIndexNames = false, + query, + }: { raw?: boolean; keepIndexNames?: boolean; query?: Record } = {} ) { return await saveAction({ outputDir: Path.resolve(this.baseDir, path), indices, raw, + keepIndexNames, client: this.client, log: this.log, query, @@ -74,18 +80,21 @@ export class EsArchiver { * @property {Boolean} options.skipExisting - should existing indices * be ignored or overwritten * @property {Boolean} options.useCreate - use a create operation instead of index for documents + * @property {Boolean} options.docsOnly - load only documents, not indices */ async load( path: string, { skipExisting = false, useCreate = false, - }: { skipExisting?: boolean; useCreate?: boolean } = {} + docsOnly = false, + }: { skipExisting?: boolean; useCreate?: boolean; docsOnly?: boolean } = {} ) { return await loadAction({ inputDir: this.findArchive(path), skipExisting: !!skipExisting, useCreate: !!useCreate, + docsOnly, client: this.client, log: this.log, kbnClient: this.kbnClient, diff --git a/packages/kbn-es-archiver/src/lib/directory.ts b/packages/kbn-es-archiver/src/lib/directory.ts index f82e59a0ed252..2ff5b7e704edf 100644 --- a/packages/kbn-es-archiver/src/lib/directory.ts +++ b/packages/kbn-es-archiver/src/lib/directory.ts @@ -6,10 +6,9 @@ * Side Public License, v 1. */ -import { readdir } from 'fs'; -import { fromNode } from 'bluebird'; +import { readdir } from 'fs/promises'; export async function readDirectory(path: string) { - const allNames = await fromNode((cb) => readdir(path, cb)); + const allNames = await readdir(path); return allNames.filter((name) => !name.startsWith('.')); } diff --git a/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.test.ts b/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.test.ts index 2902812f51493..3b5f1f777b0e3 100644 --- a/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.test.ts +++ b/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.test.ts @@ -20,48 +20,24 @@ import { createStats } from '../stats'; const log = new ToolingLog(); -it('transforms each input index to a stream of docs using scrollSearch helper', async () => { - const responses: any = { - foo: [ - { - body: { - hits: { - total: 5, - hits: [ - { _index: 'foo', _type: '_doc', _id: '0', _source: {} }, - { _index: 'foo', _type: '_doc', _id: '1', _source: {} }, - { _index: 'foo', _type: '_doc', _id: '2', _source: {} }, - ], - }, - }, - }, - { - body: { - hits: { - total: 5, - hits: [ - { _index: 'foo', _type: '_doc', _id: '3', _source: {} }, - { _index: 'foo', _type: '_doc', _id: '4', _source: {} }, - ], - }, - }, - }, - ], - bar: [ - { - body: { - hits: { - total: 2, - hits: [ - { _index: 'bar', _type: '_doc', _id: '0', _source: {} }, - { _index: 'bar', _type: '_doc', _id: '1', _source: {} }, - ], - }, - }, - }, - ], - }; +interface SearchResponses { + [key: string]: Array<{ + body: { + hits: { + total: number; + hits: Array<{ + _index: string; + _type: string; + _id: string; + _source: Record; + }>; + }; + }; + }>; +} +function createMockClient(responses: SearchResponses) { + // TODO: replace with proper mocked client const client: any = { helpers: { scrollSearch: jest.fn(function* ({ index }) { @@ -71,29 +47,76 @@ it('transforms each input index to a stream of docs using scrollSearch helper', }), }, }; + return client; +} - const stats = createStats('test', log); - const progress = new Progress(); - - const results = await createPromiseFromStreams([ - createListStream(['bar', 'foo']), - createGenerateDocRecordsStream({ - client, - stats, - progress, - }), - createMapStream((record: any) => { - expect(record).toHaveProperty('type', 'doc'); - expect(record.value.source).toEqual({}); - expect(record.value.type).toBe('_doc'); - expect(record.value.index).toMatch(/^(foo|bar)$/); - expect(record.value.id).toMatch(/^\d+$/); - return `${record.value.index}:${record.value.id}`; - }), - createConcatStream([]), - ]); - - expect(client.helpers.scrollSearch).toMatchInlineSnapshot(` +describe('esArchiver: createGenerateDocRecordsStream()', () => { + it('transforms each input index to a stream of docs using scrollSearch helper', async () => { + const responses = { + foo: [ + { + body: { + hits: { + total: 5, + hits: [ + { _index: 'foo', _type: '_doc', _id: '0', _source: {} }, + { _index: 'foo', _type: '_doc', _id: '1', _source: {} }, + { _index: 'foo', _type: '_doc', _id: '2', _source: {} }, + ], + }, + }, + }, + { + body: { + hits: { + total: 5, + hits: [ + { _index: 'foo', _type: '_doc', _id: '3', _source: {} }, + { _index: 'foo', _type: '_doc', _id: '4', _source: {} }, + ], + }, + }, + }, + ], + bar: [ + { + body: { + hits: { + total: 2, + hits: [ + { _index: 'bar', _type: '_doc', _id: '0', _source: {} }, + { _index: 'bar', _type: '_doc', _id: '1', _source: {} }, + ], + }, + }, + }, + ], + }; + + const client = createMockClient(responses); + + const stats = createStats('test', log); + const progress = new Progress(); + + const results = await createPromiseFromStreams([ + createListStream(['bar', 'foo']), + createGenerateDocRecordsStream({ + client, + stats, + progress, + }), + createMapStream((record: any) => { + expect(record).toHaveProperty('type', 'doc'); + expect(record.value.source).toEqual({}); + expect(record.value.type).toBe('_doc'); + expect(record.value.index).toMatch(/^(foo|bar)$/); + expect(record.value.id).toMatch(/^\d+$/); + return `${record.value.index}:${record.value.id}`; + }), + createConcatStream([]), + ]); + + expect(client.helpers.scrollSearch).toMatchInlineSnapshot(` [MockFunction] { "calls": Array [ Array [ @@ -139,7 +162,7 @@ it('transforms each input index to a stream of docs using scrollSearch helper', ], } `); - expect(results).toMatchInlineSnapshot(` + expect(results).toMatchInlineSnapshot(` Array [ "bar:0", "bar:1", @@ -150,14 +173,14 @@ it('transforms each input index to a stream of docs using scrollSearch helper', "foo:4", ] `); - expect(progress).toMatchInlineSnapshot(` + expect(progress).toMatchInlineSnapshot(` Progress { "complete": 7, "loggingInterval": undefined, "total": 7, } `); - expect(stats).toMatchInlineSnapshot(` + expect(stats).toMatchInlineSnapshot(` Object { "bar": Object { "archived": false, @@ -193,4 +216,80 @@ it('transforms each input index to a stream of docs using scrollSearch helper', }, } `); + }); + + describe('keepIndexNames', () => { + it('changes .kibana* index names if keepIndexNames is not enabled', async () => { + const hits = [{ _index: '.kibana_7.16.0_001', _type: '_doc', _id: '0', _source: {} }]; + const responses = { + ['.kibana_7.16.0_001']: [{ body: { hits: { hits, total: hits.length } } }], + }; + const client = createMockClient(responses); + const stats = createStats('test', log); + const progress = new Progress(); + + const results = await createPromiseFromStreams([ + createListStream(['.kibana_7.16.0_001']), + createGenerateDocRecordsStream({ + client, + stats, + progress, + }), + createMapStream((record: { value: { index: string; id: string } }) => { + return `${record.value.index}:${record.value.id}`; + }), + createConcatStream([]), + ]); + expect(results).toEqual(['.kibana_1:0']); + }); + + it('does not change non-.kibana* index names if keepIndexNames is not enabled', async () => { + const hits = [{ _index: '.foo', _type: '_doc', _id: '0', _source: {} }]; + const responses = { + ['.foo']: [{ body: { hits: { hits, total: hits.length } } }], + }; + const client = createMockClient(responses); + const stats = createStats('test', log); + const progress = new Progress(); + + const results = await createPromiseFromStreams([ + createListStream(['.foo']), + createGenerateDocRecordsStream({ + client, + stats, + progress, + }), + createMapStream((record: { value: { index: string; id: string } }) => { + return `${record.value.index}:${record.value.id}`; + }), + createConcatStream([]), + ]); + expect(results).toEqual(['.foo:0']); + }); + + it('does not change .kibana* index names if keepIndexNames is enabled', async () => { + const hits = [{ _index: '.kibana_7.16.0_001', _type: '_doc', _id: '0', _source: {} }]; + const responses = { + ['.kibana_7.16.0_001']: [{ body: { hits: { hits, total: hits.length } } }], + }; + const client = createMockClient(responses); + const stats = createStats('test', log); + const progress = new Progress(); + + const results = await createPromiseFromStreams([ + createListStream(['.kibana_7.16.0_001']), + createGenerateDocRecordsStream({ + client, + stats, + progress, + keepIndexNames: true, + }), + createMapStream((record: { value: { index: string; id: string } }) => { + return `${record.value.index}:${record.value.id}`; + }), + createConcatStream([]), + ]); + expect(results).toEqual(['.kibana_7.16.0_001:0']); + }); + }); }); diff --git a/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.ts b/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.ts index a0636d6a3f76a..4bd44b649afd2 100644 --- a/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.ts +++ b/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.ts @@ -19,11 +19,13 @@ export function createGenerateDocRecordsStream({ client, stats, progress, + keepIndexNames, query, }: { client: Client; stats: Stats; progress: Progress; + keepIndexNames?: boolean; query?: Record; }) { return new Transform({ @@ -59,9 +61,10 @@ export function createGenerateDocRecordsStream({ this.push({ type: 'doc', value: { - // always rewrite the .kibana_* index to .kibana_1 so that + // if keepIndexNames is false, rewrite the .kibana_* index to .kibana_1 so that // when it is loaded it can skip migration, if possible - index: hit._index.startsWith('.kibana') ? '.kibana_1' : hit._index, + index: + hit._index.startsWith('.kibana') && !keepIndexNames ? '.kibana_1' : hit._index, type: hit._type, id: hit._id, source: hit._source, diff --git a/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.mock.ts b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.mock.ts new file mode 100644 index 0000000000000..d17bd33fa07ab --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.mock.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { deleteKibanaIndices } from './kibana_index'; + +export const mockDeleteKibanaIndices = jest.fn() as jest.MockedFunction; + +jest.mock('./kibana_index', () => ({ + deleteKibanaIndices: mockDeleteKibanaIndices, +})); diff --git a/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.ts b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.ts index 3a8180b724e07..615555b405e44 100644 --- a/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.ts +++ b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +import { mockDeleteKibanaIndices } from './create_index_stream.test.mock'; + import sinon from 'sinon'; import Chance from 'chance'; import { createPromiseFromStreams, createConcatStream, createListStream } from '@kbn/utils'; @@ -24,6 +26,10 @@ const chance = new Chance(); const log = createStubLogger(); +beforeEach(() => { + mockDeleteKibanaIndices.mockClear(); +}); + describe('esArchiver: createCreateIndexStream()', () => { describe('defaults', () => { it('deletes existing indices, creates all', async () => { @@ -167,6 +173,73 @@ describe('esArchiver: createCreateIndexStream()', () => { }); }); + describe('deleteKibanaIndices', () => { + function doTest(...indices: string[]) { + return createPromiseFromStreams([ + createListStream(indices.map((index) => createStubIndexRecord(index))), + createCreateIndexStream({ client: createStubClient(), stats: createStubStats(), log }), + createConcatStream([]), + ]); + } + + it('does not delete Kibana indices for indexes that do not start with .kibana', async () => { + await doTest('.foo'); + + expect(mockDeleteKibanaIndices).not.toHaveBeenCalled(); + }); + + it('deletes Kibana indices at most once for indices that start with .kibana', async () => { + // If we are loading the main Kibana index, we should delete all Kibana indices for backwards compatibility reasons. + await doTest('.kibana_7.16.0_001', '.kibana_task_manager_7.16.0_001'); + + expect(mockDeleteKibanaIndices).toHaveBeenCalledTimes(1); + expect(mockDeleteKibanaIndices).toHaveBeenCalledWith( + expect.not.objectContaining({ onlyTaskManager: true }) + ); + }); + + it('deletes Kibana task manager index at most once, using onlyTaskManager: true', async () => { + // If we are loading the Kibana task manager index, we should only delete that index, not any other Kibana indices. + await doTest('.kibana_task_manager_7.16.0_001', '.kibana_task_manager_7.16.0_002'); + + expect(mockDeleteKibanaIndices).toHaveBeenCalledTimes(1); + expect(mockDeleteKibanaIndices).toHaveBeenCalledWith( + expect.objectContaining({ onlyTaskManager: true }) + ); + }); + + it('deletes Kibana task manager index AND deletes all Kibana indices', async () => { + // Because we are reading from a stream, we can't look ahead to see if we'll eventually wind up deleting all Kibana indices. + // So, we first delete only the Kibana task manager indices, then we wind up deleting all Kibana indices. + await doTest('.kibana_task_manager_7.16.0_001', '.kibana_7.16.0_001'); + + expect(mockDeleteKibanaIndices).toHaveBeenCalledTimes(2); + expect(mockDeleteKibanaIndices).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ onlyTaskManager: true }) + ); + expect(mockDeleteKibanaIndices).toHaveBeenNthCalledWith( + 2, + expect.not.objectContaining({ onlyTaskManager: true }) + ); + }); + }); + + describe('docsOnly = true', () => { + it('passes through "hit" records without attempting to create indices', async () => { + const client = createStubClient(); + const stats = createStubStats(); + const output = await createPromiseFromStreams([ + createListStream([createStubIndexRecord('index'), createStubDocRecord('index', 1)]), + createCreateIndexStream({ client, stats, log, docsOnly: true }), + createConcatStream([]), + ]); + + sinon.assert.notCalled(client.indices.create as sinon.SinonSpy); + expect(output).toEqual([createStubDocRecord('index', 1)]); + }); + }); + describe('skipExisting = true', () => { it('ignores preexisting indexes', async () => { const client = createStubClient(['existing-index']); diff --git a/packages/kbn-es-archiver/src/lib/indices/create_index_stream.ts b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.ts index 50d13fc728c79..26472d72bef0f 100644 --- a/packages/kbn-es-archiver/src/lib/indices/create_index_stream.ts +++ b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.ts @@ -29,11 +29,13 @@ export function createCreateIndexStream({ client, stats, skipExisting = false, + docsOnly = false, log, }: { client: Client; stats: Stats; skipExisting?: boolean; + docsOnly?: boolean; log: ToolingLog; }) { const skipDocsFromIndices = new Set(); @@ -42,6 +44,7 @@ export function createCreateIndexStream({ // previous indices are removed so we're starting w/ a clean slate for // migrations. This only needs to be done once per archive load operation. let kibanaIndexAlreadyDeleted = false; + let kibanaTaskManagerIndexAlreadyDeleted = false; async function handleDoc(stream: Readable, record: DocRecord) { if (skipDocsFromIndices.has(record.value.index)) { @@ -53,13 +56,21 @@ export function createCreateIndexStream({ async function handleIndex(record: DocRecord) { const { index, settings, mappings, aliases } = record.value; - const isKibana = index.startsWith('.kibana'); + const isKibanaTaskManager = index.startsWith('.kibana_task_manager'); + const isKibana = index.startsWith('.kibana') && !isKibanaTaskManager; + + if (docsOnly) { + return; + } async function attemptToCreate(attemptNumber = 1) { try { if (isKibana && !kibanaIndexAlreadyDeleted) { - await deleteKibanaIndices({ client, stats, log }); - kibanaIndexAlreadyDeleted = true; + await deleteKibanaIndices({ client, stats, log }); // delete all .kibana* indices + kibanaIndexAlreadyDeleted = kibanaTaskManagerIndexAlreadyDeleted = true; + } else if (isKibanaTaskManager && !kibanaTaskManagerIndexAlreadyDeleted) { + await deleteKibanaIndices({ client, stats, onlyTaskManager: true, log }); // delete only .kibana_task_manager* indices + kibanaTaskManagerIndexAlreadyDeleted = true; } await client.indices.create( diff --git a/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.test.ts b/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.test.ts index 0e04d6b9ba799..fbd351cea63a9 100644 --- a/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.test.ts +++ b/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.test.ts @@ -21,7 +21,7 @@ describe('esArchiver: createGenerateIndexRecordsStream()', () => { await createPromiseFromStreams([ createListStream(indices), - createGenerateIndexRecordsStream(client, stats), + createGenerateIndexRecordsStream({ client, stats }), ]); expect(stats.getTestSummary()).toEqual({ @@ -40,7 +40,7 @@ describe('esArchiver: createGenerateIndexRecordsStream()', () => { await createPromiseFromStreams([ createListStream(['index1']), - createGenerateIndexRecordsStream(client, stats), + createGenerateIndexRecordsStream({ client, stats }), ]); const params = (client.indices.get as sinon.SinonSpy).args[0][0]; @@ -58,7 +58,7 @@ describe('esArchiver: createGenerateIndexRecordsStream()', () => { const indexRecords = await createPromiseFromStreams([ createListStream(['index1', 'index2', 'index3']), - createGenerateIndexRecordsStream(client, stats), + createGenerateIndexRecordsStream({ client, stats }), createConcatStream([]), ]); @@ -83,7 +83,7 @@ describe('esArchiver: createGenerateIndexRecordsStream()', () => { const indexRecords = await createPromiseFromStreams([ createListStream(['index1']), - createGenerateIndexRecordsStream(client, stats), + createGenerateIndexRecordsStream({ client, stats }), createConcatStream([]), ]); @@ -99,4 +99,51 @@ describe('esArchiver: createGenerateIndexRecordsStream()', () => { }, ]); }); + + describe('change index names', () => { + it('changes .kibana* index names if keepIndexNames is not enabled', async () => { + const stats = createStubStats(); + const client = createStubClient(['.kibana_7.16.0_001']); + + const indexRecords = await createPromiseFromStreams([ + createListStream(['.kibana_7.16.0_001']), + createGenerateIndexRecordsStream({ client, stats }), + createConcatStream([]), + ]); + + expect(indexRecords).toEqual([ + { type: 'index', value: expect.objectContaining({ index: '.kibana_1' }) }, + ]); + }); + + it('does not change non-.kibana* index names if keepIndexNames is not enabled', async () => { + const stats = createStubStats(); + const client = createStubClient(['.foo']); + + const indexRecords = await createPromiseFromStreams([ + createListStream(['.foo']), + createGenerateIndexRecordsStream({ client, stats }), + createConcatStream([]), + ]); + + expect(indexRecords).toEqual([ + { type: 'index', value: expect.objectContaining({ index: '.foo' }) }, + ]); + }); + + it('does not change .kibana* index names if keepIndexNames is enabled', async () => { + const stats = createStubStats(); + const client = createStubClient(['.kibana_7.16.0_001']); + + const indexRecords = await createPromiseFromStreams([ + createListStream(['.kibana_7.16.0_001']), + createGenerateIndexRecordsStream({ client, stats, keepIndexNames: true }), + createConcatStream([]), + ]); + + expect(indexRecords).toEqual([ + { type: 'index', value: expect.objectContaining({ index: '.kibana_7.16.0_001' }) }, + ]); + }); + }); }); diff --git a/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.ts b/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.ts index d647a4fe5f501..e3efaa2851609 100644 --- a/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.ts +++ b/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.ts @@ -11,7 +11,15 @@ import { Transform } from 'stream'; import { Stats } from '../stats'; import { ES_CLIENT_HEADERS } from '../../client_headers'; -export function createGenerateIndexRecordsStream(client: Client, stats: Stats) { +export function createGenerateIndexRecordsStream({ + client, + stats, + keepIndexNames, +}: { + client: Client; + stats: Stats; + keepIndexNames?: boolean; +}) { return new Transform({ writableObjectMode: true, readableObjectMode: true, @@ -59,9 +67,9 @@ export function createGenerateIndexRecordsStream(client: Client, stats: Stats) { this.push({ type: 'index', value: { - // always rewrite the .kibana_* index to .kibana_1 so that + // if keepIndexNames is false, rewrite the .kibana_* index to .kibana_1 so that // when it is loaded it can skip migration, if possible - index: index.startsWith('.kibana') ? '.kibana_1' : index, + index: index.startsWith('.kibana') && !keepIndexNames ? '.kibana_1' : index, settings, mappings, aliases, diff --git a/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts b/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts index 069db636c596b..eaae1de46f1e6 100644 --- a/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts +++ b/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts @@ -16,18 +16,21 @@ import { deleteIndex } from './delete_index'; import { ES_CLIENT_HEADERS } from '../../client_headers'; /** - * Deletes all indices that start with `.kibana` + * Deletes all indices that start with `.kibana`, or if onlyTaskManager==true, all indices that start with `.kibana_task_manager` */ export async function deleteKibanaIndices({ client, stats, + onlyTaskManager = false, log, }: { client: Client; stats: Stats; + onlyTaskManager?: boolean; log: ToolingLog; }) { - const indexNames = await fetchKibanaIndices(client); + const indexPattern = onlyTaskManager ? '.kibana_task_manager*' : '.kibana*'; + const indexNames = await fetchKibanaIndices(client, indexPattern); if (!indexNames.length) { return; } @@ -75,9 +78,9 @@ function isKibanaIndex(index?: string): index is string { ); } -async function fetchKibanaIndices(client: Client) { +async function fetchKibanaIndices(client: Client, indexPattern: string) { const resp = await client.cat.indices( - { index: '.kibana*', format: 'json' }, + { index: indexPattern, format: 'json' }, { headers: ES_CLIENT_HEADERS, } diff --git a/packages/kbn-logging/src/log_record.ts b/packages/kbn-logging/src/log_record.ts index 22931a67a823d..ee9ed0d69b749 100644 --- a/packages/kbn-logging/src/log_record.ts +++ b/packages/kbn-logging/src/log_record.ts @@ -20,4 +20,7 @@ export interface LogRecord { error?: Error; meta?: { [name: string]: any }; pid: number; + spanId?: string; + traceId?: string; + transactionId?: string; } diff --git a/packages/kbn-monaco/BUILD.bazel b/packages/kbn-monaco/BUILD.bazel index caf5f7c25b569..b2efa79f7fb34 100644 --- a/packages/kbn-monaco/BUILD.bazel +++ b/packages/kbn-monaco/BUILD.bazel @@ -11,6 +11,7 @@ SOURCE_FILES = glob( "src/**/*", ], exclude = [ + "**/__jest__", "**/*.test.*", "**/README.md", ], diff --git a/packages/kbn-monaco/__jest__/jest.mocks.ts b/packages/kbn-monaco/src/__jest__/jest.mocks.ts similarity index 94% rename from packages/kbn-monaco/__jest__/jest.mocks.ts rename to packages/kbn-monaco/src/__jest__/jest.mocks.ts index a210d7e171aa8..1df4f9b115002 100644 --- a/packages/kbn-monaco/__jest__/jest.mocks.ts +++ b/packages/kbn-monaco/src/__jest__/jest.mocks.ts @@ -33,8 +33,8 @@ const createMockModel = (ID: string) => { return model; }; -jest.mock('../src/monaco_imports', () => { - const original = jest.requireActual('../src/monaco_imports'); +jest.mock('../monaco_imports', () => { + const original = jest.requireActual('../monaco_imports'); const originalMonaco = original.monaco; const originalEditor = original.monaco.editor; diff --git a/packages/kbn-monaco/__jest__/types.ts b/packages/kbn-monaco/src/__jest__/types.ts similarity index 100% rename from packages/kbn-monaco/__jest__/types.ts rename to packages/kbn-monaco/src/__jest__/types.ts diff --git a/packages/kbn-monaco/src/painless/diagnostics_adapter.test.ts b/packages/kbn-monaco/src/painless/diagnostics_adapter.test.ts index 7c3a9b66a82e1..5d00ad726d031 100644 --- a/packages/kbn-monaco/src/painless/diagnostics_adapter.test.ts +++ b/packages/kbn-monaco/src/painless/diagnostics_adapter.test.ts @@ -5,11 +5,11 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import '../../__jest__/jest.mocks'; // Make sure this is the first import +import '../__jest__/jest.mocks'; // Make sure this is the first import import { Subscription } from 'rxjs'; -import { MockIModel } from '../../__jest__/types'; +import { MockIModel } from '../__jest__/types'; import { LangValidation } from '../types'; import { monaco } from '../monaco_imports'; import { ID } from './constants'; diff --git a/packages/kbn-monaco/tsconfig.json b/packages/kbn-monaco/tsconfig.json index 4a373843c555a..959051b17b782 100644 --- a/packages/kbn-monaco/tsconfig.json +++ b/packages/kbn-monaco/tsconfig.json @@ -14,6 +14,5 @@ }, "include": [ "src/**/*", - "__jest__/**/*", ] } diff --git a/packages/kbn-test/src/functional_test_runner/integration_tests/__fixtures__/failure_hooks/config.js b/packages/kbn-test/src/functional_test_runner/integration_tests/__fixtures__/failure_hooks/config.js index 0b9cfd88b4cbb..1375e5a3df2fd 100644 --- a/packages/kbn-test/src/functional_test_runner/integration_tests/__fixtures__/failure_hooks/config.js +++ b/packages/kbn-test/src/functional_test_runner/integration_tests/__fixtures__/failure_hooks/config.js @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { delay } from 'bluebird'; +import { setTimeout as setTimeoutAsync } from 'timers/promises'; export default function () { return { @@ -22,13 +22,13 @@ export default function () { lifecycle.testFailure.add(async (err, test) => { log.info('testFailure %s %s', err.message, test.fullTitle()); - await delay(10); + await setTimeoutAsync(10); log.info('testFailureAfterDelay %s %s', err.message, test.fullTitle()); }); lifecycle.testHookFailure.add(async (err, test) => { log.info('testHookFailure %s %s', err.message, test.fullTitle()); - await delay(10); + await setTimeoutAsync(10); log.info('testHookFailureAfterDelay %s %s', err.message, test.fullTitle()); }); }, diff --git a/packages/kbn-test/src/jest/run.ts b/packages/kbn-test/src/jest/run.ts index f2592500beeee..697402adf3dd1 100644 --- a/packages/kbn-test/src/jest/run.ts +++ b/packages/kbn-test/src/jest/run.ts @@ -44,6 +44,7 @@ declare global { export function runJest(configName = 'jest.config.js') { const argv = buildArgv(process.argv); + const devConfigName = 'jest.config.dev.js'; const log = new ToolingLog({ level: argv.verbose ? 'verbose' : 'info', @@ -67,18 +68,25 @@ export function runJest(configName = 'jest.config.js') { log.verbose('commonTestFiles:', commonTestFiles); let configPath; + let devConfigPath; // sets the working directory to the cwd or the common // base directory of the provided test files let wd = testFilesProvided ? commonTestFiles : cwd; + devConfigPath = resolve(wd, devConfigName); configPath = resolve(wd, configName); - while (!existsSync(configPath)) { + while (!existsSync(configPath) && !existsSync(devConfigPath)) { wd = resolve(wd, '..'); + devConfigPath = resolve(wd, devConfigName); configPath = resolve(wd, configName); } + if (existsSync(devConfigPath)) { + configPath = devConfigPath; + } + log.verbose(`no config provided, found ${configPath}`); process.argv.push('--config', configPath); diff --git a/packages/kbn-test/src/jest/setup/polyfills.js b/packages/kbn-test/src/jest/setup/polyfills.js index 48b597d280b4a..ebe6178dbdd91 100644 --- a/packages/kbn-test/src/jest/setup/polyfills.js +++ b/packages/kbn-test/src/jest/setup/polyfills.js @@ -6,13 +6,6 @@ * Side Public License, v 1. */ -// bluebird < v3.3.5 does not work with MutationObserver polyfill -// when MutationObserver exists, bluebird avoids using node's builtin async schedulers -const bluebird = require('bluebird'); -bluebird.Promise.setScheduler(function (fn) { - global.setImmediate.call(global, fn); -}); - const MutationObserver = require('mutation-observer'); Object.defineProperty(window, 'MutationObserver', { value: MutationObserver }); diff --git a/packages/kbn-test/src/jest/utils/testbed/index.ts b/packages/kbn-test/src/jest/utils/testbed/index.ts index dfa5f011853c0..0e839c180b6b6 100644 --- a/packages/kbn-test/src/jest/utils/testbed/index.ts +++ b/packages/kbn-test/src/jest/utils/testbed/index.ts @@ -7,4 +7,12 @@ */ export { registerTestBed } from './testbed'; -export type { TestBed, TestBedConfig, SetupFunc, UnwrapPromise } from './types'; +export type { + TestBed, + TestBedConfig, + AsyncTestBedConfig, + SetupFunc, + UnwrapPromise, + SyncSetupFunc, + AsyncSetupFunc, +} from './types'; diff --git a/packages/kbn-test/src/jest/utils/testbed/testbed.ts b/packages/kbn-test/src/jest/utils/testbed/testbed.ts index 472b9f2df939c..240ec25a9c296 100644 --- a/packages/kbn-test/src/jest/utils/testbed/testbed.ts +++ b/packages/kbn-test/src/jest/utils/testbed/testbed.ts @@ -16,7 +16,14 @@ import { mountComponentAsync, getJSXComponentWithProps, } from './mount_component'; -import { TestBedConfig, TestBed, SetupFunc } from './types'; +import { + TestBedConfig, + AsyncTestBedConfig, + TestBed, + SetupFunc, + SyncSetupFunc, + AsyncSetupFunc, +} from './types'; const defaultConfig: TestBedConfig = { defaultProps: {}, @@ -48,10 +55,18 @@ const defaultConfig: TestBedConfig = { }); ``` */ -export const registerTestBed = ( +export function registerTestBed( + Component: ComponentType, + config: AsyncTestBedConfig +): AsyncSetupFunc; +export function registerTestBed( Component: ComponentType, config?: TestBedConfig -): SetupFunc => { +): SyncSetupFunc; +export function registerTestBed( + Component: ComponentType, + config?: AsyncTestBedConfig | TestBedConfig +): SetupFunc { const { defaultProps = defaultConfig.defaultProps, memoryRouter = defaultConfig.memoryRouter!, @@ -188,7 +203,7 @@ export const registerTestBed = ( value, isAsync = false ) => { - const formInput = typeof input === 'string' ? find(input) : (input as ReactWrapper); + const formInput = typeof input === 'string' ? find(input) : input; if (!formInput.length) { throw new Error(`Input "${input}" was not found.`); @@ -207,7 +222,7 @@ export const registerTestBed = ( value, doUpdateComponent = true ) => { - const formSelect = typeof select === 'string' ? find(select) : (select as ReactWrapper); + const formSelect = typeof select === 'string' ? find(select) : select; if (!formSelect.length) { throw new Error(`Select "${select}" was not found.`); @@ -314,7 +329,7 @@ export const registerTestBed = ( router.history.push(url); }; - return { + const testBed: TestBed = { component, exists, find, @@ -336,8 +351,10 @@ export const registerTestBed = ( navigateTo, }, }; + + return testBed; } }; return setup; -}; +} diff --git a/packages/kbn-test/src/jest/utils/testbed/types.ts b/packages/kbn-test/src/jest/utils/testbed/types.ts index bba504951c0bc..121b848e51b51 100644 --- a/packages/kbn-test/src/jest/utils/testbed/types.ts +++ b/packages/kbn-test/src/jest/utils/testbed/types.ts @@ -7,10 +7,13 @@ */ import { Store } from 'redux'; -import { ReactWrapper } from 'enzyme'; +import { ReactWrapper as GenericReactWrapper } from 'enzyme'; import { LocationDescriptor } from 'history'; +export type AsyncSetupFunc = (props?: any) => Promise>; +export type SyncSetupFunc = (props?: any) => TestBed; export type SetupFunc = (props?: any) => TestBed | Promise>; +export type ReactWrapper = GenericReactWrapper; export interface EuiTableMetaData { /** Array of rows of the table. Each row exposes its reactWrapper and its columns */ @@ -51,7 +54,7 @@ export interface TestBed { find('myForm.nameInput'); ``` */ - find: (testSubject: T, reactWrapper?: ReactWrapper) => ReactWrapper; + find: (testSubject: T, reactWrapper?: ReactWrapper) => ReactWrapper; /** * Update the props of the mounted component * @@ -147,15 +150,23 @@ export interface TestBed { }; } -export interface TestBedConfig { +export interface BaseTestBedConfig { /** The default props to pass to the mounted component. */ defaultProps?: Record; /** Configuration object for the react-router `MemoryRouter. */ memoryRouter?: MemoryRouterConfig; /** An optional redux store. You can also provide a function that returns a store. */ store?: (() => Store) | Store | null; +} + +export interface AsyncTestBedConfig extends BaseTestBedConfig { + /* Mount the component asynchronously. When using "hooked" components with _useEffect()_ calls, you need to set this to "true". */ + doMountAsync: true; +} + +export interface TestBedConfig extends BaseTestBedConfig { /* Mount the component asynchronously. When using "hooked" components with _useEffect()_ calls, you need to set this to "true". */ - doMountAsync?: boolean; + doMountAsync?: false; } export interface MemoryRouterConfig { diff --git a/packages/kbn-test/src/mocha/junit_report_generation.test.js b/packages/kbn-test/src/mocha/junit_report_generation.test.js index e4e4499f556fd..c19550349fd85 100644 --- a/packages/kbn-test/src/mocha/junit_report_generation.test.js +++ b/packages/kbn-test/src/mocha/junit_report_generation.test.js @@ -7,9 +7,9 @@ */ import { resolve } from 'path'; -import { readFileSync } from 'fs'; +import { readFile } from 'fs/promises'; +import { promisify } from 'util'; -import { fromNode as fcb } from 'bluebird'; import { parseString } from 'xml2js'; import del from 'del'; import Mocha from 'mocha'; @@ -22,6 +22,8 @@ const DURATION_REGEX = /^\d+\.\d{3}$/; const ISO_DATE_SEC_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/; const XML_PATH = getUniqueJunitReportPath(PROJECT_DIR, 'test'); +const parseStringAsync = promisify(parseString); + describe('dev/mocha/junit report generation', () => { afterEach(() => { del.sync(resolve(PROJECT_DIR, 'target')); @@ -39,7 +41,7 @@ describe('dev/mocha/junit report generation', () => { mocha.addFile(resolve(PROJECT_DIR, 'test.js')); await new Promise((resolve) => mocha.run(resolve)); - const report = await fcb((cb) => parseString(readFileSync(XML_PATH), cb)); + const report = await parseStringAsync(await readFile(XML_PATH)); // test case results are wrapped in expect(report).toEqual({ diff --git a/packages/kbn-ui-shared-deps-npm/BUILD.bazel b/packages/kbn-ui-shared-deps-npm/BUILD.bazel index bbad873429b2b..416a4d4799b7b 100644 --- a/packages/kbn-ui-shared-deps-npm/BUILD.bazel +++ b/packages/kbn-ui-shared-deps-npm/BUILD.bazel @@ -23,7 +23,6 @@ filegroup( ) NPM_MODULE_EXTRA_FILES = [ - "eui_theme_vars/package.json", "package.json", "README.md" ] diff --git a/packages/kbn-ui-shared-deps-npm/eui_theme_vars/package.json b/packages/kbn-ui-shared-deps-npm/eui_theme_vars/package.json deleted file mode 100644 index a2448adf4d096..0000000000000 --- a/packages/kbn-ui-shared-deps-npm/eui_theme_vars/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "main": "../target_node/eui_theme_vars.js", - "types": "../target_types/eui_theme_vars.d.ts" -} \ No newline at end of file diff --git a/packages/kbn-ui-shared-deps-src/src/index.js b/packages/kbn-ui-shared-deps-src/src/index.js index 3e3643d3e2988..630cf75c447fd 100644 --- a/packages/kbn-ui-shared-deps-src/src/index.js +++ b/packages/kbn-ui-shared-deps-src/src/index.js @@ -59,8 +59,7 @@ exports.externals = { '@elastic/eui/lib/services': '__kbnSharedDeps__.ElasticEuiLibServices', '@elastic/eui/lib/services/format': '__kbnSharedDeps__.ElasticEuiLibServicesFormat', '@elastic/eui/dist/eui_charts_theme': '__kbnSharedDeps__.ElasticEuiChartsTheme', - '@elastic/eui/dist/eui_theme_light.json': '__kbnSharedDeps__.Theme.euiLightVars', - '@elastic/eui/dist/eui_theme_dark.json': '__kbnSharedDeps__.Theme.euiDarkVars', + // transient dep of eui 'react-beautiful-dnd': '__kbnSharedDeps__.ReactBeautifulDnD', lodash: '__kbnSharedDeps__.Lodash', diff --git a/packages/kbn-ui-shared-deps-src/src/theme.ts b/packages/kbn-ui-shared-deps-src/src/theme.ts index f058913cdeeab..33b8a594bfa5d 100644 --- a/packages/kbn-ui-shared-deps-src/src/theme.ts +++ b/packages/kbn-ui-shared-deps-src/src/theme.ts @@ -6,7 +6,9 @@ * Side Public License, v 1. */ +/* eslint-disable-next-line @kbn/eslint/module_migration */ import { default as v8Light } from '@elastic/eui/dist/eui_theme_amsterdam_light.json'; +/* eslint-disable-next-line @kbn/eslint/module_migration */ import { default as v8Dark } from '@elastic/eui/dist/eui_theme_amsterdam_dark.json'; const globals: any = typeof window === 'undefined' ? {} : window; diff --git a/packages/kbn-utils/src/streams/map_stream.test.ts b/packages/kbn-utils/src/streams/map_stream.test.ts index 2c3df67fdf35c..94c01d04f7dc9 100644 --- a/packages/kbn-utils/src/streams/map_stream.test.ts +++ b/packages/kbn-utils/src/streams/map_stream.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { delay } from 'bluebird'; +import { setTimeout as setTimeoutAsync } from 'timers/promises'; import { createPromiseFromStreams } from './promise_from_streams'; import { createListStream } from './list_stream'; @@ -39,7 +39,7 @@ describe('createMapStream()', () => { const result = await createPromiseFromStreams([ createListStream([1, 2, 3]), createMapStream(async (n: number, i: number) => { - await delay(n); + await setTimeoutAsync(n); return n * i; }), createConcatStream([]), diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 41bf27c7706a9..ee4e50627074a 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -118,6 +118,7 @@ export class DocLinksService { range: `${ELASTICSEARCH_DOCS}search-aggregations-bucket-range-aggregation.html`, significant_terms: `${ELASTICSEARCH_DOCS}search-aggregations-bucket-significantterms-aggregation.html`, terms: `${ELASTICSEARCH_DOCS}search-aggregations-bucket-terms-aggregation.html`, + terms_doc_count_error: `${ELASTICSEARCH_DOCS}search-aggregations-bucket-terms-aggregation.html#_per_bucket_document_count_error`, avg: `${ELASTICSEARCH_DOCS}search-aggregations-metrics-avg-aggregation.html`, avg_bucket: `${ELASTICSEARCH_DOCS}search-aggregations-pipeline-avg-bucket-aggregation.html`, max_bucket: `${ELASTICSEARCH_DOCS}search-aggregations-pipeline-max-bucket-aggregation.html`, @@ -613,6 +614,7 @@ export interface DocLinksStart { readonly range: string; readonly significant_terms: string; readonly terms: string; + readonly terms_doc_count_error: string; readonly avg: string; readonly avg_bucket: string; readonly max_bucket: string; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 26df3ee28d5c5..67edf0cf37614 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -557,6 +557,7 @@ export interface DocLinksStart { readonly range: string; readonly significant_terms: string; readonly terms: string; + readonly terms_doc_count_error: string; readonly avg: string; readonly avg_bucket: string; readonly max_bucket: string; diff --git a/src/core/server/logging/__snapshots__/logging_system.test.ts.snap b/src/core/server/logging/__snapshots__/logging_system.test.ts.snap index 49035cdda3915..e369d7b0cba37 100644 --- a/src/core/server/logging/__snapshots__/logging_system.test.ts.snap +++ b/src/core/server/logging/__snapshots__/logging_system.test.ts.snap @@ -117,7 +117,10 @@ Object { "message": "trace message", "meta": undefined, "pid": Any, + "spanId": undefined, "timestamp": 2012-02-01T14:33:22.011Z, + "traceId": undefined, + "transactionId": undefined, } `; @@ -133,6 +136,9 @@ Object { "some": "value", }, "pid": Any, + "spanId": undefined, "timestamp": 2012-02-01T14:33:22.011Z, + "traceId": undefined, + "transactionId": undefined, } `; diff --git a/src/core/server/logging/layouts/__snapshots__/json_layout.test.ts.snap b/src/core/server/logging/layouts/__snapshots__/json_layout.test.ts.snap index 48bbb19447411..0809dbffce670 100644 --- a/src/core/server/logging/layouts/__snapshots__/json_layout.test.ts.snap +++ b/src/core/server/logging/layouts/__snapshots__/json_layout.test.ts.snap @@ -88,3 +88,26 @@ Object { }, } `; + +exports[`\`format()\` correctly formats record and includes correct ECS version. 7`] = ` +Object { + "@timestamp": "2012-02-01T09:30:22.011-05:00", + "log": Object { + "level": "TRACE", + "logger": "context-7", + }, + "message": "message-6", + "process": Object { + "pid": 5355, + }, + "span": Object { + "id": "spanId-1", + }, + "trace": Object { + "id": "traceId-1", + }, + "transaction": Object { + "id": "transactionId-1", + }, +} +`; diff --git a/src/core/server/logging/layouts/json_layout.test.ts b/src/core/server/logging/layouts/json_layout.test.ts index 56184ebd67aee..84546f777ed0b 100644 --- a/src/core/server/logging/layouts/json_layout.test.ts +++ b/src/core/server/logging/layouts/json_layout.test.ts @@ -58,6 +58,16 @@ const records: LogRecord[] = [ timestamp, pid: 5355, }, + { + context: 'context-7', + level: LogLevel.Trace, + message: 'message-6', + timestamp, + pid: 5355, + spanId: 'spanId-1', + traceId: 'traceId-1', + transactionId: 'transactionId-1', + }, ]; test('`createConfigSchema()` creates correct schema.', () => { @@ -310,3 +320,40 @@ test('format() meta can not override timestamp', () => { }, }); }); + +test('format() meta can not override tracing properties', () => { + const layout = new JsonLayout(); + expect( + JSON.parse( + layout.format({ + message: 'foo', + timestamp, + level: LogLevel.Debug, + context: 'bar', + pid: 3, + meta: { + span: 'span_override', + trace: 'trace_override', + transaction: 'transaction_override', + }, + spanId: 'spanId-1', + traceId: 'traceId-1', + transactionId: 'transactionId-1', + }) + ) + ).toStrictEqual({ + ecs: { version: expect.any(String) }, + '@timestamp': '2012-02-01T09:30:22.011-05:00', + message: 'foo', + log: { + level: 'DEBUG', + logger: 'bar', + }, + process: { + pid: 3, + }, + span: { id: 'spanId-1' }, + trace: { id: 'traceId-1' }, + transaction: { id: 'transactionId-1' }, + }); +}); diff --git a/src/core/server/logging/layouts/json_layout.ts b/src/core/server/logging/layouts/json_layout.ts index f0717f49a6b15..5c23e7ac1a911 100644 --- a/src/core/server/logging/layouts/json_layout.ts +++ b/src/core/server/logging/layouts/json_layout.ts @@ -54,6 +54,9 @@ export class JsonLayout implements Layout { process: { pid: record.pid, }, + span: record.spanId ? { id: record.spanId } : undefined, + trace: record.traceId ? { id: record.traceId } : undefined, + transaction: record.transactionId ? { id: record.transactionId } : undefined, }; const output = record.meta ? merge({ ...record.meta }, log) : log; diff --git a/src/core/server/logging/logger.ts b/src/core/server/logging/logger.ts index e025c28a88f0e..2c9283da54897 100644 --- a/src/core/server/logging/logger.ts +++ b/src/core/server/logging/logger.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import apmAgent from 'elastic-apm-node'; import { Appender, LogLevel, LogRecord, LoggerFactory, LogMeta, Logger } from '@kbn/logging'; function isError(x: any): x is Error { @@ -73,6 +73,7 @@ export class BaseLogger implements Logger { meta, timestamp: new Date(), pid: process.pid, + ...this.getTraceIds(), }; } @@ -83,6 +84,15 @@ export class BaseLogger implements Logger { meta, timestamp: new Date(), pid: process.pid, + ...this.getTraceIds(), + }; + } + + private getTraceIds() { + return { + spanId: apmAgent.currentTraceIds['span.id'], + traceId: apmAgent.currentTraceIds['trace.id'], + transactionId: apmAgent.currentTraceIds['transaction.id'], }; } } diff --git a/src/core/server/saved_objects/migrations/__snapshots__/migrations_state_action_machine.test.ts.snap b/src/core/server/saved_objects/migrations/__snapshots__/migrations_state_action_machine.test.ts.snap index c3512d8fd50bd..760a83fa65cf1 100644 --- a/src/core/server/saved_objects/migrations/__snapshots__/migrations_state_action_machine.test.ts.snap +++ b/src/core/server/saved_objects/migrations/__snapshots__/migrations_state_action_machine.test.ts.snap @@ -84,11 +84,26 @@ Object { "type": "file-upload-telemetry", }, }, + Object { + "term": Object { + "type": "fleet-agent-actions", + }, + }, Object { "term": Object { "type": "fleet-agent-events", }, }, + Object { + "term": Object { + "type": "fleet-agents", + }, + }, + Object { + "term": Object { + "type": "fleet-enrollment-api-keys", + }, + }, Object { "term": Object { "type": "ml-telemetry", @@ -225,11 +240,26 @@ Object { "type": "file-upload-telemetry", }, }, + Object { + "term": Object { + "type": "fleet-agent-actions", + }, + }, Object { "term": Object { "type": "fleet-agent-events", }, }, + Object { + "term": Object { + "type": "fleet-agents", + }, + }, + Object { + "term": Object { + "type": "fleet-enrollment-api-keys", + }, + }, Object { "term": Object { "type": "ml-telemetry", @@ -370,11 +400,26 @@ Object { "type": "file-upload-telemetry", }, }, + Object { + "term": Object { + "type": "fleet-agent-actions", + }, + }, Object { "term": Object { "type": "fleet-agent-events", }, }, + Object { + "term": Object { + "type": "fleet-agents", + }, + }, + Object { + "term": Object { + "type": "fleet-enrollment-api-keys", + }, + }, Object { "term": Object { "type": "ml-telemetry", @@ -519,11 +564,26 @@ Object { "type": "file-upload-telemetry", }, }, + Object { + "term": Object { + "type": "fleet-agent-actions", + }, + }, Object { "term": Object { "type": "fleet-agent-events", }, }, + Object { + "term": Object { + "type": "fleet-agents", + }, + }, + Object { + "term": Object { + "type": "fleet-enrollment-api-keys", + }, + }, Object { "term": Object { "type": "ml-telemetry", @@ -705,11 +765,26 @@ Object { "type": "file-upload-telemetry", }, }, + Object { + "term": Object { + "type": "fleet-agent-actions", + }, + }, Object { "term": Object { "type": "fleet-agent-events", }, }, + Object { + "term": Object { + "type": "fleet-agents", + }, + }, + Object { + "term": Object { + "type": "fleet-enrollment-api-keys", + }, + }, Object { "term": Object { "type": "ml-telemetry", @@ -857,11 +932,26 @@ Object { "type": "file-upload-telemetry", }, }, + Object { + "term": Object { + "type": "fleet-agent-actions", + }, + }, Object { "term": Object { "type": "fleet-agent-events", }, }, + Object { + "term": Object { + "type": "fleet-agents", + }, + }, + Object { + "term": Object { + "type": "fleet-enrollment-api-keys", + }, + }, Object { "term": Object { "type": "ml-telemetry", diff --git a/src/core/server/saved_objects/migrations/core/unused_types.ts b/src/core/server/saved_objects/migrations/core/unused_types.ts index f5f6647201bbf..ddcadc09502f4 100644 --- a/src/core/server/saved_objects/migrations/core/unused_types.ts +++ b/src/core/server/saved_objects/migrations/core/unused_types.ts @@ -18,6 +18,10 @@ export const REMOVED_TYPES: string[] = [ 'file-upload-telemetry', // https://github.com/elastic/kibana/issues/91869 'fleet-agent-events', + // https://github.com/elastic/obs-dc-team/issues/334 + 'fleet-agents', + 'fleet-agent-actions', + 'fleet-enrollment-api-keys', // Was removed in 7.12 'ml-telemetry', 'server', diff --git a/src/dev/notice/bundled_notices.js b/src/dev/notice/bundled_notices.js index 7ab2a5b3f03fe..00b044e9053f7 100644 --- a/src/dev/notice/bundled_notices.js +++ b/src/dev/notice/bundled_notices.js @@ -7,18 +7,20 @@ */ import { resolve } from 'path'; -import { readFile } from 'fs'; +import { readFile } from 'fs/promises'; +import { promisify } from 'util'; -import { fromNode as fcb } from 'bluebird'; import glob from 'glob'; +const globAsync = promisify(glob); + export async function getBundledNotices(packageDirectory) { const pattern = resolve(packageDirectory, '*{LICENSE,NOTICE}*'); - const paths = await fcb((cb) => glob(pattern, cb)); + const paths = await globAsync(pattern); return Promise.all( paths.map(async (path) => ({ path, - text: await fcb((cb) => readFile(path, 'utf8', cb)), + text: await readFile(path, 'utf8'), })) ); } diff --git a/src/dev/precommit_hook/get_files_for_commit.js b/src/dev/precommit_hook/get_files_for_commit.js index 44c8c9d5e6bc0..366575ad15ae7 100644 --- a/src/dev/precommit_hook/get_files_for_commit.js +++ b/src/dev/precommit_hook/get_files_for_commit.js @@ -6,8 +6,7 @@ * Side Public License, v 1. */ -import SimpleGit from 'simple-git'; -import { fromNode as fcb } from 'bluebird'; +import SimpleGit from 'simple-git/promise'; import { REPO_ROOT } from '@kbn/utils'; import { File } from '../file'; @@ -22,7 +21,7 @@ import { File } from '../file'; export async function getFilesForCommit(gitRef) { const simpleGit = new SimpleGit(REPO_ROOT); const gitRefForDiff = gitRef ? gitRef : '--cached'; - const output = await fcb((cb) => simpleGit.diff(['--name-status', gitRefForDiff], cb)); + const output = await simpleGit.diff(['--name-status', gitRefForDiff]); return ( output diff --git a/src/plugins/charts/public/static/components/current_time.tsx b/src/plugins/charts/public/static/components/current_time.tsx index 9cc261bf3ed86..ad05f451b607f 100644 --- a/src/plugins/charts/public/static/components/current_time.tsx +++ b/src/plugins/charts/public/static/components/current_time.tsx @@ -10,8 +10,10 @@ import moment, { Moment } from 'moment'; import React, { FC } from 'react'; import { LineAnnotation, AnnotationDomainType, LineAnnotationStyle } from '@elastic/charts'; -import lightEuiTheme from '@elastic/eui/dist/eui_theme_light.json'; -import darkEuiTheme from '@elastic/eui/dist/eui_theme_dark.json'; +import { + euiLightVars as lightEuiTheme, + euiDarkVars as darkEuiTheme, +} from '@kbn/ui-shared-deps-src/theme'; interface CurrentTimeProps { isDarkMode: boolean; diff --git a/src/plugins/charts/public/static/components/endzones.tsx b/src/plugins/charts/public/static/components/endzones.tsx index 85a020e54eb37..695b51c9702d2 100644 --- a/src/plugins/charts/public/static/components/endzones.tsx +++ b/src/plugins/charts/public/static/components/endzones.tsx @@ -17,8 +17,10 @@ import { } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer } from '@elastic/eui'; -import lightEuiTheme from '@elastic/eui/dist/eui_theme_light.json'; -import darkEuiTheme from '@elastic/eui/dist/eui_theme_dark.json'; +import { + euiLightVars as lightEuiTheme, + euiDarkVars as darkEuiTheme, +} from '@kbn/ui-shared-deps-src/theme'; interface EndzonesProps { isDarkMode: boolean; diff --git a/src/plugins/dashboard/public/application/lib/sync_dashboard_url_state.ts b/src/plugins/dashboard/public/application/lib/sync_dashboard_url_state.ts index d314dc631631d..e0a1526baa473 100644 --- a/src/plugins/dashboard/public/application/lib/sync_dashboard_url_state.ts +++ b/src/plugins/dashboard/public/application/lib/sync_dashboard_url_state.ts @@ -25,6 +25,8 @@ import { convertSavedPanelsToPanelMap } from './convert_dashboard_panels'; type SyncDashboardUrlStateProps = DashboardBuildContext & { savedDashboard: DashboardSavedObject }; +let awaitingRemoval = false; + export const syncDashboardUrlState = ({ dispatchDashboardStateChange, getLatestDashboardState, @@ -89,15 +91,19 @@ const loadDashboardUrlState = ({ : undefined; // remove state from URL - kbnUrlStateStorage.kbnUrlControls.updateAsync((nextUrl) => { - if (nextUrl.includes(DASHBOARD_STATE_STORAGE_KEY)) { - return replaceUrlHashQuery(nextUrl, (query) => { - delete query[DASHBOARD_STATE_STORAGE_KEY]; - return query; - }); - } - return nextUrl; - }, true); + if (!awaitingRemoval) { + awaitingRemoval = true; + kbnUrlStateStorage.kbnUrlControls.updateAsync((nextUrl) => { + if (nextUrl.includes(DASHBOARD_STATE_STORAGE_KEY)) { + return replaceUrlHashQuery(nextUrl, (query) => { + delete query[DASHBOARD_STATE_STORAGE_KEY]; + return query; + }); + } + awaitingRemoval = false; + return nextUrl; + }, true); + } return { ..._.omit(rawAppStateInUrl, ['panels', 'query']), diff --git a/src/plugins/data/common/search/aggs/agg_type.ts b/src/plugins/data/common/search/aggs/agg_type.ts index 917f80d3b7819..3f91eadd19eb4 100644 --- a/src/plugins/data/common/search/aggs/agg_type.ts +++ b/src/plugins/data/common/search/aggs/agg_type.ts @@ -53,6 +53,7 @@ export interface AggTypeConfig< json?: boolean; decorateAggConfig?: () => any; postFlightRequest?: PostFlightRequestFn; + hasPrecisionError?: (aggBucket: Record) => boolean; getSerializedFormat?: (agg: TAggConfig) => SerializedFieldFormat; getValue?: (agg: TAggConfig, bucket: any) => any; getKey?: (bucket: any, key: any, agg: TAggConfig) => any; @@ -180,6 +181,9 @@ export class AggType< * is created, giving the agg type a chance to modify the agg config */ decorateAggConfig: () => any; + + hasPrecisionError?: (aggBucket: Record) => boolean; + /** * A function that needs to be called after the main request has been made * and should return an updated response @@ -283,6 +287,7 @@ export class AggType< this.getResponseAggs = config.getResponseAggs || (() => {}); this.decorateAggConfig = config.decorateAggConfig || (() => ({})); this.postFlightRequest = config.postFlightRequest || identity; + this.hasPrecisionError = config.hasPrecisionError; this.getSerializedFormat = config.getSerializedFormat || diff --git a/src/plugins/data/common/search/aggs/buckets/terms.test.ts b/src/plugins/data/common/search/aggs/buckets/terms.test.ts index 50aa4eb2b0357..524606f7c562f 100644 --- a/src/plugins/data/common/search/aggs/buckets/terms.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/terms.test.ts @@ -286,7 +286,19 @@ describe('Terms Agg', () => { { typesRegistry: mockAggTypesRegistry() } ); const { [BUCKET_TYPES.TERMS]: params } = aggConfigs.aggs[0].toDsl(); + expect(params.order).toEqual({ 'test-orderAgg.50': 'desc' }); }); + + test('should override "hasPrecisionError" for the "terms" bucket type', () => { + const aggConfigs = getAggConfigs(); + const { type } = aggConfigs.aggs[0]; + + expect(type.hasPrecisionError).toBeInstanceOf(Function); + + expect(type.hasPrecisionError!({})).toBeFalsy(); + expect(type.hasPrecisionError!({ doc_count_error_upper_bound: 0 })).toBeFalsy(); + expect(type.hasPrecisionError!({ doc_count_error_upper_bound: -1 })).toBeTruthy(); + }); }); }); diff --git a/src/plugins/data/common/search/aggs/buckets/terms.ts b/src/plugins/data/common/search/aggs/buckets/terms.ts index b9329bcb25af3..b3872d29beaac 100644 --- a/src/plugins/data/common/search/aggs/buckets/terms.ts +++ b/src/plugins/data/common/search/aggs/buckets/terms.ts @@ -85,6 +85,7 @@ export const getTermsBucketAgg = () => }; }, createFilter: createFilterTerms, + hasPrecisionError: (aggBucket) => Boolean(aggBucket?.doc_count_error_upper_bound), postFlightRequest: async ( resp, aggConfigs, diff --git a/src/plugins/data/common/search/search_source/mocks.ts b/src/plugins/data/common/search/search_source/mocks.ts index a0897a2dacf2a..dee5c09a6b858 100644 --- a/src/plugins/data/common/search/search_source/mocks.ts +++ b/src/plugins/data/common/search/search_source/mocks.ts @@ -14,7 +14,7 @@ import { SearchSource } from './search_source'; import { ISearchStartSearchSource, ISearchSource, SearchSourceFields } from './types'; export const searchSourceInstanceMock: MockedKeys = { - setPreferredSearchStrategyId: jest.fn(), + setOverwriteDataViewType: jest.fn(), setFields: jest.fn().mockReturnThis(), setField: jest.fn().mockReturnThis(), removeField: jest.fn().mockReturnThis(), diff --git a/src/plugins/data/common/search/search_source/search_source.ts b/src/plugins/data/common/search/search_source/search_source.ts index 50752523403cf..a3979ffa6e943 100644 --- a/src/plugins/data/common/search/search_source/search_source.ts +++ b/src/plugins/data/common/search/search_source/search_source.ts @@ -124,7 +124,8 @@ export interface SearchSourceDependencies extends FetchHandlers { /** @public **/ export class SearchSource { private id: string = uniqueId('data_source'); - private searchStrategyId?: string; + private shouldOverwriteDataViewType: boolean = false; + private overwriteDataViewType?: string; private parent?: SearchSource; private requestStartHandlers: Array< (searchSource: SearchSource, options?: ISearchOptions) => Promise @@ -149,11 +150,22 @@ export class SearchSource { *****/ /** - * internal, dont use - * @param searchStrategyId + * Used to make the search source overwrite the actual data view type for the + * specific requests done. This should only be needed very rarely, since it means + * e.g. we'd be treating a rollup index pattern as a regular one. Be very sure + * you understand the consequences of using this method before using it. + * + * @param overwriteType If `false` is passed in it will disable the overwrite, otherwise + * the passed in value will be used as the data view type for this search source. */ - setPreferredSearchStrategyId(searchStrategyId: string) { - this.searchStrategyId = searchStrategyId; + setOverwriteDataViewType(overwriteType: string | undefined | false) { + if (overwriteType === false) { + this.shouldOverwriteDataViewType = false; + this.overwriteDataViewType = undefined; + } else { + this.shouldOverwriteDataViewType = true; + this.overwriteDataViewType = overwriteType; + } } /** @@ -609,11 +621,7 @@ export class SearchSource { } private getIndexType(index?: IIndexPattern) { - if (this.searchStrategyId) { - return this.searchStrategyId === 'default' ? undefined : this.searchStrategyId; - } else { - return index?.type; - } + return this.shouldOverwriteDataViewType ? this.overwriteDataViewType : index?.type; } private readonly getFieldName = (fld: string | Record): string => diff --git a/src/plugins/data/common/search/tabify/index.ts b/src/plugins/data/common/search/tabify/index.ts index 279ff705f231c..3a4b094826e78 100644 --- a/src/plugins/data/common/search/tabify/index.ts +++ b/src/plugins/data/common/search/tabify/index.ts @@ -9,3 +9,4 @@ export { tabifyDocs, flattenHit } from './tabify_docs'; export { tabifyAggResponse } from './tabify'; export { tabifyGetColumns } from './get_columns'; +export { checkColumnForPrecisionError } from './utils'; diff --git a/src/plugins/data/common/search/tabify/response_writer.test.ts b/src/plugins/data/common/search/tabify/response_writer.test.ts index 603ccc0f493c7..cee297d255db3 100644 --- a/src/plugins/data/common/search/tabify/response_writer.test.ts +++ b/src/plugins/data/common/search/tabify/response_writer.test.ts @@ -166,6 +166,7 @@ describe('TabbedAggResponseWriter class', () => { field: 'geo.src', source: 'esaggs', sourceParams: { + hasPrecisionError: false, enabled: true, id: '1', indexPatternId: '1234', @@ -193,6 +194,7 @@ describe('TabbedAggResponseWriter class', () => { }, source: 'esaggs', sourceParams: { + hasPrecisionError: false, appliedTimeRange: undefined, enabled: true, id: '2', @@ -227,6 +229,7 @@ describe('TabbedAggResponseWriter class', () => { field: 'geo.src', source: 'esaggs', sourceParams: { + hasPrecisionError: false, enabled: true, id: '1', indexPatternId: '1234', @@ -254,6 +257,7 @@ describe('TabbedAggResponseWriter class', () => { }, source: 'esaggs', sourceParams: { + hasPrecisionError: false, appliedTimeRange: undefined, enabled: true, id: '2', diff --git a/src/plugins/data/common/search/tabify/response_writer.ts b/src/plugins/data/common/search/tabify/response_writer.ts index a0ba07598e53a..6af0576b9ed4d 100644 --- a/src/plugins/data/common/search/tabify/response_writer.ts +++ b/src/plugins/data/common/search/tabify/response_writer.ts @@ -10,7 +10,7 @@ import { isEmpty } from 'lodash'; import { IAggConfigs } from '../aggs'; import { tabifyGetColumns } from './get_columns'; -import { TabbedResponseWriterOptions, TabbedAggColumn, TabbedAggRow } from './types'; +import type { TabbedResponseWriterOptions, TabbedAggColumn, TabbedAggRow } from './types'; import { Datatable, DatatableColumn } from '../../../../expressions/common/expression_types/specs'; interface BufferColumn { @@ -80,6 +80,7 @@ export class TabbedAggResponseWriter { params: column.aggConfig.toSerializedFieldFormat(), source: 'esaggs', sourceParams: { + hasPrecisionError: Boolean(column.hasPrecisionError), indexPatternId: column.aggConfig.getIndexPattern()?.id, appliedTimeRange: column.aggConfig.params.field?.name && diff --git a/src/plugins/data/common/search/tabify/tabify.ts b/src/plugins/data/common/search/tabify/tabify.ts index a4d9551da75d5..d3273accff974 100644 --- a/src/plugins/data/common/search/tabify/tabify.ts +++ b/src/plugins/data/common/search/tabify/tabify.ts @@ -42,8 +42,14 @@ export function tabifyAggResponse( switch (agg.type.type) { case AggGroupNames.Buckets: - const aggBucket = get(bucket, agg.id); + const aggBucket = get(bucket, agg.id) as Record; const tabifyBuckets = new TabifyBuckets(aggBucket, agg.params, respOpts?.timeRange); + const precisionError = agg.type.hasPrecisionError?.(aggBucket); + + if (precisionError) { + // "сolumn" mutation, we have to do this here as this value is filled in based on aggBucket value + column.hasPrecisionError = true; + } if (tabifyBuckets.length) { tabifyBuckets.forEach((subBucket, tabifyBucketKey) => { diff --git a/src/plugins/data/common/search/tabify/types.ts b/src/plugins/data/common/search/tabify/types.ts index 758a2dfb181f2..9fadb0ef860e3 100644 --- a/src/plugins/data/common/search/tabify/types.ts +++ b/src/plugins/data/common/search/tabify/types.ts @@ -41,6 +41,7 @@ export interface TabbedAggColumn { aggConfig: IAggConfig; id: string; name: string; + hasPrecisionError?: boolean; } /** @public **/ diff --git a/src/plugins/data/common/search/tabify/utils.test.ts b/src/plugins/data/common/search/tabify/utils.test.ts new file mode 100644 index 0000000000000..ed29ef58ec0bf --- /dev/null +++ b/src/plugins/data/common/search/tabify/utils.test.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { checkColumnForPrecisionError } from './utils'; +import type { DatatableColumn } from '../../../../expressions'; + +describe('tabify utils', () => { + describe('checkDatatableForPrecisionError', () => { + test('should return true if there is a precision error in the column', () => { + expect( + checkColumnForPrecisionError({ + meta: { + sourceParams: { + hasPrecisionError: true, + }, + }, + } as unknown as DatatableColumn) + ).toBeTruthy(); + }); + test('should return false if there is no precision error in the column', () => { + expect( + checkColumnForPrecisionError({ + meta: { + sourceParams: { + hasPrecisionError: false, + }, + }, + } as unknown as DatatableColumn) + ).toBeFalsy(); + }); + test('should return false if precision error is not defined', () => { + expect( + checkColumnForPrecisionError({ + meta: { + sourceParams: {}, + }, + } as unknown as DatatableColumn) + ).toBeFalsy(); + }); + }); +}); diff --git a/src/plugins/data/common/search/tabify/utils.ts b/src/plugins/data/common/search/tabify/utils.ts new file mode 100644 index 0000000000000..1a4f87e2fed73 --- /dev/null +++ b/src/plugins/data/common/search/tabify/utils.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { DatatableColumn } from '../../../../expressions'; + +/** @public **/ +export const checkColumnForPrecisionError = (column: DatatableColumn) => + column.meta.sourceParams?.hasPrecisionError; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 0b749d90f7152..a54a9c7f35e3f 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -139,6 +139,7 @@ import { // tabify tabifyAggResponse, tabifyGetColumns, + checkColumnForPrecisionError, } from '../common'; export { AggGroupLabels, AggGroupNames, METRIC_TYPES, BUCKET_TYPES } from '../common'; @@ -246,6 +247,7 @@ export const search = { getResponseInspectorStats, tabifyAggResponse, tabifyGetColumns, + checkColumnForPrecisionError, }; /* diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts index 821f16e0cf68a..2cd7993e3b183 100644 --- a/src/plugins/data/public/search/index.ts +++ b/src/plugins/data/public/search/index.ts @@ -36,6 +36,7 @@ export { parseSearchSourceJSON, SearchSource, SortDirection, + checkColumnForPrecisionError, } from '../../common/search'; export type { ISessionService, diff --git a/src/plugins/discover/public/application/main/components/chart/discover_chart.test.tsx b/src/plugins/discover/public/application/main/components/chart/discover_chart.test.tsx index cb6d96f049c51..673d831f3fc96 100644 --- a/src/plugins/discover/public/application/main/components/chart/discover_chart.test.tsx +++ b/src/plugins/discover/public/application/main/components/chart/discover_chart.test.tsx @@ -23,7 +23,7 @@ import { VIEW_MODE } from '../../../../components/view_mode_toggle'; setHeaderActionMenuMounter(jest.fn()); -function getProps(timefield?: string) { +function getProps(isTimeBased: boolean = false) { const searchSourceMock = createSearchSourceMock({}); const services = discoverServiceMock; services.data.query.timefilter.timefilter.getAbsoluteTime = () => { @@ -85,6 +85,7 @@ function getProps(timefield?: string) { }) as DataCharts$; return { + isTimeBased, resetSavedSearch: jest.fn(), savedSearch: savedSearchMock, savedSearchDataChart$: charts$, @@ -94,7 +95,6 @@ function getProps(timefield?: string) { services, state: { columns: [] }, stateContainer: {} as GetStateReturn, - timefield, viewMode: VIEW_MODE.DOCUMENT_LEVEL, setDiscoverViewMode: jest.fn(), }; @@ -106,7 +106,7 @@ describe('Discover chart', () => { expect(component.find('[data-test-subj="discoverChartOptionsToggle"]').exists()).toBeFalsy(); }); test('render with filefield', () => { - const component = mountWithIntl(); + const component = mountWithIntl(); expect(component.find('[data-test-subj="discoverChartOptionsToggle"]').exists()).toBeTruthy(); }); }); diff --git a/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx b/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx index 1c382dbdf6b5a..abda176ab7b76 100644 --- a/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx +++ b/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx @@ -37,7 +37,7 @@ export function DiscoverChart({ services, state, stateContainer, - timefield, + isTimeBased, viewMode, setDiscoverViewMode, }: { @@ -48,7 +48,7 @@ export function DiscoverChart({ services: DiscoverServices; state: AppState; stateContainer: GetStateReturn; - timefield?: string; + isTimeBased: boolean; viewMode: VIEW_MODE; setDiscoverViewMode: (viewMode: VIEW_MODE) => void; }) { @@ -123,7 +123,7 @@ export function DiscoverChart({ /> )} - {timefield && ( + {isTimeBased && ( - {timefield && !state.hideChart && ( + {isTimeBased && !state.hideChart && (
(chartRef.current.element = element)} diff --git a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx index d9d0c2f064bdc..d71e99fd2b9a8 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx @@ -49,6 +49,7 @@ import { DOCUMENTS_VIEW_CLICK, FIELD_STATISTICS_VIEW_CLICK, } from '../../../components/field_stats_table/constants'; +import { DataViewType } from '../../../../../../data_views/common'; /** * Local storage key for sidebar persistence state @@ -122,8 +123,12 @@ export function DiscoverLayout({ useSavedSearchAliasMatchRedirect({ savedSearch, spaces, history }); - const timeField = useMemo(() => { - return indexPattern.type !== 'rollup' ? indexPattern.timeFieldName : undefined; + // We treat rollup v1 data views as non time based in Discover, since we query them + // in a non time based way using the regular _search API, since the internal + // representation of those documents does not have the time field that _field_caps + // reports us. + const isTimeBased = useMemo(() => { + return indexPattern.type !== DataViewType.ROLLUP && indexPattern.isTimeBased(); }, [indexPattern]); const initialSidebarClosed = Boolean(storage.get(SIDEBAR_CLOSED_KEY)); @@ -276,7 +281,7 @@ export function DiscoverLayout({ > {resultState === 'none' && ( diff --git a/src/plugins/discover/public/application/main/components/no_results/no_results.test.tsx b/src/plugins/discover/public/application/main/components/no_results/no_results.test.tsx index 1f6a2235b916f..ef63e178ecefc 100644 --- a/src/plugins/discover/public/application/main/components/no_results/no_results.test.tsx +++ b/src/plugins/discover/public/application/main/components/no_results/no_results.test.tsx @@ -62,7 +62,7 @@ describe('DiscoverNoResults', () => { describe('timeFieldName', () => { test('renders time range feedback', () => { const result = mountAndFindSubjects({ - timeFieldName: 'awesome_time_field', + isTimeBased: true, }); expect(result).toMatchInlineSnapshot(` Object { @@ -94,7 +94,7 @@ describe('DiscoverNoResults', () => { test('renders error message', () => { const error = new Error('Fatal error'); const result = mountAndFindSubjects({ - timeFieldName: 'awesome_time_field', + isTimeBased: true, error, }); expect(result).toMatchInlineSnapshot(` diff --git a/src/plugins/discover/public/application/main/components/no_results/no_results.tsx b/src/plugins/discover/public/application/main/components/no_results/no_results.tsx index 38dce654f0daa..558760f9c0035 100644 --- a/src/plugins/discover/public/application/main/components/no_results/no_results.tsx +++ b/src/plugins/discover/public/application/main/components/no_results/no_results.tsx @@ -22,7 +22,7 @@ import './_no_results.scss'; import { NoResultsIllustration } from './assets/no_results_illustration'; export interface DiscoverNoResultsProps { - timeFieldName?: string; + isTimeBased?: boolean; error?: Error; data?: DataPublicPluginStart; hasQuery?: boolean; @@ -31,7 +31,7 @@ export interface DiscoverNoResultsProps { } export function DiscoverNoResults({ - timeFieldName, + isTimeBased, error, data, hasFilters, @@ -54,7 +54,7 @@ export function DiscoverNoResults({ - {!!timeFieldName && getTimeFieldMessage()} + {isTimeBased && getTimeFieldMessage()} {(hasFilters || hasQuery) && ( { const history = useHistory(); - const showDatePicker = useMemo(() => indexPattern.isTimeBased(), [indexPattern]); + const showDatePicker = useMemo( + () => indexPattern.isTimeBased() && indexPattern.type !== DataViewType.ROLLUP, + [indexPattern] + ); const { TopNavMenu } = services.navigation.ui; const onOpenSavedSearch = useCallback( diff --git a/src/plugins/discover/public/application/main/utils/fetch_all.ts b/src/plugins/discover/public/application/main/utils/fetch_all.ts index efaa349181fb6..471616c9d4261 100644 --- a/src/plugins/discover/public/application/main/utils/fetch_all.ts +++ b/src/plugins/discover/public/application/main/utils/fetch_all.ts @@ -26,6 +26,7 @@ import { DataPublicPluginStart } from '../../../../../data/public'; import { SavedSearchData } from './use_saved_search'; import { DiscoverServices } from '../../../build_services'; import { ReduxLikeStateContainer } from '../../../../../kibana_utils/common'; +import { DataViewType } from '../../../../../data_views/common'; export function fetchAll( dataSubjects: SavedSearchData, @@ -72,16 +73,17 @@ export function fetchAll( }, }; + const isChartVisible = + !hideChart && indexPattern.isTimeBased() && indexPattern.type !== DataViewType.ROLLUP; + const all = forkJoin({ documents: fetchDocuments(dataSubjects, searchSource.createCopy(), subFetchDeps), - totalHits: - hideChart || !indexPattern.timeFieldName - ? fetchTotalHits(dataSubjects, searchSource.createCopy(), subFetchDeps) - : of(null), - chart: - !hideChart && indexPattern.timeFieldName - ? fetchChart(dataSubjects, searchSource.createCopy(), subFetchDeps) - : of(null), + totalHits: !isChartVisible + ? fetchTotalHits(dataSubjects, searchSource.createCopy(), subFetchDeps) + : of(null), + chart: isChartVisible + ? fetchChart(dataSubjects, searchSource.createCopy(), subFetchDeps) + : of(null), }); all.subscribe( diff --git a/src/plugins/discover/public/application/main/utils/fetch_documents.ts b/src/plugins/discover/public/application/main/utils/fetch_documents.ts index 0c80aee3f909d..b23dd3a0ed932 100644 --- a/src/plugins/discover/public/application/main/utils/fetch_documents.ts +++ b/src/plugins/discover/public/application/main/utils/fetch_documents.ts @@ -38,6 +38,13 @@ export const fetchDocuments = ( searchSource.setField('trackTotalHits', false); searchSource.setField('highlightAll', true); searchSource.setField('version', true); + if (searchSource.getField('index')?.type === 'rollup') { + // We treat that index pattern as "normal" even if it was a rollup index pattern, + // since the rollup endpoint does not support querying individual documents, but we + // can get them from the regular _search API that will be used if the index pattern + // not a rollup index pattern. + searchSource.setOverwriteDataViewType(undefined); + } sendLoadingMsg(documents$); diff --git a/src/plugins/discover/public/application/main/utils/fetch_total_hits.ts b/src/plugins/discover/public/application/main/utils/fetch_total_hits.ts index b80dfa9b31e7e..197e00ce0449f 100644 --- a/src/plugins/discover/public/application/main/utils/fetch_total_hits.ts +++ b/src/plugins/discover/public/application/main/utils/fetch_total_hits.ts @@ -13,6 +13,7 @@ import { isCompleteResponse, ISearchSource, } from '../../../../../data/public'; +import { DataViewType } from '../../../../../data_views/common'; import { Adapters } from '../../../../../inspector/common'; import { FetchStatus } from '../../types'; import { SavedSearchData } from './use_saved_search'; @@ -36,13 +37,18 @@ export function fetchTotalHits( } ) { const { totalHits$ } = data$; - const indexPattern = searchSource.getField('index'); searchSource.setField('trackTotalHits', true); - searchSource.setField('filter', data.query.timefilter.timefilter.createFilter(indexPattern!)); searchSource.setField('size', 0); searchSource.removeField('sort'); searchSource.removeField('fields'); searchSource.removeField('aggs'); + if (searchSource.getField('index')?.type === DataViewType.ROLLUP) { + // We treat that index pattern as "normal" even if it was a rollup index pattern, + // since the rollup endpoint does not support querying individual documents, but we + // can get them from the regular _search API that will be used if the index pattern + // not a rollup index pattern. + searchSource.setOverwriteDataViewType(undefined); + } sendLoadingMsg(totalHits$); diff --git a/src/plugins/discover/public/application/main/utils/update_search_source.ts b/src/plugins/discover/public/application/main/utils/update_search_source.ts index a08f180f1816c..1ee15790077cc 100644 --- a/src/plugins/discover/public/application/main/utils/update_search_source.ts +++ b/src/plugins/discover/public/application/main/utils/update_search_source.ts @@ -8,6 +8,7 @@ import { SORT_DEFAULT_ORDER_SETTING } from '../../../../common'; import { IndexPattern, ISearchSource } from '../../../../../data/common'; +import { DataViewType } from '../../../../../data_views/common'; import type { SortOrder } from '../../../services/saved_searches'; import { DiscoverServices } from '../../../build_services'; import { getSortForSearchSource } from '../../../components/doc_table'; @@ -44,14 +45,9 @@ export function updateSearchSource( indexPattern, uiSettings.get(SORT_DEFAULT_ORDER_SETTING) ); - searchSource - .setField('trackTotalHits', true) - .setField('sort', usedSort) - // Even when searching rollups, we want to use the default strategy so that we get back a - // document-like response. - .setPreferredSearchStrategyId('default'); + searchSource.setField('trackTotalHits', true).setField('sort', usedSort); - if (indexPattern.type !== 'rollup') { + if (indexPattern.type !== DataViewType.ROLLUP) { // Set the date range filter fields from timeFilter using the absolute format. Search sessions requires that it be converted from a relative range searchSource.setField('filter', data.query.timefilter.timefilter.createFilter(indexPattern)); } diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_document_selection.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_document_selection.tsx index abf63d2fe76b0..b1fc8993375da 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_document_selection.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid_document_selection.tsx @@ -17,8 +17,10 @@ import { EuiDataGridCellValueElementProps, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import themeDark from '@elastic/eui/dist/eui_theme_dark.json'; -import themeLight from '@elastic/eui/dist/eui_theme_light.json'; +import { + euiLightVars as themeLight, + euiDarkVars as themeDark, +} from '@kbn/ui-shared-deps-src/theme'; import { ElasticSearchHit } from '../../services/doc_views/doc_views_types'; import { DiscoverGridContext } from './discover_grid_context'; diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_expand_button.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_expand_button.tsx index 1a7080b9613d0..3453a535f98dd 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_expand_button.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid_expand_button.tsx @@ -8,8 +8,10 @@ import React, { useContext, useEffect } from 'react'; import { EuiButtonIcon, EuiDataGridCellValueElementProps, EuiToolTip } from '@elastic/eui'; -import themeDark from '@elastic/eui/dist/eui_theme_dark.json'; -import themeLight from '@elastic/eui/dist/eui_theme_light.json'; +import { + euiLightVars as themeLight, + euiDarkVars as themeDark, +} from '@kbn/ui-shared-deps-src/theme'; import { i18n } from '@kbn/i18n'; import { DiscoverGridContext } from './discover_grid_context'; import { EsHitRecord } from '../../application/types'; diff --git a/src/plugins/discover/public/components/discover_grid/get_render_cell_value.tsx b/src/plugins/discover/public/components/discover_grid/get_render_cell_value.tsx index bf7aaac1a86a2..8fd5f73701932 100644 --- a/src/plugins/discover/public/components/discover_grid/get_render_cell_value.tsx +++ b/src/plugins/discover/public/components/discover_grid/get_render_cell_value.tsx @@ -7,8 +7,10 @@ */ import React, { Fragment, useContext, useEffect } from 'react'; -import themeLight from '@elastic/eui/dist/eui_theme_light.json'; -import themeDark from '@elastic/eui/dist/eui_theme_dark.json'; +import { + euiLightVars as themeLight, + euiDarkVars as themeDark, +} from '@kbn/ui-shared-deps-src/theme'; import type { IndexPattern } from 'src/plugins/data/common'; import { diff --git a/src/plugins/discover/public/services/saved_searches/get_saved_searches.test.ts b/src/plugins/discover/public/services/saved_searches/get_saved_searches.test.ts index 1ac5f2d9653aa..1159320c9a09f 100644 --- a/src/plugins/discover/public/services/saved_searches/get_saved_searches.test.ts +++ b/src/plugins/discover/public/services/saved_searches/get_saved_searches.test.ts @@ -124,8 +124,8 @@ describe('getSavedSearch', () => { "serialize": [MockFunction], "setField": [MockFunction], "setFields": [MockFunction], + "setOverwriteDataViewType": [MockFunction], "setParent": [MockFunction], - "setPreferredSearchStrategyId": [MockFunction], }, "sharingSavedObjectProps": Object { "aliasTargetId": undefined, diff --git a/src/plugins/discover/public/services/saved_searches/saved_searches_utils.test.ts b/src/plugins/discover/public/services/saved_searches/saved_searches_utils.test.ts index 3d7d053bd1ec5..f2ad8b92adbc8 100644 --- a/src/plugins/discover/public/services/saved_searches/saved_searches_utils.test.ts +++ b/src/plugins/discover/public/services/saved_searches/saved_searches_utils.test.ts @@ -68,9 +68,10 @@ describe('saved_searches_utils', () => { "history": Array [], "id": "data_source1", "inheritOptions": Object {}, + "overwriteDataViewType": undefined, "parent": undefined, "requestStartHandlers": Array [], - "searchStrategyId": undefined, + "shouldOverwriteDataViewType": false, }, "sharingSavedObjectProps": Object {}, "sort": Array [], diff --git a/src/plugins/expressions/public/react_expression_renderer.tsx b/src/plugins/expressions/public/react_expression_renderer.tsx index 77b4402b22c06..b42ea3f3fd149 100644 --- a/src/plugins/expressions/public/react_expression_renderer.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.tsx @@ -12,7 +12,7 @@ import { Observable, Subscription } from 'rxjs'; import { filter } from 'rxjs/operators'; import useShallowCompareEffect from 'react-use/lib/useShallowCompareEffect'; import { EuiLoadingChart, EuiProgress } from '@elastic/eui'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars as theme } from '@kbn/ui-shared-deps-src/theme'; import { IExpressionLoaderParams, ExpressionRenderError, ExpressionRendererEvent } from './types'; import { ExpressionAstExpression, IInterpreterRenderHandlers } from '../common'; import { ExpressionLoader } from './loader'; diff --git a/src/plugins/index_pattern_editor/public/components/form_fields/type_field.tsx b/src/plugins/index_pattern_editor/public/components/form_fields/type_field.tsx index e8a48c5679879..0f4a040d1317b 100644 --- a/src/plugins/index_pattern_editor/public/components/form_fields/type_field.tsx +++ b/src/plugins/index_pattern_editor/public/components/form_fields/type_field.tsx @@ -8,8 +8,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -// @ts-ignore -import { euiColorAccent } from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars } from '@kbn/ui-shared-deps-src/theme'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -54,7 +53,7 @@ const rollupSelectItem = ( defaultMessage="Rollup data view" />   - + diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx b/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx index 0ad71d9a23cc2..89230ae03a923 100644 --- a/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx +++ b/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx @@ -50,6 +50,13 @@ const title = i18n.translate('indexPatternManagement.dataViewTable.title', { defaultMessage: 'Data views', }); +const securityDataView = i18n.translate( + 'indexPatternManagement.indexPatternTable.badge.securityDataViewTitle', + { + defaultMessage: 'Security Data View', + } +); + interface Props extends RouteComponentProps { canSave: boolean; showCreateDialog?: boolean; @@ -116,6 +123,10 @@ export const IndexPatternTable = ({   + {index.id && index.id === 'security-solution' && ( + {securityDataView} + )} + {index.tags && index.tags.map(({ key: tagKey, name: tagName }) => ( {tagName} diff --git a/src/plugins/kibana_react/public/code_editor/code_editor_field.tsx b/src/plugins/kibana_react/public/code_editor/code_editor_field.tsx index 0e6ab21159f15..85263b7006c16 100644 --- a/src/plugins/kibana_react/public/code_editor/code_editor_field.tsx +++ b/src/plugins/kibana_react/public/code_editor/code_editor_field.tsx @@ -7,8 +7,10 @@ */ import React from 'react'; -import darkTheme from '@elastic/eui/dist/eui_theme_dark.json'; -import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; +import { + euiLightVars as lightTheme, + euiDarkVars as darkTheme, +} from '@kbn/ui-shared-deps-src/theme'; import { EuiFormControlLayout } from '@elastic/eui'; import { CodeEditor, Props } from './code_editor'; diff --git a/src/plugins/kibana_react/public/code_editor/editor_theme.ts b/src/plugins/kibana_react/public/code_editor/editor_theme.ts index 0f362a28ea622..6c2727b123de8 100644 --- a/src/plugins/kibana_react/public/code_editor/editor_theme.ts +++ b/src/plugins/kibana_react/public/code_editor/editor_theme.ts @@ -8,8 +8,10 @@ import { monaco } from '@kbn/monaco'; -import darkTheme from '@elastic/eui/dist/eui_theme_dark.json'; -import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; +import { + euiLightVars as lightTheme, + euiDarkVars as darkTheme, +} from '@kbn/ui-shared-deps-src/theme'; // NOTE: For talk around where this theme information will ultimately live, // please see this discuss issue: https://github.com/elastic/kibana/issues/43814 diff --git a/src/plugins/kibana_react/public/code_editor/languages/constants.ts b/src/plugins/kibana_react/public/code_editor/languages/constants.ts index af80e4ccc56e2..510cc91cf5e76 100644 --- a/src/plugins/kibana_react/public/code_editor/languages/constants.ts +++ b/src/plugins/kibana_react/public/code_editor/languages/constants.ts @@ -10,3 +10,4 @@ export { LANG as CssLang } from './css/constants'; export { LANG as MarkdownLang } from './markdown/constants'; export { LANG as YamlLang } from './yaml/constants'; export { LANG as HandlebarsLang } from './handlebars/constants'; +export { LANG as HJsonLang } from './hjson/constants'; diff --git a/src/plugins/kibana_react/public/code_editor/languages/hjson/constants.ts b/src/plugins/kibana_react/public/code_editor/languages/hjson/constants.ts new file mode 100644 index 0000000000000..61e851e0b6f58 --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/languages/hjson/constants.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const LANG = 'hjson'; diff --git a/src/plugins/kibana_react/public/code_editor/languages/hjson/index.ts b/src/plugins/kibana_react/public/code_editor/languages/hjson/index.ts new file mode 100644 index 0000000000000..ff3c08267da9b --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/languages/hjson/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { LangModuleType } from '@kbn/monaco'; +import { languageConfiguration, lexerRules } from './language'; +import { LANG } from './constants'; + +export const Lang: LangModuleType = { ID: LANG, languageConfiguration, lexerRules }; diff --git a/src/plugins/kibana_react/public/code_editor/languages/hjson/language.ts b/src/plugins/kibana_react/public/code_editor/languages/hjson/language.ts new file mode 100644 index 0000000000000..d93cdfe4c4a22 --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/languages/hjson/language.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { monaco } from '@kbn/monaco'; + +export const languageConfiguration: monaco.languages.LanguageConfiguration = { + brackets: [ + ['{', '}'], + ['[', ']'], + ], + autoClosingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '"', close: '"', notIn: ['string'] }, + ], + comments: { + lineComment: '//', + blockComment: ['/*', '*/'], + }, +}; + +export const lexerRules: monaco.languages.IMonarchLanguage = { + defaultToken: '', + tokenPostfix: '', + escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/, + digits: /-?(?:0|[1-9]\d*)(?:(?:\.\d+)?(?:[eE][+-]?\d+)?)?/, + symbols: /[,:]+/, + tokenizer: { + root: [ + [/(@digits)n?/, 'number'], + [/(@symbols)n?/, 'delimiter'], + + { include: '@keyword' }, + { include: '@url' }, + { include: '@whitespace' }, + { include: '@brackets' }, + { include: '@keyName' }, + { include: '@string' }, + ], + + keyword: [[/(?:true|false|null)\b/, 'keyword']], + + url: [ + [ + /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/, + 'string', + ], + ], + + keyName: [[/(?:[^,\{\[\}\]\s]+|"(?:[^"\\]|\\.)*")\s*(?=:)/, 'variable']], + + brackets: [[/{/, '@push'], [/}/, '@pop'], [/[[(]/], [/[\])]/]], + + whitespace: [ + [/[ \t\r\n]+/, ''], + [/\/\*/, 'comment', '@comment'], + [/\/\/.*$/, 'comment'], + ], + + comment: [ + [/[^\/*]+/, 'comment'], + [/\*\//, 'comment', '@pop'], + [/[\/*]/, 'comment'], + ], + + string: [ + [/(?:[^,\{\[\}\]\s]+|"(?:[^"\\]|\\.)*")\s*/, 'string'], + [/"""/, 'string', '@stringLiteral'], + [/"/, 'string', '@stringDouble'], + ], + + stringDouble: [ + [/[^\\"]+/, 'string'], + [/@escapes/, 'string.escape'], + [/\\./, 'string.escape.invalid'], + [/"/, 'string', '@pop'], + ], + + stringLiteral: [ + [/"""/, 'string', '@pop'], + [/\\""""/, 'string', '@pop'], + [/./, 'string'], + ], + }, +} as monaco.languages.IMonarchLanguage; diff --git a/src/plugins/kibana_react/public/code_editor/languages/index.ts b/src/plugins/kibana_react/public/code_editor/languages/index.ts index b797ea44d1f91..f862997fdc2e3 100644 --- a/src/plugins/kibana_react/public/code_editor/languages/index.ts +++ b/src/plugins/kibana_react/public/code_editor/languages/index.ts @@ -10,5 +10,6 @@ import { Lang as CssLang } from './css'; import { Lang as HandlebarsLang } from './handlebars'; import { Lang as MarkdownLang } from './markdown'; import { Lang as YamlLang } from './yaml'; +import { Lang as HJson } from './hjson'; -export { CssLang, HandlebarsLang, MarkdownLang, YamlLang }; +export { CssLang, HandlebarsLang, MarkdownLang, YamlLang, HJson }; diff --git a/src/plugins/kibana_react/public/code_editor/register_languages.ts b/src/plugins/kibana_react/public/code_editor/register_languages.ts index a32318a7e4b20..62eccdabb5d98 100644 --- a/src/plugins/kibana_react/public/code_editor/register_languages.ts +++ b/src/plugins/kibana_react/public/code_editor/register_languages.ts @@ -6,9 +6,10 @@ * Side Public License, v 1. */ import { registerLanguage } from '@kbn/monaco'; -import { CssLang, HandlebarsLang, MarkdownLang, YamlLang } from './languages'; +import { CssLang, HandlebarsLang, MarkdownLang, YamlLang, HJson } from './languages'; registerLanguage(CssLang); registerLanguage(HandlebarsLang); registerLanguage(MarkdownLang); registerLanguage(YamlLang); +registerLanguage(HJson); diff --git a/src/plugins/saved_objects/public/saved_object/saved_object.test.ts b/src/plugins/saved_objects/public/saved_object/saved_object.test.ts index bd8d69d6b693e..01fc75df459bc 100644 --- a/src/plugins/saved_objects/public/saved_object/saved_object.test.ts +++ b/src/plugins/saved_objects/public/saved_object/saved_object.test.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import Bluebird from 'bluebird'; import { createSavedObjectClass } from './saved_object'; import { SavedObject, @@ -55,16 +54,16 @@ describe('Saved Object', () => { */ function stubESResponse(mockDocResponse: SimpleSavedObject) { // Stub out search for duplicate title: - savedObjectsClientStub.get = jest.fn().mockReturnValue(Bluebird.resolve(mockDocResponse)); - savedObjectsClientStub.update = jest.fn().mockReturnValue(Bluebird.resolve(mockDocResponse)); + savedObjectsClientStub.get = jest.fn().mockReturnValue(Promise.resolve(mockDocResponse)); + savedObjectsClientStub.update = jest.fn().mockReturnValue(Promise.resolve(mockDocResponse)); savedObjectsClientStub.find = jest .fn() - .mockReturnValue(Bluebird.resolve({ savedObjects: [], total: 0 })); + .mockReturnValue(Promise.resolve({ savedObjects: [], total: 0 })); savedObjectsClientStub.bulkGet = jest .fn() - .mockReturnValue(Bluebird.resolve({ savedObjects: [mockDocResponse] })); + .mockReturnValue(Promise.resolve({ savedObjects: [mockDocResponse] })); } function stubSavedObjectsClientCreate( @@ -73,7 +72,7 @@ describe('Saved Object', () => { ) { savedObjectsClientStub.create = jest .fn() - .mockReturnValue(resolve ? Bluebird.resolve(resp) : Bluebird.reject(resp)); + .mockReturnValue(resolve ? Promise.resolve(resp) : Promise.reject(resp)); } /** @@ -262,7 +261,7 @@ describe('Saved Object', () => { return createInitializedSavedObject({ type: 'dashboard', id: myId }).then((savedObject) => { savedObjectsClientStub.create = jest.fn().mockImplementation(() => { expect(savedObject.id).toBe(myId); - return Bluebird.resolve({ id: myId }); + return Promise.resolve({ id: myId }); }); savedObject.copyOnSave = false; @@ -296,7 +295,7 @@ describe('Saved Object', () => { return createInitializedSavedObject({ type: 'dashboard', id }).then((savedObject) => { savedObjectsClientStub.create = jest.fn().mockImplementation(() => { expect(savedObject.isSaving).toBe(true); - return Bluebird.resolve({ + return Promise.resolve({ type: 'dashboard', id, _version: 'foo', @@ -315,7 +314,7 @@ describe('Saved Object', () => { return createInitializedSavedObject({ type: 'dashboard' }).then((savedObject) => { savedObjectsClientStub.create = jest.fn().mockImplementation(() => { expect(savedObject.isSaving).toBe(true); - return Bluebird.reject(''); + return Promise.reject(''); }); expect(savedObject.isSaving).toBe(false); @@ -745,7 +744,7 @@ describe('Saved Object', () => { }, }); savedObject.searchSource!.setField('index', indexPattern); - return Bluebird.resolve(indexPattern); + return Promise.resolve(indexPattern); }); expect(!!savedObject.searchSource!.getField('index')).toBe(false); diff --git a/src/plugins/vis_types/timelion/server/handlers/chain_runner.js b/src/plugins/vis_types/timelion/server/handlers/chain_runner.js index 3710d015f3f69..fac1a68acc907 100644 --- a/src/plugins/vis_types/timelion/server/handlers/chain_runner.js +++ b/src/plugins/vis_types/timelion/server/handlers/chain_runner.js @@ -7,7 +7,6 @@ */ import _ from 'lodash'; -import Bluebird from 'bluebird'; import { i18n } from '@kbn/i18n'; import moment from 'moment'; @@ -42,7 +41,7 @@ export default function chainRunner(tlConfig) { function resolveArgument(item) { if (Array.isArray(item)) { - return Bluebird.all(_.map(item, resolveArgument)); + return Promise.all(_.map(item, resolveArgument)); } if (_.isObject(item)) { @@ -51,7 +50,7 @@ export default function chainRunner(tlConfig) { const itemFunctionDef = tlConfig.getFunction(item.function); if (itemFunctionDef.cacheKey && queryCache[itemFunctionDef.cacheKey(item)]) { stats.queryCount++; - return Bluebird.resolve(_.cloneDeep(queryCache[itemFunctionDef.cacheKey(item)])); + return Promise.resolve(_.cloneDeep(queryCache[itemFunctionDef.cacheKey(item)])); } return invoke(item.function, item.arguments); } @@ -94,7 +93,7 @@ export default function chainRunner(tlConfig) { args = _.map(args, resolveArgument); - return Bluebird.all(args).then(function (args) { + return Promise.all(args).then(function (args) { args.byName = indexArguments(functionDef, args); return functionDef.fn(args, tlConfig); }); @@ -128,7 +127,7 @@ export default function chainRunner(tlConfig) { return args; }); }); - return Bluebird.all(seriesList).then(function (args) { + return Promise.all(seriesList).then(function (args) { const list = _.chain(args).map('list').flatten().value(); const seriesList = _.merge.apply(this, _.flatten([{}, args])); seriesList.list = list; @@ -158,22 +157,22 @@ export default function chainRunner(tlConfig) { }) .value(); - return Bluebird.settle(promises).then(function (resolvedDatasources) { + return Promise.allSettled(promises).then(function (resolvedDatasources) { stats.queryTime = new Date().getTime(); _.each(queries, function (query, i) { const functionDef = tlConfig.getFunction(query.function); const resolvedDatasource = resolvedDatasources[i]; - if (resolvedDatasource.isRejected()) { - if (resolvedDatasource.reason().isBoom) { - throw resolvedDatasource.reason(); + if (resolvedDatasource.status === 'rejected') { + if (resolvedDatasource.reason.isBoom) { + throw resolvedDatasource.reason; } else { - throwWithCell(query.cell, resolvedDatasource.reason()); + throwWithCell(query.cell, resolvedDatasource.reason); } } - queryCache[functionDef.cacheKey(query)] = resolvedDatasource.value(); + queryCache[functionDef.cacheKey(query)] = resolvedDatasource.value; }); stats.cacheCount = _.keys(queryCache).length; diff --git a/src/plugins/vis_types/timelion/server/lib/alter.js b/src/plugins/vis_types/timelion/server/lib/alter.js index 2e234f3405c21..47a57f213cdbb 100644 --- a/src/plugins/vis_types/timelion/server/lib/alter.js +++ b/src/plugins/vis_types/timelion/server/lib/alter.js @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import Bluebird from 'bluebird'; import _ from 'lodash'; /* @param {Array} args @@ -18,7 +17,7 @@ import _ from 'lodash'; export default function alter(args, fn) { // In theory none of the args should ever be promises. This is probably a waste. - return Bluebird.all(args) + return Promise.all(args) .then(function (args) { const seriesList = args.shift(); diff --git a/src/plugins/vis_types/timelion/server/routes/run.ts b/src/plugins/vis_types/timelion/server/routes/run.ts index b8c0ce4ea6599..d615302253a1d 100644 --- a/src/plugins/vis_types/timelion/server/routes/run.ts +++ b/src/plugins/vis_types/timelion/server/routes/run.ts @@ -8,7 +8,6 @@ import { IRouter, Logger, CoreSetup } from 'kibana/server'; import { schema } from '@kbn/config-schema'; -import Bluebird from 'bluebird'; import _ from 'lodash'; // @ts-ignore import chainRunnerFn from '../handlers/chain_runner.js'; @@ -96,7 +95,7 @@ export function runRoute( }); try { const chainRunner = chainRunnerFn(tlConfig); - const sheet = await Bluebird.all(chainRunner.processRequest(request.body)); + const sheet = await Promise.all(await chainRunner.processRequest(request.body)); return response.ok({ body: { sheet, diff --git a/src/plugins/vis_types/timelion/server/series_functions/quandl.js b/src/plugins/vis_types/timelion/server/series_functions/quandl.js index 7e3a0f6de9aba..3c209879d7a4c 100644 --- a/src/plugins/vis_types/timelion/server/series_functions/quandl.js +++ b/src/plugins/vis_types/timelion/server/series_functions/quandl.js @@ -10,7 +10,6 @@ import { i18n } from '@kbn/i18n'; import _ from 'lodash'; import fetch from 'node-fetch'; import moment from 'moment'; -fetch.Promise = require('bluebird'); import Datasource from '../lib/classes/datasource'; diff --git a/src/plugins/vis_types/timelion/server/series_functions/static.js b/src/plugins/vis_types/timelion/server/series_functions/static.js index b5e8dd6df6682..afc1bd5afbbb8 100644 --- a/src/plugins/vis_types/timelion/server/series_functions/static.js +++ b/src/plugins/vis_types/timelion/server/series_functions/static.js @@ -9,7 +9,6 @@ import { i18n } from '@kbn/i18n'; import _ from 'lodash'; import Datasource from '../lib/classes/datasource'; -import Bluebird from 'bluebird'; export default new Datasource('static', { aliases: ['value'], @@ -51,7 +50,7 @@ export default new Datasource('static', { }); } - return Bluebird.resolve({ + return Promise.resolve({ type: 'seriesList', list: [ { diff --git a/src/plugins/vis_types/timelion/server/series_functions/worldbank_indicators.js b/src/plugins/vis_types/timelion/server/series_functions/worldbank_indicators.js index ba28a82345522..c2eb07890a102 100644 --- a/src/plugins/vis_types/timelion/server/series_functions/worldbank_indicators.js +++ b/src/plugins/vis_types/timelion/server/series_functions/worldbank_indicators.js @@ -9,7 +9,6 @@ import { i18n } from '@kbn/i18n'; import _ from 'lodash'; import worldbank from './worldbank.js'; -import Bluebird from 'bluebird'; import Datasource from '../lib/classes/datasource'; export default new Datasource('worldbank_indicators', { @@ -61,9 +60,11 @@ export default new Datasource('worldbank_indicators', { return worldbank.timelionFn(wbArgs, tlConfig); }); - return Bluebird.map(seriesLists, function (seriesList) { - return seriesList.list[0]; - }).then(function (list) { + return Promise.all( + seriesLists.map(function (seriesList) { + return seriesList.list[0]; + }) + ).then(function (list) { return { type: 'seriesList', list: list, diff --git a/src/plugins/vis_types/timelion/server/series_functions/yaxis.test.js b/src/plugins/vis_types/timelion/server/series_functions/yaxis.test.js index 6d627832544de..d68aa7ac70117 100644 --- a/src/plugins/vis_types/timelion/server/series_functions/yaxis.test.js +++ b/src/plugins/vis_types/timelion/server/series_functions/yaxis.test.js @@ -7,7 +7,6 @@ */ import fn from './yaxis'; -import Bluebird from 'bluebird'; const expect = require('chai').expect; import invoke from './helpers/invoke_series_fn.js'; @@ -25,7 +24,7 @@ describe('yaxis.js', () => { }); it('puts odd numbers of the left, even on the right, by default', () => { - return Bluebird.all([ + return Promise.all([ invoke(fn, [seriesList, 1]).then((r) => { expect(r.output.list[0]._global.yaxes[0].position).to.equal('left'); }), @@ -39,7 +38,7 @@ describe('yaxis.js', () => { }); it('it lets you override default positions', () => { - return Bluebird.all([ + return Promise.all([ invoke(fn, [seriesList, 1, null, null, 'right']).then((r) => { expect(r.output.list[0]._global.yaxes[0].position).to.equal('right'); }), @@ -50,7 +49,7 @@ describe('yaxis.js', () => { }); it('sets the minimum (default: no min)', () => { - return Bluebird.all([ + return Promise.all([ invoke(fn, [seriesList, 1, null]).then((r) => { expect(r.output.list[0]._global.yaxes[0].min).to.equal(null); }), @@ -61,7 +60,7 @@ describe('yaxis.js', () => { }); it('sets the max (default: no max)', () => { - return Bluebird.all([ + return Promise.all([ invoke(fn, [seriesList, 1, null]).then((r) => { expect(r.output.list[0]._global.yaxes[0].max).to.equal(undefined); }), @@ -72,7 +71,7 @@ describe('yaxis.js', () => { }); it('sets the units (default: no unit', () => { - return Bluebird.all([ + return Promise.all([ invoke(fn, [seriesList, 1, null, null, null, null, null, null]).then((r) => { expect(r.output.list[0]._global.yaxes[0].units).to.equal(undefined); }), diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/get_series_data.ts b/src/plugins/vis_types/timeseries/server/lib/vis_data/get_series_data.ts index a9a3825f5a9df..6f96c0d23cad7 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/get_series_data.ts +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/get_series_data.ts @@ -36,7 +36,10 @@ export async function getSeriesData( fieldFormatService, } = services; - const panelIndex = await cachedIndexPatternFetcher(panel.index_pattern); + const panelIndex = await cachedIndexPatternFetcher( + panel.index_pattern, + !panel.use_kibana_indexes + ); const strategy = await searchStrategyRegistry.getViableStrategy(requestContext, req, panelIndex); diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/get_table_data.ts b/src/plugins/vis_types/timeseries/server/lib/vis_data/get_table_data.ts index 3b53147dc6f93..35b6a78d0579b 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/get_table_data.ts +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/get_table_data.ts @@ -31,7 +31,10 @@ export async function getTableData( panel: Panel, services: VisTypeTimeseriesRequestServices ) { - const panelIndex = await services.cachedIndexPatternFetcher(panel.index_pattern); + const panelIndex = await services.cachedIndexPatternFetcher( + panel.index_pattern, + !panel.use_kibana_indexes + ); const strategy = await services.searchStrategyRegistry.getViableStrategy( requestContext, diff --git a/src/plugins/vis_types/vega/kibana.json b/src/plugins/vis_types/vega/kibana.json index 1a499e284c1a8..cedd73cc6d398 100644 --- a/src/plugins/vis_types/vega/kibana.json +++ b/src/plugins/vis_types/vega/kibana.json @@ -5,7 +5,7 @@ "ui": true, "requiredPlugins": ["data", "visualizations", "mapsEms", "expressions", "inspector"], "optionalPlugins": ["home","usageCollection"], - "requiredBundles": ["kibanaUtils", "kibanaReact", "visDefaultEditor", "esUiShared"], + "requiredBundles": ["kibanaUtils", "kibanaReact", "visDefaultEditor"], "owner": { "name": "Vis Editors", "githubTeam": "kibana-vis-editors" diff --git a/src/plugins/vis_types/vega/public/components/vega_editor.scss b/src/plugins/vis_types/vega/public/components/vega_editor.scss index 709aaa2030f68..4381c0097f96d 100644 --- a/src/plugins/vis_types/vega/public/components/vega_editor.scss +++ b/src/plugins/vis_types/vega/public/components/vega_editor.scss @@ -1,18 +1,28 @@ .visEditor--vega { .visEditorSidebar__config { padding: 0; + display: flex; + flex-direction: row; + overflow: hidden; + + min-height: $euiSize * 15; + + @include euiBreakpoint('xs', 's', 'm') { + max-height: $euiSize * 15; + } } } .vgaEditor { - @include euiBreakpoint('xs', 's', 'm') { - @include euiScrollBar; - max-height: $euiSize * 15; - overflow-y: auto; + width: 100%; + flex-grow: 1; + + .kibanaCodeEditor { + width: 100%; } } -.vgaEditor__aceEditorActions { +.vgaEditor__editorActions { position: absolute; z-index: $euiZLevel1; top: $euiSizeS; diff --git a/src/plugins/vis_types/vega/public/components/vega_vis_editor.tsx b/src/plugins/vis_types/vega/public/components/vega_vis_editor.tsx index d2f586eac9885..46e5e331c9455 100644 --- a/src/plugins/vis_types/vega/public/components/vega_vis_editor.tsx +++ b/src/plugins/vis_types/vega/public/components/vega_vis_editor.tsx @@ -6,14 +6,16 @@ * Side Public License, v 1. */ -import React, { useCallback } from 'react'; -import compactStringify from 'json-stringify-pretty-compact'; +import { XJsonLang } from '@kbn/monaco'; +import useMount from 'react-use/lib/useMount'; import hjson from 'hjson'; -import 'brace/mode/hjson'; + +import React, { useCallback, useState } from 'react'; +import compactStringify from 'json-stringify-pretty-compact'; import { i18n } from '@kbn/i18n'; import { VisEditorOptionsProps } from 'src/plugins/visualizations/public'; -import { EuiCodeEditor } from '../../../../es_ui_shared/public'; +import { CodeEditor, HJsonLang } from '../../../../kibana_react/public'; import { getNotifications } from '../services'; import { VisParams } from '../vega_fn'; import { VegaHelpMenu } from './vega_help_menu'; @@ -21,20 +23,6 @@ import { VegaActionsMenu } from './vega_actions_menu'; import './vega_editor.scss'; -const aceOptions = { - maxLines: Infinity, - highlightActiveLine: false, - showPrintMargin: false, - tabSize: 2, - useSoftTabs: true, - wrap: true, -}; - -const hjsonStringifyOptions = { - bracesSameLine: true, - keepWsc: true, -}; - function format( value: string, stringify: typeof hjson.stringify | typeof compactStringify, @@ -42,7 +30,11 @@ function format( ) { try { const spec = hjson.parse(value, { legacyRoot: false, keepWsc: true }); - return stringify(spec, options); + + return { + value: stringify(spec, options), + isValid: true, + }; } catch (err) { // This is a common case - user tries to format an invalid HJSON text getNotifications().toasts.addError(err, { @@ -51,44 +43,82 @@ function format( }), }); - return value; + return { value, isValid: false }; } } function VegaVisEditor({ stateParams, setValue }: VisEditorOptionsProps) { - const onChange = useCallback( - (value: string) => { + const [languageId, setLanguageId] = useState(); + + useMount(() => { + let specLang = XJsonLang.ID; + try { + JSON.parse(stateParams.spec); + } catch { + specLang = HJsonLang; + } + setLanguageId(specLang); + }); + + const setSpec = useCallback( + (value: string, specLang?: string) => { setValue('spec', value); + if (specLang) { + setLanguageId(specLang); + } }, [setValue] ); - const formatJson = useCallback( - () => setValue('spec', format(stateParams.spec, compactStringify)), - [setValue, stateParams.spec] - ); + const onChange = useCallback((value: string) => setSpec(value), [setSpec]); - const formatHJson = useCallback( - () => setValue('spec', format(stateParams.spec, hjson.stringify, hjsonStringifyOptions)), - [setValue, stateParams.spec] - ); + const formatJson = useCallback(() => { + const { value, isValid } = format(stateParams.spec, compactStringify); + + if (isValid) { + setSpec(value, XJsonLang.ID); + } + }, [setSpec, stateParams.spec]); + + const formatHJson = useCallback(() => { + const { value, isValid } = format(stateParams.spec, hjson.stringify, { + bracesSameLine: true, + keepWsc: true, + }); + + if (isValid) { + setSpec(value, HJsonLang); + } + }, [setSpec, stateParams.spec]); + + if (!languageId) { + return null; + } return ( -
- -
+
+
+
); } diff --git a/src/plugins/vis_types/vislib/public/vislib/components/tooltip/tooltip.js b/src/plugins/vis_types/vislib/public/vislib/components/tooltip/tooltip.js index e2decb86c9032..1faebdf0ce89c 100644 --- a/src/plugins/vis_types/vislib/public/vislib/components/tooltip/tooltip.js +++ b/src/plugins/vis_types/vislib/public/vislib/components/tooltip/tooltip.js @@ -12,7 +12,7 @@ import $ from 'jquery'; import { Binder } from '../../lib/binder'; import { positionTooltip } from './position_tooltip'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars as theme } from '@kbn/ui-shared-deps-src/theme'; let allContents = []; diff --git a/test/api_integration/apis/kql_telemetry/kql_telemetry.ts b/test/api_integration/apis/kql_telemetry/kql_telemetry.ts index 4825b454bc42f..310b99a5fb781 100644 --- a/test/api_integration/apis/kql_telemetry/kql_telemetry.ts +++ b/test/api_integration/apis/kql_telemetry/kql_telemetry.ts @@ -7,7 +7,6 @@ */ import expect from '@kbn/expect'; -import Bluebird from 'bluebird'; import { get } from 'lodash'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -89,7 +88,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('should only accept literal boolean values for the opt_in POST body param', function () { - return Bluebird.all([ + return Promise.all([ supertest .post('/api/kibana/kql_opt_in_stats') .set('content-type', 'application/json') diff --git a/test/functional/apps/management/_index_pattern_create_delete.js b/test/functional/apps/management/_index_pattern_create_delete.js index acb554fa7310b..62612ad5a9080 100644 --- a/test/functional/apps/management/_index_pattern_create_delete.js +++ b/test/functional/apps/management/_index_pattern_create_delete.js @@ -9,6 +9,7 @@ import expect from '@kbn/expect'; export default function ({ getService, getPageObjects }) { + const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const browser = getService('browser'); const log = getService('log'); @@ -18,16 +19,11 @@ export default function ({ getService, getPageObjects }) { const PageObjects = getPageObjects(['settings', 'common', 'header']); describe('creating and deleting default index', function describeIndexTests() { - before(function () { - // Delete .kibana index and then wait for Kibana to re-create it - return kibanaServer.uiSettings - .replace({}) - .then(function () { - return PageObjects.settings.navigateTo(); - }) - .then(function () { - return PageObjects.settings.clickKibanaIndexPatterns(); - }); + before(async function () { + await esArchiver.emptyKibanaIndex(); + await kibanaServer.uiSettings.replace({}); + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaIndexPatterns(); }); describe('can open and close editor', function () { @@ -39,17 +35,16 @@ export default function ({ getService, getPageObjects }) { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/107831 - describe.skip('validation', function () { + describe('validation', function () { it('can display errors', async function () { await PageObjects.settings.clickAddNewIndexPatternButton(); - await PageObjects.settings.setIndexPatternField('log*'); + await PageObjects.settings.setIndexPatternField('log-fake*'); await (await PageObjects.settings.getSaveIndexPatternButton()).click(); await find.byClassName('euiFormErrorText'); }); it('can resolve errors and submit', async function () { - await PageObjects.settings.selectTimeFieldOption('@timestamp'); + await PageObjects.settings.setIndexPatternField('log*'); await (await PageObjects.settings.getSaveIndexPatternButton()).click(); await PageObjects.settings.removeIndexPattern(); }); diff --git a/test/functional/apps/management/_scripted_fields_preview.js b/test/functional/apps/management/_scripted_fields_preview.js index 6347531b0cda8..12a6cb9537c8d 100644 --- a/test/functional/apps/management/_scripted_fields_preview.js +++ b/test/functional/apps/management/_scripted_fields_preview.js @@ -13,10 +13,11 @@ export default function ({ getService, getPageObjects }) { const PageObjects = getPageObjects(['settings']); const SCRIPTED_FIELD_NAME = 'myScriptedField'; - // FLAKY: https://github.com/elastic/kibana/issues/89475 - describe.skip('scripted fields preview', () => { + describe('scripted fields preview', () => { before(async function () { await browser.setWindowSize(1200, 800); + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaIndexPatterns(); await PageObjects.settings.createIndexPattern(); await PageObjects.settings.navigateTo(); diff --git a/test/functional/fixtures/es_archiver/date_nested/data.json b/test/functional/fixtures/es_archiver/date_nested/data.json index 0bdb3fc510a63..bb623f93627c7 100644 --- a/test/functional/fixtures/es_archiver/date_nested/data.json +++ b/test/functional/fixtures/es_archiver/date_nested/data.json @@ -6,7 +6,7 @@ "source": { "index-pattern": { "fields":"[]", - "timeFieldName": "@timestamp", + "timeFieldName": "nested.timestamp", "title": "date-nested" }, "type": "index-pattern" diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index 3955e457b5ffc..0150daec3afb5 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { delay } from 'bluebird'; +import { setTimeout as setTimeoutAsync } from 'timers/promises'; import expect from '@kbn/expect'; // @ts-ignore import fetch from 'node-fetch'; @@ -214,7 +214,7 @@ export class CommonPageObject extends FtrService { async sleep(sleepMilliseconds: number) { this.log.debug(`... sleep(${sleepMilliseconds}) start`); - await delay(sleepMilliseconds); + await setTimeoutAsync(sleepMilliseconds); this.log.debug(`... sleep(${sleepMilliseconds}) end`); } diff --git a/test/functional/page_objects/login_page.ts b/test/functional/page_objects/login_page.ts index 5318a2b2d0c15..74e85e60d1a69 100644 --- a/test/functional/page_objects/login_page.ts +++ b/test/functional/page_objects/login_page.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { delay } from 'bluebird'; +import { setTimeout as setTimeoutAsync } from 'timers/promises'; import { FtrService } from '../ftr_provider_context'; export class LoginPageObject extends FtrService { @@ -40,7 +40,7 @@ export class LoginPageObject extends FtrService { async sleep(sleepMilliseconds: number) { this.log.debug(`... sleep(${sleepMilliseconds}) start`); - await delay(sleepMilliseconds); + await setTimeoutAsync(sleepMilliseconds); this.log.debug(`... sleep(${sleepMilliseconds}) end`); } diff --git a/test/functional/page_objects/management/saved_objects_page.ts b/test/functional/page_objects/management/saved_objects_page.ts index 21af7aa477abd..87d5537d53ca3 100644 --- a/test/functional/page_objects/management/saved_objects_page.ts +++ b/test/functional/page_objects/management/saved_objects_page.ts @@ -7,7 +7,6 @@ */ import { keyBy } from 'lodash'; -import { map as mapAsync } from 'bluebird'; import { FtrService } from '../../ftr_provider_context'; export class SavedObjectsPageObject extends FtrService { @@ -201,51 +200,55 @@ export class SavedObjectsPageObject extends FtrService { async getElementsInTable() { const rows = await this.testSubjects.findAll('~savedObjectsTableRow'); - return mapAsync(rows, async (row) => { - const checkbox = await row.findByCssSelector('[data-test-subj*="checkboxSelectRow"]'); - // return the object type aria-label="index patterns" - const objectType = await row.findByTestSubject('objectType'); - const titleElement = await row.findByTestSubject('savedObjectsTableRowTitle'); - // not all rows have inspect button - Advanced Settings objects don't - // Advanced Settings has 2 actions, - // data-test-subj="savedObjectsTableAction-relationships" - // data-test-subj="savedObjectsTableAction-copy_saved_objects_to_space" - // Some other objects have the ... - // data-test-subj="euiCollapsedItemActionsButton" - // Maybe some objects still have the inspect element visible? - // !!! Also note that since we don't have spaces on OSS, the actions for the same object can be different depending on OSS or not - let menuElement = null; - let inspectElement = null; - let relationshipsElement = null; - let copySaveObjectsElement = null; - const actions = await row.findByClassName('euiTableRowCell--hasActions'); - // getting the innerHTML and checking if it 'includes' a string is faster than a timeout looking for each element - const actionsHTML = await actions.getAttribute('innerHTML'); - if (actionsHTML.includes('euiCollapsedItemActionsButton')) { - menuElement = await row.findByTestSubject('euiCollapsedItemActionsButton'); - } - if (actionsHTML.includes('savedObjectsTableAction-inspect')) { - inspectElement = await row.findByTestSubject('savedObjectsTableAction-inspect'); - } - if (actionsHTML.includes('savedObjectsTableAction-relationships')) { - relationshipsElement = await row.findByTestSubject('savedObjectsTableAction-relationships'); - } - if (actionsHTML.includes('savedObjectsTableAction-copy_saved_objects_to_space')) { - copySaveObjectsElement = await row.findByTestSubject( - 'savedObjectsTableAction-copy_saved_objects_to_space' - ); - } - return { - checkbox, - objectType: await objectType.getAttribute('aria-label'), - titleElement, - title: await titleElement.getVisibleText(), - menuElement, - inspectElement, - relationshipsElement, - copySaveObjectsElement, - }; - }); + return await Promise.all( + rows.map(async (row) => { + const checkbox = await row.findByCssSelector('[data-test-subj*="checkboxSelectRow"]'); + // return the object type aria-label="index patterns" + const objectType = await row.findByTestSubject('objectType'); + const titleElement = await row.findByTestSubject('savedObjectsTableRowTitle'); + // not all rows have inspect button - Advanced Settings objects don't + // Advanced Settings has 2 actions, + // data-test-subj="savedObjectsTableAction-relationships" + // data-test-subj="savedObjectsTableAction-copy_saved_objects_to_space" + // Some other objects have the ... + // data-test-subj="euiCollapsedItemActionsButton" + // Maybe some objects still have the inspect element visible? + // !!! Also note that since we don't have spaces on OSS, the actions for the same object can be different depending on OSS or not + let menuElement = null; + let inspectElement = null; + let relationshipsElement = null; + let copySaveObjectsElement = null; + const actions = await row.findByClassName('euiTableRowCell--hasActions'); + // getting the innerHTML and checking if it 'includes' a string is faster than a timeout looking for each element + const actionsHTML = await actions.getAttribute('innerHTML'); + if (actionsHTML.includes('euiCollapsedItemActionsButton')) { + menuElement = await row.findByTestSubject('euiCollapsedItemActionsButton'); + } + if (actionsHTML.includes('savedObjectsTableAction-inspect')) { + inspectElement = await row.findByTestSubject('savedObjectsTableAction-inspect'); + } + if (actionsHTML.includes('savedObjectsTableAction-relationships')) { + relationshipsElement = await row.findByTestSubject( + 'savedObjectsTableAction-relationships' + ); + } + if (actionsHTML.includes('savedObjectsTableAction-copy_saved_objects_to_space')) { + copySaveObjectsElement = await row.findByTestSubject( + 'savedObjectsTableAction-copy_saved_objects_to_space' + ); + } + return { + checkbox, + objectType: await objectType.getAttribute('aria-label'), + titleElement, + title: await titleElement.getVisibleText(), + menuElement, + inspectElement, + relationshipsElement, + copySaveObjectsElement, + }; + }) + ); } async getRowTitles() { @@ -259,35 +262,39 @@ export class SavedObjectsPageObject extends FtrService { async getRelationshipFlyout() { const rows = await this.testSubjects.findAll('relationshipsTableRow'); - return mapAsync(rows, async (row) => { - const objectType = await row.findByTestSubject('relationshipsObjectType'); - const relationship = await row.findByTestSubject('directRelationship'); - const titleElement = await row.findByTestSubject('relationshipsTitle'); - const inspectElement = await row.findByTestSubject('relationshipsTableAction-inspect'); - return { - objectType: await objectType.getAttribute('aria-label'), - relationship: await relationship.getVisibleText(), - titleElement, - title: await titleElement.getVisibleText(), - inspectElement, - }; - }); + return await Promise.all( + rows.map(async (row) => { + const objectType = await row.findByTestSubject('relationshipsObjectType'); + const relationship = await row.findByTestSubject('directRelationship'); + const titleElement = await row.findByTestSubject('relationshipsTitle'); + const inspectElement = await row.findByTestSubject('relationshipsTableAction-inspect'); + return { + objectType: await objectType.getAttribute('aria-label'), + relationship: await relationship.getVisibleText(), + titleElement, + title: await titleElement.getVisibleText(), + inspectElement, + }; + }) + ); } async getInvalidRelations() { const rows = await this.testSubjects.findAll('invalidRelationshipsTableRow'); - return mapAsync(rows, async (row) => { - const objectType = await row.findByTestSubject('relationshipsObjectType'); - const objectId = await row.findByTestSubject('relationshipsObjectId'); - const relationship = await row.findByTestSubject('directRelationship'); - const error = await row.findByTestSubject('relationshipsError'); - return { - type: await objectType.getVisibleText(), - id: await objectId.getVisibleText(), - relationship: await relationship.getVisibleText(), - error: await error.getVisibleText(), - }; - }); + return await Promise.all( + rows.map(async (row) => { + const objectType = await row.findByTestSubject('relationshipsObjectType'); + const objectId = await row.findByTestSubject('relationshipsObjectId'); + const relationship = await row.findByTestSubject('directRelationship'); + const error = await row.findByTestSubject('relationshipsError'); + return { + type: await objectType.getVisibleText(), + id: await objectId.getVisibleText(), + relationship: await relationship.getVisibleText(), + error: await error.getVisibleText(), + }; + }) + ); } async getTableSummary() { diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts index d3443f9cf4925..38f29702059ed 100644 --- a/test/functional/page_objects/settings_page.ts +++ b/test/functional/page_objects/settings_page.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { map as mapAsync } from 'bluebird'; import expect from '@kbn/expect'; import { FtrService } from '../ftr_provider_context'; @@ -234,23 +233,29 @@ export class SettingsPageObject extends FtrService { async getFieldNames() { const fieldNameCells = await this.testSubjects.findAll('editIndexPattern > indexedFieldName'); - return await mapAsync(fieldNameCells, async (cell) => { - return (await cell.getVisibleText()).trim(); - }); + return await Promise.all( + fieldNameCells.map(async (cell) => { + return (await cell.getVisibleText()).trim(); + }) + ); } async getFieldTypes() { const fieldNameCells = await this.testSubjects.findAll('editIndexPattern > indexedFieldType'); - return await mapAsync(fieldNameCells, async (cell) => { - return (await cell.getVisibleText()).trim(); - }); + return await Promise.all( + fieldNameCells.map(async (cell) => { + return (await cell.getVisibleText()).trim(); + }) + ); } async getScriptedFieldLangs() { const fieldNameCells = await this.testSubjects.findAll('editIndexPattern > scriptedFieldLang'); - return await mapAsync(fieldNameCells, async (cell) => { - return (await cell.getVisibleText()).trim(); - }); + return await Promise.all( + fieldNameCells.map(async (cell) => { + return (await cell.getVisibleText()).trim(); + }) + ); } async setFieldTypeFilter(type: string) { @@ -327,9 +332,11 @@ export class SettingsPageObject extends FtrService { async getAllIndexPatternNames() { const indexPatterns = await this.getIndexPatternList(); - return await mapAsync(indexPatterns, async (index) => { - return await index.getVisibleText(); - }); + return await Promise.all( + indexPatterns.map(async (index) => { + return await index.getVisibleText(); + }) + ); } async isIndexPatternListEmpty() { @@ -437,7 +444,8 @@ export class SettingsPageObject extends FtrService { async setIndexPatternField(indexPatternName = 'logstash-*') { this.log.debug(`setIndexPatternField(${indexPatternName})`); const field = await this.getIndexPatternField(); - await field.clearValue(); + await field.clearValueWithKeyboard(); + if ( indexPatternName.charAt(0) === '*' && indexPatternName.charAt(indexPatternName.length - 1) === '*' @@ -565,9 +573,11 @@ export class SettingsPageObject extends FtrService { const table = await this.find.byClassName('euiTable'); await this.retry.waitFor('field filter to be added', async () => { const tableCells = await table.findAllByCssSelector('td'); - const fieldNames = await mapAsync(tableCells, async (cell) => { - return (await cell.getVisibleText()).trim(); - }); + const fieldNames = await Promise.all( + tableCells.map(async (cell) => { + return (await cell.getVisibleText()).trim(); + }) + ); return fieldNames.includes(name); }); } diff --git a/test/functional/page_objects/vega_chart_page.ts b/test/functional/page_objects/vega_chart_page.ts index f83c5e193034e..045e5eedb86f0 100644 --- a/test/functional/page_objects/vega_chart_page.ts +++ b/test/functional/page_objects/vega_chart_page.ts @@ -19,6 +19,7 @@ export class VegaChartPageObject extends FtrService { private readonly testSubjects = this.ctx.getService('testSubjects'); private readonly browser = this.ctx.getService('browser'); private readonly retry = this.ctx.getService('retry'); + private readonly monacoEditor = this.ctx.getService('monacoEditor'); public getEditor() { return this.testSubjects.find('vega-editor'); @@ -36,63 +37,31 @@ export class VegaChartPageObject extends FtrService { return this.find.byCssSelector('[aria-label^="Y-axis"]'); } - public async getAceGutterContainer() { - const editor = await this.getEditor(); - return editor.findByClassName('ace_gutter'); - } - - public async getRawSpec() { - // Adapted from console_page.js:getVisibleTextFromAceEditor(). Is there a common utilities file? - const editor = await this.getEditor(); - const lines = await editor.findAllByClassName('ace_line_group'); - - return await Promise.all( - lines.map(async (line) => { - return await line.getVisibleText(); - }) - ); - } - public async getSpec() { - return (await this.getRawSpec()).join('\n'); - } - - public async focusEditor() { - const editor = await this.getEditor(); - const textarea = await editor.findByClassName('ace_content'); - - await textarea.click(); + return this.monacoEditor.getCodeEditorValue(); } public async fillSpec(newSpec: string) { await this.retry.try(async () => { await this.cleanSpec(); - await this.focusEditor(); - await this.browser.pressKeys(newSpec); + await this.monacoEditor.setCodeEditorValue(newSpec); expect(compareSpecs(await this.getSpec(), newSpec)).to.be(true); }); } public async typeInSpec(text: string) { - const aceGutter = await this.getAceGutterContainer(); + const editor = await this.testSubjects.find('vega-editor'); + const textarea = await editor.findByCssSelector('textarea'); - await aceGutter.doubleClick(); + await textarea.focus(); + await this.browser.pressKeys(this.browser.keys.RIGHT); await this.browser.pressKeys(this.browser.keys.RIGHT); - await this.browser.pressKeys(this.browser.keys.LEFT); - await this.browser.pressKeys(this.browser.keys.LEFT); - await this.browser.pressKeys(text); + await textarea.type(text); } public async cleanSpec() { - const aceGutter = await this.getAceGutterContainer(); - - await this.retry.try(async () => { - await aceGutter.doubleClick(); - await this.browser.pressKeys(this.browser.keys.BACK_SPACE); - - expect(await this.getSpec()).to.be(''); - }); + await this.monacoEditor.setCodeEditorValue(''); } public async getYAxisLabels() { diff --git a/test/functional/services/common/browser.ts b/test/functional/services/common/browser.ts index 73d92f8ff722b..7581c17a58ebf 100644 --- a/test/functional/services/common/browser.ts +++ b/test/functional/services/common/browser.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { delay } from 'bluebird'; +import { setTimeout as setTimeoutAsync } from 'timers/promises'; import { cloneDeepWith } from 'lodash'; import { Key, Origin, WebDriver } from 'selenium-webdriver'; // @ts-ignore internal modules are not typed @@ -303,7 +303,7 @@ class BrowserService extends FtrService { to ); // wait for 150ms to make sure the script has run - await delay(150); + await setTimeoutAsync(150); } /** diff --git a/test/functional/services/common/test_subjects.ts b/test/functional/services/common/test_subjects.ts index 3f47c6155f175..09c54af7b8811 100644 --- a/test/functional/services/common/test_subjects.ts +++ b/test/functional/services/common/test_subjects.ts @@ -7,7 +7,6 @@ */ import testSubjSelector from '@kbn/test-subj-selector'; -import { map as mapAsync } from 'bluebird'; import { WebElementWrapper } from '../lib/web_element_wrapper'; import { FtrService } from '../../ftr_provider_context'; @@ -271,11 +270,11 @@ export class TestSubjects extends FtrService { private async _mapAll( selectorAll: string, - mapFn: (element: WebElementWrapper, index?: number, arrayLength?: number) => Promise + mapFn: (element: WebElementWrapper, index: number, array: WebElementWrapper[]) => Promise ): Promise { return await this.retry.try(async () => { const elements = await this.findAll(selectorAll); - return await mapAsync(elements, mapFn); + return await Promise.all(elements.map(mapFn)); }); } diff --git a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts index 4b164402bfb70..d4fe5080bdfef 100644 --- a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts +++ b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { delay } from 'bluebird'; +import { setTimeout as setTimeoutAsync } from 'timers/promises'; import { WebElement, WebDriver, By, Key } from 'selenium-webdriver'; import { PNG } from 'pngjs'; import cheerio from 'cheerio'; @@ -121,7 +121,7 @@ export class WebElementWrapper { `finding element '${this.locator.toString()}' again, ${attemptsRemaining - 1} attempts left` ); - await delay(200); + await setTimeoutAsync(200); this._webElement = await this.driver.findElement(this.locator); return await this.retryCall(fn, attemptsRemaining - 1); } @@ -240,7 +240,7 @@ export class WebElementWrapper { const value = await this.getAttribute('value'); for (let i = 0; i <= value.length; i++) { await this.pressKeys(this.Keys.BACK_SPACE); - await delay(100); + await setTimeoutAsync(100); } } else { if (this.isChromium) { @@ -279,7 +279,7 @@ export class WebElementWrapper { for (const char of value) { await this.retryCall(async function type(wrapper) { await wrapper._webElement.sendKeys(char); - await delay(100); + await setTimeoutAsync(100); }); } } else { diff --git a/test/functional/services/monaco_editor.ts b/test/functional/services/monaco_editor.ts index 63a5a7105ddb8..2cad852ac16b5 100644 --- a/test/functional/services/monaco_editor.ts +++ b/test/functional/services/monaco_editor.ts @@ -31,7 +31,8 @@ export class MonacoEditorService extends FtrService { public async typeCodeEditorValue(value: string, testSubjId: string) { const editor = await this.testSubjects.find(testSubjId); const textarea = await editor.findByCssSelector('textarea'); - textarea.type(value); + + await textarea.type(value); } public async setCodeEditorValue(value: string, nthIndex = 0) { diff --git a/test/interactive_setup_api_integration/fixtures/test_helpers.ts b/test/interactive_setup_api_integration/fixtures/test_helpers.ts index f1e72785af02d..6001f12b0a551 100644 --- a/test/interactive_setup_api_integration/fixtures/test_helpers.ts +++ b/test/interactive_setup_api_integration/fixtures/test_helpers.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { delay } from 'bluebird'; +import { setTimeout as setTimeoutAsync } from 'timers/promises'; import expect from '@kbn/expect'; @@ -19,7 +19,7 @@ export async function hasKibanaBooted(context: FtrProviderContext) { // Run 30 consecutive requests with 1.5s delay to check if Kibana is up and running. let kibanaHasBooted = false; for (const counter of [...Array(30).keys()]) { - await delay(1500); + await setTimeoutAsync(1500); try { expect((await supertest.get('/api/status').expect(200)).body).to.have.keys([ diff --git a/test/interpreter_functional/snapshots/baseline/combined_test2.json b/test/interpreter_functional/snapshots/baseline/combined_test2.json index 4870694e6adbc..3b030ec8fb597 100644 --- a/test/interpreter_functional/snapshots/baseline/combined_test2.json +++ b/test/interpreter_functional/snapshots/baseline/combined_test2.json @@ -1 +1 @@ -{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"} \ No newline at end of file +{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/combined_test3.json b/test/interpreter_functional/snapshots/baseline/combined_test3.json index 8e6d59933716d..2ddf40eb79006 100644 --- a/test/interpreter_functional/snapshots/baseline/combined_test3.json +++ b/test/interpreter_functional/snapshots/baseline/combined_test3.json @@ -1 +1 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/final_output_test.json b/test/interpreter_functional/snapshots/baseline/final_output_test.json index 8e6d59933716d..2ddf40eb79006 100644 --- a/test/interpreter_functional/snapshots/baseline/final_output_test.json +++ b/test/interpreter_functional/snapshots/baseline/final_output_test.json @@ -1 +1 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_all_data.json b/test/interpreter_functional/snapshots/baseline/metric_all_data.json index f176dfdb83e5c..fb16bf98ce761 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_all_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_all_data.json @@ -1 +1 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_empty_data.json b/test/interpreter_functional/snapshots/baseline/metric_empty_data.json index f9df8409edfcb..d667cc6088a3a 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_empty_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_empty_data.json @@ -1 +1 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json b/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json index ab19a031e8c71..6ef90caf3da3e 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json @@ -1 +1 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json b/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json index 2112c5bccf507..bc1ec6278dc32 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json +++ b/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json @@ -1 +1 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":{"colors":["rgb(0,0,0,0)","rgb(100, 100, 100)"],"continuity":"none","gradient":false,"range":"number","rangeMax":10000,"rangeMin":0,"stops":[0,10000]},"percentageMode":true,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":{"colors":["rgb(0,0,0,0)","rgb(100, 100, 100)"],"continuity":"none","gradient":false,"range":"number","rangeMax":10000,"rangeMin":0,"stops":[0,10000]},"percentageMode":true,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json b/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json index 6bacc8f885e1b..b5cc75694b4ba 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json @@ -1 +1 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/partial_test_1.json b/test/interpreter_functional/snapshots/baseline/partial_test_1.json index 9877a0d3138c0..5b081f4d0713e 100644 --- a/test/interpreter_functional/snapshots/baseline/partial_test_1.json +++ b/test/interpreter_functional/snapshots/baseline/partial_test_1.json @@ -1 +1 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":100,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":100,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/partial_test_2.json b/test/interpreter_functional/snapshots/baseline/partial_test_2.json index 8e6d59933716d..2ddf40eb79006 100644 --- a/test/interpreter_functional/snapshots/baseline/partial_test_2.json +++ b/test/interpreter_functional/snapshots/baseline/partial_test_2.json @@ -1 +1 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/step_output_test2.json b/test/interpreter_functional/snapshots/baseline/step_output_test2.json index 4870694e6adbc..3b030ec8fb597 100644 --- a/test/interpreter_functional/snapshots/baseline/step_output_test2.json +++ b/test/interpreter_functional/snapshots/baseline/step_output_test2.json @@ -1 +1 @@ -{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"} \ No newline at end of file +{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/step_output_test3.json b/test/interpreter_functional/snapshots/baseline/step_output_test3.json index 8e6d59933716d..2ddf40eb79006 100644 --- a/test/interpreter_functional/snapshots/baseline/step_output_test3.json +++ b/test/interpreter_functional/snapshots/baseline/step_output_test3.json @@ -1 +1 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json b/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json index 5ddf081c54d95..8f079b49ed98d 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json @@ -1 +1 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":100,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":100,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_empty_data.json b/test/interpreter_functional/snapshots/baseline/tagcloud_empty_data.json index 723ebb6e9f460..e0026b189949d 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_empty_data.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_empty_data.json @@ -1 +1 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":100,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":100,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json b/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json index 1655451d41d03..4eef2bcb1fc48 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json @@ -1 +1 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":100,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":100,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json b/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json index f0bfd56ac99b8..26ca82acd7563 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json @@ -1 +1 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":100,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":100,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_options.json b/test/interpreter_functional/snapshots/baseline/tagcloud_options.json index ba034fa2e435a..d13cc180e1e7d 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_options.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_options.json @@ -1 +1 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":100,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"log","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":100,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"log","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/combined_test0.json b/test/interpreter_functional/snapshots/session/combined_test0.json deleted file mode 100644 index 8f00d72df8ab3..0000000000000 --- a/test/interpreter_functional/snapshots/session/combined_test0.json +++ /dev/null @@ -1 +0,0 @@ -{"filters":[],"query":[],"timeRange":null,"type":"kibana_context"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/combined_test1.json b/test/interpreter_functional/snapshots/session/combined_test1.json deleted file mode 100644 index 8f00d72df8ab3..0000000000000 --- a/test/interpreter_functional/snapshots/session/combined_test1.json +++ /dev/null @@ -1 +0,0 @@ -{"filters":[],"query":[],"timeRange":null,"type":"kibana_context"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/combined_test2.json b/test/interpreter_functional/snapshots/session/combined_test2.json deleted file mode 100644 index 4870694e6adbc..0000000000000 --- a/test/interpreter_functional/snapshots/session/combined_test2.json +++ /dev/null @@ -1 +0,0 @@ -{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/combined_test3.json b/test/interpreter_functional/snapshots/session/combined_test3.json deleted file mode 100644 index 8e6d59933716d..0000000000000 --- a/test/interpreter_functional/snapshots/session/combined_test3.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/final_output_test.json b/test/interpreter_functional/snapshots/session/final_output_test.json deleted file mode 100644 index 8e6d59933716d..0000000000000 --- a/test/interpreter_functional/snapshots/session/final_output_test.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_all_data.json b/test/interpreter_functional/snapshots/session/metric_all_data.json deleted file mode 100644 index f176dfdb83e5c..0000000000000 --- a/test/interpreter_functional/snapshots/session/metric_all_data.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_empty_data.json b/test/interpreter_functional/snapshots/session/metric_empty_data.json deleted file mode 100644 index f9df8409edfcb..0000000000000 --- a/test/interpreter_functional/snapshots/session/metric_empty_data.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_invalid_data.json b/test/interpreter_functional/snapshots/session/metric_invalid_data.json deleted file mode 100644 index f23b9b0915774..0000000000000 --- a/test/interpreter_functional/snapshots/session/metric_invalid_data.json +++ /dev/null @@ -1 +0,0 @@ -"[metricVis] > [visdimension] > Column name or index provided is invalid" \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json b/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json deleted file mode 100644 index ab19a031e8c71..0000000000000 --- a/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_percentage_mode.json b/test/interpreter_functional/snapshots/session/metric_percentage_mode.json deleted file mode 100644 index 2112c5bccf507..0000000000000 --- a/test/interpreter_functional/snapshots/session/metric_percentage_mode.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":{"colors":["rgb(0,0,0,0)","rgb(100, 100, 100)"],"continuity":"none","gradient":false,"range":"number","rangeMax":10000,"rangeMin":0,"stops":[0,10000]},"percentageMode":true,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_single_metric_data.json b/test/interpreter_functional/snapshots/session/metric_single_metric_data.json deleted file mode 100644 index 6bacc8f885e1b..0000000000000 --- a/test/interpreter_functional/snapshots/session/metric_single_metric_data.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/partial_test_1.json b/test/interpreter_functional/snapshots/session/partial_test_1.json deleted file mode 100644 index 9877a0d3138c0..0000000000000 --- a/test/interpreter_functional/snapshots/session/partial_test_1.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":100,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/partial_test_2.json b/test/interpreter_functional/snapshots/session/partial_test_2.json deleted file mode 100644 index 8e6d59933716d..0000000000000 --- a/test/interpreter_functional/snapshots/session/partial_test_2.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/step_output_test0.json b/test/interpreter_functional/snapshots/session/step_output_test0.json deleted file mode 100644 index 8f00d72df8ab3..0000000000000 --- a/test/interpreter_functional/snapshots/session/step_output_test0.json +++ /dev/null @@ -1 +0,0 @@ -{"filters":[],"query":[],"timeRange":null,"type":"kibana_context"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/step_output_test1.json b/test/interpreter_functional/snapshots/session/step_output_test1.json deleted file mode 100644 index 8f00d72df8ab3..0000000000000 --- a/test/interpreter_functional/snapshots/session/step_output_test1.json +++ /dev/null @@ -1 +0,0 @@ -{"filters":[],"query":[],"timeRange":null,"type":"kibana_context"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/step_output_test2.json b/test/interpreter_functional/snapshots/session/step_output_test2.json deleted file mode 100644 index 4870694e6adbc..0000000000000 --- a/test/interpreter_functional/snapshots/session/step_output_test2.json +++ /dev/null @@ -1 +0,0 @@ -{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/step_output_test3.json b/test/interpreter_functional/snapshots/session/step_output_test3.json deleted file mode 100644 index 8e6d59933716d..0000000000000 --- a/test/interpreter_functional/snapshots/session/step_output_test3.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"metricVis","type":"render","value":{"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"labels":{"show":true},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_all_data.json b/test/interpreter_functional/snapshots/session/tagcloud_all_data.json deleted file mode 100644 index 5ddf081c54d95..0000000000000 --- a/test/interpreter_functional/snapshots/session/tagcloud_all_data.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":100,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_empty_data.json b/test/interpreter_functional/snapshots/session/tagcloud_empty_data.json deleted file mode 100644 index 723ebb6e9f460..0000000000000 --- a/test/interpreter_functional/snapshots/session/tagcloud_empty_data.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":100,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json b/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json deleted file mode 100644 index 1655451d41d03..0000000000000 --- a/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":100,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_invalid_data.json b/test/interpreter_functional/snapshots/session/tagcloud_invalid_data.json deleted file mode 100644 index b5ae1a2cb59fc..0000000000000 --- a/test/interpreter_functional/snapshots/session/tagcloud_invalid_data.json +++ /dev/null @@ -1 +0,0 @@ -"[tagcloud] > [visdimension] > Column name or index provided is invalid" \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json b/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json deleted file mode 100644 index f0bfd56ac99b8..0000000000000 --- a/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":100,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_options.json b/test/interpreter_functional/snapshots/session/tagcloud_options.json deleted file mode 100644 index ba034fa2e435a..0000000000000 --- a/test/interpreter_functional/snapshots/session/tagcloud_options.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":100,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"log","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy index daf6ca7a8e993..8a7596a591175 100644 --- a/vars/kibanaPipeline.groovy +++ b/vars/kibanaPipeline.groovy @@ -93,7 +93,7 @@ def withFunctionalTestEnv(List additionalEnvs = [], Closure closure) { def corsTestServerPort = "64${parallelId}3" // needed for https://github.com/elastic/kibana/issues/107246 def proxyTestServerPort = "64${parallelId}4" - def apmActive = githubPr.isPr() ? "false" : "true" + def contextPropagationOnly = githubPr.isPr() ? "true" : "false" withEnv([ "CI_GROUP=${parallelId}", @@ -109,7 +109,8 @@ def withFunctionalTestEnv(List additionalEnvs = [], Closure closure) { "KBN_NP_PLUGINS_BUILT=true", "FLEET_PACKAGE_REGISTRY_PORT=${fleetPackageRegistryPort}", "ALERTING_PROXY_PORT=${alertingProxyPort}", - "ELASTIC_APM_ACTIVE=${apmActive}", + "ELASTIC_APM_ACTIVE=true", + "ELASTIC_APM_CONTEXT_PROPAGATION_ONLY=${contextPropagationOnly}", "ELASTIC_APM_TRANSACTION_SAMPLE_RATE=0.1", ] + additionalEnvs) { closure() diff --git a/x-pack/plugins/alerting/README.md b/x-pack/plugins/alerting/README.md index 343960aee9dfb..9c4f27fa945be 100644 --- a/x-pack/plugins/alerting/README.md +++ b/x-pack/plugins/alerting/README.md @@ -75,29 +75,6 @@ To change the schedule for the invalidation task, use the kibana.yml configurati To change the default delay for the API key invalidation, use the kibana.yml configuration option `xpack.alerting.invalidateApiKeysTask.removalDelay`. -## Plugin Status - -The plugin status of the Alerting Framework is customized by including information about checking for failures during framework decryption: - -```js -core.status.set( - combineLatest([ - core.status.derivedStatus$, - getHealthStatusStream(startPlugins.taskManager), - ]).pipe( - map(([derivedStatus, healthStatus]) => { - if (healthStatus.level > derivedStatus.level) { - return healthStatus as ServiceStatus; - } else { - return derivedStatus; - } - }) - ) - ); -``` - -To check for framework decryption failures, we use the task `alerting_health_check`, which runs every 60 minutes by default. To change the default schedule, use the kibana.yml configuration option `xpack.alerting.healthCheck.interval`. - ## Rule Types ### Methods diff --git a/x-pack/plugins/alerting/server/health/get_state.test.ts b/x-pack/plugins/alerting/server/health/get_state.test.ts deleted file mode 100644 index f4306b8250b81..0000000000000 --- a/x-pack/plugins/alerting/server/health/get_state.test.ts +++ /dev/null @@ -1,328 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ServiceStatusLevels } from '../../../../../src/core/server'; -import { taskManagerMock } from '../../../task_manager/server/mocks'; -import { - getHealthStatusStream, - getHealthServiceStatusWithRetryAndErrorHandling, - MAX_RETRY_ATTEMPTS, -} from './get_state'; -import { ConcreteTaskInstance, TaskStatus } from '../../../task_manager/server'; -import { HealthStatus } from '../types'; -import { loggingSystemMock, savedObjectsServiceMock } from 'src/core/server/mocks'; - -jest.mock('./get_health', () => ({ - getAlertingHealthStatus: jest.fn().mockReturnValue({ - state: { - runs: 0, - health_status: 'warn', - }, - }), -})); - -const tick = () => new Promise((resolve) => setImmediate(resolve)); - -const getHealthCheckTask = (overrides = {}): ConcreteTaskInstance => ({ - id: 'test', - attempts: 0, - status: TaskStatus.Running, - version: '123', - runAt: new Date(), - scheduledAt: new Date(), - startedAt: new Date(), - retryAt: new Date(Date.now() + 5 * 60 * 1000), - state: { - runs: 1, - health_status: HealthStatus.OK, - }, - taskType: 'alerting:alerting_health_check', - params: { - alertId: '1', - }, - ownerId: null, - ...overrides, -}); - -const logger = loggingSystemMock.create().get(); -const savedObjects = savedObjectsServiceMock.createStartContract(); - -describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { - beforeEach(() => jest.useFakeTimers()); - - it('should get status at each interval', async () => { - const mockTaskManager = taskManagerMock.createStart(); - mockTaskManager.get.mockResolvedValue(getHealthCheckTask()); - const pollInterval = 100; - - getHealthStatusStream( - mockTaskManager, - logger, - savedObjects, - Promise.resolve({ - healthCheck: { - interval: '5m', - }, - invalidateApiKeysTask: { - interval: '5m', - removalDelay: '1h', - }, - maxEphemeralActionsPerAlert: 100, - defaultRuleTaskTimeout: '20m', - }), - pollInterval - ).subscribe(); - - // should fire before poll interval passes - // should fire once each poll interval - expect(mockTaskManager.get).toHaveBeenCalledTimes(1); - jest.advanceTimersByTime(pollInterval); - expect(mockTaskManager.get).toHaveBeenCalledTimes(2); - jest.advanceTimersByTime(pollInterval); - expect(mockTaskManager.get).toHaveBeenCalledTimes(3); - jest.advanceTimersByTime(pollInterval); - expect(mockTaskManager.get).toHaveBeenCalledTimes(4); - }); - - it('should retry on error', async () => { - const mockTaskManager = taskManagerMock.createStart(); - mockTaskManager.get.mockRejectedValue(new Error('Failure')); - const retryDelay = 10; - const pollInterval = 100; - - getHealthStatusStream( - mockTaskManager, - logger, - savedObjects, - Promise.resolve({ - healthCheck: { - interval: '5m', - }, - invalidateApiKeysTask: { - interval: '5m', - removalDelay: '1h', - }, - maxEphemeralActionsPerAlert: 100, - defaultRuleTaskTimeout: '20m', - }), - pollInterval, - retryDelay - ).subscribe(); - - expect(mockTaskManager.get).toHaveBeenCalledTimes(1); - jest.advanceTimersByTime(pollInterval); - expect(mockTaskManager.get).toHaveBeenCalledTimes(2); - - // Retry on failure - let numTimesCalled = 1; - for (let i = 0; i < MAX_RETRY_ATTEMPTS; i++) { - await tick(); - jest.advanceTimersByTime(retryDelay); - expect(mockTaskManager.get).toHaveBeenCalledTimes(numTimesCalled++ + 2); - } - - // Once we've exceeded max retries, should not try again - await tick(); - jest.advanceTimersByTime(retryDelay); - expect(mockTaskManager.get).toHaveBeenCalledTimes(numTimesCalled + 1); - - // Once another poll interval passes, should call fn again - await tick(); - jest.advanceTimersByTime(pollInterval - MAX_RETRY_ATTEMPTS * retryDelay); - expect(mockTaskManager.get).toHaveBeenCalledTimes(numTimesCalled + 2); - }); - - it('should return healthy status when health status is "ok"', async () => { - const mockTaskManager = taskManagerMock.createStart(); - mockTaskManager.get.mockResolvedValue(getHealthCheckTask()); - - const status = await getHealthServiceStatusWithRetryAndErrorHandling( - mockTaskManager, - logger, - savedObjects, - Promise.resolve({ - healthCheck: { - interval: '5m', - }, - invalidateApiKeysTask: { - interval: '5m', - removalDelay: '1h', - }, - maxEphemeralActionsPerAlert: 100, - defaultRuleTaskTimeout: '20m', - }) - ).toPromise(); - - expect(status.level).toEqual(ServiceStatusLevels.available); - expect(status.summary).toEqual('Alerting framework is available'); - }); - - it('should return degraded status when health status is "warn"', async () => { - const mockTaskManager = taskManagerMock.createStart(); - mockTaskManager.get.mockResolvedValue( - getHealthCheckTask({ - state: { - runs: 1, - health_status: HealthStatus.Warning, - }, - }) - ); - - const status = await getHealthServiceStatusWithRetryAndErrorHandling( - mockTaskManager, - logger, - savedObjects, - Promise.resolve({ - healthCheck: { - interval: '5m', - }, - invalidateApiKeysTask: { - interval: '5m', - removalDelay: '1h', - }, - maxEphemeralActionsPerAlert: 100, - defaultRuleTaskTimeout: '20m', - }) - ).toPromise(); - - expect(status.level).toEqual(ServiceStatusLevels.degraded); - expect(status.summary).toEqual('Alerting framework is degraded'); - }); - - it('should return unavailable status when health status is "error"', async () => { - const mockTaskManager = taskManagerMock.createStart(); - mockTaskManager.get.mockResolvedValue( - getHealthCheckTask({ - state: { - runs: 1, - health_status: HealthStatus.Error, - }, - }) - ); - - const status = await getHealthServiceStatusWithRetryAndErrorHandling( - mockTaskManager, - logger, - savedObjects, - Promise.resolve({ - healthCheck: { - interval: '5m', - }, - invalidateApiKeysTask: { - interval: '5m', - removalDelay: '1h', - }, - maxEphemeralActionsPerAlert: 100, - defaultRuleTaskTimeout: '20m', - }) - ).toPromise(); - - expect(status.level).toEqual(ServiceStatusLevels.degraded); - expect(status.summary).toEqual('Alerting framework is degraded'); - expect(status.meta).toBeUndefined(); - }); - - it('should retry on error and return healthy status if retry succeeds', async () => { - const retryDelay = 10; - const mockTaskManager = taskManagerMock.createStart(); - mockTaskManager.get - .mockRejectedValueOnce(new Error('Failure')) - .mockResolvedValue(getHealthCheckTask()); - - getHealthServiceStatusWithRetryAndErrorHandling( - mockTaskManager, - logger, - savedObjects, - Promise.resolve({ - healthCheck: { - interval: '5m', - }, - invalidateApiKeysTask: { - interval: '5m', - removalDelay: '1h', - }, - maxEphemeralActionsPerAlert: 100, - defaultRuleTaskTimeout: '20m', - }), - retryDelay - ).subscribe((status) => { - expect(status.level).toEqual(ServiceStatusLevels.available); - expect(logger.warn).toHaveBeenCalledTimes(1); - expect(status.summary).toEqual('Alerting framework is available'); - }); - - await tick(); - jest.advanceTimersByTime(retryDelay * 2); - }); - - it('should retry on error and return unavailable status if retry fails', async () => { - const retryDelay = 10; - const err = new Error('Failure'); - const mockTaskManager = taskManagerMock.createStart(); - mockTaskManager.get.mockRejectedValue(err); - - getHealthServiceStatusWithRetryAndErrorHandling( - mockTaskManager, - logger, - savedObjects, - Promise.resolve({ - healthCheck: { - interval: '5m', - }, - invalidateApiKeysTask: { - interval: '5m', - removalDelay: '1h', - }, - maxEphemeralActionsPerAlert: 100, - defaultRuleTaskTimeout: '20m', - }), - retryDelay - ).subscribe((status) => { - expect(status.level).toEqual(ServiceStatusLevels.degraded); - expect(status.summary).toEqual('Alerting framework is degraded'); - expect(status.meta).toEqual({ error: err }); - }); - - for (let i = 0; i < MAX_RETRY_ATTEMPTS + 1; i++) { - await tick(); - jest.advanceTimersByTime(retryDelay); - } - expect(mockTaskManager.get).toHaveBeenCalledTimes(MAX_RETRY_ATTEMPTS + 1); - }); - - it('should schedule a new health check task if it does not exist without throwing an error', async () => { - const mockTaskManager = taskManagerMock.createStart(); - mockTaskManager.get.mockRejectedValue({ - output: { - statusCode: 404, - message: 'Not Found', - }, - }); - - const status = await getHealthServiceStatusWithRetryAndErrorHandling( - mockTaskManager, - logger, - savedObjects, - Promise.resolve({ - healthCheck: { - interval: '5m', - }, - invalidateApiKeysTask: { - interval: '5m', - removalDelay: '1h', - }, - maxEphemeralActionsPerAlert: 100, - defaultRuleTaskTimeout: '20m', - }) - ).toPromise(); - - expect(mockTaskManager.ensureScheduled).toHaveBeenCalledTimes(1); - expect(status.level).toEqual(ServiceStatusLevels.degraded); - expect(status.summary).toEqual('Alerting framework is degraded'); - expect(status.meta).toBeUndefined(); - }); -}); diff --git a/x-pack/plugins/alerting/server/health/get_state.ts b/x-pack/plugins/alerting/server/health/get_state.ts deleted file mode 100644 index 34f897ad5b73c..0000000000000 --- a/x-pack/plugins/alerting/server/health/get_state.ts +++ /dev/null @@ -1,142 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import { defer, of, interval, Observable, throwError, timer } from 'rxjs'; -import { catchError, mergeMap, retryWhen, startWith, switchMap } from 'rxjs/operators'; -import { - Logger, - SavedObjectsServiceStart, - ServiceStatus, - ServiceStatusLevels, -} from '../../../../../src/core/server'; -import { TaskManagerStartContract } from '../../../task_manager/server'; -import { HEALTH_TASK_ID, scheduleAlertingHealthCheck } from './task'; -import { HealthStatus } from '../types'; -import { getAlertingHealthStatus } from './get_health'; -import { AlertsConfig } from '../config'; - -export const MAX_RETRY_ATTEMPTS = 3; -const HEALTH_STATUS_INTERVAL = 60000 * 5; // Five minutes -const RETRY_DELAY = 5000; // Wait 5 seconds before retrying on errors - -async function getLatestTaskState( - taskManager: TaskManagerStartContract, - logger: Logger, - savedObjects: SavedObjectsServiceStart, - config: Promise -) { - try { - return await taskManager.get(HEALTH_TASK_ID); - } catch (err) { - // if task is not found - if (err?.output?.statusCode === 404) { - await scheduleAlertingHealthCheck(logger, config, taskManager); - return await getAlertingHealthStatus(savedObjects); - } - throw err; - } -} - -const LEVEL_SUMMARY = { - [ServiceStatusLevels.available.toString()]: i18n.translate( - 'xpack.alerting.server.healthStatus.available', - { - defaultMessage: 'Alerting framework is available', - } - ), - [ServiceStatusLevels.degraded.toString()]: i18n.translate( - 'xpack.alerting.server.healthStatus.degraded', - { - defaultMessage: 'Alerting framework is degraded', - } - ), - [ServiceStatusLevels.unavailable.toString()]: i18n.translate( - 'xpack.alerting.server.healthStatus.unavailable', - { - defaultMessage: 'Alerting framework is unavailable', - } - ), -}; - -const getHealthServiceStatus = async ( - taskManager: TaskManagerStartContract, - logger: Logger, - savedObjects: SavedObjectsServiceStart, - config: Promise -): Promise> => { - const doc = await getLatestTaskState(taskManager, logger, savedObjects, config); - const level = - doc.state?.health_status === HealthStatus.OK - ? ServiceStatusLevels.available - : ServiceStatusLevels.degraded; - return { - level, - summary: LEVEL_SUMMARY[level.toString()], - }; -}; - -export const getHealthServiceStatusWithRetryAndErrorHandling = ( - taskManager: TaskManagerStartContract, - logger: Logger, - savedObjects: SavedObjectsServiceStart, - config: Promise, - retryDelay?: number -): Observable> => { - return defer(() => getHealthServiceStatus(taskManager, logger, savedObjects, config)).pipe( - retryWhen((errors) => { - return errors.pipe( - mergeMap((error, i) => { - const retryAttempt = i + 1; - if (retryAttempt > MAX_RETRY_ATTEMPTS) { - return throwError(error); - } - return timer(retryDelay ?? RETRY_DELAY); - }) - ); - }), - catchError((error) => { - logger.warn(`Alerting framework is degraded due to the error: ${error}`); - return of({ - level: ServiceStatusLevels.degraded, - summary: LEVEL_SUMMARY[ServiceStatusLevels.degraded.toString()], - meta: { error }, - }); - }) - ); -}; - -export const getHealthStatusStream = ( - taskManager: TaskManagerStartContract, - logger: Logger, - savedObjects: SavedObjectsServiceStart, - config: Promise, - healthStatusInterval?: number, - retryDelay?: number -): Observable> => - interval(healthStatusInterval ?? HEALTH_STATUS_INTERVAL).pipe( - // Emit an initial check - startWith( - getHealthServiceStatusWithRetryAndErrorHandling( - taskManager, - logger, - savedObjects, - config, - retryDelay - ) - ), - // On each interval do a new check - switchMap(() => - getHealthServiceStatusWithRetryAndErrorHandling( - taskManager, - logger, - savedObjects, - config, - retryDelay - ) - ) - ); diff --git a/x-pack/plugins/alerting/server/health/index.ts b/x-pack/plugins/alerting/server/health/index.ts index 8ac0e7be6867c..f7f8e15b6f36f 100644 --- a/x-pack/plugins/alerting/server/health/index.ts +++ b/x-pack/plugins/alerting/server/health/index.ts @@ -5,5 +5,4 @@ * 2.0. */ -export { getHealthStatusStream } from './get_state'; export { scheduleAlertingHealthCheck, initializeAlertingHealth } from './task'; diff --git a/x-pack/plugins/alerting/server/lib/get_security_health.test.ts b/x-pack/plugins/alerting/server/lib/get_security_health.test.ts new file mode 100644 index 0000000000000..1253e0c3379b5 --- /dev/null +++ b/x-pack/plugins/alerting/server/lib/get_security_health.test.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getSecurityHealth } from './get_security_health'; + +const createDependencies = ( + isSecurityEnabled: boolean | null, + canEncrypt: boolean, + apiKeysEnabled: boolean +) => { + const isEsSecurityEnabled = async () => isSecurityEnabled; + const isAbleToEncrypt = async () => canEncrypt; + const areApikeysEnabled = async () => apiKeysEnabled; + + const deps: [() => Promise, () => Promise, () => Promise] = [ + isEsSecurityEnabled, + isAbleToEncrypt, + areApikeysEnabled, + ]; + + return deps; +}; + +describe('Get security health', () => { + describe('Correctly returns the overall security health', () => { + test('When ES security enabled status cannot be determined', async () => { + const deps = createDependencies(null, true, true); + const securityHealth = await getSecurityHealth(...deps); + expect(securityHealth).toEqual({ + isSufficientlySecure: false, + hasPermanentEncryptionKey: true, + }); + }); + + test('When ES security is disabled', async () => { + const deps = createDependencies(false, true, true); + const securityHealth = await getSecurityHealth(...deps); + expect(securityHealth).toEqual({ + isSufficientlySecure: true, + hasPermanentEncryptionKey: true, + }); + }); + + test('When ES security is enabled, and API keys are disabled', async () => { + const deps = createDependencies(true, true, false); + const securityHealth = await getSecurityHealth(...deps); + expect(securityHealth).toEqual({ + isSufficientlySecure: false, + hasPermanentEncryptionKey: true, + }); + }); + + test('When ES security is enabled, and API keys are enabled', async () => { + const deps = createDependencies(true, true, true); + const securityHealth = await getSecurityHealth(...deps); + expect(securityHealth).toEqual({ + isSufficientlySecure: true, + hasPermanentEncryptionKey: true, + }); + }); + + test('With encryption enabled', async () => { + const deps = createDependencies(true, true, true); + const securityHealth = await getSecurityHealth(...deps); + expect(securityHealth).toEqual({ + isSufficientlySecure: true, + hasPermanentEncryptionKey: true, + }); + }); + + test('With encryption disabled', async () => { + const deps = createDependencies(true, false, true); + const securityHealth = await getSecurityHealth(...deps); + expect(securityHealth).toEqual({ + isSufficientlySecure: true, + hasPermanentEncryptionKey: false, + }); + }); + }); +}); diff --git a/x-pack/plugins/alerting/server/lib/get_security_health.ts b/x-pack/plugins/alerting/server/lib/get_security_health.ts new file mode 100644 index 0000000000000..1a2097221433b --- /dev/null +++ b/x-pack/plugins/alerting/server/lib/get_security_health.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export interface SecurityHealth { + isSufficientlySecure: boolean; + hasPermanentEncryptionKey: boolean; +} + +export const getSecurityHealth = async ( + isEsSecurityEnabled: () => Promise, + isAbleToEncrypt: () => Promise, + areApiKeysEnabled: () => Promise +) => { + const esSecurityIsEnabled = await isEsSecurityEnabled(); + const apiKeysAreEnabled = await areApiKeysEnabled(); + const ableToEncrypt = await isAbleToEncrypt(); + + let isSufficientlySecure: boolean; + + if (esSecurityIsEnabled === null) { + isSufficientlySecure = false; + } else { + // if esSecurityIsEnabled = true, then areApiKeysEnabled must be true to enable alerting + // if esSecurityIsEnabled = false, then it does not matter what areApiKeysEnabled is + isSufficientlySecure = !esSecurityIsEnabled || (esSecurityIsEnabled && apiKeysAreEnabled); + } + + const securityHealth: SecurityHealth = { + isSufficientlySecure, + hasPermanentEncryptionKey: ableToEncrypt, + }; + + return securityHealth; +}; diff --git a/x-pack/plugins/alerting/server/mocks.ts b/x-pack/plugins/alerting/server/mocks.ts index 639ba166e00a8..7fb748a305037 100644 --- a/x-pack/plugins/alerting/server/mocks.ts +++ b/x-pack/plugins/alerting/server/mocks.ts @@ -19,6 +19,7 @@ export { rulesClientMock }; const createSetupMock = () => { const mock: jest.Mocked = { registerType: jest.fn(), + getSecurityHealth: jest.fn(), }; return mock; }; diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index f0703defbca3d..bd3eab19d220d 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -65,6 +65,7 @@ import { AlertsConfig } from './config'; import { getHealth } from './health/get_health'; import { AlertingAuthorizationClientFactory } from './alerting_authorization_client_factory'; import { AlertingAuthorization } from './authorization'; +import { getSecurityHealth, SecurityHealth } from './lib/get_security_health'; export const EVENT_LOG_PROVIDER = 'alerting'; export const EVENT_LOG_ACTIONS = { @@ -99,6 +100,7 @@ export interface PluginSetupContract { RecoveryActionGroupId > ): void; + getSecurityHealth: () => Promise; } export interface PluginStartContract { @@ -242,29 +244,6 @@ export class AlertingPlugin { }); core.status.set(serviceStatus$); - // core.getStartServices().then(async ([coreStart, startPlugins]) => { - // combineLatest([ - // core.status.derivedStatus$, - // getHealthStatusStream( - // startPlugins.taskManager, - // this.logger, - // coreStart.savedObjects, - // this.config - // ), - // ]) - // .pipe( - // map(([derivedStatus, healthStatus]) => { - // if (healthStatus.level > derivedStatus.level) { - // return healthStatus as ServiceStatus; - // } else { - // return derivedStatus; - // } - // }), - // share() - // ) - // .subscribe(serviceStatus$); - // }); - initializeAlertingHealth(this.logger, plugins.taskManager, core.getStartServices()); core.http.registerRouteHandlerContext( @@ -315,6 +294,16 @@ export class AlertingPlugin { ruleTypeRegistry.register(alertType); } }, + getSecurityHealth: async () => { + return await getSecurityHealth( + async () => (this.licenseState ? this.licenseState.getIsSecurityEnabled() : null), + async () => plugins.encryptedSavedObjects.canEncrypt, + async () => { + const [, { security }] = await core.getStartServices(); + return security?.authc.apiKeys.areAPIKeysEnabled() ?? false; + } + ); + }, }; } diff --git a/x-pack/plugins/alerting/server/routes/health.ts b/x-pack/plugins/alerting/server/routes/health.ts index fa09213dada3a..4f3ed2b542611 100644 --- a/x-pack/plugins/alerting/server/routes/health.ts +++ b/x-pack/plugins/alerting/server/routes/health.ts @@ -14,6 +14,7 @@ import { BASE_ALERTING_API_PATH, AlertingFrameworkHealth, } from '../types'; +import { getSecurityHealth } from '../lib/get_security_health'; const rewriteBodyRes: RewriteResponseCase = ({ isSufficientlySecure, @@ -44,23 +45,16 @@ export const healthRoute = ( router.handleLegacyErrors( verifyAccessAndContext(licenseState, async function (context, req, res) { try { - const isEsSecurityEnabled: boolean | null = licenseState.getIsSecurityEnabled(); - const areApiKeysEnabled = await context.alerting.areApiKeysEnabled(); const alertingFrameworkHeath = await context.alerting.getFrameworkHealth(); - let isSufficientlySecure; - if (isEsSecurityEnabled === null) { - isSufficientlySecure = false; - } else { - // if isEsSecurityEnabled = true, then areApiKeysEnabled must be true to enable alerting - // if isEsSecurityEnabled = false, then it does not matter what areApiKeysEnabled is - isSufficientlySecure = - !isEsSecurityEnabled || (isEsSecurityEnabled && areApiKeysEnabled); - } + const securityHealth = await getSecurityHealth( + async () => (licenseState ? licenseState.getIsSecurityEnabled() : null), + async () => encryptedSavedObjects.canEncrypt, + context.alerting.areApiKeysEnabled + ); const frameworkHealth: AlertingFrameworkHealth = { - isSufficientlySecure, - hasPermanentEncryptionKey: encryptedSavedObjects.canEncrypt, + ...securityHealth, alertingFrameworkHeath, }; diff --git a/x-pack/plugins/alerting/server/routes/legacy/health.ts b/x-pack/plugins/alerting/server/routes/legacy/health.ts index 8c654f103ea86..abea724b63c6f 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/health.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/health.ts @@ -12,6 +12,7 @@ import { verifyApiAccess } from '../../lib/license_api_access'; import { AlertingFrameworkHealth } from '../../types'; import { EncryptedSavedObjectsPluginSetup } from '../../../../encrypted_saved_objects/server'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; +import { getSecurityHealth } from '../../lib/get_security_health'; export function healthRoute( router: AlertingRouter, @@ -31,22 +32,16 @@ export function healthRoute( } trackLegacyRouteUsage('health', usageCounter); try { - const isEsSecurityEnabled: boolean | null = licenseState.getIsSecurityEnabled(); const alertingFrameworkHeath = await context.alerting.getFrameworkHealth(); - const areApiKeysEnabled = await context.alerting.areApiKeysEnabled(); - let isSufficientlySecure; - if (isEsSecurityEnabled === null) { - isSufficientlySecure = false; - } else { - // if isEsSecurityEnabled = true, then areApiKeysEnabled must be true to enable alerting - // if isEsSecurityEnabled = false, then it does not matter what areApiKeysEnabled is - isSufficientlySecure = !isEsSecurityEnabled || (isEsSecurityEnabled && areApiKeysEnabled); - } + const securityHealth = await getSecurityHealth( + async () => (licenseState ? licenseState.getIsSecurityEnabled() : null), + async () => encryptedSavedObjects.canEncrypt, + context.alerting.areApiKeysEnabled + ); const frameworkHealth: AlertingFrameworkHealth = { - isSufficientlySecure, - hasPermanentEncryptionKey: encryptedSavedObjects.canEncrypt, + ...securityHealth, alertingFrameworkHeath, }; diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index d8a42aa78d910..e10af37e0936b 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -377,10 +377,11 @@ export class RulesClient { if (data.enabled) { let scheduledTask; try { - scheduledTask = await this.scheduleAlert( + scheduledTask = await this.scheduleRule( createdAlert.id, rawAlert.alertTypeId, - data.schedule + data.schedule, + true ); } catch (e) { // Cleanup data, something went wrong scheduling the task @@ -1149,10 +1150,11 @@ export class RulesClient { ); throw e; } - const scheduledTask = await this.scheduleAlert( + const scheduledTask = await this.scheduleRule( id, attributes.alertTypeId, - attributes.schedule as IntervalSchedule + attributes.schedule as IntervalSchedule, + false ); await this.unsecuredSavedObjectsClient.update('alert', id, { scheduledTaskId: scheduledTask.id, @@ -1563,9 +1565,15 @@ export class RulesClient { return this.spaceId; } - private async scheduleAlert(id: string, alertTypeId: string, schedule: IntervalSchedule) { - return await this.taskManager.schedule({ - taskType: `alerting:${alertTypeId}`, + private async scheduleRule( + id: string, + ruleTypeId: string, + schedule: IntervalSchedule, + throwOnConflict: boolean // whether to throw conflict errors or swallow them + ) { + const taskInstance = { + id, // use the same ID for task document as the rule + taskType: `alerting:${ruleTypeId}`, schedule, params: { alertId: id, @@ -1577,7 +1585,15 @@ export class RulesClient { alertInstances: {}, }, scope: ['alerting'], - }); + }; + try { + return await this.taskManager.schedule(taskInstance); + } catch (err) { + if (err.statusCode === 409 && !throwOnConflict) { + return taskInstance; + } + throw err; + } } private injectReferencesIntoActions( diff --git a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts index fc8f272702e0d..aa8ecfd73bb61 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts @@ -441,6 +441,7 @@ describe('create()', () => { expect(taskManager.schedule.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { + "id": "1", "params": Object { "alertId": "1", "spaceId": "default", @@ -1923,6 +1924,52 @@ describe('create()', () => { }); }); + test('fails if task scheduling fails due to conflict', async () => { + const data = getMockData(); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + alertTypeId: '123', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + actions: [ + { + group: 'default', + actionRef: 'action_0', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + ], + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }); + taskManager.schedule.mockRejectedValueOnce( + Object.assign(new Error('Conflict!'), { statusCode: 409 }) + ); + unsecuredSavedObjectsClient.delete.mockResolvedValueOnce({}); + await expect(rulesClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot( + `"Conflict!"` + ); + expect(unsecuredSavedObjectsClient.delete).toHaveBeenCalledTimes(1); + expect(unsecuredSavedObjectsClient.delete.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "alert", + "1", + ] + `); + }); + test('attempts to remove saved object if scheduling failed', async () => { const data = getMockData(); unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ diff --git a/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts index 5e3f148c2fc11..afa7db98cab08 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts @@ -98,7 +98,7 @@ describe('enable()', () => { }, }); taskManager.schedule.mockResolvedValue({ - id: 'task-123', + id: '1', scheduledAt: new Date(), attempts: 0, status: TaskStatus.Idle, @@ -113,27 +113,6 @@ describe('enable()', () => { }); describe('authorization', () => { - beforeEach(() => { - encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValue(existingAlert); - unsecuredSavedObjectsClient.get.mockResolvedValue(existingAlert); - rulesClientParams.createAPIKey.mockResolvedValue({ - apiKeysEnabled: false, - }); - taskManager.schedule.mockResolvedValue({ - id: 'task-123', - scheduledAt: new Date(), - attempts: 0, - status: TaskStatus.Idle, - runAt: new Date(), - state: {}, - params: {}, - taskType: '', - startedAt: null, - retryAt: null, - ownerId: null, - }); - }); - test('ensures user is authorised to enable this type of alert under the consumer', async () => { await rulesClient.enable({ id: '1' }); @@ -203,7 +182,7 @@ describe('enable()', () => { }); }); - test('enables an alert', async () => { + test('enables a rule', async () => { const createdAt = new Date().toISOString(); unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ ...existingAlert, @@ -270,6 +249,7 @@ describe('enable()', () => { } ); expect(taskManager.schedule).toHaveBeenCalledWith({ + id: '1', taskType: `alerting:myType`, params: { alertId: '1', @@ -286,7 +266,7 @@ describe('enable()', () => { scope: ['alerting'], }); expect(unsecuredSavedObjectsClient.update).toHaveBeenCalledWith('alert', '1', { - scheduledTaskId: 'task-123', + scheduledTaskId: '1', }); }); @@ -477,4 +457,95 @@ describe('enable()', () => { expect(rulesClientParams.createAPIKey).toHaveBeenCalled(); expect(unsecuredSavedObjectsClient.update).toHaveBeenCalled(); }); + + test('enables a rule if conflict errors received when scheduling a task', async () => { + const createdAt = new Date().toISOString(); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + ...existingAlert, + attributes: { + ...existingAlert.attributes, + enabled: true, + apiKey: null, + apiKeyOwner: null, + updatedBy: 'elastic', + }, + }); + unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'api_key_pending_invalidation', + attributes: { + apiKeyId: '123', + createdAt, + }, + references: [], + }); + taskManager.schedule.mockRejectedValueOnce( + Object.assign(new Error('Conflict!'), { statusCode: 409 }) + ); + + await rulesClient.enable({ id: '1' }); + expect(unsecuredSavedObjectsClient.get).not.toHaveBeenCalled(); + expect(encryptedSavedObjects.getDecryptedAsInternalUser).toHaveBeenCalledWith('alert', '1', { + namespace: 'default', + }); + expect(unsecuredSavedObjectsClient.create).not.toBeCalledWith('api_key_pending_invalidation'); + expect(rulesClientParams.createAPIKey).toHaveBeenCalled(); + expect(unsecuredSavedObjectsClient.update).toHaveBeenCalledWith( + 'alert', + '1', + { + schedule: { interval: '10s' }, + alertTypeId: 'myType', + consumer: 'myApp', + enabled: true, + meta: { + versionApiKeyLastmodified: kibanaVersion, + }, + updatedAt: '2019-02-12T21:01:22.479Z', + updatedBy: 'elastic', + apiKey: null, + apiKeyOwner: null, + actions: [ + { + group: 'default', + id: '1', + actionTypeId: '1', + actionRef: '1', + params: { + foo: true, + }, + }, + ], + executionStatus: { + status: 'pending', + lastDuration: 0, + lastExecutionDate: '2019-02-12T21:01:22.479Z', + error: null, + }, + }, + { + version: '123', + } + ); + expect(taskManager.schedule).toHaveBeenCalledWith({ + id: '1', + taskType: `alerting:myType`, + params: { + alertId: '1', + spaceId: 'default', + }, + schedule: { + interval: '10s', + }, + state: { + alertInstances: {}, + alertTypeState: {}, + previousStartedAt: null, + }, + scope: ['alerting'], + }); + expect(unsecuredSavedObjectsClient.update).toHaveBeenCalledWith('alert', '1', { + scheduledTaskId: '1', + }); + }); }); diff --git a/x-pack/plugins/apm/common/anomaly_detection/apm_ml_anomaly_query.ts b/x-pack/plugins/apm/common/anomaly_detection/apm_ml_anomaly_query.ts new file mode 100644 index 0000000000000..00fb4b5ee4a54 --- /dev/null +++ b/x-pack/plugins/apm/common/anomaly_detection/apm_ml_anomaly_query.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ApmMlDetectorIndex } from './apm_ml_detectors'; + +export function apmMlAnomalyQuery(detectorIndex: ApmMlDetectorIndex) { + return [ + { + bool: { + filter: [ + { + terms: { + result_type: ['model_plot', 'record'], + }, + }, + { + term: { detector_index: detectorIndex }, + }, + ], + }, + }, + ]; +} diff --git a/x-pack/plugins/apm/common/anomaly_detection/apm_ml_detectors.ts b/x-pack/plugins/apm/common/anomaly_detection/apm_ml_detectors.ts new file mode 100644 index 0000000000000..7c68232122408 --- /dev/null +++ b/x-pack/plugins/apm/common/anomaly_detection/apm_ml_detectors.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const enum ApmMlDetectorIndex { + txLatency = 0, + txThroughput = 1, + txFailureRate = 2, +} diff --git a/x-pack/plugins/apm/common/anomaly_detection.ts b/x-pack/plugins/apm/common/anomaly_detection/index.ts similarity index 93% rename from x-pack/plugins/apm/common/anomaly_detection.ts rename to x-pack/plugins/apm/common/anomaly_detection/index.ts index 43a779407d2a4..cdb1c62b9eed5 100644 --- a/x-pack/plugins/apm/common/anomaly_detection.ts +++ b/x-pack/plugins/apm/common/anomaly_detection/index.ts @@ -6,12 +6,12 @@ */ import { i18n } from '@kbn/i18n'; -import { ANOMALY_SEVERITY } from './ml_constants'; +import { ANOMALY_SEVERITY } from '../ml_constants'; import { getSeverityType, getSeverityColor as mlGetSeverityColor, -} from '../../ml/common'; -import { ServiceHealthStatus } from './service_health_status'; +} from '../../../ml/common'; +import { ServiceHealthStatus } from '../service_health_status'; export interface ServiceAnomalyStats { transactionType?: string; diff --git a/x-pack/plugins/apm/common/viz_colors.ts b/x-pack/plugins/apm/common/viz_colors.ts index 20287f6e097bc..5b4946f346841 100644 --- a/x-pack/plugins/apm/common/viz_colors.ts +++ b/x-pack/plugins/apm/common/viz_colors.ts @@ -5,7 +5,7 @@ * 2.0. */ -import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars as lightTheme } from '@kbn/ui-shared-deps-src/theme'; function getVizColorsForTheme(theme = lightTheme) { return [ diff --git a/x-pack/plugins/apm/dev_docs/routing_and_linking.md b/x-pack/plugins/apm/dev_docs/routing_and_linking.md index 1f6160a6c4a99..562af3d01ef77 100644 --- a/x-pack/plugins/apm/dev_docs/routing_and_linking.md +++ b/x-pack/plugins/apm/dev_docs/routing_and_linking.md @@ -6,7 +6,7 @@ This document describes routing in the APM plugin. ### Server-side -Route definitions for APM's server-side API are in the [server/routes directory](../server/routes). Routes are created with [the `createApmServerRoute` function](../server/routes/create_apm_server_route.ts). Routes are added to the API in [the `registerRoutes` function](../server/routes/register_routes.ts), which is initialized in the plugin `setup` lifecycle method. +Route definitions for APM's server-side API are in the [server/routes directory](../server/routes). Routes are created with [the `createApmServerRoute` function](../server/routes/apm_routes/create_apm_server_route.ts). Routes are added to the API in [the `registerRoutes` function](../server/routes/apm_routes/register_apm_server_routes.ts), which is initialized in the plugin `setup` lifecycle method. The path and query string parameters are defined in the calls to `createApmServerRoute` with io-ts types, so that each route has its parameters type checked. diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/errors/error_details.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/errors/error_details.spec.ts new file mode 100644 index 0000000000000..f3d3d75cad00d --- /dev/null +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/errors/error_details.spec.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import url from 'url'; +import { synthtrace } from '../../../../synthtrace'; +import { generateData } from './generate_data'; + +const start = '2021-10-10T00:00:00.000Z'; +const end = '2021-10-10T00:15:00.000Z'; +const errorDetailsPageHref = url.format({ + pathname: + '/app/apm/services/opbeans-java/errors/0000000000000000000000000Error%201', + query: { + rangeFrom: start, + rangeTo: end, + }, +}); + +describe('Error details', () => { + beforeEach(() => { + cy.loginAsReadOnlyUser(); + }); + + describe('when data is loaded', () => { + before(async () => { + await synthtrace.index( + generateData({ + from: new Date(start).getTime(), + to: new Date(end).getTime(), + }) + ); + }); + + after(async () => { + await synthtrace.clean(); + }); + + describe('when error has no occurrences', () => { + it('shows empty an message', () => { + cy.visit( + url.format({ + pathname: + '/app/apm/services/opbeans-java/errors/0000000000000000000000000Error%201', + query: { + rangeFrom: start, + rangeTo: end, + kuery: 'service.name: "opbeans-node"', + }, + }) + ); + cy.contains('No data to display'); + }); + }); + + describe('when error has data', () => { + it('shows errors distribution chart', () => { + cy.visit(errorDetailsPageHref); + cy.contains('Error group 00000'); + cy.get('[data-test-subj="errorDistribution"]').contains('Occurrences'); + }); + + it('shows a Stacktrace and Metadata tabs', () => { + cy.visit(errorDetailsPageHref); + cy.contains('button', 'Exception stack trace'); + cy.contains('button', 'Metadata'); + }); + + describe('when clicking on related transaction sample', () => { + it('should redirects to the transaction details page', () => { + cy.visit(errorDetailsPageHref); + cy.contains('Error group 00000'); + cy.contains('a', 'GET /apple 🍎').click(); + cy.url().should('include', 'opbeans-java/transactions/view'); + }); + }); + + describe('when clicking on View x occurences in discover', () => { + it('should redirects the user to discover', () => { + cy.visit(errorDetailsPageHref); + cy.contains('span', 'Discover').click(); + cy.url().should('include', 'app/discover'); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/service_inventory.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/service_inventory.spec.ts index f82510c86116b..f74a1d122e426 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/service_inventory.spec.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/read_only_user/service_inventory/service_inventory.spec.ts @@ -93,10 +93,14 @@ describe('When navigating to the service inventory', () => { cy.wait(aliasNames); }); - it('when selecting a different time range and clicking the refresh button', () => { + it.skip('when selecting a different time range and clicking the update button', () => { cy.wait(aliasNames); - cy.changeTimeRange('Last 30 days'); + cy.selectAbsoluteTimeRange( + 'Oct 10, 2021 @ 01:00:00.000', + 'Oct 10, 2021 @ 01:30:00.000' + ); + cy.contains('Update').click(); cy.wait(aliasNames); cy.contains('Refresh').click(); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts index 519cb0aa31cdb..cb66d6db809f3 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts @@ -42,6 +42,22 @@ Cypress.Commands.add('changeTimeRange', (value: string) => { cy.contains(value).click(); }); +Cypress.Commands.add( + 'selectAbsoluteTimeRange', + (start: string, end: string) => { + cy.get('[data-test-subj="superDatePickerstartDatePopoverButton"]').click(); + cy.get('[data-test-subj="superDatePickerAbsoluteDateInput"]') + .eq(0) + .clear() + .type(start, { force: true }); + cy.get('[data-test-subj="superDatePickerendDatePopoverButton"]').click(); + cy.get('[data-test-subj="superDatePickerAbsoluteDateInput"]') + .eq(1) + .clear() + .type(end, { force: true }); + } +); + Cypress.Commands.add( 'expectAPIsToHaveBeenCalledWith', ({ diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts b/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts index 2d9ef090eef65..413f38be892f1 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts @@ -11,6 +11,7 @@ declare namespace Cypress { loginAsPowerUser(): void; loginAs(params: { username: string; password: string }): void; changeTimeRange(value: string): void; + selectAbsoluteTimeRange(start: string, end: string): void; expectAPIsToHaveBeenCalledWith(params: { apisIntercepted: string[]; value: string; diff --git a/x-pack/plugins/apm/public/application/uxApp.tsx b/x-pack/plugins/apm/public/application/uxApp.tsx index 51ce192327043..cfb1a5c354c2d 100644 --- a/x-pack/plugins/apm/public/application/uxApp.tsx +++ b/x-pack/plugins/apm/public/application/uxApp.tsx @@ -5,8 +5,7 @@ * 2.0. */ -import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars, euiDarkVars } from '@kbn/ui-shared-deps-src/theme'; import { EuiErrorBoundary } from '@elastic/eui'; import { AppMountParameters, CoreStart } from 'kibana/public'; import React from 'react'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx index 3bf21de7487de..2a1badd0ae1d8 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx @@ -19,14 +19,14 @@ import { InspectorHeaderLink } from '../../../shared/apm_header_action_menu/insp import { SERVICE_NAME } from '../../../../../common/elasticsearch_fieldnames'; const ANALYZE_DATA = i18n.translate('xpack.apm.analyzeDataButtonLabel', { - defaultMessage: 'Analyze data', + defaultMessage: 'Explore data', }); const ANALYZE_MESSAGE = i18n.translate( 'xpack.apm.analyzeDataButtonLabel.message', { defaultMessage: - 'EXPERIMENTAL - Analyze Data allows you to select and filter result data in any dimension and look for the cause or impact of performance problems.', + 'EXPERIMENTAL - Explore Data allows you to select and filter result data in any dimension and look for the cause or impact of performance problems.', } ); diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/Metrics.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/Metrics.tsx index 3a9100a0712aa..ded242e2ce558 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/Metrics.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/Metrics.tsx @@ -55,7 +55,7 @@ export function Metrics() { (callApmApi) => { if (uxQuery) { return callApmApi({ - endpoint: 'GET /api/apm/rum/client-metrics', + endpoint: 'GET /internal/apm/ux/client-metrics', params: { query: { ...uxQuery, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx index b8bdc36ed4e0d..7f481d1c14dc2 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx @@ -41,7 +41,7 @@ export function JSErrors() { (callApmApi) => { if (start && end && serviceName) { return callApmApi({ - endpoint: 'GET /api/apm/rum-client/js-errors', + endpoint: 'GET /internal/apm/ux/js-errors', params: { query: { start, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/PercentileAnnotations.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/PercentileAnnotations.tsx index ac713ad8dd8a8..35c6fb3c634cc 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/PercentileAnnotations.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/PercentileAnnotations.tsx @@ -13,7 +13,7 @@ import { LineAnnotationStyle, Position, } from '@elastic/charts'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars } from '@kbn/ui-shared-deps-src/theme'; import { EuiToolTip } from '@elastic/eui'; interface Props { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx index f75d0fd093b5a..6798fbe90e4de 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx @@ -51,7 +51,7 @@ export function PageLoadDistribution() { (callApmApi) => { if (start && end && serviceName) { return callApmApi({ - endpoint: 'GET /api/apm/rum-client/page-load-distribution', + endpoint: 'GET /internal/apm/ux/page-load-distribution', params: { query: { start, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts index 0cfa293c87844..ee3acac73211f 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts @@ -24,7 +24,7 @@ export const useBreakdowns = ({ percentileRange, field, value }: Props) => { (callApmApi) => { if (start && end && field && value) { return callApmApi({ - endpoint: 'GET /api/apm/rum-client/page-load-distribution/breakdown', + endpoint: 'GET /internal/apm/ux/page-load-distribution/breakdown', params: { query: { start, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx index 581260f5931e7..16605a83505ff 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx @@ -39,7 +39,7 @@ export function PageViewsTrend() { (callApmApi) => { if (start && end && serviceName) { return callApmApi({ - endpoint: 'GET /api/apm/rum-client/page-view-trends', + endpoint: 'GET /internal/apm/ux/page-view-trends', params: { query: { start, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/WebApplicationSelect.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/WebApplicationSelect.tsx index 5b1cca0ec44fa..ecba89b2651ac 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/WebApplicationSelect.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/WebApplicationSelect.tsx @@ -20,7 +20,7 @@ export function WebApplicationSelect() { (callApmApi) => { if (start && end) { return callApmApi({ - endpoint: 'GET /api/apm/rum-client/services', + endpoint: 'GET /internal/apm/ux/services', params: { query: { start, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/render_option.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/render_option.tsx index f5f5a04353c50..4a4d8e9d3e191 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/render_option.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/render_option.tsx @@ -8,7 +8,7 @@ import React, { ReactNode } from 'react'; import { EuiHighlight, EuiSelectableOption } from '@elastic/eui'; import styled from 'styled-components'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars } from '@kbn/ui-shared-deps-src/theme'; const StyledSpan = styled.span` color: ${euiLightVars.euiColorSecondaryText}; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/use_url_search.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/use_url_search.tsx index 7b6b093c70367..8228ab4c6e83e 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/use_url_search.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/use_url_search.tsx @@ -38,7 +38,7 @@ export const useUrlSearch = ({ popoverIsOpen, query }: Props) => { (callApmApi) => { if (uxQuery && popoverIsOpen) { return callApmApi({ - endpoint: 'GET /api/apm/rum-client/url-search', + endpoint: 'GET /internal/apm/ux/url-search', params: { query: { ...uxQuery, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx index b8766e8b5ce67..4eaf0dccc3225 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx @@ -56,7 +56,7 @@ export function KeyUXMetrics({ data, loading }: Props) { (callApmApi) => { if (uxQuery) { return callApmApi({ - endpoint: 'GET /api/apm/rum-client/long-task-metrics', + endpoint: 'GET /internal/apm/ux/long-task-metrics', params: { query: { ...uxQuery, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx index 673f045ecfb97..ab6843f94ee43 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx @@ -34,7 +34,7 @@ export function UXMetrics() { (callApmApi) => { if (uxQuery) { return callApmApi({ - endpoint: 'GET /api/apm/rum-client/web-core-vitals', + endpoint: 'GET /internal/apm/ux/web-core-vitals', params: { query: uxQuery, }, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx index f044890a9b649..7a19690a4582e 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx @@ -23,7 +23,7 @@ export function VisitorBreakdown() { if (start && end && serviceName) { return callApmApi({ - endpoint: 'GET /api/apm/rum-client/visitor-breakdown', + endpoint: 'GET /internal/apm/ux/visitor-breakdown', params: { query: { start, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ux_overview_fetchers.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/ux_overview_fetchers.ts index 34dd2d53daf8e..61310a5c5ad2c 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ux_overview_fetchers.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ux_overview_fetchers.ts @@ -21,7 +21,7 @@ export const fetchUxOverviewDate = async ({ serviceName, }: FetchDataParams): Promise => { const data = await callApmApi({ - endpoint: 'GET /api/apm/rum-client/web-core-vitals', + endpoint: 'GET /internal/apm/ux/web-core-vitals', signal: null, params: { query: { diff --git a/x-pack/plugins/apm/public/components/app/Settings/schema/confirm_switch_modal.tsx b/x-pack/plugins/apm/public/components/app/Settings/schema/confirm_switch_modal.tsx index a13f31e8a6566..1847ea90bd7fa 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/schema/confirm_switch_modal.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/schema/confirm_switch_modal.tsx @@ -58,12 +58,6 @@ export function ConfirmSwitchModal({ }} confirmButtonDisabled={!isConfirmChecked} > -

- {i18n.translate('xpack.apm.settings.schema.confirm.descriptionText', { - defaultMessage: - 'Please note Stack monitoring is not currently supported with Fleet-managed APM.', - })} -

{!hasUnsupportedConfigs && (

{i18n.translate( diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.tsx index 4c6aa78278093..8aa132bb85595 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.tsx @@ -56,7 +56,7 @@ export function ErrorDistribution({ distribution, title, fetchStatus }: Props) { data: distribution.currentPeriod, color: theme.eui.euiColorVis1, title: i18n.translate('xpack.apm.errorGroup.chart.ocurrences', { - defaultMessage: 'Occurences', + defaultMessage: 'Occurrences', }), }, ...(comparisonEnabled diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx index d4ffd8ece9d4d..5438fce7c4881 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx @@ -72,12 +72,14 @@ export function DetailView({ errorGroup, urlParams, kuery }: Props) { const history = useHistory(); const { transaction, error, occurrencesCount } = errorGroup; + const { detailTab, comparisonType, comparisonEnabled } = urlParams; + if (!error) { return null; } const tabs = getTabs(error); - const currentTab = getCurrentTab(tabs, urlParams.detailTab) as ErrorTab; + const currentTab = getCurrentTab(tabs, detailTab) as ErrorTab; const errorUrl = error.error.page?.url || error.url?.full; @@ -139,6 +141,8 @@ export function DetailView({ errorGroup, urlParams, kuery }: Props) { transactionName={transaction.transaction.name} transactionType={transaction.transaction.type} serviceName={transaction.service.name} + comparisonType={comparisonType} + comparisonEnabled={comparisonEnabled} > diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/index.tsx index f3bd8812dfd36..0765e0dd01061 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_details/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_details/index.tsx @@ -26,6 +26,7 @@ import { useApmRouter } from '../../../hooks/use_apm_router'; import { useErrorGroupDistributionFetcher } from '../../../hooks/use_error_group_distribution_fetcher'; import { useFetcher } from '../../../hooks/use_fetcher'; import { useTimeRange } from '../../../hooks/use_time_range'; +import type { APIReturnType } from '../../../services/rest/createCallApmApi'; import { DetailView } from './detail_view'; import { ErrorDistribution } from './Distribution'; @@ -50,6 +51,15 @@ const Culprit = euiStyled.div` font-family: ${({ theme }) => theme.eui.euiCodeFontFamily}; `; +type ErrorDistributionAPIResponse = + APIReturnType<'GET /internal/apm/services/{serviceName}/errors/distribution'>; + +const emptyState: ErrorDistributionAPIResponse = { + currentPeriod: [], + previousPeriod: [], + bucketSize: 0, +}; + function getShortGroupId(errorGroupId?: string) { if (!errorGroupId) { return NOT_AVAILABLE_LABEL; @@ -210,7 +220,7 @@ export function ErrorGroupDetails() { )} {i18n.translate('xpack.apm.analyzeDataButton.label', { - defaultMessage: 'Analyze data', + defaultMessage: 'Explore data', })} diff --git a/x-pack/plugins/apm/public/utils/httpStatusCodeToColor.ts b/x-pack/plugins/apm/public/utils/httpStatusCodeToColor.ts index 345eb7aa3f635..1b44a90fe7bfc 100644 --- a/x-pack/plugins/apm/public/utils/httpStatusCodeToColor.ts +++ b/x-pack/plugins/apm/public/utils/httpStatusCodeToColor.ts @@ -5,7 +5,7 @@ * 2.0. */ -import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars as theme } from '@kbn/ui-shared-deps-src/theme'; const { euiColorDarkShade, euiColorWarning } = theme; export const errorColor = '#c23c2b'; diff --git a/x-pack/plugins/apm/server/index.ts b/x-pack/plugins/apm/server/index.ts index bd30f9e212687..416a873bac0a9 100644 --- a/x-pack/plugins/apm/server/index.ts +++ b/x-pack/plugins/apm/server/index.ts @@ -116,7 +116,7 @@ export type { APMPluginSetup } from './types'; export type { APMServerRouteRepository, APIEndpoint, -} from './routes/get_global_apm_server_route_repository'; +} from './routes/apm_routes/get_global_apm_server_route_repository'; export type { APMRouteHandlerResources } from './routes/typings'; export type { ProcessorEvent } from '../common/processor_event'; diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts index 9d6abad0ff6a6..7277a12c2bf14 100644 --- a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts +++ b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts @@ -5,21 +5,21 @@ * 2.0. */ +import Boom from '@hapi/boom'; import { Logger } from 'kibana/server'; -import uuid from 'uuid/v4'; import { snakeCase } from 'lodash'; -import Boom from '@hapi/boom'; import moment from 'moment'; +import uuid from 'uuid/v4'; import { ML_ERRORS } from '../../../common/anomaly_detection'; -import { ProcessorEvent } from '../../../common/processor_event'; -import { environmentQuery } from '../../../common/utils/environment_query'; -import { Setup } from '../helpers/setup_request'; import { - TRANSACTION_DURATION, + METRICSET_NAME, PROCESSOR_EVENT, } from '../../../common/elasticsearch_fieldnames'; -import { APM_ML_JOB_GROUP, ML_MODULE_ID_APM_TRANSACTION } from './constants'; +import { ProcessorEvent } from '../../../common/processor_event'; +import { environmentQuery } from '../../../common/utils/environment_query'; import { withApmSpan } from '../../utils/with_apm_span'; +import { Setup } from '../helpers/setup_request'; +import { APM_ML_JOB_GROUP, ML_MODULE_ID_APM_TRANSACTION } from './constants'; import { getAnomalyDetectionJobs } from './get_anomaly_detection_jobs'; export async function createAnomalyDetectionJobs( @@ -92,8 +92,8 @@ async function createAnomalyDetectionJob({ query: { bool: { filter: [ - { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, - { exists: { field: TRANSACTION_DURATION } }, + { term: { [PROCESSOR_EVENT]: ProcessorEvent.metric } }, + { term: { [METRICSET_NAME]: 'transaction' } }, ...environmentQuery(environment), ], }, @@ -105,7 +105,7 @@ async function createAnomalyDetectionJob({ job_tags: { environment, // identifies this as an APM ML job & facilitates future migrations - apm_ml_version: 2, + apm_ml_version: 3, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts index fb58357d68437..a9799c0cfb60c 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts @@ -11,7 +11,7 @@ import chalk from 'chalk'; import { KibanaRequest } from '../../../../../../../src/core/server'; import { RequestStatus } from '../../../../../../../src/plugins/inspector'; import { WrappedElasticsearchClientError } from '../../../../../observability/server'; -import { inspectableEsQueriesMap } from '../../../routes/register_routes'; +import { inspectableEsQueriesMap } from '../../../routes/apm_routes/register_apm_server_routes'; import { getInspectResponse } from '../../../../../observability/server'; function formatObj(obj: Record) { diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts index fb66cb9649085..117b372d445d2 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts @@ -6,7 +6,7 @@ */ import { sum, round } from 'lodash'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars as theme } from '@kbn/ui-shared-deps-src/theme'; import { isFiniteNumber } from '../../../../../../common/utils/is_finite_number'; import { Setup } from '../../../../helpers/setup_request'; import { getMetricsDateHistogramParams } from '../../../../helpers/metrics'; diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_rate_chart.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_rate_chart.ts index 07f02bb6f8fdc..22dcb3e0f08ff 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_rate_chart.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_rate_chart.ts @@ -5,7 +5,7 @@ * 2.0. */ -import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars as theme } from '@kbn/ui-shared-deps-src/theme'; import { i18n } from '@kbn/i18n'; import { METRIC_JAVA_GC_COUNT } from '../../../../../../common/elasticsearch_fieldnames'; import { Setup } from '../../../../helpers/setup_request'; diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_time_chart.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_time_chart.ts index 9f2fc2ba582f3..4b85ad94f6494 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_time_chart.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_time_chart.ts @@ -5,7 +5,7 @@ * 2.0. */ -import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars as theme } from '@kbn/ui-shared-deps-src/theme'; import { i18n } from '@kbn/i18n'; import { METRIC_JAVA_GC_TIME } from '../../../../../../common/elasticsearch_fieldnames'; import { Setup } from '../../../../helpers/setup_request'; diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts index 71f3973f51998..a872a3af76d7e 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars as theme } from '@kbn/ui-shared-deps-src/theme'; import { i18n } from '@kbn/i18n'; import { METRIC_JAVA_HEAP_MEMORY_MAX, diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts index 2ed70bf846dfa..9fa758cb4dbd8 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars as theme } from '@kbn/ui-shared-deps-src/theme'; import { i18n } from '@kbn/i18n'; import { METRIC_JAVA_NON_HEAP_MEMORY_MAX, diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts index e5e98fc418e5d..306666d27cd1c 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars as theme } from '@kbn/ui-shared-deps-src/theme'; import { i18n } from '@kbn/i18n'; import { METRIC_JAVA_THREAD_COUNT, diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts index e5042c8c80c70..0911081b20324 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars as theme } from '@kbn/ui-shared-deps-src/theme'; import { i18n } from '@kbn/i18n'; import { METRIC_SYSTEM_CPU_PERCENT, diff --git a/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.ts b/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.ts index f4829f2d5faa0..fea853af93b84 100644 --- a/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.ts +++ b/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.ts @@ -5,7 +5,7 @@ * 2.0. */ -import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars as theme } from '@kbn/ui-shared-deps-src/theme'; import { ESSearchResponse } from '../../../../../../src/core/types/elasticsearch'; import { getVizColorForIndex } from '../../../common/viz_colors'; import { GenericMetricsRequest } from './fetch_and_transform_metrics'; diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts index 2ed1966dcacbd..2aa2f5c6eead5 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts @@ -22,6 +22,8 @@ import { rangeQuery } from '../../../../observability/server'; import { withApmSpan } from '../../utils/with_apm_span'; import { getMlJobsWithAPMGroup } from '../anomaly_detection/get_ml_jobs_with_apm_group'; import { Setup } from '../helpers/setup_request'; +import { apmMlAnomalyQuery } from '../../../common/anomaly_detection/apm_ml_anomaly_query'; +import { ApmMlDetectorIndex } from '../../../common/anomaly_detection/apm_ml_detectors'; export const DEFAULT_ANOMALIES: ServiceAnomaliesResponse = { mlJobIds: [], @@ -56,7 +58,7 @@ export async function getServiceAnomalies({ query: { bool: { filter: [ - { terms: { result_type: ['model_plot', 'record'] } }, + ...apmMlAnomalyQuery(ApmMlDetectorIndex.txLatency), ...rangeQuery( Math.min(end - 30 * 60 * 1000, start), end, diff --git a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts index 2fcbf5842d746..dd723f24abe1b 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts @@ -12,6 +12,8 @@ import { rangeQuery } from '../../../../../observability/server'; import { asMutableArray } from '../../../../common/utils/as_mutable_array'; import { withApmSpan } from '../../../utils/with_apm_span'; import { Setup } from '../../helpers/setup_request'; +import { apmMlAnomalyQuery } from '../../../../common/anomaly_detection/apm_ml_anomaly_query'; +import { ApmMlDetectorIndex } from '../../../../common/anomaly_detection/apm_ml_detectors'; export type ESResponse = Exclude< PromiseReturnType, @@ -40,7 +42,7 @@ export function anomalySeriesFetcher({ query: { bool: { filter: [ - { terms: { result_type: ['model_plot', 'record'] } }, + ...apmMlAnomalyQuery(ApmMlDetectorIndex.txLatency), { term: { partition_field_value: serviceName } }, { term: { by_field_value: transactionType } }, ...rangeQuery(start, end, 'timestamp'), diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index 4e2ee4f37a8e6..b273fc867e5a8 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -38,8 +38,8 @@ import { APMPluginSetupDependencies, APMPluginStartDependencies, } from './types'; -import { registerRoutes } from './routes/register_routes'; -import { getGlobalApmServerRouteRepository } from './routes/get_global_apm_server_route_repository'; +import { registerRoutes } from './routes/apm_routes/register_apm_server_routes'; +import { getGlobalApmServerRouteRepository } from './routes/apm_routes/get_global_apm_server_route_repository'; import { PROCESSOR_EVENT, SERVICE_ENVIRONMENT, diff --git a/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts b/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts index 23a794bb7976a..cae35c7f06a85 100644 --- a/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts +++ b/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts @@ -10,8 +10,8 @@ import { getTransactionDurationChartPreview } from '../../lib/alerts/chart_previ import { getTransactionErrorCountChartPreview } from '../../lib/alerts/chart_preview/get_transaction_error_count'; import { getTransactionErrorRateChartPreview } from '../../lib/alerts/chart_preview/get_transaction_error_rate'; import { setupRequest } from '../../lib/helpers/setup_request'; -import { createApmServerRoute } from '../create_apm_server_route'; -import { createApmServerRouteRepository } from '../create_apm_server_route_repository'; +import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; +import { createApmServerRouteRepository } from '../apm_routes/create_apm_server_route_repository'; import { environmentRt, rangeRt } from '../default_api_types'; const alertParamsRt = t.intersection([ diff --git a/x-pack/plugins/apm/server/routes/create_apm_server_route.ts b/x-pack/plugins/apm/server/routes/apm_routes/create_apm_server_route.ts similarity index 97% rename from x-pack/plugins/apm/server/routes/create_apm_server_route.ts rename to x-pack/plugins/apm/server/routes/apm_routes/create_apm_server_route.ts index 86330a87a8c55..b00b1ad6a1fa5 100644 --- a/x-pack/plugins/apm/server/routes/create_apm_server_route.ts +++ b/x-pack/plugins/apm/server/routes/apm_routes/create_apm_server_route.ts @@ -5,7 +5,7 @@ * 2.0. */ import { createServerRouteFactory } from '@kbn/server-route-repository'; -import { APMRouteCreateOptions, APMRouteHandlerResources } from './typings'; +import { APMRouteCreateOptions, APMRouteHandlerResources } from '../typings'; export const createApmServerRoute = createServerRouteFactory< APMRouteHandlerResources, diff --git a/x-pack/plugins/apm/server/routes/create_apm_server_route_repository.ts b/x-pack/plugins/apm/server/routes/apm_routes/create_apm_server_route_repository.ts similarity index 97% rename from x-pack/plugins/apm/server/routes/create_apm_server_route_repository.ts rename to x-pack/plugins/apm/server/routes/apm_routes/create_apm_server_route_repository.ts index b7cbe890c57db..43a5c2e33c9f8 100644 --- a/x-pack/plugins/apm/server/routes/create_apm_server_route_repository.ts +++ b/x-pack/plugins/apm/server/routes/apm_routes/create_apm_server_route_repository.ts @@ -5,7 +5,7 @@ * 2.0. */ import { createServerRouteRepository } from '@kbn/server-route-repository'; -import { APMRouteCreateOptions, APMRouteHandlerResources } from './typings'; +import { APMRouteCreateOptions, APMRouteHandlerResources } from '../typings'; export function createApmServerRouteRepository() { return createServerRouteRepository< diff --git a/x-pack/plugins/apm/server/routes/get_global_apm_server_route_repository.ts b/x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts similarity index 57% rename from x-pack/plugins/apm/server/routes/get_global_apm_server_route_repository.ts rename to x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts index 0c5be4890ba05..fa8bc1e54ebfb 100644 --- a/x-pack/plugins/apm/server/routes/get_global_apm_server_route_repository.ts +++ b/x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts @@ -10,33 +10,33 @@ import type { EndpointOf, } from '@kbn/server-route-repository'; import { PickByValue } from 'utility-types'; -import { alertsChartPreviewRouteRepository } from './alerts/chart_preview'; -import { backendsRouteRepository } from './backends'; -import { correlationsRouteRepository } from './correlations'; -import { createApmServerRouteRepository } from './create_apm_server_route_repository'; -import { environmentsRouteRepository } from './environments'; -import { errorsRouteRepository } from './errors'; -import { apmFleetRouteRepository } from './fleet'; -import { dataViewRouteRepository } from './data_view'; -import { latencyDistributionRouteRepository } from './latency_distribution'; -import { metricsRouteRepository } from './metrics'; -import { observabilityOverviewRouteRepository } from './observability_overview'; -import { rumRouteRepository } from './rum_client'; -import { fallbackToTransactionsRouteRepository } from './fallback_to_transactions'; -import { serviceRouteRepository } from './services'; -import { serviceMapRouteRepository } from './service_map'; -import { serviceNodeRouteRepository } from './service_nodes'; -import { agentConfigurationRouteRepository } from './settings/agent_configuration'; -import { anomalyDetectionRouteRepository } from './settings/anomaly_detection'; -import { apmIndicesRouteRepository } from './settings/apm_indices'; -import { customLinkRouteRepository } from './settings/custom_link'; -import { sourceMapsRouteRepository } from './source_maps'; -import { traceRouteRepository } from './traces'; -import { transactionRouteRepository } from './transactions'; -import { APMRouteHandlerResources } from './typings'; -import { historicalDataRouteRepository } from './historical_data'; -import { eventMetadataRouteRepository } from './event_metadata'; -import { suggestionsRouteRepository } from './suggestions'; +import { correlationsRouteRepository } from '../correlations'; +import { alertsChartPreviewRouteRepository } from '../alerts/chart_preview'; +import { backendsRouteRepository } from '../backends/route'; +import { createApmServerRouteRepository } from '../apm_routes/create_apm_server_route_repository'; +import { environmentsRouteRepository } from '../environments'; +import { errorsRouteRepository } from '../errors'; +import { apmFleetRouteRepository } from '../fleet'; +import { dataViewRouteRepository } from '../data_view'; +import { latencyDistributionRouteRepository } from '../latency_distribution'; +import { metricsRouteRepository } from '../metrics'; +import { observabilityOverviewRouteRepository } from '../observability_overview'; +import { rumRouteRepository } from '../rum_client'; +import { fallbackToTransactionsRouteRepository } from '../fallback_to_transactions'; +import { serviceRouteRepository } from '../services'; +import { serviceMapRouteRepository } from '../service_map'; +import { serviceNodeRouteRepository } from '../service_nodes'; +import { agentConfigurationRouteRepository } from '../settings/agent_configuration'; +import { anomalyDetectionRouteRepository } from '../settings/anomaly_detection'; +import { apmIndicesRouteRepository } from '../settings/apm_indices'; +import { customLinkRouteRepository } from '../settings/custom_link'; +import { sourceMapsRouteRepository } from '../source_maps'; +import { traceRouteRepository } from '../traces'; +import { transactionRouteRepository } from '../transactions'; +import { APMRouteHandlerResources } from '../typings'; +import { historicalDataRouteRepository } from '../historical_data'; +import { eventMetadataRouteRepository } from '../event_metadata'; +import { suggestionsRouteRepository } from '../suggestions'; const getTypedGlobalApmServerRouteRepository = () => { const repository = createApmServerRouteRepository() diff --git a/x-pack/plugins/apm/server/routes/register_routes/index.test.ts b/x-pack/plugins/apm/server/routes/apm_routes/register_apm_server_routes.test.ts similarity index 99% rename from x-pack/plugins/apm/server/routes/register_routes/index.test.ts rename to x-pack/plugins/apm/server/routes/apm_routes/register_apm_server_routes.test.ts index fd554ce1f335c..371652cdab957 100644 --- a/x-pack/plugins/apm/server/routes/register_routes/index.test.ts +++ b/x-pack/plugins/apm/server/routes/apm_routes/register_apm_server_routes.test.ts @@ -12,7 +12,7 @@ import * as t from 'io-ts'; import { CoreSetup, Logger } from 'src/core/server'; import { APMConfig } from '../..'; import { APMRouteCreateOptions, APMRouteHandlerResources } from '../typings'; -import { registerRoutes } from './index'; +import { registerRoutes } from './register_apm_server_routes'; type RegisterRouteDependencies = Parameters[0]; diff --git a/x-pack/plugins/apm/server/routes/register_routes/index.ts b/x-pack/plugins/apm/server/routes/apm_routes/register_apm_server_routes.ts similarity index 100% rename from x-pack/plugins/apm/server/routes/register_routes/index.ts rename to x-pack/plugins/apm/server/routes/apm_routes/register_apm_server_routes.ts diff --git a/x-pack/plugins/apm/server/lib/backends/get_error_rate_charts_for_backend.ts b/x-pack/plugins/apm/server/routes/backends/get_error_rate_charts_for_backend.ts similarity index 94% rename from x-pack/plugins/apm/server/lib/backends/get_error_rate_charts_for_backend.ts rename to x-pack/plugins/apm/server/routes/backends/get_error_rate_charts_for_backend.ts index aa20b4b586335..378db134e7cf7 100644 --- a/x-pack/plugins/apm/server/lib/backends/get_error_rate_charts_for_backend.ts +++ b/x-pack/plugins/apm/server/routes/backends/get_error_rate_charts_for_backend.ts @@ -13,8 +13,8 @@ import { import { environmentQuery } from '../../../common/utils/environment_query'; import { kqlQuery, rangeQuery } from '../../../../observability/server'; import { ProcessorEvent } from '../../../common/processor_event'; -import { Setup } from '../helpers/setup_request'; -import { getMetricsDateHistogramParams } from '../helpers/metrics'; +import { Setup } from '../../lib/helpers/setup_request'; +import { getMetricsDateHistogramParams } from '../../lib/helpers/metrics'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; export async function getErrorRateChartsForBackend({ diff --git a/x-pack/plugins/apm/server/lib/backends/get_latency_charts_for_backend.ts b/x-pack/plugins/apm/server/routes/backends/get_latency_charts_for_backend.ts similarity index 94% rename from x-pack/plugins/apm/server/lib/backends/get_latency_charts_for_backend.ts rename to x-pack/plugins/apm/server/routes/backends/get_latency_charts_for_backend.ts index 9ef238fa13147..8f72d83fcc0b0 100644 --- a/x-pack/plugins/apm/server/lib/backends/get_latency_charts_for_backend.ts +++ b/x-pack/plugins/apm/server/routes/backends/get_latency_charts_for_backend.ts @@ -13,8 +13,8 @@ import { import { environmentQuery } from '../../../common/utils/environment_query'; import { kqlQuery, rangeQuery } from '../../../../observability/server'; import { ProcessorEvent } from '../../../common/processor_event'; -import { Setup } from '../helpers/setup_request'; -import { getMetricsDateHistogramParams } from '../helpers/metrics'; +import { Setup } from '../../lib/helpers/setup_request'; +import { getMetricsDateHistogramParams } from '../../lib/helpers/metrics'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; export async function getLatencyChartsForBackend({ diff --git a/x-pack/plugins/apm/server/lib/backends/get_metadata_for_backend.ts b/x-pack/plugins/apm/server/routes/backends/get_metadata_for_backend.ts similarity index 96% rename from x-pack/plugins/apm/server/lib/backends/get_metadata_for_backend.ts rename to x-pack/plugins/apm/server/routes/backends/get_metadata_for_backend.ts index 912014602dd13..1f40b975a8f9e 100644 --- a/x-pack/plugins/apm/server/lib/backends/get_metadata_for_backend.ts +++ b/x-pack/plugins/apm/server/routes/backends/get_metadata_for_backend.ts @@ -9,7 +9,7 @@ import { maybe } from '../../../common/utils/maybe'; import { ProcessorEvent } from '../../../common/processor_event'; import { SPAN_DESTINATION_SERVICE_RESOURCE } from '../../../common/elasticsearch_fieldnames'; import { rangeQuery } from '../../../../observability/server'; -import { Setup } from '../helpers/setup_request'; +import { Setup } from '../../lib/helpers/setup_request'; export async function getMetadataForBackend({ setup, diff --git a/x-pack/plugins/apm/server/lib/backends/get_throughput_charts_for_backend.ts b/x-pack/plugins/apm/server/routes/backends/get_throughput_charts_for_backend.ts similarity index 95% rename from x-pack/plugins/apm/server/lib/backends/get_throughput_charts_for_backend.ts rename to x-pack/plugins/apm/server/routes/backends/get_throughput_charts_for_backend.ts index 5a7e06683f25a..64fdc3eb264f8 100644 --- a/x-pack/plugins/apm/server/lib/backends/get_throughput_charts_for_backend.ts +++ b/x-pack/plugins/apm/server/routes/backends/get_throughput_charts_for_backend.ts @@ -12,9 +12,9 @@ import { import { environmentQuery } from '../../../common/utils/environment_query'; import { kqlQuery, rangeQuery } from '../../../../observability/server'; import { ProcessorEvent } from '../../../common/processor_event'; -import { Setup } from '../helpers/setup_request'; +import { Setup } from '../../lib/helpers/setup_request'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; -import { getBucketSize } from '../helpers/get_bucket_size'; +import { getBucketSize } from '../../lib/helpers/get_bucket_size'; export async function getThroughputChartsForBackend({ backendName, diff --git a/x-pack/plugins/apm/server/lib/backends/get_top_backends.ts b/x-pack/plugins/apm/server/routes/backends/get_top_backends.ts similarity index 78% rename from x-pack/plugins/apm/server/lib/backends/get_top_backends.ts rename to x-pack/plugins/apm/server/routes/backends/get_top_backends.ts index 15fb58345e5c0..7251718396660 100644 --- a/x-pack/plugins/apm/server/lib/backends/get_top_backends.ts +++ b/x-pack/plugins/apm/server/routes/backends/get_top_backends.ts @@ -8,9 +8,9 @@ import { kqlQuery } from '../../../../observability/server'; import { NodeType } from '../../../common/connections'; import { environmentQuery } from '../../../common/utils/environment_query'; -import { getConnectionStats } from '../connections/get_connection_stats'; -import { getConnectionStatsItemsWithRelativeImpact } from '../connections/get_connection_stats/get_connection_stats_items_with_relative_impact'; -import { Setup } from '../helpers/setup_request'; +import { getConnectionStats } from '../../lib/connections/get_connection_stats'; +import { getConnectionStatsItemsWithRelativeImpact } from '../../lib/connections/get_connection_stats/get_connection_stats_items_with_relative_impact'; +import { Setup } from '../../lib/helpers/setup_request'; export async function getTopBackends({ setup, diff --git a/x-pack/plugins/apm/server/lib/backends/get_upstream_services_for_backend.ts b/x-pack/plugins/apm/server/routes/backends/get_upstream_services_for_backend.ts similarity index 79% rename from x-pack/plugins/apm/server/lib/backends/get_upstream_services_for_backend.ts rename to x-pack/plugins/apm/server/routes/backends/get_upstream_services_for_backend.ts index adc461f882216..31204c960c87d 100644 --- a/x-pack/plugins/apm/server/lib/backends/get_upstream_services_for_backend.ts +++ b/x-pack/plugins/apm/server/routes/backends/get_upstream_services_for_backend.ts @@ -8,9 +8,9 @@ import { kqlQuery } from '../../../../observability/server'; import { SPAN_DESTINATION_SERVICE_RESOURCE } from '../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../common/utils/environment_query'; -import { getConnectionStats } from '../connections/get_connection_stats'; -import { getConnectionStatsItemsWithRelativeImpact } from '../connections/get_connection_stats/get_connection_stats_items_with_relative_impact'; -import { Setup } from '../helpers/setup_request'; +import { getConnectionStats } from '../../lib/connections/get_connection_stats'; +import { getConnectionStatsItemsWithRelativeImpact } from '../../lib/connections/get_connection_stats/get_connection_stats_items_with_relative_impact'; +import { Setup } from '../../lib/helpers/setup_request'; export async function getUpstreamServicesForBackend({ setup, diff --git a/x-pack/plugins/apm/server/routes/backends.ts b/x-pack/plugins/apm/server/routes/backends/route.ts similarity index 89% rename from x-pack/plugins/apm/server/routes/backends.ts rename to x-pack/plugins/apm/server/routes/backends/route.ts index 4dcd8a1db9575..58160477994bd 100644 --- a/x-pack/plugins/apm/server/routes/backends.ts +++ b/x-pack/plugins/apm/server/routes/backends/route.ts @@ -7,16 +7,21 @@ import * as t from 'io-ts'; import { toNumberRt } from '@kbn/io-ts-utils/to_number_rt'; -import { setupRequest } from '../lib/helpers/setup_request'; -import { environmentRt, kueryRt, offsetRt, rangeRt } from './default_api_types'; -import { createApmServerRoute } from './create_apm_server_route'; -import { createApmServerRouteRepository } from './create_apm_server_route_repository'; -import { getMetadataForBackend } from '../lib/backends/get_metadata_for_backend'; -import { getLatencyChartsForBackend } from '../lib/backends/get_latency_charts_for_backend'; -import { getTopBackends } from '../lib/backends/get_top_backends'; -import { getUpstreamServicesForBackend } from '../lib/backends/get_upstream_services_for_backend'; -import { getThroughputChartsForBackend } from '../lib/backends/get_throughput_charts_for_backend'; -import { getErrorRateChartsForBackend } from '../lib/backends/get_error_rate_charts_for_backend'; +import { setupRequest } from '../../lib/helpers/setup_request'; +import { + environmentRt, + kueryRt, + offsetRt, + rangeRt, +} from '../default_api_types'; +import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; +import { createApmServerRouteRepository } from '../apm_routes/create_apm_server_route_repository'; +import { getMetadataForBackend } from './get_metadata_for_backend'; +import { getLatencyChartsForBackend } from './get_latency_charts_for_backend'; +import { getTopBackends } from './get_top_backends'; +import { getUpstreamServicesForBackend } from './get_upstream_services_for_backend'; +import { getThroughputChartsForBackend } from './get_throughput_charts_for_backend'; +import { getErrorRateChartsForBackend } from './get_error_rate_charts_for_backend'; const topBackendsRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/backends/top_backends', diff --git a/x-pack/plugins/apm/server/routes/correlations.ts b/x-pack/plugins/apm/server/routes/correlations.ts index ac6835fdd6474..f6ca064b4385f 100644 --- a/x-pack/plugins/apm/server/routes/correlations.ts +++ b/x-pack/plugins/apm/server/routes/correlations.ts @@ -24,8 +24,8 @@ import { fetchFieldsStats } from '../lib/correlations/queries/field_stats/get_fi import { withApmSpan } from '../utils/with_apm_span'; -import { createApmServerRoute } from './create_apm_server_route'; -import { createApmServerRouteRepository } from './create_apm_server_route_repository'; +import { createApmServerRoute } from './apm_routes/create_apm_server_route'; +import { createApmServerRouteRepository } from './apm_routes/create_apm_server_route_repository'; import { environmentRt, kueryRt, rangeRt } from './default_api_types'; const INVALID_LICENSE = i18n.translate('xpack.apm.correlations.license.text', { diff --git a/x-pack/plugins/apm/server/routes/data_view.ts b/x-pack/plugins/apm/server/routes/data_view.ts index 5b06b51078ec7..3590ef9db9bd0 100644 --- a/x-pack/plugins/apm/server/routes/data_view.ts +++ b/x-pack/plugins/apm/server/routes/data_view.ts @@ -6,10 +6,10 @@ */ import { createStaticDataView } from '../lib/data_view/create_static_data_view'; -import { createApmServerRouteRepository } from './create_apm_server_route_repository'; +import { createApmServerRouteRepository } from './apm_routes/create_apm_server_route_repository'; import { setupRequest } from '../lib/helpers/setup_request'; import { getDynamicDataView } from '../lib/data_view/get_dynamic_data_view'; -import { createApmServerRoute } from './create_apm_server_route'; +import { createApmServerRoute } from './apm_routes/create_apm_server_route'; const staticDataViewRoute = createApmServerRoute({ endpoint: 'POST /internal/apm/data_view/static', diff --git a/x-pack/plugins/apm/server/routes/environments.ts b/x-pack/plugins/apm/server/routes/environments.ts index e54ad79f177c4..38328a63a411e 100644 --- a/x-pack/plugins/apm/server/routes/environments.ts +++ b/x-pack/plugins/apm/server/routes/environments.ts @@ -11,8 +11,8 @@ import { getSearchAggregatedTransactions } from '../lib/helpers/transactions'; import { setupRequest } from '../lib/helpers/setup_request'; import { getEnvironments } from '../lib/environments/get_environments'; import { rangeRt } from './default_api_types'; -import { createApmServerRoute } from './create_apm_server_route'; -import { createApmServerRouteRepository } from './create_apm_server_route_repository'; +import { createApmServerRoute } from './apm_routes/create_apm_server_route'; +import { createApmServerRouteRepository } from './apm_routes/create_apm_server_route_repository'; const environmentsRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/environments', diff --git a/x-pack/plugins/apm/server/routes/errors.ts b/x-pack/plugins/apm/server/routes/errors.ts index 3a6e07acd14bc..02df03f108083 100644 --- a/x-pack/plugins/apm/server/routes/errors.ts +++ b/x-pack/plugins/apm/server/routes/errors.ts @@ -6,7 +6,7 @@ */ import * as t from 'io-ts'; -import { createApmServerRoute } from './create_apm_server_route'; +import { createApmServerRoute } from './apm_routes/create_apm_server_route'; import { getErrorDistribution } from '../lib/errors/distribution/get_distribution'; import { getErrorGroupSample } from '../lib/errors/get_error_group_sample'; import { getErrorGroups } from '../lib/errors/get_error_groups'; @@ -17,7 +17,7 @@ import { rangeRt, comparisonRangeRt, } from './default_api_types'; -import { createApmServerRouteRepository } from './create_apm_server_route_repository'; +import { createApmServerRouteRepository } from './apm_routes/create_apm_server_route_repository'; const errorsRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/services/{serviceName}/errors', diff --git a/x-pack/plugins/apm/server/routes/event_metadata.ts b/x-pack/plugins/apm/server/routes/event_metadata.ts index 00241d2ef1c68..3a40e445007ee 100644 --- a/x-pack/plugins/apm/server/routes/event_metadata.ts +++ b/x-pack/plugins/apm/server/routes/event_metadata.ts @@ -6,8 +6,8 @@ */ import * as t from 'io-ts'; -import { createApmServerRouteRepository } from './create_apm_server_route_repository'; -import { createApmServerRoute } from './create_apm_server_route'; +import { createApmServerRouteRepository } from './apm_routes/create_apm_server_route_repository'; +import { createApmServerRoute } from './apm_routes/create_apm_server_route'; import { getEventMetadata } from '../lib/event_metadata/get_event_metadata'; import { processorEventRt } from '../../common/processor_event'; import { setupRequest } from '../lib/helpers/setup_request'; diff --git a/x-pack/plugins/apm/server/routes/fallback_to_transactions.ts b/x-pack/plugins/apm/server/routes/fallback_to_transactions.ts index 99c6a290e34b1..53e3ebae0d4ff 100644 --- a/x-pack/plugins/apm/server/routes/fallback_to_transactions.ts +++ b/x-pack/plugins/apm/server/routes/fallback_to_transactions.ts @@ -8,8 +8,8 @@ import * as t from 'io-ts'; import { getIsUsingTransactionEvents } from '../lib/helpers/transactions/get_is_using_transaction_events'; import { setupRequest } from '../lib/helpers/setup_request'; -import { createApmServerRoute } from './create_apm_server_route'; -import { createApmServerRouteRepository } from './create_apm_server_route_repository'; +import { createApmServerRoute } from './apm_routes/create_apm_server_route'; +import { createApmServerRouteRepository } from './apm_routes/create_apm_server_route_repository'; import { kueryRt, rangeRt } from './default_api_types'; const fallbackToTransactionsRoute = createApmServerRoute({ diff --git a/x-pack/plugins/apm/server/routes/fleet.ts b/x-pack/plugins/apm/server/routes/fleet.ts index e18aefcd6e0d8..a6e0cb09d894a 100644 --- a/x-pack/plugins/apm/server/routes/fleet.ts +++ b/x-pack/plugins/apm/server/routes/fleet.ts @@ -24,8 +24,8 @@ import { getUnsupportedApmServerSchema } from '../lib/fleet/get_unsupported_apm_ import { isSuperuser } from '../lib/fleet/is_superuser'; import { getInternalSavedObjectsClient } from '../lib/helpers/get_internal_saved_objects_client'; import { setupRequest } from '../lib/helpers/setup_request'; -import { createApmServerRoute } from './create_apm_server_route'; -import { createApmServerRouteRepository } from './create_apm_server_route_repository'; +import { createApmServerRoute } from './apm_routes/create_apm_server_route'; +import { createApmServerRouteRepository } from './apm_routes/create_apm_server_route_repository'; const hasFleetDataRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/fleet/has_data', diff --git a/x-pack/plugins/apm/server/routes/historical_data/index.ts b/x-pack/plugins/apm/server/routes/historical_data/index.ts index fb67dc4f5b649..f488669fffa11 100644 --- a/x-pack/plugins/apm/server/routes/historical_data/index.ts +++ b/x-pack/plugins/apm/server/routes/historical_data/index.ts @@ -6,8 +6,8 @@ */ import { setupRequest } from '../../lib/helpers/setup_request'; -import { createApmServerRoute } from '../create_apm_server_route'; -import { createApmServerRouteRepository } from '../create_apm_server_route_repository'; +import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; +import { createApmServerRouteRepository } from '../apm_routes/create_apm_server_route_repository'; import { hasHistoricalAgentData } from './has_historical_agent_data'; const hasDataRoute = createApmServerRoute({ diff --git a/x-pack/plugins/apm/server/routes/latency_distribution.ts b/x-pack/plugins/apm/server/routes/latency_distribution.ts index ba646c0fc92bb..826898784835e 100644 --- a/x-pack/plugins/apm/server/routes/latency_distribution.ts +++ b/x-pack/plugins/apm/server/routes/latency_distribution.ts @@ -9,8 +9,8 @@ import * as t from 'io-ts'; import { toNumberRt } from '@kbn/io-ts-utils/to_number_rt'; import { getOverallLatencyDistribution } from '../lib/latency/get_overall_latency_distribution'; import { setupRequest } from '../lib/helpers/setup_request'; -import { createApmServerRoute } from './create_apm_server_route'; -import { createApmServerRouteRepository } from './create_apm_server_route_repository'; +import { createApmServerRoute } from './apm_routes/create_apm_server_route'; +import { createApmServerRouteRepository } from './apm_routes/create_apm_server_route_repository'; import { environmentRt, kueryRt, rangeRt } from './default_api_types'; const latencyOverallDistributionRoute = createApmServerRoute({ diff --git a/x-pack/plugins/apm/server/routes/metrics.ts b/x-pack/plugins/apm/server/routes/metrics.ts index 8b6b16a26f1d8..1817c3e1546bd 100644 --- a/x-pack/plugins/apm/server/routes/metrics.ts +++ b/x-pack/plugins/apm/server/routes/metrics.ts @@ -8,8 +8,8 @@ import * as t from 'io-ts'; import { setupRequest } from '../lib/helpers/setup_request'; import { getMetricsChartDataByAgent } from '../lib/metrics/get_metrics_chart_data_by_agent'; -import { createApmServerRoute } from './create_apm_server_route'; -import { createApmServerRouteRepository } from './create_apm_server_route_repository'; +import { createApmServerRoute } from './apm_routes/create_apm_server_route'; +import { createApmServerRouteRepository } from './apm_routes/create_apm_server_route_repository'; import { environmentRt, kueryRt, rangeRt } from './default_api_types'; const metricsChartsRoute = createApmServerRoute({ diff --git a/x-pack/plugins/apm/server/routes/observability_overview.ts b/x-pack/plugins/apm/server/routes/observability_overview.ts index b4fb4804a9bda..2df3212d8da70 100644 --- a/x-pack/plugins/apm/server/routes/observability_overview.ts +++ b/x-pack/plugins/apm/server/routes/observability_overview.ts @@ -14,8 +14,8 @@ import { getHasData } from '../lib/observability_overview/has_data'; import { rangeRt } from './default_api_types'; import { getSearchAggregatedTransactions } from '../lib/helpers/transactions'; import { withApmSpan } from '../utils/with_apm_span'; -import { createApmServerRouteRepository } from './create_apm_server_route_repository'; -import { createApmServerRoute } from './create_apm_server_route'; +import { createApmServerRouteRepository } from './apm_routes/create_apm_server_route_repository'; +import { createApmServerRoute } from './apm_routes/create_apm_server_route'; const observabilityOverviewHasDataRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/observability_overview/has_data', diff --git a/x-pack/plugins/apm/server/routes/rum_client.ts b/x-pack/plugins/apm/server/routes/rum_client.ts index c465e0e02da1c..e84a281a7ce1b 100644 --- a/x-pack/plugins/apm/server/routes/rum_client.ts +++ b/x-pack/plugins/apm/server/routes/rum_client.ts @@ -19,8 +19,8 @@ import { getUrlSearch } from '../lib/rum_client/get_url_search'; import { getVisitorBreakdown } from '../lib/rum_client/get_visitor_breakdown'; import { getWebCoreVitals } from '../lib/rum_client/get_web_core_vitals'; import { hasRumData } from '../lib/rum_client/has_rum_data'; -import { createApmServerRoute } from './create_apm_server_route'; -import { createApmServerRouteRepository } from './create_apm_server_route_repository'; +import { createApmServerRoute } from './apm_routes/create_apm_server_route'; +import { createApmServerRouteRepository } from './apm_routes/create_apm_server_route_repository'; import { rangeRt } from './default_api_types'; import { UxUIFilters } from '../../typings/ui_filters'; import { APMRouteHandlerResources } from '../routes/typings'; @@ -65,7 +65,7 @@ const uxQueryRt = t.intersection([ ]); const rumClientMetricsRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/rum/client-metrics', + endpoint: 'GET /internal/apm/ux/client-metrics', params: t.type({ query: uxQueryRt, }), @@ -88,7 +88,7 @@ const rumClientMetricsRoute = createApmServerRoute({ }); const rumPageLoadDistributionRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/rum-client/page-load-distribution', + endpoint: 'GET /internal/apm/ux/page-load-distribution', params: t.type({ query: t.intersection([uxQueryRt, percentileRangeRt]), }), @@ -114,7 +114,7 @@ const rumPageLoadDistributionRoute = createApmServerRoute({ }); const rumPageLoadDistBreakdownRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/rum-client/page-load-distribution/breakdown', + endpoint: 'GET /internal/apm/ux/page-load-distribution/breakdown', params: t.type({ query: t.intersection([ uxQueryRt, @@ -145,7 +145,7 @@ const rumPageLoadDistBreakdownRoute = createApmServerRoute({ }); const rumPageViewsTrendRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/rum-client/page-view-trends', + endpoint: 'GET /internal/apm/ux/page-view-trends', params: t.type({ query: t.intersection([uxQueryRt, t.partial({ breakdowns: t.string })]), }), @@ -168,7 +168,7 @@ const rumPageViewsTrendRoute = createApmServerRoute({ }); const rumServicesRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/rum-client/services', + endpoint: 'GET /internal/apm/ux/services', params: t.type({ query: t.intersection([uiFiltersRt, rangeRt]), }), @@ -184,7 +184,7 @@ const rumServicesRoute = createApmServerRoute({ }); const rumVisitorsBreakdownRoute = createApmServerRoute({ - endpoint: 'GET /api/apm/rum-client/visitor-breakdown', + endpoint: 'GET /internal/apm/ux/visitor-breakdown', params: t.type({ query: uxQueryRt, }), @@ -206,7 +206,7 @@ const rumVisitorsBreakdownRoute = createApmServerRoute({ }); const rumWebCoreVitals = createApmServerRoute({ - endpoint: 'GET /api/apm/rum-client/web-core-vitals', + endpoint: 'GET /internal/apm/ux/web-core-vitals', params: t.type({ query: uxQueryRt, }), @@ -229,7 +229,7 @@ const rumWebCoreVitals = createApmServerRoute({ }); const rumLongTaskMetrics = createApmServerRoute({ - endpoint: 'GET /api/apm/rum-client/long-task-metrics', + endpoint: 'GET /internal/apm/ux/long-task-metrics', params: t.type({ query: uxQueryRt, }), @@ -252,7 +252,7 @@ const rumLongTaskMetrics = createApmServerRoute({ }); const rumUrlSearch = createApmServerRoute({ - endpoint: 'GET /api/apm/rum-client/url-search', + endpoint: 'GET /internal/apm/ux/url-search', params: t.type({ query: uxQueryRt, }), @@ -275,7 +275,7 @@ const rumUrlSearch = createApmServerRoute({ }); const rumJSErrors = createApmServerRoute({ - endpoint: 'GET /api/apm/rum-client/js-errors', + endpoint: 'GET /internal/apm/ux/js-errors', params: t.type({ query: t.intersection([ uiFiltersRt, diff --git a/x-pack/plugins/apm/server/routes/service_map.ts b/x-pack/plugins/apm/server/routes/service_map.ts index 3711ee20d814b..e75b4ec832d82 100644 --- a/x-pack/plugins/apm/server/routes/service_map.ts +++ b/x-pack/plugins/apm/server/routes/service_map.ts @@ -15,8 +15,8 @@ import { setupRequest } from '../lib/helpers/setup_request'; import { getServiceMap } from '../lib/service_map/get_service_map'; import { getServiceMapBackendNodeInfo } from '../lib/service_map/get_service_map_backend_node_info'; import { getServiceMapServiceNodeInfo } from '../lib/service_map/get_service_map_service_node_info'; -import { createApmServerRoute } from './create_apm_server_route'; -import { createApmServerRouteRepository } from './create_apm_server_route_repository'; +import { createApmServerRoute } from './apm_routes/create_apm_server_route'; +import { createApmServerRouteRepository } from './apm_routes/create_apm_server_route_repository'; import { environmentRt, rangeRt } from './default_api_types'; const serviceMapRoute = createApmServerRoute({ diff --git a/x-pack/plugins/apm/server/routes/service_nodes.ts b/x-pack/plugins/apm/server/routes/service_nodes.ts index 2081b794f8ab1..61d58bfa3cf38 100644 --- a/x-pack/plugins/apm/server/routes/service_nodes.ts +++ b/x-pack/plugins/apm/server/routes/service_nodes.ts @@ -6,8 +6,8 @@ */ import * as t from 'io-ts'; -import { createApmServerRouteRepository } from './create_apm_server_route_repository'; -import { createApmServerRoute } from './create_apm_server_route'; +import { createApmServerRouteRepository } from './apm_routes/create_apm_server_route_repository'; +import { createApmServerRoute } from './apm_routes/create_apm_server_route'; import { setupRequest } from '../lib/helpers/setup_request'; import { getServiceNodes } from '../lib/service_nodes'; import { rangeRt, kueryRt } from './default_api_types'; diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts index 60036a9a3da76..cb557f56d8165 100644 --- a/x-pack/plugins/apm/server/routes/services.ts +++ b/x-pack/plugins/apm/server/routes/services.ts @@ -34,8 +34,8 @@ import { getServiceProfilingStatistics } from '../lib/services/profiling/get_ser import { getServiceProfilingTimeline } from '../lib/services/profiling/get_service_profiling_timeline'; import { getServiceInfrastructure } from '../lib/services/get_service_infrastructure'; import { withApmSpan } from '../utils/with_apm_span'; -import { createApmServerRoute } from './create_apm_server_route'; -import { createApmServerRouteRepository } from './create_apm_server_route_repository'; +import { createApmServerRoute } from './apm_routes/create_apm_server_route'; +import { createApmServerRouteRepository } from './apm_routes/create_apm_server_route_repository'; import { comparisonRangeRt, environmentRt, diff --git a/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts b/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts index 5385cd74cd779..563fa40c6c0d9 100644 --- a/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts +++ b/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts @@ -17,7 +17,7 @@ import { findExactConfiguration } from '../../lib/settings/agent_configuration/f import { listConfigurations } from '../../lib/settings/agent_configuration/list_configurations'; import { getEnvironments } from '../../lib/settings/agent_configuration/get_environments'; import { deleteConfiguration } from '../../lib/settings/agent_configuration/delete_configuration'; -import { createApmServerRoute } from '../create_apm_server_route'; +import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { getAgentNameByService } from '../../lib/settings/agent_configuration/get_agent_name_by_service'; import { markAppliedByAgent } from '../../lib/settings/agent_configuration/mark_applied_by_agent'; import { @@ -25,7 +25,7 @@ import { agentConfigurationIntakeRt, } from '../../../common/agent_configuration/runtime_types/agent_configuration_intake_rt'; import { getSearchAggregatedTransactions } from '../../lib/helpers/transactions'; -import { createApmServerRouteRepository } from '../create_apm_server_route_repository'; +import { createApmServerRouteRepository } from '../apm_routes/create_apm_server_route_repository'; import { syncAgentConfigsToApmPackagePolicies } from '../../lib/fleet/sync_agent_configs_to_apm_package_policies'; // get list of configurations diff --git a/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts b/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts index f614f35810c57..e8b2ef5e119cd 100644 --- a/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts +++ b/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts @@ -10,7 +10,7 @@ import Boom from '@hapi/boom'; import { maxSuggestions } from '../../../../observability/common'; import { isActivePlatinumLicense } from '../../../common/license_check'; import { ML_ERRORS } from '../../../common/anomaly_detection'; -import { createApmServerRoute } from '../create_apm_server_route'; +import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { getAnomalyDetectionJobs } from '../../lib/anomaly_detection/get_anomaly_detection_jobs'; import { createAnomalyDetectionJobs } from '../../lib/anomaly_detection/create_anomaly_detection_jobs'; import { setupRequest } from '../../lib/helpers/setup_request'; @@ -19,7 +19,7 @@ import { hasLegacyJobs } from '../../lib/anomaly_detection/has_legacy_jobs'; import { getSearchAggregatedTransactions } from '../../lib/helpers/transactions'; import { notifyFeatureUsage } from '../../feature'; import { withApmSpan } from '../../utils/with_apm_span'; -import { createApmServerRouteRepository } from '../create_apm_server_route_repository'; +import { createApmServerRouteRepository } from '../apm_routes/create_apm_server_route_repository'; // get ML anomaly detection jobs for each environment const anomalyDetectionJobsRoute = createApmServerRoute({ diff --git a/x-pack/plugins/apm/server/routes/settings/apm_indices.ts b/x-pack/plugins/apm/server/routes/settings/apm_indices.ts index 156f4d1af0bb2..ed99f0c8862f0 100644 --- a/x-pack/plugins/apm/server/routes/settings/apm_indices.ts +++ b/x-pack/plugins/apm/server/routes/settings/apm_indices.ts @@ -6,8 +6,8 @@ */ import * as t from 'io-ts'; -import { createApmServerRouteRepository } from '../create_apm_server_route_repository'; -import { createApmServerRoute } from '../create_apm_server_route'; +import { createApmServerRouteRepository } from '../apm_routes/create_apm_server_route_repository'; +import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { getApmIndices, getApmIndexSettings, diff --git a/x-pack/plugins/apm/server/routes/settings/custom_link.ts b/x-pack/plugins/apm/server/routes/settings/custom_link.ts index af880898176bb..044b56c3c273d 100644 --- a/x-pack/plugins/apm/server/routes/settings/custom_link.ts +++ b/x-pack/plugins/apm/server/routes/settings/custom_link.ts @@ -21,8 +21,8 @@ import { import { deleteCustomLink } from '../../lib/settings/custom_link/delete_custom_link'; import { getTransaction } from '../../lib/settings/custom_link/get_transaction'; import { listCustomLinks } from '../../lib/settings/custom_link/list_custom_links'; -import { createApmServerRoute } from '../create_apm_server_route'; -import { createApmServerRouteRepository } from '../create_apm_server_route_repository'; +import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; +import { createApmServerRouteRepository } from '../apm_routes/create_apm_server_route_repository'; const customLinkTransactionRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/settings/custom_links/transaction', diff --git a/x-pack/plugins/apm/server/routes/source_maps.ts b/x-pack/plugins/apm/server/routes/source_maps.ts index d009b68c6919b..602a3a725eac4 100644 --- a/x-pack/plugins/apm/server/routes/source_maps.ts +++ b/x-pack/plugins/apm/server/routes/source_maps.ts @@ -16,8 +16,8 @@ import { getCleanedBundleFilePath, } from '../lib/fleet/source_maps'; import { getInternalSavedObjectsClient } from '../lib/helpers/get_internal_saved_objects_client'; -import { createApmServerRoute } from './create_apm_server_route'; -import { createApmServerRouteRepository } from './create_apm_server_route_repository'; +import { createApmServerRoute } from './apm_routes/create_apm_server_route'; +import { createApmServerRouteRepository } from './apm_routes/create_apm_server_route_repository'; import { stringFromBufferRt } from '../utils/string_from_buffer_rt'; export const sourceMapRt = t.intersection([ diff --git a/x-pack/plugins/apm/server/routes/suggestions.ts b/x-pack/plugins/apm/server/routes/suggestions.ts index 4834d894f364a..9b8952d09d162 100644 --- a/x-pack/plugins/apm/server/routes/suggestions.ts +++ b/x-pack/plugins/apm/server/routes/suggestions.ts @@ -10,8 +10,8 @@ import { maxSuggestions } from '../../../observability/common'; import { getSuggestions } from '../lib/suggestions/get_suggestions'; import { getSearchAggregatedTransactions } from '../lib/helpers/transactions'; import { setupRequest } from '../lib/helpers/setup_request'; -import { createApmServerRoute } from './create_apm_server_route'; -import { createApmServerRouteRepository } from './create_apm_server_route_repository'; +import { createApmServerRoute } from './apm_routes/create_apm_server_route'; +import { createApmServerRouteRepository } from './apm_routes/create_apm_server_route_repository'; const suggestionsRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/suggestions', diff --git a/x-pack/plugins/apm/server/routes/traces.ts b/x-pack/plugins/apm/server/routes/traces.ts index cc800c348b165..5fdac470a81ed 100644 --- a/x-pack/plugins/apm/server/routes/traces.ts +++ b/x-pack/plugins/apm/server/routes/traces.ts @@ -9,11 +9,11 @@ import * as t from 'io-ts'; import { setupRequest } from '../lib/helpers/setup_request'; import { getTraceItems } from '../lib/traces/get_trace_items'; import { getTopTransactionGroupList } from '../lib/transaction_groups'; -import { createApmServerRoute } from './create_apm_server_route'; +import { createApmServerRoute } from './apm_routes/create_apm_server_route'; import { environmentRt, kueryRt, rangeRt } from './default_api_types'; import { getSearchAggregatedTransactions } from '../lib/helpers/transactions'; import { getRootTransactionByTraceId } from '../lib/transactions/get_transaction_by_trace'; -import { createApmServerRouteRepository } from './create_apm_server_route_repository'; +import { createApmServerRouteRepository } from './apm_routes/create_apm_server_route_repository'; import { getTransaction } from '../lib/transactions/get_transaction'; const tracesRoute = createApmServerRoute({ diff --git a/x-pack/plugins/apm/server/routes/transactions.ts b/x-pack/plugins/apm/server/routes/transactions.ts index 4f4ca7eb1ba85..c0d83bac6e8e4 100644 --- a/x-pack/plugins/apm/server/routes/transactions.ts +++ b/x-pack/plugins/apm/server/routes/transactions.ts @@ -21,8 +21,8 @@ import { getTransactionTraceSamples } from '../lib/transactions/trace_samples'; import { getAnomalySeries } from '../lib/transactions/get_anomaly_data'; import { getLatencyPeriods } from '../lib/transactions/get_latency_charts'; import { getErrorRatePeriods } from '../lib/transaction_groups/get_error_rate'; -import { createApmServerRoute } from './create_apm_server_route'; -import { createApmServerRouteRepository } from './create_apm_server_route_repository'; +import { createApmServerRoute } from './apm_routes/create_apm_server_route'; +import { createApmServerRouteRepository } from './apm_routes/create_apm_server_route_repository'; import { comparisonRangeRt, environmentRt, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/elements/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/elements/index.ts index 667854bf3e7e2..b73957b500196 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/elements/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/elements/index.ts @@ -32,6 +32,7 @@ import { verticalBarChart } from './vert_bar_chart'; import { verticalProgressBar } from './vertical_progress_bar'; import { verticalProgressPill } from './vertical_progress_pill'; import { tagCloud } from './tag_cloud'; +import { metricVis } from './metric_vis'; import { SetupInitializer } from '../plugin'; import { ElementFactory } from '../../types'; @@ -73,3 +74,9 @@ export const initializeElements: SetupInitializer = (core, plu ]; return applyElementStrings(specs); }; + +// For testing purpose. Will be removed after exposing `metricVis` element. +export const initializeElementsSpec: SetupInitializer = (core, plugins) => { + const specs = initializeElements(core, plugins); + return [...applyElementStrings([metricVis]), ...specs]; +}; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/elements/metric_vis/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/elements/metric_vis/index.ts new file mode 100644 index 0000000000000..3f01a8ccb3e73 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/elements/metric_vis/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ElementFactory } from '../../../types'; + +export const metricVis: ElementFactory = () => ({ + name: 'metricVis', + displayName: '(New) Metric Vis', + type: 'chart', + help: 'Metric visualization', + icon: 'visMetric', + expression: `filters + | demodata + | head 1 + | metricVis metric={visdimension "percent_uptime"} colorMode="Labels" + | render`, +}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/__stories__/palette.stories.tsx b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/__stories__/palette.stories.tsx index ed9a47ad97484..27bec26750874 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/__stories__/palette.stories.tsx +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/__stories__/palette.stories.tsx @@ -29,6 +29,7 @@ storiesOf('arguments/Palette', module).add('default', () => ( }} onValueChange={action('onValueChange')} renderError={action('renderError')} + typeInstance={{}} />

)); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts index dd013116bb808..c6a220062227e 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts @@ -16,7 +16,7 @@ import { imageUpload } from './image_upload'; // @ts-expect-error untyped local import { number } from './number'; import { numberFormatInitializer } from './number_format'; -import { palette } from './palette'; +import { palette, stopsPalette } from './palette'; // @ts-expect-error untyped local import { percentage } from './percentage'; // @ts-expect-error untyped local @@ -42,6 +42,7 @@ export const args = [ imageUpload, number, palette, + stopsPalette, percentage, range, select, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette.tsx b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette.tsx deleted file mode 100644 index d01424af3a584..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette.tsx +++ /dev/null @@ -1,105 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { FC } from 'react'; -import PropTypes from 'prop-types'; -import { get } from 'lodash'; -import { getType } from '@kbn/interpreter/common'; -import { ExpressionAstFunction, ExpressionAstExpression } from 'src/plugins/expressions'; -import { PalettePicker } from '../../../public/components/palette_picker'; -import { templateFromReactComponent } from '../../../public/lib/template_from_react_component'; -import { ArgumentStrings } from '../../../i18n'; -import { identifyPalette, ColorPalette } from '../../../common/lib'; - -const { Palette: strings } = ArgumentStrings; - -interface Props { - onValueChange: (value: ExpressionAstExpression) => void; - argValue: ExpressionAstExpression; - renderError: () => void; - argId?: string; -} - -export const PaletteArgInput: FC = ({ onValueChange, argId, argValue, renderError }) => { - // TODO: This is weird, its basically a reimplementation of what the interpretter would return. - // Probably a better way todo this, and maybe a better way to handle template type objects in general? - const astToPalette = ({ chain }: { chain: ExpressionAstFunction[] }): ColorPalette | null => { - if (chain.length !== 1 || chain[0].function !== 'palette') { - renderError(); - return null; - } - - try { - const colors = chain[0].arguments._.map((astObj) => { - if (getType(astObj) !== 'string') { - renderError(); - } - return astObj; - }) as string[]; - - const gradient = get(chain[0].arguments.gradient, '[0]') as boolean; - const palette = identifyPalette({ colors, gradient }); - - if (palette) { - return palette; - } - - return { - id: 'custom', - label: strings.getCustomPaletteLabel(), - colors, - gradient, - } as any as ColorPalette; - } catch (e) { - renderError(); - } - return null; - }; - - const handleChange = (palette: ColorPalette): void => { - const astObj: ExpressionAstExpression = { - type: 'expression', - chain: [ - { - type: 'function', - function: 'palette', - arguments: { - _: palette.colors, - gradient: [palette.gradient], - }, - }, - ], - }; - - onValueChange(astObj); - }; - - const palette = astToPalette(argValue); - - if (!palette) { - renderError(); - return null; - } - - return ; -}; - -PaletteArgInput.propTypes = { - argId: PropTypes.string, - onValueChange: PropTypes.func.isRequired, - argValue: PropTypes.any.isRequired, - renderError: PropTypes.func, -}; - -export const palette = () => ({ - name: 'palette', - displayName: strings.getDisplayName(), - help: strings.getHelp(), - default: - '{palette #882E72 #B178A6 #D6C1DE #1965B0 #5289C7 #7BAFDE #4EB265 #90C987 #CAE0AB #F7EE55 #F6C141 #F1932D #E8601C #DC050C}', - simpleTemplate: templateFromReactComponent(PaletteArgInput), -}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette/index.tsx b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette/index.tsx new file mode 100644 index 0000000000000..2eb756d34fff3 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette/index.tsx @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { PaletteArgInput, SimplePaletteArgInput, palette, stopsPalette } from './palette'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette/palette.tsx b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette/palette.tsx new file mode 100644 index 0000000000000..eddefb8dadd2c --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette/palette.tsx @@ -0,0 +1,121 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; +import PropTypes from 'prop-types'; +import { ExpressionAstExpression } from 'src/plugins/expressions'; +import { templateFromReactComponent } from '../../../../public/lib/template_from_react_component'; +import { ArgumentStrings } from '../../../../i18n'; +import { ColorPalette } from '../../../../common/lib'; +import { astToPalette } from './utils'; +import { ColorPaletteName, getPaletteType } from './palette_types'; +import { CustomColorPalette } from '../../../../public/components/palette_picker'; + +const { Palette: strings, StopsPalette: stopsPaletteStrings } = ArgumentStrings; + +interface Props { + onValueChange: (value: ExpressionAstExpression) => void; + argValue: ExpressionAstExpression; + renderError: () => void; + argId?: string; + typeInstance: { + options?: { + type?: ColorPaletteName; + }; + }; +} + +export const PaletteArgInput: FC = ({ + onValueChange, + argId, + argValue, + renderError, + typeInstance, +}) => { + const handleChange = (palette: ColorPalette | CustomColorPalette): void => { + let colorStopsPaletteConfig = {}; + if (palette.stops?.length) { + colorStopsPaletteConfig = { + stop: palette.stops, + ...(palette.range ? { range: [palette.range] } : {}), + ...(palette.continuity ? { continuity: [palette.continuity] } : {}), + }; + } + + const astObj: ExpressionAstExpression = { + type: 'expression', + chain: [ + { + type: 'function', + function: 'palette', + arguments: { + _: palette.colors, + gradient: [palette.gradient], + ...colorStopsPaletteConfig, + }, + }, + ], + }; + + onValueChange(astObj); + }; + + const palette = astToPalette(argValue, renderError); + if (!palette) { + renderError(); + return null; + } + + const PalettePicker = getPaletteType(typeInstance.options?.type); + return ; +}; + +export const SimplePaletteArgInput: FC = (props) => { + const { typeInstance } = props; + const { type, ...restOptions } = typeInstance.options ?? {}; + return ( + + ); +}; + +export const StopsPaletteArgInput: FC = (props) => ( + +); + +PaletteArgInput.propTypes = { + argId: PropTypes.string, + onValueChange: PropTypes.func.isRequired, + argValue: PropTypes.any.isRequired, + renderError: PropTypes.func, +}; + +const defaultPaletteOptions = { + default: + '{palette #882E72 #B178A6 #D6C1DE #1965B0 #5289C7 #7BAFDE #4EB265 #90C987 #CAE0AB #F7EE55 #F6C141 #F1932D #E8601C #DC050C}', +}; + +export const palette = () => ({ + name: 'palette', + displayName: strings.getDisplayName(), + help: strings.getHelp(), + simpleTemplate: templateFromReactComponent(SimplePaletteArgInput), + ...defaultPaletteOptions, +}); + +export const stopsPalette = () => ({ + name: 'stops_palette', + help: stopsPaletteStrings.getHelp(), + displayName: stopsPaletteStrings.getDisplayName(), + template: templateFromReactComponent(StopsPaletteArgInput), + ...defaultPaletteOptions, +}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette/palette_types.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette/palette_types.ts new file mode 100644 index 0000000000000..8a0ec11af3448 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette/palette_types.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { PalettePicker, StopsPalettePicker } from '../../../../public/components/palette_picker'; + +const DEFAULT_PALETTE = 'default'; +const STOPS_PALETTE = 'stops'; + +export type ColorPaletteName = typeof DEFAULT_PALETTE | typeof STOPS_PALETTE; + +const paletteTypes = { + [DEFAULT_PALETTE]: PalettePicker, + [STOPS_PALETTE]: StopsPalettePicker, +}; + +export const getPaletteType = (type: ColorPaletteName = DEFAULT_PALETTE) => + paletteTypes[type] ?? paletteTypes[DEFAULT_PALETTE]; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette/utils.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette/utils.ts new file mode 100644 index 0000000000000..5734bf7ce4f66 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette/utils.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getType } from '@kbn/interpreter/common'; +import { ExpressionAstArgument, ExpressionAstFunction } from 'src/plugins/expressions'; +import { identifyPalette, ColorPalette, identifyPartialPalette } from '../../../../common/lib'; +import { ArgumentStrings } from '../../../../i18n'; + +const { Palette: strings } = ArgumentStrings; + +export const CUSTOM_PALETTE = 'custom'; + +interface PaletteParams { + colors: string[]; + gradient: boolean; + stops?: number[]; +} + +export const createCustomPalette = ( + paletteParams: PaletteParams +): ColorPalette => ({ + id: CUSTOM_PALETTE, + label: strings.getCustomPaletteLabel(), + ...paletteParams, +}); + +type UnboxArray = T extends Array ? U : T; + +function reduceElementsWithType( + arr: any[], + arg: ExpressionAstArgument, + type: string, + onError: () => void +) { + if (getType(arg) !== type) { + onError(); + } + return [...arr, arg as UnboxArray]; +} + +// TODO: This is weird, its basically a reimplementation of what the interpretter would return. +// Probably a better way todo this, and maybe a better way to handle template type objects in general? +export const astToPalette = ( + { chain }: { chain: ExpressionAstFunction[] }, + onError: () => void +): ColorPalette | ColorPalette | null => { + if (chain.length !== 1 || chain[0].function !== 'palette') { + onError(); + return null; + } + + const { _, stop: argStops, gradient: argGradient, ...restArgs } = chain[0].arguments ?? {}; + + try { + const colors = + _?.reduce((args, arg) => { + return reduceElementsWithType(args, arg, 'string', onError); + }, []) ?? []; + + const stops = + argStops?.reduce((args, arg) => { + return reduceElementsWithType(args, arg, 'number', onError); + }, []) ?? []; + + const gradient = !!argGradient?.[0]; + const palette = (stops.length ? identifyPartialPalette : identifyPalette)({ colors, gradient }); + const restPreparedArgs = Object.keys(restArgs).reduce< + Record + >((acc, argName) => { + acc[argName] = restArgs[argName]?.length > 1 ? restArgs[argName] : restArgs[argName]?.[0]; + return acc; + }, {}); + + if (palette) { + return { + ...palette, + ...restPreparedArgs, + stops, + }; + } + + return createCustomPalette({ colors, gradient, stops, ...restPreparedArgs }); + } catch (e) { + onError(); + } + return null; +}; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/vis_dimension.tsx b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/vis_dimension.tsx index df75704ababb5..312457b658ad9 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/vis_dimension.tsx +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/vis_dimension.tsx @@ -31,9 +31,6 @@ const VisDimensionArgInput: React.FC = ({ onValueChange, argId, columns, -}: { - // @todo define types - [key: string]: any; }) => { const [value, setValue] = useState(argValue); const confirm = typeInstance?.options?.confirm; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js index 817851b53c186..150b7c0616887 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useEffect, useCallback } from 'react'; import PropTypes from 'prop-types'; import { EuiFormRow, @@ -27,13 +27,16 @@ import { DataSourceStrings, LUCENE_QUERY_URL } from '../../../i18n'; const { ESDocs: strings } = DataSourceStrings; const EsdocsDatasource = ({ args, updateArgs, defaultIndex }) => { - const setArg = (name, value) => { - updateArgs && - updateArgs({ - ...args, - ...setSimpleArg(name, value), - }); - }; + const setArg = useCallback( + (name, value) => { + updateArgs && + updateArgs({ + ...args, + ...setSimpleArg(name, value), + }); + }, + [args, updateArgs] + ); // TODO: This is a terrible way of doing defaults. We need to find a way to read the defaults for the function // and set them for the data source UI. @@ -73,6 +76,12 @@ const EsdocsDatasource = ({ args, updateArgs, defaultIndex }) => { const index = getIndex(); + useEffect(() => { + if (getSimpleArg('index', args)[0] !== index) { + setArg('index', index); + } + }, [args, index, setArg]); + const sortOptions = [ { value: 'asc', text: strings.getAscendingOption() }, { value: 'desc', text: strings.getDescendingOption() }, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/models/index.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/models/index.js index 0762f70b19858..8fadc9e2e6c8a 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/models/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/models/index.js @@ -8,5 +8,6 @@ import { pointseries } from './point_series'; import { math } from './math'; import { tagcloud } from './tagcloud'; +import { metricVis } from './metric_vis'; -export const modelSpecs = [pointseries, math, tagcloud]; +export const modelSpecs = [pointseries, math, tagcloud, metricVis]; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/models/metric_vis.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/models/metric_vis.ts new file mode 100644 index 0000000000000..9796c4553978e --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/models/metric_vis.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { get } from 'lodash'; + +import { ViewStrings } from '../../../i18n'; +import { getState, getValue } from '../../../public/lib/resolved_arg'; + +const { MetricVis: strings } = ViewStrings; + +export const metricVis = () => ({ + name: 'metricVis', + displayName: strings.getDisplayName(), + args: [ + { + name: 'metric', + displayName: strings.getMetricColumnDisplayName(), + help: strings.getMetricColumnHelp(), + argType: 'vis_dimension', + multi: true, + default: `{visdimension}`, + }, + { + name: 'bucket', + displayName: strings.getBucketColumnDisplayName(), + help: strings.getBucketColumnHelp(), + argType: 'vis_dimension', + default: `{visdimension}`, + }, + { + name: 'palette', + argType: 'stops_palette', + }, + { + name: 'font', + displayName: strings.getFontColumnDisplayName(), + help: strings.getFontColumnHelp(), + argType: 'font', + default: `{font size=60 align="center"}`, + }, + { + name: 'colorMode', + displayName: strings.getColorModeColumnDisplayName(), + help: strings.getColorModeColumnHelp(), + argType: 'select', + default: 'Labels', + options: { + choices: [ + { value: 'None', name: strings.getColorModeNoneOption() }, + { value: 'Labels', name: strings.getColorModeLabelOption() }, + { value: 'Background', name: strings.getColorModeBackgroundOption() }, + ], + }, + }, + { + name: 'showLabels', + displayName: strings.getShowLabelsColumnDisplayName(), + help: strings.getShowLabelsColumnHelp(), + argType: 'toggle', + default: true, + }, + { + name: 'percentageMode', + displayName: strings.getPercentageModeColumnDisplayName(), + help: strings.getPercentageModeColumnHelp(), + argType: 'toggle', + }, + ], + resolve({ context }: any) { + if (getState(context) !== 'ready') { + return { columns: [] }; + } + return { columns: get(getValue(context), 'columns', []) }; + }, +}); diff --git a/x-pack/plugins/canvas/common/lib/palettes.ts b/x-pack/plugins/canvas/common/lib/palettes.ts index e9c2f23b62dfb..2e7eac1e1a84a 100644 --- a/x-pack/plugins/canvas/common/lib/palettes.ts +++ b/x-pack/plugins/canvas/common/lib/palettes.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { isEqual } from 'lodash'; +import { difference, isEqual } from 'lodash'; import { LibStrings } from '../../i18n'; const { Palettes: strings } = LibStrings; @@ -19,11 +19,14 @@ export type PaletteID = typeof palettes[number]['id']; * An interface representing a color palette in Canvas, with a textual label and a set of * hex values. */ -export interface ColorPalette { - id: PaletteID; +export interface ColorPalette { + id: PaletteID | AdditionalPaletteID; label: string; colors: string[]; gradient: boolean; + stops?: number[]; + range?: 'number' | 'percent'; + continuity?: 'above' | 'below' | 'all' | 'none'; } // This function allows one to create a strongly-typed palette for inclusion in @@ -52,6 +55,15 @@ export const identifyPalette = ( }); }; +export const identifyPartialPalette = ( + input: Pick +): ColorPalette | undefined => { + return palettes.find((palette) => { + const { colors, gradient } = palette; + return gradient === input.gradient && difference(input.colors, colors).length === 0; + }); +}; + export const paulTor14 = createPalette({ id: 'paul_tor_14', label: 'Paul Tor 14', diff --git a/x-pack/plugins/canvas/i18n/elements/element_strings.test.ts b/x-pack/plugins/canvas/i18n/elements/element_strings.test.ts index 8070e86b7d7fd..6589bf36cbec5 100644 --- a/x-pack/plugins/canvas/i18n/elements/element_strings.test.ts +++ b/x-pack/plugins/canvas/i18n/elements/element_strings.test.ts @@ -6,10 +6,10 @@ */ import { getElementStrings } from './element_strings'; -import { initializeElements } from '../../canvas_plugin_src/elements'; +import { initializeElementsSpec } from '../../canvas_plugin_src/elements'; import { coreMock } from '../../../../../src/core/public/mocks'; -const elementSpecs = initializeElements(coreMock.createSetup() as any, {} as any); +const elementSpecs = initializeElementsSpec(coreMock.createSetup() as any, {} as any); describe('ElementStrings', () => { const elementStrings = getElementStrings(); diff --git a/x-pack/plugins/canvas/i18n/elements/element_strings.ts b/x-pack/plugins/canvas/i18n/elements/element_strings.ts index e1540572f4af6..c97dd1b434d51 100644 --- a/x-pack/plugins/canvas/i18n/elements/element_strings.ts +++ b/x-pack/plugins/canvas/i18n/elements/element_strings.ts @@ -230,4 +230,12 @@ export const getElementStrings = (): ElementStringDict => ({ defaultMessage: 'Tagcloud visualization', }), }, + metricVis: { + displayName: i18n.translate('xpack.canvas.elements.metricVisDisplayName', { + defaultMessage: '(New) Metric Vis', + }), + help: i18n.translate('xpack.canvas.elements.metricVisHelpText', { + defaultMessage: 'Metric visualization', + }), + }, }); diff --git a/x-pack/plugins/canvas/i18n/ui.ts b/x-pack/plugins/canvas/i18n/ui.ts index 30a09d51ffab4..4856de96885e7 100644 --- a/x-pack/plugins/canvas/i18n/ui.ts +++ b/x-pack/plugins/canvas/i18n/ui.ts @@ -328,6 +328,16 @@ export const ArgumentStrings = { defaultMessage: 'Select column', }), }, + StopsPalette: { + getDisplayName: () => + i18n.translate('xpack.canvas.uis.arguments.stopsPaletteTitle', { + defaultMessage: 'Palette picker with bounds', + }), + getHelp: () => + i18n.translate('xpack.canvas.uis.arguments.stopsPaletteLabel', { + defaultMessage: 'Provides colors for the values, based on the bounds', + }), + }, }; export const DataSourceStrings = { @@ -1273,4 +1283,70 @@ export const ViewStrings = { defaultMessage: 'Bucket dimension configuration', }), }, + MetricVis: { + getDisplayName: () => + i18n.translate('xpack.canvas.uis.views.metricVisTitle', { + defaultMessage: 'Metric Vis', + }), + getMetricColumnDisplayName: () => + i18n.translate('xpack.canvas.uis.views.metricVis.args.metricDisplayName', { + defaultMessage: 'Metric', + }), + getMetricColumnHelp: () => + i18n.translate('xpack.canvas.uis.views.metricVis.args.metricHelp', { + defaultMessage: 'Metric dimension configuration', + }), + getBucketColumnDisplayName: () => + i18n.translate('xpack.canvas.uis.views.metricVis.args.bucketDisplayName', { + defaultMessage: 'Bucket', + }), + getBucketColumnHelp: () => + i18n.translate('xpack.canvas.uis.views.metricVis.args.bucketHelp', { + defaultMessage: 'Bucket dimension configuration', + }), + getFontColumnDisplayName: () => + i18n.translate('xpack.canvas.uis.views.metricVis.args.fontDisplayName', { + defaultMessage: 'Font', + }), + getFontColumnHelp: () => + i18n.translate('xpack.canvas.uis.views.metricVis.args.fontHelp', { + defaultMessage: 'Metric font configuration', + }), + getPercentageModeColumnDisplayName: () => + i18n.translate('xpack.canvas.uis.views.metricVis.args.percentageModeDisplayName', { + defaultMessage: 'Enable percentage mode', + }), + getPercentageModeColumnHelp: () => + i18n.translate('xpack.canvas.uis.views.metricVis.args.percentageModeHelp', { + defaultMessage: 'Shows metric in percentage mode.', + }), + getShowLabelsColumnDisplayName: () => + i18n.translate('xpack.canvas.uis.views.metricVis.args.showLabelsDisplayName', { + defaultMessage: 'Show metric labels', + }), + getShowLabelsColumnHelp: () => + i18n.translate('xpack.canvas.uis.views.metricVis.args.showLabelsHelp', { + defaultMessage: 'Shows labels under the metric values.', + }), + getColorModeColumnDisplayName: () => + i18n.translate('xpack.canvas.uis.views.metricVis.args.colorModeDisplayName', { + defaultMessage: 'Metric color mode', + }), + getColorModeColumnHelp: () => + i18n.translate('xpack.canvas.uis.views.metricVis.args.colorModeHelp', { + defaultMessage: 'Which part of metric to fill with color.', + }), + getColorModeNoneOption: () => + i18n.translate('xpack.canvas.uis.views.metricVis.args.colorMode.noneOption', { + defaultMessage: 'None', + }), + getColorModeLabelOption: () => + i18n.translate('xpack.canvas.uis.views.metricVis.args.colorMode.labelsOption', { + defaultMessage: 'Labels', + }), + getColorModeBackgroundOption: () => + i18n.translate('xpack.canvas.uis.views.metricVis.args.colorMode.backgroundOption', { + defaultMessage: 'Background', + }), + }, }; diff --git a/x-pack/plugins/canvas/public/components/arg_form/arg_form.js b/x-pack/plugins/canvas/public/components/arg_form/arg_form.js index 1e79b8152c9d1..0bbac0e4dd25d 100644 --- a/x-pack/plugins/canvas/public/components/arg_form/arg_form.js +++ b/x-pack/plugins/canvas/public/components/arg_form/arg_form.js @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useRef, useEffect } from 'react'; +import React, { useRef, useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { ErrorBoundary } from '../enhance/error_boundary'; import { ArgSimpleForm } from './arg_simple_form'; @@ -39,11 +39,9 @@ export const ArgForm = (props) => { onValueRemove, workpad, assets, - renderError, - setRenderError, resolvedArgValue, } = props; - + const [renderError, setRenderError] = useState(false); const isMounted = useRef(); useEffect(() => { @@ -62,21 +60,15 @@ export const ArgForm = (props) => { {({ error, resetErrorState }) => { const { template, simpleTemplate } = argTypeInstance.argType; const hasError = Boolean(error) || renderError; - const argumentProps = { ...templateProps, resolvedArgValue, defaultValue: argTypeInstance.default, renderError: () => { - // TODO: don't do this - // It's an ugly hack to avoid React's render cycle and ensure the error happens on the next tick - // This is important; Otherwise we end up updating state in the middle of a render cycle - Promise.resolve().then(() => { - // Provide templates with a renderError method, and wrap the error in a known error type - // to stop Kibana's window.error from being called - isMounted.current && setRenderError(true); - }); + // Provide templates with a renderError method, and wrap the error in a known error type + // to stop Kibana's window.error from being called + isMounted.current && setRenderError(true); }, error: hasError, setLabel: (label) => isMounted.current && setLabel(label), @@ -154,7 +146,5 @@ ArgForm.propTypes = { expand: PropTypes.bool, setExpand: PropTypes.func, onValueRemove: PropTypes.func, - renderError: PropTypes.bool.isRequired, - setRenderError: PropTypes.func.isRequired, resolvedArgValue: PropTypes.any, }; diff --git a/x-pack/plugins/canvas/public/components/arg_form/arg_template_form.tsx b/x-pack/plugins/canvas/public/components/arg_form/arg_template_form.tsx index aec68993df98c..172b3f1a590e6 100644 --- a/x-pack/plugins/canvas/public/components/arg_form/arg_template_form.tsx +++ b/x-pack/plugins/canvas/public/components/arg_form/arg_template_form.tsx @@ -7,14 +7,17 @@ import React, { useState, useEffect, useCallback, useRef } from 'react'; import usePrevious from 'react-use/lib/usePrevious'; +import useEffectOnce from 'react-use/lib/useEffectOnce'; import { RenderToDom } from '../render_to_dom'; import { ExpressionFormHandlers } from '../../../common/lib/expression_form_handlers'; +import { UpdatePropsRef } from '../../../types/arguments'; interface ArgTemplateFormProps { template?: ( domNode: HTMLElement, config: ArgTemplateFormProps['argumentProps'], - handlers: ArgTemplateFormProps['handlers'] + handlers: ArgTemplateFormProps['handlers'], + onMount?: (ref: UpdatePropsRef | null) => void ) => void; argumentProps: { valueMissing?: boolean; @@ -42,11 +45,27 @@ export const ArgTemplateForm: React.FunctionComponent = ({ errorTemplate, }) => { const [updatedHandlers, setHandlers] = useState(mergeWithFormHandlers(handlers)); - const previousError = usePrevious(error); + const [mounted, setMounted] = useState(false); + const prevError = usePrevious(error); + const prevMounted = usePrevious(mounted); + const mountedArgumentRef = useRef>(); + const domNodeRef = useRef(); + + useEffectOnce(() => () => { + mountedArgumentRef.current = undefined; + }); + const renderTemplate = useCallback( - (domNode) => template && template(domNode, argumentProps, updatedHandlers), - [template, argumentProps, updatedHandlers] + (domNode) => + template && + template(domNode, argumentProps, updatedHandlers, (ref) => { + if (!mountedArgumentRef.current && ref) { + mountedArgumentRef.current = ref; + setMounted(true); + } + }), + [argumentProps, template, updatedHandlers] ); const renderErrorTemplate = useCallback( @@ -59,22 +78,30 @@ export const ArgTemplateForm: React.FunctionComponent = ({ }, [handlers]); useEffect(() => { - if (previousError !== error) { + if (!prevError && error) { updatedHandlers.destroy(); } - }, [previousError, error, updatedHandlers]); + }, [prevError, error, updatedHandlers]); useEffect(() => { - if (!error) { + if ((!error && prevError && mounted) || (mounted && !prevMounted && !error)) { renderTemplate(domNodeRef.current); } - }, [error, renderTemplate, domNodeRef]); + }, [error, mounted, prevError, prevMounted, renderTemplate]); + + useEffect(() => { + if (mountedArgumentRef.current) { + mountedArgumentRef.current?.updateProps(argumentProps); + } + }, [argumentProps]); if (error) { + mountedArgumentRef.current = undefined; return renderErrorTemplate(); } if (!template) { + mountedArgumentRef.current = undefined; return null; } @@ -82,7 +109,7 @@ export const ArgTemplateForm: React.FunctionComponent = ({ { domNodeRef.current = domNode; - renderTemplate(domNode); + setMounted(true); }} /> ); diff --git a/x-pack/plugins/canvas/public/components/arg_form/index.js b/x-pack/plugins/canvas/public/components/arg_form/index.js index 5dbc6c33db706..4ba510c120552 100644 --- a/x-pack/plugins/canvas/public/components/arg_form/index.js +++ b/x-pack/plugins/canvas/public/components/arg_form/index.js @@ -19,12 +19,10 @@ export const ArgForm = (props) => { const { argTypeInstance, label: labelFromProps, templateProps } = props; const [label, setLabel] = useState(getLabel(labelFromProps, argTypeInstance)); const [resolvedArgValue, setResolvedArgValue] = useState(null); - const [renderError, setRenderError] = useState(false); const workpad = useSelector(getWorkpadInfo); const assets = useSelector(getAssets); useEffect(() => { - setRenderError(false); setResolvedArgValue(); }, [templateProps?.argValue]); @@ -37,8 +35,6 @@ export const ArgForm = (props) => { setLabel={setLabel} resolvedArgValue={resolvedArgValue} setResolvedArgValue={setResolvedArgValue} - renderError={renderError} - setRenderError={setRenderError} /> ); }; diff --git a/x-pack/plugins/canvas/public/components/function_form/function_form.tsx b/x-pack/plugins/canvas/public/components/function_form/function_form.tsx index abe31f0105108..491bb6becf988 100644 --- a/x-pack/plugins/canvas/public/components/function_form/function_form.tsx +++ b/x-pack/plugins/canvas/public/components/function_form/function_form.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { FunctionFormComponent } from './function_form_component'; +import { FunctionFormComponent as Component } from './function_form_component'; import { FunctionUnknown } from './function_unknown'; import { FunctionFormContextPending } from './function_form_context_pending'; import { FunctionFormContextError } from './function_form_context_error'; @@ -56,5 +56,5 @@ export const FunctionForm: React.FunctionComponent = (props) ); } - return ; + return ; }; diff --git a/x-pack/plugins/canvas/public/components/function_form/index.tsx b/x-pack/plugins/canvas/public/components/function_form/index.tsx index aff019d1cb69c..47c6925f6fbbb 100644 --- a/x-pack/plugins/canvas/public/components/function_form/index.tsx +++ b/x-pack/plugins/canvas/public/components/function_form/index.tsx @@ -6,8 +6,9 @@ */ import React, { useCallback } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import { Ast } from '@kbn/interpreter/common'; +import deepEqual from 'react-fast-compare'; import { ExpressionAstExpression, ExpressionValue, @@ -49,15 +50,21 @@ interface FunctionFormProps { export const FunctionForm: React.FunctionComponent = (props) => { const { expressionIndex, argType, nextArgType } = props; const dispatch = useDispatch(); - const context = useSelector((state) => - getContextForIndex(state, expressionIndex) + const context = useSelector( + (state) => getContextForIndex(state, expressionIndex), + deepEqual ); - const element = useSelector((state) => - getSelectedElement(state) + const element = useSelector( + (state) => getSelectedElement(state), + deepEqual ); - const pageId = useSelector((state) => getSelectedPage(state)); - const assets = useSelector((state) => getAssets(state)); - const filterGroups = useSelector((state) => getGlobalFilterGroups(state)); + const pageId = useSelector((state) => getSelectedPage(state), shallowEqual); + const assets = useSelector((state) => getAssets(state), shallowEqual); + const filterGroups = useSelector( + (state) => getGlobalFilterGroups(state), + shallowEqual + ); + const addArgument = useCallback( (argName: string, argValue: string | Ast | null) => () => { dispatch( @@ -131,7 +138,6 @@ export const FunctionForm: React.FunctionComponent = (props) }, [assets, onAssetAddDispatch] ); - return ( - i18n.translate('xpack.canvas.palettePicker.emptyPaletteLabel', { - defaultMessage: 'None', - }), - getNoPaletteFoundErrorTitle: () => - i18n.translate('xpack.canvas.palettePicker.noPaletteFoundErrorTitle', { - defaultMessage: 'Color palette not found', - }), -}; - -interface RequiredProps { - id?: string; - onChange?: (palette: ColorPalette) => void; - palette: ColorPalette; - clearable?: false; -} - -interface ClearableProps { - id?: string; - onChange?: (palette: ColorPalette | null) => void; - palette: ColorPalette | null; - clearable: true; -} - -type Props = RequiredProps | ClearableProps; - -const findPalette = (colorPalette: ColorPalette | null, colorPalettes: ColorPalette[] = []) => { - const palette = colorPalettes.filter((cp) => cp.id === colorPalette?.id)[0] ?? null; - if (palette === null) { - return colorPalettes.filter((cp) => isEqual(cp.colors, colorPalette?.colors))[0] ?? null; - } - - return palette; -}; - -export const PalettePicker: FC = (props) => { - const colorPalettes: EuiColorPalettePickerPaletteProps[] = palettes.map((item) => ({ - value: item.id, - title: item.label, - type: item.gradient ? 'gradient' : 'fixed', - palette: item.colors, - })); - - if (props.clearable) { - const { palette, onChange = () => {} } = props; - - colorPalettes.unshift({ - value: 'clear', - title: strings.getEmptyPaletteLabel(), - type: 'text', - }); - - const onPickerChange = (value: string) => { - const canvasPalette = palettes.find((item) => item.id === value); - onChange(canvasPalette || null); - }; - - const foundPalette = findPalette(palette, palettes); - - return ( - - ); - } - - const { palette, onChange = () => {} } = props; - - const onPickerChange = (value: string) => { - const canvasPalette = palettes.find((item) => item.id === value); - - if (!canvasPalette) { - throw new Error(strings.getNoPaletteFoundErrorTitle()); - } - - onChange(canvasPalette); - }; - - const foundPalette = findPalette(palette, palettes); - - return ( - - ); -}; - -PalettePicker.propTypes = { - id: PropTypes.string, - palette: PropTypes.object, - onChange: PropTypes.func, - clearable: PropTypes.bool, -}; diff --git a/x-pack/plugins/canvas/public/components/palette_picker/__stories__/__snapshots__/palette_picker.stories.storyshot b/x-pack/plugins/canvas/public/components/palette_picker/palette_picker/__stories__/__snapshots__/palette_picker.stories.storyshot similarity index 100% rename from x-pack/plugins/canvas/public/components/palette_picker/__stories__/__snapshots__/palette_picker.stories.storyshot rename to x-pack/plugins/canvas/public/components/palette_picker/palette_picker/__stories__/__snapshots__/palette_picker.stories.storyshot diff --git a/x-pack/plugins/canvas/public/components/palette_picker/__stories__/palette_picker.stories.tsx b/x-pack/plugins/canvas/public/components/palette_picker/palette_picker/__stories__/palette_picker.stories.tsx similarity index 76% rename from x-pack/plugins/canvas/public/components/palette_picker/__stories__/palette_picker.stories.tsx rename to x-pack/plugins/canvas/public/components/palette_picker/palette_picker/__stories__/palette_picker.stories.tsx index d097556285a6e..3c1b5f3537ac0 100644 --- a/x-pack/plugins/canvas/public/components/palette_picker/__stories__/palette_picker.stories.tsx +++ b/x-pack/plugins/canvas/public/components/palette_picker/palette_picker/__stories__/palette_picker.stories.tsx @@ -8,12 +8,13 @@ import React, { FC, useState } from 'react'; import { action } from '@storybook/addon-actions'; import { storiesOf } from '@storybook/react'; -import { PalettePicker } from '../palette_picker'; +import { PalettePicker } from '../../palette_picker'; -import { paulTor14, ColorPalette } from '../../../../common/lib/palettes'; +import { paulTor14, ColorPalette } from '../../../../../common/lib/palettes'; +import { CustomColorPalette } from '../../types'; const Interactive: FC = () => { - const [palette, setPalette] = useState(paulTor14); + const [palette, setPalette] = useState(paulTor14); return ; }; diff --git a/x-pack/plugins/canvas/public/components/palette_picker/palette_picker/clearable_palette_picker.tsx b/x-pack/plugins/canvas/public/components/palette_picker/palette_picker/clearable_palette_picker.tsx new file mode 100644 index 0000000000000..1dd4d7355050d --- /dev/null +++ b/x-pack/plugins/canvas/public/components/palette_picker/palette_picker/clearable_palette_picker.tsx @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiColorPalettePicker } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { FC } from 'react'; +import { ClearableComponentProps } from '../types'; +import { findPalette, prepareColorPalette } from '../utils'; + +const strings = { + getEmptyPaletteLabel: () => + i18n.translate('xpack.canvas.palettePicker.emptyPaletteLabel', { + defaultMessage: 'None', + }), +}; + +export const ClearablePalettePicker: FC = (props) => { + const { palette, palettes, onChange = () => {} } = props; + const colorPalettes = palettes.map(prepareColorPalette); + + const onPickerChange = (value: string) => { + const canvasPalette = palettes.find((item) => item.id === value); + onChange(canvasPalette || null); + }; + + const foundPalette = findPalette(palette ?? null, palettes); + + return ( + + ); +}; diff --git a/x-pack/plugins/canvas/public/components/palette_picker/palette_picker/default_palette_picker.tsx b/x-pack/plugins/canvas/public/components/palette_picker/palette_picker/default_palette_picker.tsx new file mode 100644 index 0000000000000..c63964075e5b4 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/palette_picker/palette_picker/default_palette_picker.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiColorPalettePicker } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { FC } from 'react'; +import { RequiredComponentProps } from '../types'; +import { findPalette, prepareColorPalette } from '../utils'; + +const strings = { + getNoPaletteFoundErrorTitle: () => + i18n.translate('xpack.canvas.palettePicker.noPaletteFoundErrorTitle', { + defaultMessage: 'Color palette not found', + }), +}; + +export const DefaultPalettePicker: FC = (props) => { + const { palette, palettes, onChange = () => {} } = props; + const colorPalettes = palettes.map(prepareColorPalette); + + const onPickerChange = (value: string) => { + const canvasPalette = palettes.find((item) => item.id === value); + if (!canvasPalette) { + throw new Error(strings.getNoPaletteFoundErrorTitle()); + } + + onChange(canvasPalette); + }; + + const foundPalette = findPalette(palette ?? null, palettes); + + return ( + + ); +}; diff --git a/x-pack/plugins/canvas/public/components/palette_picker/palette_picker/index.ts b/x-pack/plugins/canvas/public/components/palette_picker/palette_picker/index.ts new file mode 100644 index 0000000000000..ac9085abf0a5a --- /dev/null +++ b/x-pack/plugins/canvas/public/components/palette_picker/palette_picker/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { PalettePicker } from './palette_picker'; diff --git a/x-pack/plugins/canvas/public/components/palette_picker/palette_picker/palette_picker.tsx b/x-pack/plugins/canvas/public/components/palette_picker/palette_picker/palette_picker.tsx new file mode 100644 index 0000000000000..064a9bd217abd --- /dev/null +++ b/x-pack/plugins/canvas/public/components/palette_picker/palette_picker/palette_picker.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; +import PropTypes from 'prop-types'; +import { ClearablePalettePicker } from './clearable_palette_picker'; +import { palettes as defaultPalettes } from '../../../../common/lib/palettes'; +import { PalettePickerProps } from '../types'; +import { DefaultPalettePicker } from './default_palette_picker'; + +export const PalettePicker: FC = (props) => { + const { additionalPalettes = [] } = props; + const palettes = [...defaultPalettes, ...additionalPalettes]; + + if (props.clearable) { + return ( + + ); + } + + return ( + + ); +}; + +PalettePicker.propTypes = { + id: PropTypes.string, + palette: PropTypes.object, + onChange: PropTypes.func, + clearable: PropTypes.bool, +}; diff --git a/x-pack/plugins/canvas/public/components/palette_picker/stops_palette_picker/index.ts b/x-pack/plugins/canvas/public/components/palette_picker/stops_palette_picker/index.ts new file mode 100644 index 0000000000000..8782055147c49 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/palette_picker/stops_palette_picker/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { StopsPalettePicker } from './stops_palette_picker'; diff --git a/x-pack/plugins/canvas/public/components/palette_picker/stops_palette_picker/stop_color_picker.tsx b/x-pack/plugins/canvas/public/components/palette_picker/stops_palette_picker/stop_color_picker.tsx new file mode 100644 index 0000000000000..6c5ff2923c8d2 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/palette_picker/stops_palette_picker/stop_color_picker.tsx @@ -0,0 +1,133 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiButtonIcon, + EuiColorPicker, + EuiFieldNumber, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { FC, useEffect, useState } from 'react'; +import useDebounce from 'react-use/lib/useDebounce'; +import { ColorStop } from '../types'; + +interface Props { + removable?: boolean; + stop?: number; + color?: string; + onDelete: () => void; + onChange: (colorStop: ColorStop) => void; +} + +interface ValidationResult { + color: boolean; + stop: boolean; +} + +const strings = { + getDeleteStopColorLabel: () => + i18n.translate('xpack.canvas.stopsColorPicker.deleteColorStopLabel', { + defaultMessage: 'Delete', + }), +}; + +const isValidColorStop = (colorStop: ColorStop): ValidationResult & { valid: boolean } => { + const valid = !isNaN(colorStop.stop); + return { + valid, + stop: valid, + color: true, + }; +}; + +export const StopColorPicker: FC = (props) => { + const { stop, color, onDelete, onChange, removable = true } = props; + + const [colorStop, setColorStop] = useState({ stop: stop ?? 0, color: color ?? '' }); + const [areValidFields, setAreValidFields] = useState({ + stop: true, + color: true, + }); + + const onChangeInput = (updatedColorStop: ColorStop) => { + setColorStop(updatedColorStop); + }; + + const [, cancel] = useDebounce( + () => { + if (color === colorStop.color && stop === colorStop.stop) { + return; + } + + const { valid, ...validationResult } = isValidColorStop(colorStop); + if (!valid) { + setAreValidFields(validationResult); + return; + } + + onChange(colorStop); + }, + 150, + [colorStop] + ); + + useEffect(() => { + const newColorStop = { stop: stop ?? 0, color: color ?? '' }; + setColorStop(newColorStop); + + const { valid, ...validationResult } = isValidColorStop(newColorStop); + setAreValidFields(validationResult); + }, [color, stop]); + + useEffect(() => { + return () => { + cancel(); + }; + }, [cancel]); + + return ( + + + + onChangeInput({ ...colorStop, stop: valueAsNumber }) + } + isInvalid={!areValidFields.stop} + /> + + + + { + onChangeInput({ ...colorStop, color: newColor }); + }} + isInvalid={!areValidFields.color} + /> + + + + + + + ); +}; diff --git a/x-pack/plugins/canvas/public/components/palette_picker/stops_palette_picker/stops_palette_picker.tsx b/x-pack/plugins/canvas/public/components/palette_picker/stops_palette_picker/stops_palette_picker.tsx new file mode 100644 index 0000000000000..aab48528770cb --- /dev/null +++ b/x-pack/plugins/canvas/public/components/palette_picker/stops_palette_picker/stops_palette_picker.tsx @@ -0,0 +1,147 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, useCallback, useMemo } from 'react'; +import { flowRight, identity } from 'lodash'; +import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSpacer } from '@elastic/eui'; +import useEffectOnce from 'react-use/lib/useEffectOnce'; +import { i18n } from '@kbn/i18n'; +import { ColorStop, CustomColorPalette, StopsPalettePickerProps } from '../types'; +import { PalettePicker } from '../palette_picker'; +import { StopColorPicker } from './stop_color_picker'; +import { Palette } from './types'; +import { + reduceColorsByStopsSize, + transformPaletteToColorStops, + mergeColorStopsWithPalette, + deleteColorStop, + updateColorStop, + addNewColorStop, + getOverridenPaletteOptions, +} from './utils'; +import { ColorPalette } from '../../../../common/lib/palettes'; + +const strings = { + getAddColorStopLabel: () => + i18n.translate('xpack.canvas.stopsPalettePicker.addColorStopLabel', { + defaultMessage: 'Add color stop', + }), + getColorStopsLabel: () => + i18n.translate('xpack.canvas.stopsPalettePicker.colorStopsLabel', { + defaultMessage: 'Color stops', + }), +}; + +const defaultStops = [0, 1]; +const MIN_STOPS = 2; + +export const StopsPalettePicker: FC = (props) => { + const { palette, onChange } = props; + const stops = useMemo( + () => (!palette?.stops || !palette.stops.length ? defaultStops : palette.stops), + [palette?.stops] + ); + + const colors = useMemo( + () => reduceColorsByStopsSize(palette?.colors, stops.length), + [palette?.colors, stops.length] + ); + + const onChangePalette = useCallback( + (newPalette: ColorPalette | CustomColorPalette | null) => { + if (newPalette) { + const newColors = reduceColorsByStopsSize(newPalette?.colors, stops.length); + props.onChange?.({ + ...palette, + ...newPalette, + colors: newColors, + stops, + }); + } + }, + [palette, props, stops] + ); + + useEffectOnce(() => { + onChangePalette({ ...getOverridenPaletteOptions(), ...palette }); + }); + + const paletteColorStops = useMemo( + () => transformPaletteToColorStops({ stops, colors }), + [colors, stops] + ); + + const updatePalette = useCallback( + (fn: (colorStops: ColorStop[]) => ColorStop[]) => + flowRight( + onChange ?? identity, + mergeColorStopsWithPalette(palette), + fn + ), + [onChange, palette] + ); + + const deleteColorStopAndApply = useCallback( + (index: number) => updatePalette(deleteColorStop(index))(paletteColorStops), + [paletteColorStops, updatePalette] + ); + + const updateColorStopAndApply = useCallback( + (index: number, colorStop: ColorStop) => + updatePalette(updateColorStop(index, colorStop))(paletteColorStops), + [paletteColorStops, updatePalette] + ); + + const addColorStopAndApply = useCallback( + () => updatePalette(addNewColorStop(palette))(paletteColorStops), + [palette, paletteColorStops, updatePalette] + ); + + const stopColorPickers = paletteColorStops.map(({ id, ...rest }, index) => ( + + = MIN_STOPS} + onDelete={() => deleteColorStopAndApply(index)} + onChange={(cp: ColorStop) => updateColorStopAndApply(index, cp)} + /> + + )); + + return ( + <> + + + + + + + {stopColorPickers} + + + + + + + {strings.getAddColorStopLabel()} + + + ); +}; diff --git a/x-pack/plugins/canvas/public/components/palette_picker/stops_palette_picker/types.ts b/x-pack/plugins/canvas/public/components/palette_picker/stops_palette_picker/types.ts new file mode 100644 index 0000000000000..fab6fd218608d --- /dev/null +++ b/x-pack/plugins/canvas/public/components/palette_picker/stops_palette_picker/types.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { CustomColorPalette } from '../types'; +import { ColorPalette } from '../../../../common/lib/palettes'; + +export type Palette = ColorPalette | CustomColorPalette; +export type PaletteColorStops = Pick; diff --git a/x-pack/plugins/canvas/public/components/palette_picker/stops_palette_picker/utils.ts b/x-pack/plugins/canvas/public/components/palette_picker/stops_palette_picker/utils.ts new file mode 100644 index 0000000000000..0dad7f7c3abe3 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/palette_picker/stops_palette_picker/utils.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { zip, take } from 'lodash'; +import { htmlIdGenerator } from '@elastic/eui'; +import { ColorPalette } from '../../../../common/lib'; +import { ColorStop } from '../types'; +import { Palette, PaletteColorStops } from './types'; + +const id = htmlIdGenerator(); + +export const getOverridenPaletteOptions = (): Pick => ({ + range: 'number', + continuity: 'below', +}); + +export const createColorStop = (stop: number = 0, color: string = '') => ({ + stop, + color, + id: id(), +}); + +export const transformPaletteToColorStops = ({ stops = [], colors }: PaletteColorStops) => + zip(stops, colors).map(([stop, color]) => createColorStop(stop, color)); + +export const mergeColorStopsWithPalette = + (palette: Palette) => + (colorStops: ColorStop[]): Palette => { + const stopsWithColors = colorStops.reduce<{ colors: string[]; stops: number[] }>( + (acc, { color, stop }) => { + acc.colors.push(color ?? ''); + acc.stops.push(stop); + return acc; + }, + { colors: [], stops: [] } + ); + return { ...palette, ...stopsWithColors }; + }; + +export const updateColorStop = + (index: number, colorStop: ColorStop) => (colorStops: ColorStop[]) => { + colorStops.splice(index, 1, colorStop); + return colorStops; + }; + +export const deleteColorStop = (index: number) => (colorStops: ColorStop[]) => { + colorStops.splice(index, 1); + return colorStops; +}; + +export const addNewColorStop = (palette: Palette) => (colorStops: ColorStop[]) => { + const lastColorStopIndex = colorStops.length - 1; + const lastStop = lastColorStopIndex >= 0 ? colorStops[lastColorStopIndex].stop + 1 : 0; + const newIndex = lastColorStopIndex + 1; + return [ + ...colorStops, + { + stop: lastStop, + color: + palette.colors.length > newIndex + 1 + ? palette.colors[newIndex] + : palette.colors[palette.colors.length - 1], + }, + ]; +}; + +export const reduceColorsByStopsSize = (colors: string[] = [], stopsSize: number) => { + const reducedColors = take(colors, stopsSize); + const colorsLength = reducedColors.length; + if (colorsLength === stopsSize) { + return reducedColors; + } + + return [ + ...reducedColors, + ...Array(stopsSize - colorsLength).fill(reducedColors[colorsLength - 1] ?? ''), + ]; +}; diff --git a/x-pack/plugins/canvas/public/components/palette_picker/types.ts b/x-pack/plugins/canvas/public/components/palette_picker/types.ts new file mode 100644 index 0000000000000..f297c0064a265 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/palette_picker/types.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ColorPalette } from '../../../common/lib/palettes'; + +export type CustomColorPalette = ColorPalette<'custom'>; + +export interface RequiredProps { + id?: string; + onChange?: (palette: ColorPalette | CustomColorPalette) => void; + palette: ColorPalette | CustomColorPalette; + clearable?: false; + additionalPalettes?: Array; +} + +export interface ClearableProps { + id?: string; + onChange?: (palette: ColorPalette | CustomColorPalette | null) => void; + palette: ColorPalette | CustomColorPalette | null; + clearable: true; + additionalPalettes?: Array; +} + +export type PalettePickerProps = RequiredProps | ClearableProps; +export type StopsPalettePickerProps = RequiredProps; + +export type ClearableComponentProps = { + palettes: Array; +} & Partial> & + Pick; + +export type RequiredComponentProps = { + palettes: Array; +} & Partial>; + +export interface ColorStop { + color: string; + stop: number; +} diff --git a/x-pack/plugins/canvas/public/components/palette_picker/utils.ts b/x-pack/plugins/canvas/public/components/palette_picker/utils.ts new file mode 100644 index 0000000000000..57d9220069ffd --- /dev/null +++ b/x-pack/plugins/canvas/public/components/palette_picker/utils.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { EuiColorPalettePickerPaletteProps } from '@elastic/eui'; +import { isEqual } from 'lodash'; +import { ColorPalette } from '../../../common/lib/palettes'; +import { CustomColorPalette } from './types'; + +export const findPalette = ( + colorPalette: ColorPalette | CustomColorPalette | null, + colorPalettes: Array = [] +) => { + const palette = colorPalettes.filter((cp) => cp.id === colorPalette?.id)[0] ?? null; + if (palette === null) { + return colorPalettes.filter((cp) => isEqual(cp.colors, colorPalette?.colors))[0] ?? null; + } + + return palette; +}; + +export const prepareColorPalette = ({ + id, + label, + gradient, + colors, +}: ColorPalette | CustomColorPalette): EuiColorPalettePickerPaletteProps => ({ + value: id, + title: label, + type: gradient ? 'gradient' : 'fixed', + palette: colors, +}); diff --git a/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.component.tsx b/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.component.tsx index e8f2c7a559f58..a912668d91432 100644 --- a/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.component.tsx +++ b/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.component.tsx @@ -61,7 +61,6 @@ export const ElementSettings: FunctionComponent = ({ element }) => { ), }, ]; - return ; }; diff --git a/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.tsx b/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.tsx index 4935647ca6810..2de52c996e7dd 100644 --- a/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.tsx +++ b/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.tsx @@ -6,31 +6,24 @@ */ import React from 'react'; -import { connect } from 'react-redux'; +import deepEqual from 'react-fast-compare'; +import { useSelector } from 'react-redux'; import { getElementById, getSelectedPage } from '../../../state/selectors/workpad'; import { ElementSettings as Component } from './element_settings.component'; -import { State, PositionedElement } from '../../../../types'; +import { State } from '../../../../types'; interface Props { selectedElementId: string | null; } -const mapStateToProps = (state: State, { selectedElementId }: Props): StateProps => ({ - element: getElementById(state, selectedElementId, getSelectedPage(state)), -}); +export const ElementSettings: React.FC = ({ selectedElementId }) => { + const element = useSelector((state: State) => { + return getElementById(state, selectedElementId, getSelectedPage(state)); + }, deepEqual); -interface StateProps { - element: PositionedElement | undefined; -} - -const renderIfElement: React.FunctionComponent = (props) => { - if (props.element) { - return ; + if (element) { + return ; } return null; }; - -export const ElementSettings = connect(mapStateToProps)( - renderIfElement -); diff --git a/x-pack/plugins/canvas/public/components/sidebar/sidebar_content/sidebar_content.tsx b/x-pack/plugins/canvas/public/components/sidebar/sidebar_content/sidebar_content.tsx index e53f5d6d515df..cc7bfa7d11195 100644 --- a/x-pack/plugins/canvas/public/components/sidebar/sidebar_content/sidebar_content.tsx +++ b/x-pack/plugins/canvas/public/components/sidebar/sidebar_content/sidebar_content.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { useSelector } from 'react-redux'; +import { shallowEqual, useSelector } from 'react-redux'; import { getSelectedToplevelNodes, getSelectedElementId } from '../../../state/selectors/workpad'; import { State } from '../../../../types'; import { SidebarContent as Component } from './sidebar_content.component'; @@ -16,12 +16,14 @@ interface SidebarContentProps { } export const SidebarContent: React.FC = ({ commit }) => { - const selectedToplevelNodes = useSelector((state) => - getSelectedToplevelNodes(state) + const selectedToplevelNodes = useSelector( + (state) => getSelectedToplevelNodes(state), + shallowEqual ); - const selectedElementId = useSelector((state) => - getSelectedElementId(state) + const selectedElementId = useSelector( + (state) => getSelectedElementId(state), + shallowEqual ); return ( diff --git a/x-pack/plugins/canvas/public/components/workpad_app/workpad_app.component.tsx b/x-pack/plugins/canvas/public/components/workpad_app/workpad_app.component.tsx index 6e380088bd84b..0d9ff68c9f46f 100644 --- a/x-pack/plugins/canvas/public/components/workpad_app/workpad_app.component.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_app/workpad_app.component.tsx @@ -16,7 +16,7 @@ import { CommitFn } from '../../../types'; export const WORKPAD_CONTAINER_ID = 'canvasWorkpadContainer'; -interface Props { +export interface Props { deselectElement?: MouseEventHandler; isWriteable: boolean; } diff --git a/x-pack/plugins/canvas/public/components/workpad_app/workpad_app.ts b/x-pack/plugins/canvas/public/components/workpad_app/workpad_app.ts index fe98e0f4b1bff..bf859ceb43161 100644 --- a/x-pack/plugins/canvas/public/components/workpad_app/workpad_app.ts +++ b/x-pack/plugins/canvas/public/components/workpad_app/workpad_app.ts @@ -6,8 +6,8 @@ */ import { MouseEventHandler } from 'react'; -import { Dispatch } from 'redux'; import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; // @ts-expect-error untyped local import { selectToplevelNodes } from '../../state/actions/transient'; import { canUserWrite } from '../../state/selectors/app'; @@ -18,6 +18,8 @@ import { State } from '../../../types'; export { WORKPAD_CONTAINER_ID } from './workpad_app.component'; +const WorkpadAppComponent = withElementsLoadedTelemetry(Component); + const mapDispatchToProps = (dispatch: Dispatch): { deselectElement: MouseEventHandler } => ({ deselectElement: (ev) => { ev.stopPropagation(); @@ -31,4 +33,4 @@ export const WorkpadApp = connect( workpad: getWorkpad(state), }), mapDispatchToProps -)(withElementsLoadedTelemetry(Component)); +)(WorkpadAppComponent); diff --git a/x-pack/plugins/canvas/public/components/workpad_app/workpad_telemetry.test.tsx b/x-pack/plugins/canvas/public/components/workpad_app/workpad_telemetry.test.tsx index 54fd871203f1f..3c2ab0bf8175f 100644 --- a/x-pack/plugins/canvas/public/components/workpad_app/workpad_telemetry.test.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_app/workpad_telemetry.test.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import { useSelector } from 'react-redux'; import { render } from '@testing-library/react'; import { withUnconnectedElementsLoadedTelemetry, @@ -13,148 +14,137 @@ import { WorkpadLoadedWithErrorsMetric, } from './workpad_telemetry'; import { METRIC_TYPE } from '../../lib/ui_metric'; -import { ResolvedArgType } from '../../../types'; +import { ExpressionContext, ResolvedArgType } from '../../../types'; + +jest.mock('react-redux', () => { + const originalModule = jest.requireActual('react-redux'); + + return { + ...originalModule, + useSelector: jest.fn(), + }; +}); const trackMetric = jest.fn(); +const useSelectorMock = useSelector as jest.Mock; + const Component = withUnconnectedElementsLoadedTelemetry(() =>
, trackMetric); const mockWorkpad = { id: 'workpadid', pages: [ { - elements: [{ id: '1' }, { id: '2' }, { id: '3' }, { id: '4' }], + elements: [{ id: '0' }, { id: '1' }, { id: '2' }, { id: '3' }], }, { - elements: [{ id: '5' }], + elements: [{ id: '4' }], }, ], }; -const resolvedArgsMatchWorkpad = { - '1': {} as ResolvedArgType, - '2': {} as ResolvedArgType, - '3': {} as ResolvedArgType, - '4': {} as ResolvedArgType, - '5': {} as ResolvedArgType, -}; +const getMockState = (resolvedArgs: Record) => ({ + transient: { resolvedArgs }, +}); -const resolvedArgsNotMatchWorkpad = { - 'non-matching-id': {} as ResolvedArgType, -}; +const getResolveArgWithState = (state: 'pending' | 'ready' | 'error') => + ({ + expressionRenderable: { value: { as: state, type: 'render' }, state, error: null }, + expressionContext: {} as ExpressionContext, + } as ResolvedArgType); -const pendingCounts = { - pending: 5, - error: 0, - ready: 0, -}; +const arrayToObject = (array: ResolvedArgType[]) => + array.reduce>((acc, el, index) => { + acc[index] = el; + return acc; + }, {}); -const readyCounts = { - pending: 0, - error: 0, - ready: 5, -}; +const pendingMockState = getMockState( + arrayToObject(Array(5).fill(getResolveArgWithState('pending'))) +); -const errorCounts = { - pending: 0, - error: 1, - ready: 4, -}; +const readyMockState = getMockState(arrayToObject(Array(5).fill(getResolveArgWithState('ready')))); + +const errorMockState = getMockState( + arrayToObject([ + ...Array(4).fill(getResolveArgWithState('ready')), + ...Array(1).fill(getResolveArgWithState('error')), + ]) +); + +const emptyElementsMockState = getMockState({}); + +const notMatchedMockState = getMockState({ + 'non-matching-id': getResolveArgWithState('ready'), +}); describe('Elements Loaded Telemetry', () => { beforeEach(() => { trackMetric.mockReset(); }); + afterEach(() => { + useSelectorMock.mockClear(); + }); + it('tracks when all resolvedArgs are completed', () => { - const { rerender } = render( - - ); + useSelectorMock.mockImplementation((callback) => { + return callback(pendingMockState); + }); + const { rerender } = render(); expect(trackMetric).not.toBeCalled(); - rerender( - - ); + useSelectorMock.mockClear(); + useSelectorMock.mockImplementation((callback) => { + return callback(readyMockState); + }); + rerender(); expect(trackMetric).toBeCalledWith(METRIC_TYPE.LOADED, WorkpadLoadedMetric); }); it('only tracks loaded once', () => { - const { rerender } = render( - - ); + useSelectorMock.mockImplementation((callback) => { + return callback(pendingMockState); + }); + const { rerender } = render(); expect(trackMetric).not.toBeCalled(); - rerender( - - ); - rerender( - - ); + useSelectorMock.mockClear(); + useSelectorMock.mockImplementation((callback) => { + return callback(readyMockState); + }); + rerender(); + rerender(); expect(trackMetric).toBeCalledTimes(1); }); it('does not track if resolvedArgs are never pending', () => { - const { rerender } = render( - - ); - - rerender( - - ); + useSelectorMock.mockImplementation((callback) => { + return callback(readyMockState); + }); + const { rerender } = render(); + rerender(); expect(trackMetric).not.toBeCalled(); }); it('tracks if elements are in error state after load', () => { - const { rerender } = render( - - ); + useSelectorMock.mockImplementation((callback) => { + return callback(pendingMockState); + }); + const { rerender } = render(); expect(trackMetric).not.toBeCalled(); - rerender( - - ); + useSelectorMock.mockClear(); + useSelectorMock.mockImplementation((callback) => { + return callback(errorMockState); + }); + rerender(); expect(trackMetric).toBeCalledWith(METRIC_TYPE.LOADED, [ WorkpadLoadedMetric, WorkpadLoadedWithErrorsMetric, @@ -166,42 +156,30 @@ describe('Elements Loaded Telemetry', () => { id: 'otherworkpad', pages: [ { - elements: [{ id: '1' }, { id: '2' }, { id: '3' }, { id: '4' }], + elements: [{ id: '0' }, { id: '1' }, { id: '2' }, { id: '3' }], }, { - elements: [{ id: '5' }], + elements: [{ id: '4' }], }, ], }; - const { rerender } = render( - - ); + useSelectorMock.mockImplementation((callback) => { + return callback(notMatchedMockState); + }); + const { rerender } = render(); expect(trackMetric).not.toBeCalled(); - rerender( - - ); - + rerender(); expect(trackMetric).not.toBeCalled(); - rerender( - - ); + useSelectorMock.mockClear(); + useSelectorMock.mockImplementation((callback) => { + return callback(readyMockState); + }); + rerender(); expect(trackMetric).toBeCalledWith(METRIC_TYPE.LOADED, WorkpadLoadedMetric); }); @@ -211,24 +189,12 @@ describe('Elements Loaded Telemetry', () => { pages: [], }; - const resolvedArgs = {}; - - const { rerender } = render( - - ); - - rerender( - - ); + useSelectorMock.mockImplementation((callback) => { + return callback(emptyElementsMockState); + }); + const { rerender } = render(); + rerender(); expect(trackMetric).not.toBeCalled(); }); }); diff --git a/x-pack/plugins/canvas/public/components/workpad_app/workpad_telemetry.tsx b/x-pack/plugins/canvas/public/components/workpad_app/workpad_telemetry.tsx index 0915c757ff893..d74f8693bc9bd 100644 --- a/x-pack/plugins/canvas/public/components/workpad_app/workpad_telemetry.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_app/workpad_telemetry.tsx @@ -6,21 +6,18 @@ */ import React, { useState, useEffect } from 'react'; -import { connect, ConnectedProps } from 'react-redux'; +import { shallowEqual, useSelector } from 'react-redux'; +import deepEqual from 'react-fast-compare'; import { trackCanvasUiMetric, METRIC_TYPE } from '../../lib/ui_metric'; import { getElementCounts } from '../../state/selectors/workpad'; import { getArgs } from '../../state/selectors/resolved_args'; +import { State } from '../../../types'; const WorkpadLoadedMetric = 'workpad-loaded'; const WorkpadLoadedWithErrorsMetric = 'workpad-loaded-with-errors'; export { WorkpadLoadedMetric, WorkpadLoadedWithErrorsMetric }; -const mapStateToProps = (state: any) => ({ - telemetryElementCounts: getElementCounts(state), - telemetryResolvedArgs: getArgs(state), -}); - // TODO: Build out full workpad types /** Individual Page of a Workpad @@ -47,7 +44,7 @@ interface ResolvedArgs { [keys: string]: any; } -export interface ElementsLoadedTelemetryProps extends PropsFromRedux { +export interface ElementsLoadedTelemetryProps { workpad: Workpad; } @@ -65,33 +62,32 @@ export const withUnconnectedElementsLoadedTelemetry =

( Component: React.ComponentType

, trackMetric = trackCanvasUiMetric ) => - function ElementsLoadedTelemetry(props: ElementsLoadedTelemetryProps) { - const { telemetryElementCounts, workpad, telemetryResolvedArgs, ...other } = props; - const { error, pending } = telemetryElementCounts; + function ElementsLoadedTelemetry(props: P & ElementsLoadedTelemetryProps) { + const { workpad } = props; const [currentWorkpadId, setWorkpadId] = useState(undefined); const [hasReported, setHasReported] = useState(false); + const telemetryElementCounts = useSelector( + (state: State) => getElementCounts(state), + shallowEqual + ); - useEffect(() => { - const resolvedArgsAreForWorkpad = areAllElementsInResolvedArgs( - workpad, - telemetryResolvedArgs - ); + const telemetryResolvedArgs = useSelector((state: State) => getArgs(state), deepEqual); - if (workpad.id !== currentWorkpadId) { - setWorkpadId(workpad.id); + const resolvedArgsAreForWorkpad = areAllElementsInResolvedArgs(workpad, telemetryResolvedArgs); + const { error, pending } = telemetryElementCounts; + const resolved = resolvedArgsAreForWorkpad && pending === 0; + useEffect(() => { + if (workpad.id !== currentWorkpadId) { const workpadElementCount = workpad.pages.reduce( (reduction, page) => reduction + page.elements.length, 0 ); - if (workpadElementCount === 0 || (resolvedArgsAreForWorkpad && pending === 0)) { - setHasReported(true); - } else { - setHasReported(false); - } - } else if (!hasReported && pending === 0 && resolvedArgsAreForWorkpad) { + setWorkpadId(workpad.id); + setHasReported(workpadElementCount === 0 || resolved); + } else if (!hasReported && resolved) { if (error > 0) { trackMetric(METRIC_TYPE.LOADED, [WorkpadLoadedMetric, WorkpadLoadedWithErrorsMetric]); } else { @@ -99,16 +95,9 @@ export const withUnconnectedElementsLoadedTelemetry =

( } setHasReported(true); } - }, [currentWorkpadId, hasReported, error, pending, telemetryResolvedArgs, workpad]); - - return ; + }, [currentWorkpadId, hasReported, error, workpad.id, resolved, workpad.pages]); + return ; }; -const connector = connect(mapStateToProps, {}); - -type PropsFromRedux = ConnectedProps; - -export const withElementsLoadedTelemetry =

(Component: React.ComponentType

) => { - const telemetry = withUnconnectedElementsLoadedTelemetry(Component); - return connector(telemetry); -}; +export const withElementsLoadedTelemetry =

(Component: React.ComponentType

) => + withUnconnectedElementsLoadedTelemetry(Component); diff --git a/x-pack/plugins/canvas/public/expression_types/datasource.tsx b/x-pack/plugins/canvas/public/expression_types/datasource.tsx index 7566c473a720a..8ecfedbf948d7 100644 --- a/x-pack/plugins/canvas/public/expression_types/datasource.tsx +++ b/x-pack/plugins/canvas/public/expression_types/datasource.tsx @@ -12,6 +12,7 @@ import { RenderToDom } from '../components/render_to_dom'; import { BaseForm, BaseFormProps } from './base_form'; import { ExpressionFormHandlers } from '../../common/lib'; import { ExpressionFunction } from '../../types'; +import { UpdatePropsRef } from '../../types/arguments'; const defaultTemplate = () => (

@@ -22,7 +23,8 @@ const defaultTemplate = () => ( type TemplateFn = ( domNode: HTMLElement, config: DatasourceRenderProps, - handlers: ExpressionFormHandlers + handlers: ExpressionFormHandlers, + onMount?: (ref: UpdatePropsRef | null) => void ) => void; export type DatasourceProps = { @@ -49,6 +51,8 @@ interface DatasourceWrapperProps { const DatasourceWrapper: React.FunctionComponent = (props) => { const domNodeRef = useRef(); + const datasourceRef = useRef>(); + const { spec, datasourceProps, handlers } = props; const callRenderFn = useCallback(() => { @@ -58,14 +62,23 @@ const DatasourceWrapper: React.FunctionComponent = (prop return; } - template(domNodeRef.current, datasourceProps, handlers); + template(domNodeRef.current, datasourceProps, handlers, (ref) => { + datasourceRef.current = ref ?? undefined; + }); }, [datasourceProps, handlers, spec]); useEffect(() => { callRenderFn(); - }, [callRenderFn, props]); + }, [callRenderFn]); + + useEffect(() => { + if (datasourceRef.current) { + datasourceRef.current.updateProps(datasourceProps); + } + }, [datasourceProps]); useEffectOnce(() => () => { + datasourceRef.current = undefined; handlers.destroy(); }); diff --git a/x-pack/plugins/canvas/public/expression_types/function_form.tsx b/x-pack/plugins/canvas/public/expression_types/function_form.tsx index 70279453ac658..9028a0999149c 100644 --- a/x-pack/plugins/canvas/public/expression_types/function_form.tsx +++ b/x-pack/plugins/canvas/public/expression_types/function_form.tsx @@ -140,7 +140,6 @@ export class FunctionForm extends BaseForm { // Don't instaniate these until render time, to give the registries a chance to populate. const argInstances = this.args.map((argSpec) => new Arg(argSpec)); - if (args === null || !isPlainObject(args)) { throw new Error(`Form "${this.name}" expects "args" object`); } @@ -153,7 +152,6 @@ export class FunctionForm extends BaseForm { // otherwise, leave the value alone (including if the arg is not defined) const isMulti = arg && arg.multi; const argValues = args[argName] && !isMulti ? [last(args[argName]) ?? null] : args[argName]; - return { arg, argValues }; }); diff --git a/x-pack/plugins/canvas/public/lib/template_from_react_component.tsx b/x-pack/plugins/canvas/public/lib/template_from_react_component.tsx index b2dfd67a2d34e..a783d6de2916d 100644 --- a/x-pack/plugins/canvas/public/lib/template_from_react_component.tsx +++ b/x-pack/plugins/canvas/public/lib/template_from_react_component.tsx @@ -5,42 +5,72 @@ * 2.0. */ -import React, { ComponentType, FC } from 'react'; +import React, { + ComponentType, + forwardRef, + ForwardRefRenderFunction, + useImperativeHandle, + useState, +} from 'react'; import { unmountComponentAtNode, render } from 'react-dom'; import PropTypes from 'prop-types'; import { I18nProvider } from '@kbn/i18n/react'; import { ErrorBoundary } from '../components/enhance/error_boundary'; -import { ArgumentHandlers } from '../../types/arguments'; +import { ArgumentHandlers, UpdatePropsRef } from '../../types/arguments'; export interface Props { renderError: Function; } export const templateFromReactComponent = (Component: ComponentType) => { - const WrappedComponent: FC = (props) => ( - - {({ error }) => { - if (error) { - props.renderError(); - return null; - } - - return ( - - - - ); - }} - - ); - - WrappedComponent.propTypes = { + const WrappedComponent: ForwardRefRenderFunction, Props> = (props, ref) => { + const [updatedProps, setUpdatedProps] = useState(props); + + useImperativeHandle(ref, () => ({ + updateProps: (newProps: Props) => { + setUpdatedProps(newProps); + }, + })); + + return ( + + {({ error }) => { + if (error) { + props.renderError(); + return null; + } + + return ( + + + + ); + }} + + ); + }; + + const ForwardRefWrappedComponent = forwardRef(WrappedComponent); + + ForwardRefWrappedComponent.propTypes = { renderError: PropTypes.func, }; - return (domNode: HTMLElement, config: Props, handlers: ArgumentHandlers) => { + return ( + domNode: HTMLElement, + config: Props, + handlers: ArgumentHandlers, + onMount?: (ref: UpdatePropsRef | null) => void + ) => { try { - const el = React.createElement(WrappedComponent, config); + const el = ( + { + onMount?.(ref); + }} + /> + ); render(el, domNode, () => { handlers.done(); }); diff --git a/x-pack/plugins/canvas/types/arguments.ts b/x-pack/plugins/canvas/types/arguments.ts index 5dfaa3f8e6759..0ecc696165919 100644 --- a/x-pack/plugins/canvas/types/arguments.ts +++ b/x-pack/plugins/canvas/types/arguments.ts @@ -19,6 +19,10 @@ export interface ArgumentHandlers { onDestroy: GenericCallback; } +export interface UpdatePropsRef { + updateProps: (newProps: Props) => void; +} + export interface ArgumentSpec { /** The argument type */ name: string; @@ -33,13 +37,19 @@ export interface ArgumentSpec { simpleTemplate?: ( domNode: HTMLElement, config: ArgumentConfig, - handlers: ArgumentHandlers + handlers: ArgumentHandlers, + onMount: (ref: UpdatePropsRef | null) => void ) => void; /** * A function that renders a complex/large argument * This is nested in an accordian so it can be expanded/collapsed */ - template?: (domNode: HTMLElement, config: ArgumentConfig, handlers: ArgumentHandlers) => void; + template?: ( + domNode: HTMLElement, + config: ArgumentConfig, + handlers: ArgumentHandlers, + onMount: (ref: UpdatePropsRef | null) => void + ) => void; } export type ArgumentFactory = () => ArgumentSpec; diff --git a/x-pack/plugins/cases/public/common/mock/test_providers.tsx b/x-pack/plugins/cases/public/common/mock/test_providers.tsx index b31e9e4e1b19d..8a4b66d38cc0f 100644 --- a/x-pack/plugins/cases/public/common/mock/test_providers.tsx +++ b/x-pack/plugins/cases/public/common/mock/test_providers.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; +import { euiDarkVars } from '@kbn/ui-shared-deps-src/theme'; import { I18nProvider } from '@kbn/i18n/react'; import React from 'react'; import { BehaviorSubject } from 'rxjs'; diff --git a/x-pack/plugins/cases/public/components/case_action_bar/index.tsx b/x-pack/plugins/cases/public/components/case_action_bar/index.tsx index af17ea0dca895..8149fd6591645 100644 --- a/x-pack/plugins/cases/public/components/case_action_bar/index.tsx +++ b/x-pack/plugins/cases/public/components/case_action_bar/index.tsx @@ -86,7 +86,7 @@ const CaseActionBarComponent: React.FC = ({ {caseData.type !== CaseType.collection && ( - + {i18n.STATUS} = ({ )} - + {title} { expect(onStatusChanged).toHaveBeenCalledWith('in-progress'); }); + + it('it does not call onStatusChanged if selection is same as current status', async () => { + const wrapper = mount( + + ); + + wrapper.find(`[data-test-subj="case-view-status-dropdown"] button`).simulate('click'); + wrapper.find(`[data-test-subj="case-view-status-dropdown-open"] button`).simulate('click'); + + expect(onStatusChanged).not.toHaveBeenCalled(); + }); }); diff --git a/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.tsx b/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.tsx index 603efb253f051..ab86f589bfdd0 100644 --- a/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.tsx +++ b/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.tsx @@ -33,9 +33,11 @@ const StatusContextMenuComponent: React.FC = ({ const onContextMenuItemClick = useCallback( (status: CaseStatuses) => { closePopover(); - onStatusChanged(status); + if (currentStatus !== status) { + onStatusChanged(status); + } }, - [closePopover, onStatusChanged] + [closePopover, currentStatus, onStatusChanged] ); const panelItems = useMemo( diff --git a/x-pack/plugins/cases/public/components/case_view/index.tsx b/x-pack/plugins/cases/public/components/case_view/index.tsx index 87550ab66927b..75ac42ecd24ee 100644 --- a/x-pack/plugins/cases/public/components/case_view/index.tsx +++ b/x-pack/plugins/cases/public/components/case_view/index.tsx @@ -26,7 +26,7 @@ import { UserActionTree } from '../user_action_tree'; import { UserList } from '../user_list'; import { useUpdateCase } from '../../containers/use_update_case'; import { getTypedPayload } from '../../containers/utils'; -import { ContentWrapper, WhitePageWrapper, HeaderWrapper } from '../wrappers'; +import { ContentWrapper, WhitePageWrapper } from '../wrappers'; import { CaseActionBar } from '../case_action_bar'; import { useGetCaseUserActions } from '../../containers/use_get_case_user_actions'; import { EditConnector } from '../edit_connector'; @@ -389,32 +389,31 @@ export const CaseComponent = React.memo( return ( <> - - - } - title={caseData.title} - > - - - + } + title={caseData.title} + > + + + diff --git a/x-pack/plugins/cases/public/components/header_page/index.test.tsx b/x-pack/plugins/cases/public/components/header_page/index.test.tsx index d84a6d9272def..55e5d0907c869 100644 --- a/x-pack/plugins/cases/public/components/header_page/index.test.tsx +++ b/x-pack/plugins/cases/public/components/header_page/index.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; +import { euiDarkVars } from '@kbn/ui-shared-deps-src/theme'; import { shallow } from 'enzyme'; import React from 'react'; diff --git a/x-pack/plugins/cases/public/components/truncated_text/index.tsx b/x-pack/plugins/cases/public/components/truncated_text/index.tsx index 8a480ed9dbdd1..3cf7f8322d797 100644 --- a/x-pack/plugins/cases/public/components/truncated_text/index.tsx +++ b/x-pack/plugins/cases/public/components/truncated_text/index.tsx @@ -16,6 +16,7 @@ const Text = styled.span` -webkit-line-clamp: ${LINE_CLAMP}; -webkit-box-orient: vertical; overflow: hidden; + word-break: normal; `; interface Props { diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar.test.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar.test.tsx index 98af25a9af466..43ebd9bee3ca9 100644 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar.test.tsx +++ b/x-pack/plugins/cases/public/components/utility_bar/utility_bar.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; +import { euiDarkVars } from '@kbn/ui-shared-deps-src/theme'; import { mount, shallow } from 'enzyme'; import React from 'react'; import { TestProviders } from '../../common/mock'; diff --git a/x-pack/plugins/cases/public/components/wrappers/index.tsx b/x-pack/plugins/cases/public/components/wrappers/index.tsx index 4c8a3a681f024..7a3d611413be6 100644 --- a/x-pack/plugins/cases/public/components/wrappers/index.tsx +++ b/x-pack/plugins/cases/public/components/wrappers/index.tsx @@ -20,24 +20,10 @@ export const SectionWrapper = styled.div` width: 100%; `; -export const HeaderWrapper = styled.div` - ${({ theme }) => - ` - padding: ${theme.eui.paddingSizes.l} ${theme.eui.paddingSizes.l} 0 ${theme.eui.paddingSizes.l}; - @media only screen and (max-width: ${theme.eui.euiBreakpoints.s}) { - padding: ${theme.eui.paddingSizes.s} ${theme.eui.paddingSizes.s} 0 - ${theme.eui.paddingSizes.s}; - } - `}; -`; const gutterTimeline = '70px'; // seems to be a timeline reference from the original file export const ContentWrapper = styled.div` ${({ theme }) => ` - padding: ${theme.eui.paddingSizes.l} ${theme.eui.paddingSizes.l} ${gutterTimeline} ${theme.eui.paddingSizes.l}; - @media only screen and (max-width: ${theme.eui.euiBreakpoints.s}) { - padding: ${theme.eui.paddingSizes.s} ${theme.eui.paddingSizes.s} ${gutterTimeline} - ${theme.eui.paddingSizes.s}; - } + padding: ${theme.eui.paddingSizes.l} 0 ${gutterTimeline} 0; `}; `; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/hooks/use_color_range.ts b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/hooks/use_color_range.ts index b1d26a5437b44..92a88f4d60670 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/hooks/use_color_range.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/hooks/use_color_range.ts @@ -7,8 +7,10 @@ import d3 from 'd3'; import { useMemo } from 'react'; -import euiThemeLight from '@elastic/eui/dist/eui_theme_light.json'; -import euiThemeDark from '@elastic/eui/dist/eui_theme_dark.json'; +import { + euiLightVars as euiThemeLight, + euiDarkVars as euiThemeDark, +} from '@kbn/ui-shared-deps-src/theme'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/flash_messages_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/flash_messages_logic.mock.ts index 36a10fd234bfe..d55c69d478b16 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/flash_messages_logic.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/flash_messages_logic.mock.ts @@ -27,6 +27,7 @@ export const mockFlashMessageHelpers = { clearFlashMessages: jest.fn(), flashSuccessToast: jest.fn(), flashErrorToast: jest.fn(), + toastAPIErrors: jest.fn(), }; jest.mock('../../shared/flash_messages', () => ({ diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.test.ts index 7b877419a2977..62ba44128663a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.test.ts @@ -5,12 +5,7 @@ * 2.0. */ -import { - LogicMounter, - mockKibanaValues, - mockHttpValues, - mockFlashMessageHelpers, -} from '../../../__mocks__/kea_logic'; +import { LogicMounter, mockKibanaValues, mockHttpValues } from '../../../__mocks__/kea_logic'; jest.mock('../engine', () => ({ EngineLogic: { values: { engineName: 'test-engine' } }, @@ -18,6 +13,8 @@ jest.mock('../engine', () => ({ import { nextTick } from '@kbn/test/jest'; +import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; + import { DEFAULT_START_DATE, DEFAULT_END_DATE } from './constants'; import { AnalyticsLogic } from './'; @@ -26,7 +23,6 @@ describe('AnalyticsLogic', () => { const { mount } = new LogicMounter(AnalyticsLogic); const { history } = mockKibanaValues; const { http } = mockHttpValues; - const { flashAPIErrors } = mockFlashMessageHelpers; const DEFAULT_VALUES = { dataLoading: true, @@ -197,14 +193,9 @@ describe('AnalyticsLogic', () => { ); }); - it('handles errors', async () => { - http.get.mockReturnValueOnce(Promise.reject('error')); + itShowsServerErrorAsFlashMessage(http.get, () => { mount(); - AnalyticsLogic.actions.loadAnalyticsData(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); }); @@ -259,14 +250,9 @@ describe('AnalyticsLogic', () => { ); }); - it('handles errors', async () => { - http.get.mockReturnValueOnce(Promise.reject('error')); + itShowsServerErrorAsFlashMessage(http.get, () => { mount(); - AnalyticsLogic.actions.loadQueryData('some-query'); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.test.ts index 2f3aedc8fa11d..51d51b5aee88c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.test.ts @@ -17,12 +17,14 @@ import { nextTick } from '@kbn/test/jest'; import { DEFAULT_META } from '../../../shared/constants'; +import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; + import { ApiLogsLogic } from './'; describe('ApiLogsLogic', () => { const { mount, unmount } = new LogicMounter(ApiLogsLogic); const { http } = mockHttpValues; - const { flashAPIErrors, flashErrorToast } = mockFlashMessageHelpers; + const { flashErrorToast } = mockFlashMessageHelpers; const DEFAULT_VALUES = { dataLoading: true, @@ -176,14 +178,9 @@ describe('ApiLogsLogic', () => { expect(ApiLogsLogic.actions.updateView).toHaveBeenCalledWith(MOCK_API_RESPONSE); }); - it('handles API errors', async () => { - http.get.mockReturnValueOnce(Promise.reject('error')); + itShowsServerErrorAsFlashMessage(http.get, () => { mount(); - ApiLogsLogic.actions.fetchApiLogs(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_domains_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_domains_logic.test.ts index 6cf2f21fc6d2e..fda96ca5f8381 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_domains_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_domains_logic.test.ts @@ -18,6 +18,8 @@ import { Meta } from '../../../../../common/types'; import { DEFAULT_META } from '../../../shared/constants'; +import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers/error_handling'; + import { CrawlerDomainsLogic, CrawlerDomainsValues } from './crawler_domains_logic'; import { CrawlerDataFromServer, CrawlerDomain, CrawlerDomainFromServer } from './types'; import { crawlerDataServerToClient } from './utils'; @@ -193,13 +195,8 @@ describe('CrawlerDomainsLogic', () => { ); }); - it('calls flashApiErrors when there is an error', async () => { - http.delete.mockReturnValue(Promise.reject('error')); - + itShowsServerErrorAsFlashMessage(http.delete, () => { CrawlerDomainsLogic.actions.deleteDomain({ id: '1234' } as CrawlerDomain); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.test.ts index 7ba1adb51bbfb..b2321073f3d95 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.test.ts @@ -14,6 +14,8 @@ import '../../__mocks__/engine_logic.mock'; import { nextTick } from '@kbn/test/jest'; +import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; + import { CrawlerDomainsLogic } from './crawler_domains_logic'; import { CrawlerLogic, CrawlerValues } from './crawler_logic'; import { @@ -280,15 +282,8 @@ describe('CrawlerLogic', () => { }); }); - describe('on failure', () => { - it('flashes an error message', async () => { - http.post.mockReturnValueOnce(Promise.reject('error')); - - CrawlerLogic.actions.startCrawl(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); - }); + itShowsServerErrorAsFlashMessage(http.post, () => { + CrawlerLogic.actions.startCrawl(); }); }); @@ -308,16 +303,8 @@ describe('CrawlerLogic', () => { }); }); - describe('on failure', () => { - it('flashes an error message', async () => { - jest.spyOn(CrawlerLogic.actions, 'fetchCrawlerData'); - http.post.mockReturnValueOnce(Promise.reject('error')); - - CrawlerLogic.actions.stopCrawl(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); - }); + itShowsServerErrorAsFlashMessage(http.post, () => { + CrawlerLogic.actions.stopCrawl(); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.test.ts index 03e20ea988f98..547218ad6a2c1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.test.ts @@ -23,6 +23,8 @@ jest.mock('./crawler_logic', () => ({ import { nextTick } from '@kbn/test/jest'; +import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; + import { CrawlerLogic } from './crawler_logic'; import { CrawlerSingleDomainLogic, CrawlerSingleDomainValues } from './crawler_single_domain_logic'; import { CrawlerDomain, CrawlerPolicies, CrawlerRules } from './types'; @@ -35,7 +37,7 @@ const DEFAULT_VALUES: CrawlerSingleDomainValues = { describe('CrawlerSingleDomainLogic', () => { const { mount } = new LogicMounter(CrawlerSingleDomainLogic); const { http } = mockHttpValues; - const { flashAPIErrors, flashSuccessToast } = mockFlashMessageHelpers; + const { flashSuccessToast } = mockFlashMessageHelpers; beforeEach(() => { jest.clearAllMocks(); @@ -176,13 +178,8 @@ describe('CrawlerSingleDomainLogic', () => { expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/crawler'); }); - it('calls flashApiErrors when there is an error', async () => { - http.delete.mockReturnValue(Promise.reject('error')); - + itShowsServerErrorAsFlashMessage(http.delete, () => { CrawlerSingleDomainLogic.actions.deleteDomain({ id: '1234' } as CrawlerDomain); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); }); @@ -218,13 +215,8 @@ describe('CrawlerSingleDomainLogic', () => { }); }); - it('displays any errors to the user', async () => { - http.get.mockReturnValueOnce(Promise.reject('error')); - + itShowsServerErrorAsFlashMessage(http.get, () => { CrawlerSingleDomainLogic.actions.fetchDomainData('507f1f77bcf86cd799439011'); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); }); @@ -272,16 +264,11 @@ describe('CrawlerSingleDomainLogic', () => { }); }); - it('displays any errors to the user', async () => { - http.put.mockReturnValueOnce(Promise.reject('error')); - + itShowsServerErrorAsFlashMessage(http.put, () => { CrawlerSingleDomainLogic.actions.submitDeduplicationUpdate( { id: '507f1f77bcf86cd799439011' } as CrawlerDomain, { fields: ['title'], enabled: true } ); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.test.ts index 3afa531239dc1..decb98d227975 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.test.ts @@ -20,6 +20,7 @@ jest.mock('../../app_logic', () => ({ selectors: { myRole: jest.fn(() => ({})) }, }, })); +import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; import { AppLogic } from '../../app_logic'; import { EngineTypes } from '../engine/types'; @@ -31,7 +32,7 @@ import { CredentialsLogic } from './credentials_logic'; describe('CredentialsLogic', () => { const { mount } = new LogicMounter(CredentialsLogic); const { http } = mockHttpValues; - const { clearFlashMessages, flashSuccessToast, flashAPIErrors } = mockFlashMessageHelpers; + const { clearFlashMessages, flashSuccessToast } = mockFlashMessageHelpers; const DEFAULT_VALUES = { activeApiToken: { @@ -1059,14 +1060,9 @@ describe('CredentialsLogic', () => { expect(CredentialsLogic.actions.setCredentialsData).toHaveBeenCalledWith(meta, results); }); - it('handles errors', async () => { + itShowsServerErrorAsFlashMessage(http.get, () => { mount(); - http.get.mockReturnValue(Promise.reject('An error occured')); - CredentialsLogic.actions.fetchCredentials(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('An error occured'); }); }); @@ -1086,14 +1082,9 @@ describe('CredentialsLogic', () => { ); }); - it('handles errors', async () => { + itShowsServerErrorAsFlashMessage(http.get, () => { mount(); - http.get.mockReturnValue(Promise.reject('An error occured')); - CredentialsLogic.actions.fetchDetails(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('An error occured'); }); }); @@ -1113,14 +1104,9 @@ describe('CredentialsLogic', () => { expect(flashSuccessToast).toHaveBeenCalled(); }); - it('handles errors', async () => { + itShowsServerErrorAsFlashMessage(http.delete, () => { mount(); - http.delete.mockReturnValue(Promise.reject('An error occured')); - CredentialsLogic.actions.deleteApiKey(tokenName); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('An error occured'); }); }); @@ -1172,14 +1158,9 @@ describe('CredentialsLogic', () => { expect(flashSuccessToast).toHaveBeenCalled(); }); - it('handles errors', async () => { + itShowsServerErrorAsFlashMessage(http.post, () => { mount(); - http.post.mockReturnValue(Promise.reject('An error occured')); - CredentialsLogic.actions.onApiTokenChange(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('An error occured'); }); describe('token type data', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_callout.tsx index 490e6323290f0..ed46a878f0cea 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_callout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_callout.tsx @@ -28,6 +28,7 @@ interface SuggestionsCalloutProps { description: string; buttonTo: string; lastUpdatedTimestamp: string; // ISO string like '2021-10-04T18:53:02.784Z' + style?: React.CSSProperties; } export const SuggestionsCallout: React.FC = ({ @@ -35,6 +36,7 @@ export const SuggestionsCallout: React.FC = ({ description, buttonTo, lastUpdatedTimestamp, + style, }) => { const { pathname } = useLocation(); @@ -49,7 +51,7 @@ export const SuggestionsCallout: React.FC = ({ return ( <> - +

{description}

@@ -80,7 +82,6 @@ export const SuggestionsCallout: React.FC = ({
- ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.test.tsx index 3e12aa7b629f0..a3ca646bd9f54 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.test.tsx @@ -5,17 +5,15 @@ * 2.0. */ -import { - LogicMounter, - mockFlashMessageHelpers, - mockHttpValues, -} from '../../../../__mocks__/kea_logic'; +import { LogicMounter, mockHttpValues } from '../../../../__mocks__/kea_logic'; import '../../../__mocks__/engine_logic.mock'; import { nextTick } from '@kbn/test/jest'; import { DEFAULT_META } from '../../../../shared/constants'; +import { itShowsServerErrorAsFlashMessage } from '../../../../test_helpers'; + import { SuggestionsAPIResponse, SuggestionsLogic } from './suggestions_logic'; const DEFAULT_VALUES = { @@ -52,7 +50,6 @@ const MOCK_RESPONSE: SuggestionsAPIResponse = { describe('SuggestionsLogic', () => { const { mount } = new LogicMounter(SuggestionsLogic); - const { flashAPIErrors } = mockFlashMessageHelpers; const { http } = mockHttpValues; beforeEach(() => { @@ -140,14 +137,9 @@ describe('SuggestionsLogic', () => { expect(SuggestionsLogic.actions.onSuggestionsLoaded).toHaveBeenCalledWith(MOCK_RESPONSE); }); - it('handles errors', async () => { - http.post.mockReturnValueOnce(Promise.reject('error')); + itShowsServerErrorAsFlashMessage(http.post, () => { mount(); - SuggestionsLogic.actions.loadSuggestions(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts index 2b51cbb884ff9..644139250c07c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts @@ -15,6 +15,8 @@ import '../../../__mocks__/engine_logic.mock'; import { nextTick } from '@kbn/test/jest'; +import { itShowsServerErrorAsFlashMessage } from '../../../../test_helpers'; + import { CurationLogic } from './'; describe('CurationLogic', () => { @@ -309,14 +311,8 @@ describe('CurationLogic', () => { expect(CurationLogic.actions.loadCuration).toHaveBeenCalled(); }); - it('flashes any error messages', async () => { - http.put.mockReturnValueOnce(Promise.reject('error')); - mount({ activeQuery: 'some query' }); - + itShowsServerErrorAsFlashMessage(http.put, () => { CurationLogic.actions.convertToManual(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); }); @@ -336,14 +332,9 @@ describe('CurationLogic', () => { expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations'); }); - it('flashes any errors', async () => { - http.delete.mockReturnValueOnce(Promise.reject('error')); + itShowsServerErrorAsFlashMessage(http.delete, () => { mount({}, { curationId: 'cur-404' }); - CurationLogic.actions.deleteCuration(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/suggested_documents_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/suggested_documents_callout.tsx index ec296089a1086..cd9b57651c00a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/suggested_documents_callout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/suggested_documents_callout.tsx @@ -35,6 +35,7 @@ export const SuggestedDocumentsCallout: React.FC = () => { return ( { @@ -130,14 +132,9 @@ describe('CurationsLogic', () => { ); }); - it('handles errors', async () => { - http.get.mockReturnValueOnce(Promise.reject('error')); + itShowsServerErrorAsFlashMessage(http.get, () => { mount(); - CurationsLogic.actions.loadCurations(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts index 171c774d8add2..01d8107067e18 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts @@ -16,6 +16,7 @@ import '../../../../__mocks__/engine_logic.mock'; import { nextTick } from '@kbn/test/jest'; +import { itShowsServerErrorAsFlashMessage } from '../../../../../test_helpers'; import { HydratedCurationSuggestion } from '../../types'; import { CurationSuggestionLogic } from './curation_suggestion_logic'; @@ -180,20 +181,6 @@ describe('CurationSuggestionLogic', () => { }); }; - const itHandlesErrors = (httpMethod: any, callback: () => void) => { - it('handles errors', async () => { - httpMethod.mockReturnValueOnce(Promise.reject('error')); - mountLogic({ - suggestion, - }); - - callback(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); - }); - }; - beforeEach(() => { jest.clearAllMocks(); }); @@ -271,7 +258,7 @@ describe('CurationSuggestionLogic', () => { expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations'); }); - itHandlesErrors(http.get, () => { + itShowsServerErrorAsFlashMessage(http.get, () => { CurationSuggestionLogic.actions.loadSuggestion(); }); }); @@ -350,7 +337,8 @@ describe('CurationSuggestionLogic', () => { }); }); - itHandlesErrors(http.put, () => { + itShowsServerErrorAsFlashMessage(http.put, () => { + jest.spyOn(global, 'confirm').mockReturnValueOnce(true); CurationSuggestionLogic.actions.acceptSuggestion(); }); @@ -433,7 +421,8 @@ describe('CurationSuggestionLogic', () => { }); }); - itHandlesErrors(http.put, () => { + itShowsServerErrorAsFlashMessage(http.put, () => { + jest.spyOn(global, 'confirm').mockReturnValueOnce(true); CurationSuggestionLogic.actions.acceptAndAutomateSuggestion(); }); @@ -478,7 +467,7 @@ describe('CurationSuggestionLogic', () => { expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations'); }); - itHandlesErrors(http.put, () => { + itShowsServerErrorAsFlashMessage(http.put, () => { CurationSuggestionLogic.actions.rejectSuggestion(); }); @@ -523,7 +512,7 @@ describe('CurationSuggestionLogic', () => { expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations'); }); - itHandlesErrors(http.put, () => { + itShowsServerErrorAsFlashMessage(http.put, () => { CurationSuggestionLogic.actions.rejectAndDisableSuggestion(); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_logic.test.ts index 8c2545fad651a..af9f876820790 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_logic.test.ts @@ -11,13 +11,13 @@ import { mockHttpValues, } from '../../../../../../../__mocks__/kea_logic'; import '../../../../../../__mocks__/engine_logic.mock'; +import { DEFAULT_META } from '../../../../../../../shared/constants'; +import { itShowsServerErrorAsFlashMessage } from '../../../../../../../test_helpers'; // I don't know why eslint is saying this line is out of order // eslint-disable-next-line import/order import { nextTick } from '@kbn/test/jest'; -import { DEFAULT_META } from '../../../../../../../shared/constants'; - import { IgnoredQueriesLogic } from './ignored_queries_logic'; const DEFAULT_VALUES = { @@ -142,13 +142,9 @@ describe('IgnoredQueriesLogic', () => { ); }); - it('handles errors', async () => { - http.post.mockReturnValueOnce(Promise.reject('error')); - + itShowsServerErrorAsFlashMessage(http.post, () => { + mount(); IgnoredQueriesLogic.actions.loadIgnoredQueries(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); }); @@ -185,13 +181,9 @@ describe('IgnoredQueriesLogic', () => { expect(flashSuccessToast).toHaveBeenCalledWith(expect.any(String)); }); - it('handles errors', async () => { - http.put.mockReturnValueOnce(Promise.reject('error')); - + itShowsServerErrorAsFlashMessage(http.put, () => { + mount(); IgnoredQueriesLogic.actions.allowIgnoredQuery('test query'); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); it('handles inline errors', async () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings_logic.test.ts index 0d09f2d28f396..e9643f92f2f71 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings_logic.test.ts @@ -5,11 +5,7 @@ * 2.0. */ -import { - LogicMounter, - mockHttpValues, - mockFlashMessageHelpers, -} from '../../../../../__mocks__/kea_logic'; +import { LogicMounter, mockHttpValues } from '../../../../../__mocks__/kea_logic'; import '../../../../__mocks__/engine_logic.mock'; jest.mock('../../curations_logic', () => ({ @@ -24,6 +20,7 @@ jest.mock('../../curations_logic', () => ({ import { nextTick } from '@kbn/test/jest'; import { CurationsLogic } from '../..'; +import { itShowsServerErrorAsFlashMessage } from '../../../../../test_helpers'; import { EngineLogic } from '../../../engine'; import { CurationsSettingsLogic } from './curations_settings_logic'; @@ -39,7 +36,6 @@ const DEFAULT_VALUES = { describe('CurationsSettingsLogic', () => { const { mount } = new LogicMounter(CurationsSettingsLogic); const { http } = mockHttpValues; - const { flashAPIErrors } = mockFlashMessageHelpers; beforeEach(() => { jest.clearAllMocks(); @@ -105,14 +101,8 @@ describe('CurationsSettingsLogic', () => { }); }); - it('presents any API errors to the user', async () => { - http.get.mockReturnValueOnce(Promise.reject('error')); - mount(); - + itShowsServerErrorAsFlashMessage(http.get, () => { CurationsSettingsLogic.actions.loadCurationsSettings(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); }); @@ -223,14 +213,8 @@ describe('CurationsSettingsLogic', () => { expect(CurationsLogic.actions.loadCurations).toHaveBeenCalled(); }); - it('presents any API errors to the user', async () => { - http.put.mockReturnValueOnce(Promise.reject('error')); - mount(); - + itShowsServerErrorAsFlashMessage(http.put, () => { CurationsSettingsLogic.actions.updateCurationsSetting({}); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.test.ts index 5705e5ae2ee98..848a85f23c2cb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.test.ts @@ -17,6 +17,8 @@ import { nextTick } from '@kbn/test/jest'; import { InternalSchemaType } from '../../../shared/schema/types'; +import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; + import { DocumentDetailLogic } from './document_detail_logic'; describe('DocumentDetailLogic', () => { @@ -117,14 +119,9 @@ describe('DocumentDetailLogic', () => { await nextTick(); }); - it('handles errors', async () => { + itShowsServerErrorAsFlashMessage(http.delete, () => { mount(); - http.delete.mockReturnValue(Promise.reject('An error occured')); - DocumentDetailLogic.actions.deleteDocument('1'); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('An error occured'); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/suggested_curations_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/suggested_curations_callout.tsx index e1f984581438f..a79c0394fc903 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/suggested_curations_callout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/suggested_curations_callout.tsx @@ -34,6 +34,7 @@ export const SuggestedCurationsCallout: React.FC = () => { return ( ({ EngineLogic: { values: { engineName: 'some-engine' } }, @@ -17,12 +13,13 @@ jest.mock('../engine', () => ({ import { nextTick } from '@kbn/test/jest'; +import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; + import { EngineOverviewLogic } from './'; describe('EngineOverviewLogic', () => { const { mount } = new LogicMounter(EngineOverviewLogic); const { http } = mockHttpValues; - const { flashAPIErrors } = mockFlashMessageHelpers; const mockEngineMetrics = { documentCount: 10, @@ -83,14 +80,9 @@ describe('EngineOverviewLogic', () => { ); }); - it('handles errors', async () => { + itShowsServerErrorAsFlashMessage(http.get, () => { mount(); - http.get.mockReturnValue(Promise.reject('An error occurred')); - EngineOverviewLogic.actions.loadOverviewMetrics(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('An error occurred'); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.test.ts index f3dc8a378a7d3..d0a227c8c6fbe 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.test.ts @@ -15,6 +15,7 @@ import { nextTick } from '@kbn/test/jest'; import { DEFAULT_META } from '../../../shared/constants'; +import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; import { EngineDetails, EngineTypes } from '../engine/types'; import { EnginesLogic } from './'; @@ -171,14 +172,9 @@ describe('EnginesLogic', () => { expect(EnginesLogic.actions.onEnginesLoad).toHaveBeenCalledWith(MOCK_ENGINES_API_RESPONSE); }); - it('handles errors', async () => { - http.get.mockReturnValueOnce(Promise.reject('error')); + itShowsServerErrorAsFlashMessage(http.get, () => { mount(); - EnginesLogic.actions.loadEngines(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); }); @@ -203,14 +199,9 @@ describe('EnginesLogic', () => { ); }); - it('handles errors', async () => { - http.get.mockReturnValueOnce(Promise.reject('error')); + itShowsServerErrorAsFlashMessage(http.get, () => { mount(); - EnginesLogic.actions.loadMetaEngines(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts index 193c5dbe8ac24..6ac1c27a27959 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts @@ -14,6 +14,8 @@ import { mockEngineValues, mockEngineActions } from '../../__mocks__'; import { nextTick } from '@kbn/test/jest'; +import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; + import { Boost, BoostOperation, BoostType, FunctionalBoostFunction } from './types'; import { RelevanceTuningLogic } from './'; @@ -319,14 +321,9 @@ describe('RelevanceTuningLogic', () => { }); }); - it('handles errors', async () => { + itShowsServerErrorAsFlashMessage(http.get, () => { mount(); - http.get.mockReturnValueOnce(Promise.reject('error')); - RelevanceTuningLogic.actions.initializeRelevanceTuning(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts index 94d5e84c67f6d..92cb2346e0a26 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts @@ -5,11 +5,7 @@ * 2.0. */ -import { - LogicMounter, - mockFlashMessageHelpers, - mockHttpValues, -} from '../../../__mocks__/kea_logic'; +import { LogicMounter, mockHttpValues } from '../../../__mocks__/kea_logic'; import { mockEngineValues } from '../../__mocks__'; import { omit } from 'lodash'; @@ -18,6 +14,8 @@ import { nextTick } from '@kbn/test/jest'; import { Schema, SchemaConflicts, SchemaType } from '../../../shared/schema/types'; +import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; + import { ServerFieldResultSettingObject } from './types'; import { ResultSettingsLogic } from '.'; @@ -508,7 +506,6 @@ describe('ResultSettingsLogic', () => { describe('listeners', () => { const { http } = mockHttpValues; - const { flashAPIErrors } = mockFlashMessageHelpers; let confirmSpy: jest.SpyInstance; beforeAll(() => { @@ -844,14 +841,9 @@ describe('ResultSettingsLogic', () => { ); }); - it('handles errors', async () => { + itShowsServerErrorAsFlashMessage(http.get, () => { mount(); - http.get.mockReturnValueOnce(Promise.reject('error')); - ResultSettingsLogic.actions.initializeResultSettingsData(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); }); @@ -923,14 +915,9 @@ describe('ResultSettingsLogic', () => { ); }); - it('handles errors', async () => { + itShowsServerErrorAsFlashMessage(http.put, () => { mount(); - http.put.mockReturnValueOnce(Promise.reject('error')); - ResultSettingsLogic.actions.saveResultSettings(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); it('does nothing if the user does not confirm', async () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts index 14d97c7dd3f4d..b35865f279817 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts @@ -23,13 +23,15 @@ import { } from '../../../shared/role_mapping/__mocks__/roles'; import { ANY_AUTH_PROVIDER } from '../../../shared/role_mapping/constants'; +import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; + import { RoleMappingsLogic } from './role_mappings_logic'; const emptyUser = { username: '', email: '' }; describe('RoleMappingsLogic', () => { const { http } = mockHttpValues; - const { clearFlashMessages, flashAPIErrors, flashSuccessToast } = mockFlashMessageHelpers; + const { clearFlashMessages, flashSuccessToast } = mockFlashMessageHelpers; const { mount } = new LogicMounter(RoleMappingsLogic); const DEFAULT_VALUES = { attributes: [], @@ -391,12 +393,8 @@ describe('RoleMappingsLogic', () => { expect(setRoleMappingsSpy).toHaveBeenCalledWith(mappingsServerProps); }); - it('handles error', async () => { - http.post.mockReturnValue(Promise.reject('this is an error')); + itShowsServerErrorAsFlashMessage(http.post, () => { RoleMappingsLogic.actions.enableRoleBasedAccess(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); @@ -411,12 +409,8 @@ describe('RoleMappingsLogic', () => { expect(setRoleMappingsDataSpy).toHaveBeenCalledWith(mappingsServerProps); }); - it('handles error', async () => { - http.get.mockReturnValue(Promise.reject('this is an error')); + itShowsServerErrorAsFlashMessage(http.get, () => { RoleMappingsLogic.actions.initializeRoleMappings(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); it('resets roleMapping state', () => { @@ -691,13 +685,9 @@ describe('RoleMappingsLogic', () => { expect(flashSuccessToast).toHaveBeenCalled(); }); - it('handles error', async () => { + itShowsServerErrorAsFlashMessage(http.delete, () => { mount(mappingsServerProps); - http.delete.mockReturnValue(Promise.reject('this is an error')); RoleMappingsLogic.actions.handleDeleteMapping(roleMappingId); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.test.ts index c5611420442c8..5b40b362bc665 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.test.ts @@ -5,23 +5,20 @@ * 2.0. */ -import { - LogicMounter, - mockFlashMessageHelpers, - mockHttpValues, -} from '../../../__mocks__/kea_logic'; +import { LogicMounter, mockHttpValues } from '../../../__mocks__/kea_logic'; import '../../__mocks__/engine_logic.mock'; import { nextTick } from '@kbn/test/jest'; import { SchemaType } from '../../../shared/schema/types'; +import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; + import { SchemaBaseLogic } from './schema_base_logic'; describe('SchemaBaseLogic', () => { const { mount } = new LogicMounter(SchemaBaseLogic); const { http } = mockHttpValues; - const { flashAPIErrors } = mockFlashMessageHelpers; const MOCK_SCHEMA = { some_text_field: SchemaType.Text, @@ -99,14 +96,9 @@ describe('SchemaBaseLogic', () => { expect(SchemaBaseLogic.actions.onSchemaLoad).toHaveBeenCalledWith(MOCK_RESPONSE); }); - it('handles errors', async () => { - http.get.mockReturnValueOnce(Promise.reject('error')); + itShowsServerErrorAsFlashMessage(http.get, () => { mount(); - SchemaBaseLogic.actions.loadSchema(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.test.ts index 33144d4188ec1..49444fbd0c5c5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.test.ts @@ -14,6 +14,8 @@ import { mockEngineValues } from '../../__mocks__'; import { nextTick } from '@kbn/test/jest'; +import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; + import { ActiveField } from './types'; import { SearchUILogic } from './'; @@ -21,7 +23,7 @@ import { SearchUILogic } from './'; describe('SearchUILogic', () => { const { mount } = new LogicMounter(SearchUILogic); const { http } = mockHttpValues; - const { flashAPIErrors, setErrorMessage } = mockFlashMessageHelpers; + const { setErrorMessage } = mockFlashMessageHelpers; const DEFAULT_VALUES = { dataLoading: true, @@ -182,14 +184,9 @@ describe('SearchUILogic', () => { ); }); - it('handles errors', async () => { - http.get.mockReturnValueOnce(Promise.reject('error')); + itShowsServerErrorAsFlashMessage(http.get, () => { mount(); - SearchUILogic.actions.loadFieldData(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.test.ts index 0ff84ad4cb9cb..7376bc11df79e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.test.ts @@ -14,6 +14,8 @@ import '../../__mocks__/engine_logic.mock'; import { nextTick } from '@kbn/test/jest'; +import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; + import { SYNONYMS_PAGE_META } from './constants'; import { SynonymsLogic } from './'; @@ -146,14 +148,9 @@ describe('SynonymsLogic', () => { expect(SynonymsLogic.actions.onSynonymsLoad).toHaveBeenCalledWith(MOCK_SYNONYMS_RESPONSE); }); - it('handles errors', async () => { - http.get.mockReturnValueOnce(Promise.reject('error')); + itShowsServerErrorAsFlashMessage(http.get, () => { mount(); - SynonymsLogic.actions.loadSynonyms(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/recursively_fetch_engines/index.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/recursively_fetch_engines/index.test.ts index 555a880d544f4..c03ca8267993a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/recursively_fetch_engines/index.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/recursively_fetch_engines/index.test.ts @@ -5,15 +5,16 @@ * 2.0. */ -import { mockFlashMessageHelpers, mockHttpValues } from '../../../__mocks__/kea_logic'; +import { mockHttpValues } from '../../../__mocks__/kea_logic'; import { nextTick } from '@kbn/test/jest'; +import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; + import { recursivelyFetchEngines } from './'; describe('recursivelyFetchEngines', () => { const { http } = mockHttpValues; - const { flashAPIErrors } = mockFlashMessageHelpers; const MOCK_PAGE_1 = { meta: { @@ -100,12 +101,7 @@ describe('recursivelyFetchEngines', () => { }); }); - it('handles errors', async () => { - http.get.mockReturnValueOnce(Promise.reject('error')); - + itShowsServerErrorAsFlashMessage(http.get, () => { recursivelyFetchEngines({ endpoint: '/error', onComplete: MOCK_CALLBACK }); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.test.ts index 47cbef0bfd953..1e3705a3800ed 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.test.ts @@ -5,11 +5,14 @@ * 2.0. */ +jest.mock('./set_message_helpers', () => ({ + flashErrorToast: jest.fn(), +})); import '../../__mocks__/kea_logic/kibana_logic.mock'; import { FlashMessagesLogic } from './flash_messages_logic'; - -import { flashAPIErrors, getErrorsFromHttpResponse } from './handle_api_errors'; +import { flashAPIErrors, getErrorsFromHttpResponse, toastAPIErrors } from './handle_api_errors'; +import { flashErrorToast } from './set_message_helpers'; describe('flashAPIErrors', () => { const mockHttpError = { @@ -75,6 +78,56 @@ describe('flashAPIErrors', () => { }); }); +describe('toastAPIErrors', () => { + const mockHttpError = { + body: { + statusCode: 404, + error: 'Not Found', + message: 'Could not find X,Could not find Y,Something else bad happened', + attributes: { + errors: ['Could not find X', 'Could not find Y', 'Something else bad happened'], + }, + }, + } as any; + + beforeEach(() => { + jest.clearAllMocks(); + FlashMessagesLogic.mount(); + jest.spyOn(FlashMessagesLogic.actions, 'setFlashMessages'); + jest.spyOn(FlashMessagesLogic.actions, 'setQueuedMessages'); + }); + + it('converts API errors into flash messages', () => { + toastAPIErrors(mockHttpError); + + expect(flashErrorToast).toHaveBeenNthCalledWith(1, 'Could not find X'); + expect(flashErrorToast).toHaveBeenNthCalledWith(2, 'Could not find Y'); + expect(flashErrorToast).toHaveBeenNthCalledWith(3, 'Something else bad happened'); + }); + + it('falls back to the basic message for http responses without an errors array', () => { + toastAPIErrors({ + body: { + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }, + } as any); + + expect(flashErrorToast).toHaveBeenCalledWith('Not Found'); + }); + + it('displays a generic error message and re-throws non-API errors', () => { + const error = Error('whatever'); + + expect(() => { + toastAPIErrors(error as any); + }).toThrowError(error); + + expect(flashErrorToast).toHaveBeenCalledWith(expect.any(String)); + }); +}); + describe('getErrorsFromHttpResponse', () => { it('should return errors from the response if present', () => { expect( diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts index abaa67e06f606..40863087004d6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts @@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n'; import { HttpResponse } from 'src/core/public'; import { FlashMessagesLogic } from './flash_messages_logic'; +import { flashErrorToast } from './set_message_helpers'; import { IFlashMessage } from './types'; /** @@ -69,3 +70,16 @@ export const flashAPIErrors = ( throw response; } }; + +export const toastAPIErrors = (response: HttpResponse) => { + const messages = getErrorsFromHttpResponse(response); + + for (const message of messages) { + flashErrorToast(message); + } + // If this was a programming error or a failed request (such as a CORS) error, + // we rethrow the error so it shows up in the developer console + if (!response?.body?.message) { + throw response; + } +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts index 097d38e0691c5..3d3775aee9607 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts @@ -8,7 +8,7 @@ export { FlashMessages, Toasts } from './flash_messages'; export { FlashMessagesLogic, mountFlashMessagesLogic } from './flash_messages_logic'; export type { IFlashMessage } from './types'; -export { flashAPIErrors } from './handle_api_errors'; +export { flashAPIErrors, toastAPIErrors } from './handle_api_errors'; export { setSuccessMessage, setErrorMessage, diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/generic_endpoint_inline_editable_table_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/generic_endpoint_inline_editable_table_logic.test.ts index 5cd4b5af8f517..481013d91bf6f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/generic_endpoint_inline_editable_table_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/generic_endpoint_inline_editable_table_logic.test.ts @@ -5,6 +5,16 @@ * 2.0. */ +const MOCK_SET_ROW_ERRORS = jest.fn(); + +jest.mock('../inline_editable_table/inline_editable_table_logic', () => ({ + InlineEditableTableLogic: () => ({ + actions: { + setRowErrors: MOCK_SET_ROW_ERRORS, + }, + }), +})); + import { LogicMounter, mockFlashMessageHelpers, @@ -18,7 +28,7 @@ import { GenericEndpointInlineEditableTableLogic } from './generic_endpoint_inli describe('GenericEndpointInlineEditableTableLogic', () => { const { mount } = new LogicMounter(GenericEndpointInlineEditableTableLogic); const { http } = mockHttpValues; - const { flashAPIErrors } = mockFlashMessageHelpers; + const { toastAPIErrors } = mockFlashMessageHelpers; const DEFAULT_VALUES = { isLoading: false, @@ -119,14 +129,13 @@ describe('GenericEndpointInlineEditableTableLogic', () => { expect(logic.actions.clearLoading).toHaveBeenCalled(); }); - it('handles errors', async () => { + it('passes API errors to the nested inline editable table', async () => { http.post.mockReturnValueOnce(Promise.reject('error')); const logic = mountLogic(); - logic.actions.addItem(item, onSuccess); await nextTick(); - expect(flashAPIErrors).toHaveBeenCalledWith('error'); + expect(MOCK_SET_ROW_ERRORS).toHaveBeenCalledWith(['An unexpected error occurred']); }); }); @@ -167,14 +176,13 @@ describe('GenericEndpointInlineEditableTableLogic', () => { expect(logic.actions.clearLoading).toHaveBeenCalled(); }); - it('handles errors', async () => { + it('passes errors to the nested inline editable table', async () => { http.delete.mockReturnValueOnce(Promise.reject('error')); const logic = mountLogic(); - logic.actions.deleteItem(item, onSuccess); await nextTick(); - expect(flashAPIErrors).toHaveBeenCalledWith('error'); + expect(MOCK_SET_ROW_ERRORS).toHaveBeenCalledWith(['An unexpected error occurred']); }); }); @@ -221,14 +229,13 @@ describe('GenericEndpointInlineEditableTableLogic', () => { expect(logic.actions.clearLoading).toHaveBeenCalled(); }); - it('handles errors', async () => { + it('passes errors to the nested inline editable table', async () => { http.put.mockReturnValueOnce(Promise.reject('error')); const logic = mountLogic(); - logic.actions.updateItem(item, onSuccess); await nextTick(); - expect(flashAPIErrors).toHaveBeenCalledWith('error'); + expect(MOCK_SET_ROW_ERRORS).toHaveBeenCalledWith(['An unexpected error occurred']); }); }); @@ -294,7 +301,7 @@ describe('GenericEndpointInlineEditableTableLogic', () => { // It again calls back to the configured 'onReorder' to reset the order expect(DEFAULT_LOGIC_PARAMS.onReorder).toHaveBeenCalledWith(oldItems); - expect(flashAPIErrors).toHaveBeenCalledWith('error'); + expect(toastAPIErrors).toHaveBeenCalledWith('error'); }); it('does nothing if there are no reorder props', async () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/generic_endpoint_inline_editable_table_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/generic_endpoint_inline_editable_table_logic.ts index 71c993dca9cb9..b5beb2a74757e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/generic_endpoint_inline_editable_table_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/generic_endpoint_inline_editable_table_logic.ts @@ -7,9 +7,14 @@ import { kea, MakeLogicType } from 'kea'; -import { flashAPIErrors } from '../../flash_messages'; +import { toastAPIErrors } from '../../flash_messages'; +import { getErrorsFromHttpResponse } from '../../flash_messages/handle_api_errors'; import { HttpLogic } from '../../http'; +import { + InlineEditableTableLogic, + InlineEditableTableProps as InlineEditableTableLogicProps, +} from '../inline_editable_table/inline_editable_table_logic'; import { ItemWithAnID } from '../types'; @@ -91,7 +96,10 @@ export const GenericEndpointInlineEditableTableLogic = kea< onAdd(item, itemsFromResponse); onSuccess(); } catch (e) { - flashAPIErrors(e); + const errors = getErrorsFromHttpResponse(e); + InlineEditableTableLogic({ + instanceId: props.instanceId, + } as InlineEditableTableLogicProps).actions.setRowErrors(errors); } finally { actions.clearLoading(); } @@ -107,7 +115,10 @@ export const GenericEndpointInlineEditableTableLogic = kea< onDelete(item, itemsFromResponse); onSuccess(); } catch (e) { - flashAPIErrors(e); + const errors = getErrorsFromHttpResponse(e); + InlineEditableTableLogic({ + instanceId: props.instanceId, + } as InlineEditableTableLogicProps).actions.setRowErrors(errors); } finally { actions.clearLoading(); } @@ -126,7 +137,10 @@ export const GenericEndpointInlineEditableTableLogic = kea< onUpdate(item, itemsFromResponse); onSuccess(); } catch (e) { - flashAPIErrors(e); + const errors = getErrorsFromHttpResponse(e); + InlineEditableTableLogic({ + instanceId: props.instanceId, + } as InlineEditableTableLogicProps).actions.setRowErrors(errors); } finally { actions.clearLoading(); } @@ -152,7 +166,7 @@ export const GenericEndpointInlineEditableTableLogic = kea< onSuccess(); } catch (e) { onReorder(oldItems); - flashAPIErrors(e); + toastAPIErrors(e); } actions.clearLoading(); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/action_column.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/action_column.test.tsx index 6328b01cd2be7..4c9532b038a8c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/action_column.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/action_column.test.tsx @@ -23,9 +23,10 @@ describe('ActionColumn', () => { const mockValues = { doesEditingItemValueContainEmptyProperty: false, editingItemId: 1, - formErrors: [], + fieldErrors: {}, isEditing: false, isEditingUnsavedItem: false, + rowErrors: [], }; const mockActions = { editExistingItem: jest.fn(), @@ -87,10 +88,20 @@ describe('ActionColumn', () => { expect(subject(wrapper).prop('disabled')).toBe(true); }); - it('which is disabled if there are form errors', () => { + it('which is disabled if there are field errors', () => { setMockValues({ ...mockValues, - formErrors: ['I am an error'], + fieldErrors: { foo: ['I am an error for foo'] }, + }); + + const wrapper = shallow(); + expect(subject(wrapper).prop('disabled')).toBe(true); + }); + + it('which is disabled if there are row errors', () => { + setMockValues({ + ...mockValues, + rowErrors: ['I am a row error'], }); const wrapper = shallow(); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/action_column.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/action_column.tsx index ec52b18adf648..3293e8c5021d5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/action_column.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/action_column.tsx @@ -41,7 +41,7 @@ export const ActionColumn = ({ lastItemWarning, uneditableItems, }: ActionColumnProps) => { - const { doesEditingItemValueContainEmptyProperty, formErrors, isEditingUnsavedItem } = + const { doesEditingItemValueContainEmptyProperty, fieldErrors, rowErrors, isEditingUnsavedItem } = useValues(InlineEditableTableLogic); const { editExistingItem, deleteItem, doneEditing, saveExistingItem, saveNewItem } = useActions(InlineEditableTableLogic); @@ -50,6 +50,8 @@ export const ActionColumn = ({ return null; } + const isInvalid = Object.keys(fieldErrors).length > 0 || rowErrors.length > 0; + if (isActivelyEditing(item)) { return ( @@ -59,11 +61,7 @@ export const ActionColumn = ({ color="primary" iconType="checkInCircleFilled" onClick={isEditingUnsavedItem ? saveNewItem : saveExistingItem} - disabled={ - isLoading || - Object.keys(formErrors).length > 0 || - doesEditingItemValueContainEmptyProperty - } + disabled={isLoading || isInvalid || doesEditingItemValueContainEmptyProperty} > {SAVE_BUTTON_LABEL} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/editing_column.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/editing_column.test.tsx index 43ced1bd87492..3dcdc8a84ce38 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/editing_column.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/editing_column.test.tsx @@ -28,8 +28,9 @@ describe('EditingColumn', () => { }; const mockValues = { - formErrors: [], editingItemValue: { id: 1 }, + fieldErrors: {}, + rowErrors: [], }; const mockActions = { @@ -52,7 +53,7 @@ describe('EditingColumn', () => { beforeEach(() => { setMockValues({ ...mockValues, - formErrors: { + fieldErrors: { foo: 'I am an error for foo and should be displayed', }, }); @@ -70,7 +71,7 @@ describe('EditingColumn', () => { ); }); - it('renders form errors for this field if any are present', () => { + it('renders field errors for this field if any are present', () => { expect(shallow(wrapper.find(EuiFormRow).prop('helpText') as any).html()).toContain( 'I am an error for foo and should be displayed' ); @@ -81,6 +82,22 @@ describe('EditingColumn', () => { }); }); + describe('when there is a form error for this row', () => { + let wrapper: ShallowWrapper; + beforeEach(() => { + setMockValues({ + ...mockValues, + rowErrors: ['I am an error for this row'], + }); + + wrapper = shallow(); + }); + + it('renders as invalid', () => { + expect(wrapper.find(EuiFormRow).prop('isInvalid')).toBe(true); + }); + }); + it('renders nothing if there is no editingItemValue in state', () => { setMockValues({ ...mockValues, @@ -95,7 +112,7 @@ describe('EditingColumn', () => { setMockValues({ ...mockValues, editingItemValue: { id: 1, foo: 'foo', bar: 'bar' }, - formErrors: { foo: ['I am an error for foo'] }, + fieldErrors: { foo: ['I am an error for foo'] }, }); const wrapper = shallow( diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/editing_column.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/editing_column.tsx index d3d36046dc0a6..99b06ef827ded 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/editing_column.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/editing_column.tsx @@ -25,20 +25,25 @@ export const EditingColumn = ({ column, isLoading = false, }: EditingColumnProps) => { - const { formErrors, editingItemValue } = useValues(InlineEditableTableLogic); + const { fieldErrors, rowErrors, editingItemValue } = useValues(InlineEditableTableLogic); const { setEditingItemValue } = useActions(InlineEditableTableLogic); if (!editingItemValue) return null; + const fieldError = fieldErrors[column.field]; + const isInvalid = !!fieldError || rowErrors.length > 0; + return ( - {formErrors[column.field]} - + fieldError && ( + + {fieldError} + + ) } - isInvalid={!!formErrors[column.field]} + isInvalid={isInvalid} > <> {column.editingRender( @@ -50,7 +55,7 @@ export const EditingColumn = ({ }); }, { - isInvalid: !!formErrors[column.field], + isInvalid, isLoading, } )} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.test.tsx index ab59616e9ce78..c8d3079b033c0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.test.tsx @@ -153,6 +153,21 @@ describe('InlineEditableTable', () => { expect(rowProps(items[1])).toEqual({ className: 'is-being-edited' }); }); + it('will pass errors for row that is currently being edited', () => { + setMockValues({ + ...mockValues, + isEditing: true, + editingItemId: 2, + rowErrors: ['first error', 'second error'], + }); + const itemList = [{ id: 1 }, { id: 2 }]; + const wrapper = shallow(); + const rowErrors = wrapper.find(ReorderableTable).prop('rowErrors') as (item: any) => object; + expect(rowErrors(items[0])).toEqual(undefined); + // Since editingItemId is 2 and the second item (position 1) in item list has an id of 2, it gets the errors + expect(rowErrors(items[1])).toEqual(['first error', 'second error']); + }); + it('will update the passed columns and pass them through to the underlying table', () => { const updatedColumns = {}; const canRemoveLastItem = true; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.tsx index 093692dfde335..3c670264dff22 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.tsx @@ -95,7 +95,8 @@ export const InlineEditableTableContents = ({ uneditableItems, ...rest }: InlineEditableTableProps) => { - const { editingItemId, isEditing, isEditingUnsavedItem } = useValues(InlineEditableTableLogic); + const { editingItemId, isEditing, isEditingUnsavedItem, rowErrors } = + useValues(InlineEditableTableLogic); const { editNewItem, reorderItems } = useActions(InlineEditableTableLogic); // TODO These two things shoud just be selectors @@ -168,6 +169,7 @@ export const InlineEditableTableContents = ({ 'is-being-edited': isActivelyEditing(item), }), })} + rowErrors={(item) => (isActivelyEditing(item) ? rowErrors : undefined)} noItemsMessage={noItemsMessage(editNewItem)} onReorder={reorderItems} disableDragging={isEditing} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table_logic.test.ts index f690a38620ecb..5a8a724076223 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table_logic.test.ts @@ -29,8 +29,9 @@ describe('InlineEditableTableLogic', () => { const DEFAULT_VALUES = { editingItemId: null, editingItemValue: null, - formErrors: {}, + fieldErrors: {}, isEditing: false, + rowErrors: [], }; const SELECTORS = { @@ -91,7 +92,7 @@ describe('InlineEditableTableLogic', () => { isEditing: true, editingItemId: 1, editingItemValue: {}, - formErrors: { foo: 'I am error' }, + fieldErrors: { foo: 'I am error for foo' }, }); logic.actions.doneEditing(); expect(logicValuesWithoutSelectors(logic)).toEqual(DEFAULT_VALUES); @@ -152,29 +153,41 @@ describe('InlineEditableTableLogic', () => { }); }); - describe('setFormErrors', () => { - it('sets formErrors', () => { - const formErrors = { - bar: 'I am an error', + describe('setFieldErrors', () => { + it('sets fieldErrors', () => { + const fieldErrors = { + foo: 'I am an error for foo', }; const logic = mountLogic(); - logic.actions.setFormErrors(formErrors); + logic.actions.setFieldErrors(fieldErrors); expect(logicValuesWithoutSelectors(logic)).toEqual({ ...DEFAULT_VALUES, - formErrors, + fieldErrors, + }); + }); + }); + + describe('setRowErrors', () => { + it('sets rowErrors', () => { + const rowErrors = ['I am a row error']; + const logic = mountLogic(); + logic.actions.setRowErrors(rowErrors); + expect(logicValuesWithoutSelectors(logic)).toEqual({ + ...DEFAULT_VALUES, + rowErrors, }); }); }); describe('setEditingItemValue', () => { - it('updates the state of the item currently being edited and resets form errors', () => { + it('updates the state of the item currently being edited and resets field errors', () => { const logic = mountLogic({ editingItemValue: { id: 1, foo: '', bar: '', }, - formErrors: { foo: 'I am error' }, + fieldErrors: { foo: 'I am error for foo' }, }); logic.actions.setEditingItemValue({ id: 1, @@ -188,7 +201,7 @@ describe('InlineEditableTableLogic', () => { foo: 'blah blah', bar: '', }, - formErrors: {}, + fieldErrors: {}, }); }); }); @@ -297,20 +310,20 @@ describe('InlineEditableTableLogic', () => { ); }); - it('will set form errors and not call the provided onUpdate callback if the item being edited does not validate', () => { + it('will set field errors and not call the provided onUpdate callback if the item being edited does not validate', () => { const editingItemValue = {}; - const formErrors = { + const fieldErrors = { foo: 'some error', }; - DEFAULT_LOGIC_PARAMS.validateItem.mockReturnValue(formErrors); + DEFAULT_LOGIC_PARAMS.validateItem.mockReturnValue(fieldErrors); const logic = mountLogic({ ...DEFAULT_VALUES, editingItemValue, }); - jest.spyOn(logic.actions, 'setFormErrors'); + jest.spyOn(logic.actions, 'setFieldErrors'); logic.actions.saveExistingItem(); expect(DEFAULT_LOGIC_PARAMS.onUpdate).not.toHaveBeenCalled(); - expect(logic.actions.setFormErrors).toHaveBeenCalledWith(formErrors); + expect(logic.actions.setFieldErrors).toHaveBeenCalledWith(fieldErrors); }); it('will do neither if no value is currently being edited', () => { @@ -319,10 +332,10 @@ describe('InlineEditableTableLogic', () => { ...DEFAULT_VALUES, editingItemValue, }); - jest.spyOn(logic.actions, 'setFormErrors'); + jest.spyOn(logic.actions, 'setFieldErrors'); logic.actions.saveExistingItem(); expect(DEFAULT_LOGIC_PARAMS.onUpdate).not.toHaveBeenCalled(); - expect(logic.actions.setFormErrors).not.toHaveBeenCalled(); + expect(logic.actions.setFieldErrors).not.toHaveBeenCalled(); }); it('will always call the provided onUpdate callback if no validateItem param was provided', () => { @@ -382,20 +395,20 @@ describe('InlineEditableTableLogic', () => { ); }); - it('will set form errors and not call the provided onAdd callback if the item being edited does not validate', () => { + it('will set field errors and not call the provided onAdd callback if the item being edited does not validate', () => { const editingItemValue = {}; - const formErrors = { + const fieldErrors = { foo: 'some error', }; - DEFAULT_LOGIC_PARAMS.validateItem.mockReturnValue(formErrors); + DEFAULT_LOGIC_PARAMS.validateItem.mockReturnValue(fieldErrors); const logic = mountLogic({ ...DEFAULT_VALUES, editingItemValue, }); - jest.spyOn(logic.actions, 'setFormErrors'); + jest.spyOn(logic.actions, 'setFieldErrors'); logic.actions.saveNewItem(); expect(DEFAULT_LOGIC_PARAMS.onAdd).not.toHaveBeenCalled(); - expect(logic.actions.setFormErrors).toHaveBeenCalledWith(formErrors); + expect(logic.actions.setFieldErrors).toHaveBeenCalledWith(fieldErrors); }); it('will do nothing if no value is currently being edited', () => { @@ -404,10 +417,10 @@ describe('InlineEditableTableLogic', () => { ...DEFAULT_VALUES, editingItemValue, }); - jest.spyOn(logic.actions, 'setFormErrors'); + jest.spyOn(logic.actions, 'setFieldErrors'); logic.actions.saveNewItem(); expect(DEFAULT_LOGIC_PARAMS.onAdd).not.toHaveBeenCalled(); - expect(logic.actions.setFormErrors).not.toHaveBeenCalled(); + expect(logic.actions.setFieldErrors).not.toHaveBeenCalled(); }); it('will always call the provided onAdd callback if no validateItem param was provided', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table_logic.ts index 0230fc0754120..04b5ceb998851 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table_logic.ts @@ -20,7 +20,8 @@ interface InlineEditableTableActions { saveExistingItem(): void; saveNewItem(): void; setEditingItemValue(newValue: Item): { item: Item }; - setFormErrors(formErrors: FormErrors): { formErrors: FormErrors }; + setFieldErrors(fieldErrors: FormErrors): { fieldErrors: FormErrors }; + setRowErrors(rowErrors: string[]): { rowErrors: string[] }; } const generateEmptyItem = ( @@ -39,12 +40,13 @@ interface InlineEditableTableValues { // TODO we should editingItemValue have editingItemValue and editingItemId should be a selector editingItemId: Item['id'] | null; // editingItem is null when the user is editing a new but not saved item editingItemValue: Item | null; - formErrors: FormErrors; + fieldErrors: FormErrors; + rowErrors: string[]; isEditingUnsavedItem: boolean; doesEditingItemValueContainEmptyProperty: boolean; } -interface InlineEditableTableProps { +export interface InlineEditableTableProps { columns: Array>; instanceId: string; // TODO Because these callbacks are params, they are only set on the logic once (i.e., they are cached) @@ -75,7 +77,8 @@ export const InlineEditableTableLogic = kea ({ item: newValue }), - setFormErrors: (formErrors) => ({ formErrors }), + setFieldErrors: (fieldErrors) => ({ fieldErrors }), + setRowErrors: (rowErrors) => ({ rowErrors }), }), reducers: ({ props: { columns } }) => ({ isEditing: [ @@ -103,12 +106,20 @@ export const InlineEditableTableLogic = kea item, }, ], - formErrors: [ + fieldErrors: [ {}, { doneEditing: () => ({}), setEditingItemValue: () => ({}), - setFormErrors: (_, { formErrors }) => formErrors, + setFieldErrors: (_, { fieldErrors }) => fieldErrors, + }, + ], + rowErrors: [ + [], + { + doneEditing: () => [], + setEditingItemValue: () => [], + setRowErrors: (_, { rowErrors }) => rowErrors, }, ], }), @@ -144,7 +155,7 @@ export const InlineEditableTableLogic = kea { const cells = wrapper.find(Cell); expect(cells.length).toBe(3); }); + + it('will render row errors', () => { + const wrapper = shallow( + + ); + const callouts = wrapper.find(EuiCallOut); + expect(callouts.length).toBe(2); + expect(callouts.at(0).prop('title')).toEqual('first error'); + expect(callouts.at(1).prop('title')).toEqual('second error'); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/reorderable_table/body_row.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/tables/reorderable_table/body_row.tsx index 474d49f5eef0f..588f14190d274 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/reorderable_table/body_row.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/reorderable_table/body_row.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiCallOut, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { Cell } from './cell'; import { DRAGGABLE_UX_STYLE } from './constants'; @@ -19,6 +19,7 @@ export interface BodyRowProps { additionalProps?: object; // Cell to put in first column before other columns leftAction?: React.ReactNode; + errors?: string[]; } export const BodyRow = ({ @@ -26,6 +27,7 @@ export const BodyRow = ({ item, additionalProps, leftAction, + errors = [], }: BodyRowProps) => { return (
@@ -46,6 +48,15 @@ export const BodyRow = ({ + {errors.length > 0 && ( + + {errors.map((errorMessage, errorMessageIndex) => ( + + + + ))} + + )}
); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/reorderable_table/draggable_body_row.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/tables/reorderable_table/draggable_body_row.tsx index 191843a2e6e78..9a21d5a9c8c25 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/reorderable_table/draggable_body_row.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/reorderable_table/draggable_body_row.tsx @@ -18,6 +18,7 @@ export interface DraggableBodyRowProps { rowIndex: number; additionalProps?: object; disableDragging?: boolean; + errors?: string[]; } export const DraggableBodyRow = ({ @@ -26,6 +27,7 @@ export const DraggableBodyRow = ({ rowIndex, additionalProps, disableDragging = false, + errors, }: DraggableBodyRowProps) => { const draggableId = `draggable_row_${rowIndex}`; @@ -42,6 +44,7 @@ export const DraggableBodyRow = ({ item={item} additionalProps={additionalProps} leftAction={!disableDragging ? : <>} + errors={errors} /> ); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/reorderable_table/reorderable_table.scss b/x-pack/plugins/enterprise_search/public/applications/shared/tables/reorderable_table/reorderable_table.scss index d2ea90bbbfec8..81ae229dcdd48 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/reorderable_table/reorderable_table.scss +++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/reorderable_table/reorderable_table.scss @@ -9,14 +9,18 @@ border-top: $euiBorderThin; background-color: $euiColorEmptyShade; - > .euiFlexGroup--directionRow.euiFlexGroup--gutterLarge { + > .euiFlexGroup { margin: 0; } + + > .euiFlexGroup:nth-child(2) > .euiFlexItem { + margin-top: 0; + } } &Header { - > .euiFlexGroup--directionRow.euiFlexGroup--gutterLarge { - margin: -12px 0; + > .euiFlexGroup { + margin: ($euiSizeM * -1) 0; } } diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/reorderable_table/reorderable_table.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/tables/reorderable_table/reorderable_table.tsx index 4cb12321bdfcf..88e80f1d5401b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/reorderable_table/reorderable_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/reorderable_table/reorderable_table.tsx @@ -30,6 +30,7 @@ interface ReorderableTableProps { disableReordering?: boolean; onReorder?: (items: Item[], oldItems: Item[]) => void; rowProps?: (item: Item) => object; + rowErrors?: (item: Item) => string[] | undefined; } export const ReorderableTable = ({ @@ -42,6 +43,7 @@ export const ReorderableTable = ({ disableReordering = false, onReorder = () => undefined, rowProps = () => ({}), + rowErrors = () => undefined, }: ReorderableTableProps) => { return (
@@ -67,6 +69,7 @@ export const ReorderableTable = ({ additionalProps={rowProps(item)} disableDragging={disableDragging} rowIndex={itemIndex} + errors={rowErrors(item)} /> )} onReorder={onReorder} @@ -83,6 +86,7 @@ export const ReorderableTable = ({ columns={columns} item={item} additionalProps={rowProps(item)} + errors={rowErrors(item)} /> )} /> @@ -97,6 +101,7 @@ export const ReorderableTable = ({ columns={columns} item={item} additionalProps={rowProps(item)} + errors={rowErrors(item)} leftAction={<>} /> )} diff --git a/x-pack/plugins/enterprise_search/public/applications/test_helpers/error_handling.ts b/x-pack/plugins/enterprise_search/public/applications/test_helpers/error_handling.ts new file mode 100644 index 0000000000000..4f1f4a40aa503 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/test_helpers/error_handling.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { mockFlashMessageHelpers } from '../__mocks__/kea_logic'; + +import { nextTick } from '@kbn/test/jest'; +import { HttpHandler } from 'src/core/public'; + +export const itShowsServerErrorAsFlashMessage = (httpMethod: HttpHandler, callback: () => void) => { + const { flashAPIErrors } = mockFlashMessageHelpers; + it('shows any server errors as flash messages', async () => { + (httpMethod as jest.Mock).mockReturnValueOnce(Promise.reject('error')); + callback(); + await nextTick(); + + expect(flashAPIErrors).toHaveBeenCalledWith('error'); + }); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/test_helpers/index.ts b/x-pack/plugins/enterprise_search/public/applications/test_helpers/index.ts index 35836d5526615..b0705dd7e134b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/test_helpers/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/test_helpers/index.ts @@ -21,3 +21,4 @@ export { // Misc export { expectedAsyncError } from './expected_async_error'; +export { itShowsServerErrorAsFlashMessage } from './error_handling'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts index 5ff3964b8f83a..da4e9cad9e276 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts @@ -15,6 +15,8 @@ import { sourceConfigData } from '../../../../__mocks__/content_sources.mock'; import { nextTick } from '@kbn/test/jest'; +import { itShowsServerErrorAsFlashMessage } from '../../../../../test_helpers'; + jest.mock('../../../../app_logic', () => ({ AppLogic: { values: { isOrganization: true } }, })); @@ -413,13 +415,8 @@ describe('AddSourceLogic', () => { expect(setSourceConfigDataSpy).toHaveBeenCalledWith(sourceConfigData); }); - it('handles error', async () => { - http.get.mockReturnValue(Promise.reject('this is an error')); - + itShowsServerErrorAsFlashMessage(http.get, () => { AddSourceLogic.actions.getSourceConfigData('github'); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); @@ -474,13 +471,8 @@ describe('AddSourceLogic', () => { ); }); - it('handles error', async () => { - http.get.mockReturnValue(Promise.reject('this is an error')); - + itShowsServerErrorAsFlashMessage(http.get, () => { AddSourceLogic.actions.getSourceConnectData('github', successCallback); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); @@ -506,13 +498,8 @@ describe('AddSourceLogic', () => { expect(setSourceConnectDataSpy).toHaveBeenCalledWith(sourceConnectData); }); - it('handles error', async () => { - http.get.mockReturnValue(Promise.reject('this is an error')); - + itShowsServerErrorAsFlashMessage(http.get, () => { AddSourceLogic.actions.getSourceReConnectData('github'); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); @@ -532,13 +519,8 @@ describe('AddSourceLogic', () => { expect(setPreContentSourceConfigDataSpy).toHaveBeenCalledWith(config); }); - it('handles error', async () => { - http.get.mockReturnValue(Promise.reject('this is an error')); - + itShowsServerErrorAsFlashMessage(http.get, () => { AddSourceLogic.actions.getPreContentSourceConfigData(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); @@ -601,13 +583,8 @@ describe('AddSourceLogic', () => { ); }); - it('handles error', async () => { - http.put.mockReturnValue(Promise.reject('this is an error')); - + itShowsServerErrorAsFlashMessage(http.put, () => { AddSourceLogic.actions.saveSourceConfig(true); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts index 62e305f72365d..81a97c2d19e16 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts @@ -15,6 +15,8 @@ import { exampleResult } from '../../../../__mocks__/content_sources.mock'; import { nextTick } from '@kbn/test/jest'; +import { itShowsServerErrorAsFlashMessage } from '../../../../../test_helpers'; + const contentSource = { id: 'source123' }; jest.mock('../../source_logic', () => ({ SourceLogic: { values: { contentSource } }, @@ -31,7 +33,7 @@ import { DisplaySettingsLogic, defaultSearchResultConfig } from './display_setti describe('DisplaySettingsLogic', () => { const { http } = mockHttpValues; const { navigateToUrl } = mockKibanaValues; - const { clearFlashMessages, flashAPIErrors, flashSuccessToast } = mockFlashMessageHelpers; + const { clearFlashMessages, flashSuccessToast } = mockFlashMessageHelpers; const { mount } = new LogicMounter(DisplaySettingsLogic); const { searchResultConfig, exampleDocuments } = exampleResult; @@ -406,12 +408,8 @@ describe('DisplaySettingsLogic', () => { }); }); - it('handles error', async () => { - http.get.mockReturnValue(Promise.reject('this is an error')); + itShowsServerErrorAsFlashMessage(http.get, () => { DisplaySettingsLogic.actions.initializeDisplaySettings(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); @@ -434,12 +432,8 @@ describe('DisplaySettingsLogic', () => { }); }); - it('handles error', async () => { - http.post.mockReturnValue(Promise.reject('this is an error')); + itShowsServerErrorAsFlashMessage(http.post, () => { DisplaySettingsLogic.actions.setServerData(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts index af9d85237335c..d284f5c741eb3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts @@ -29,6 +29,7 @@ Object.defineProperty(global.window, 'scrollTo', { value: spyScrollTo }); import { ADD, UPDATE } from '../../../../../shared/constants/operations'; import { defaultErrorMessage } from '../../../../../shared/flash_messages/handle_api_errors'; import { SchemaType } from '../../../../../shared/schema/types'; +import { itShowsServerErrorAsFlashMessage } from '../../../../../test_helpers'; import { AppLogic } from '../../../../app_logic'; import { @@ -40,8 +41,7 @@ import { SchemaLogic, dataTypeOptions } from './schema_logic'; describe('SchemaLogic', () => { const { http } = mockHttpValues; - const { clearFlashMessages, flashAPIErrors, flashSuccessToast, setErrorMessage } = - mockFlashMessageHelpers; + const { clearFlashMessages, flashSuccessToast, setErrorMessage } = mockFlashMessageHelpers; const { mount } = new LogicMounter(SchemaLogic); const defaultValues = { @@ -224,12 +224,8 @@ describe('SchemaLogic', () => { expect(onInitializeSchemaSpy).toHaveBeenCalledWith(serverResponse); }); - it('handles error', async () => { - http.get.mockReturnValue(Promise.reject('this is an error')); + itShowsServerErrorAsFlashMessage(http.get, () => { SchemaLogic.actions.initializeSchema(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); @@ -447,12 +443,8 @@ describe('SchemaLogic', () => { expect(onSchemaSetSuccessSpy).toHaveBeenCalledWith(serverResponse); }); - it('handles error', async () => { - http.post.mockReturnValue(Promise.reject('this is an error')); + itShowsServerErrorAsFlashMessage(http.post, () => { SchemaLogic.actions.setServerField(schema, UPDATE); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.test.ts index 25fb256e85f01..0ccfd6aa63ae4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.test.ts @@ -15,7 +15,7 @@ import { fullContentSources } from '../../../../__mocks__/content_sources.mock'; import { nextTick } from '@kbn/test/jest'; -import { expectedAsyncError } from '../../../../../test_helpers'; +import { itShowsServerErrorAsFlashMessage } from '../../../../../test_helpers'; jest.mock('../../source_logic', () => ({ SourceLogic: { actions: { setContentSource: jest.fn() } }, @@ -34,7 +34,7 @@ import { describe('SynchronizationLogic', () => { const { http } = mockHttpValues; - const { flashAPIErrors, flashSuccessToast } = mockFlashMessageHelpers; + const { flashSuccessToast } = mockFlashMessageHelpers; const { navigateToUrl } = mockKibanaValues; const { mount } = new LogicMounter(SynchronizationLogic); const contentSource = fullContentSources[0]; @@ -328,19 +328,8 @@ describe('SynchronizationLogic', () => { expect(flashSuccessToast).toHaveBeenCalledWith('Source synchronization settings updated.'); }); - it('handles error', async () => { - const error = { - response: { - error: 'this is an error', - status: 400, - }, - }; - const promise = Promise.reject(error); - http.patch.mockReturnValue(promise); + itShowsServerErrorAsFlashMessage(http.patch, () => { SynchronizationLogic.actions.updateServerSettings(body); - await expectedAsyncError(promise); - - expect(flashAPIErrors).toHaveBeenCalledWith(error); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts index fb88360de5df0..be288ea208858 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts @@ -20,6 +20,7 @@ import { expectedAsyncError } from '../../../test_helpers'; jest.mock('../../app_logic', () => ({ AppLogic: { values: { isOrganization: true } }, })); +import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; import { AppLogic } from '../../app_logic'; import { SourceLogic } from './source_logic'; @@ -235,19 +236,8 @@ describe('SourceLogic', () => { expect(onUpdateSummarySpy).toHaveBeenCalledWith(contentSource.summary); }); - it('handles error', async () => { - const error = { - response: { - error: 'this is an error', - status: 400, - }, - }; - const promise = Promise.reject(error); - http.get.mockReturnValue(promise); + itShowsServerErrorAsFlashMessage(http.get, () => { SourceLogic.actions.initializeFederatedSummary(contentSource.id); - await expectedAsyncError(promise); - - expect(flashAPIErrors).toHaveBeenCalledWith(error); }); }); @@ -295,20 +285,8 @@ describe('SourceLogic', () => { expect(actions.setSearchResults).toHaveBeenCalledWith(searchServerResponse); }); - it('handles error', async () => { - const error = { - response: { - error: 'this is an error', - status: 400, - }, - }; - const promise = Promise.reject(error); - http.post.mockReturnValue(promise); - - await searchContentSourceDocuments({ sourceId: contentSource.id }, mockBreakpoint); - await expectedAsyncError(promise); - - expect(flashAPIErrors).toHaveBeenCalledWith(error); + itShowsServerErrorAsFlashMessage(http.post, () => { + searchContentSourceDocuments({ sourceId: contentSource.id }, mockBreakpoint); }); }); @@ -367,19 +345,8 @@ describe('SourceLogic', () => { expect(onUpdateSourceNameSpy).toHaveBeenCalledWith(contentSource.name); }); - it('handles error', async () => { - const error = { - response: { - error: 'this is an error', - status: 400, - }, - }; - const promise = Promise.reject(error); - http.patch.mockReturnValue(promise); + itShowsServerErrorAsFlashMessage(http.patch, () => { SourceLogic.actions.updateContentSource(contentSource.id, contentSource); - await expectedAsyncError(promise); - - expect(flashAPIErrors).toHaveBeenCalledWith(error); }); }); @@ -413,19 +380,8 @@ describe('SourceLogic', () => { expect(setButtonNotLoadingSpy).toHaveBeenCalled(); }); - it('handles error', async () => { - const error = { - response: { - error: 'this is an error', - status: 400, - }, - }; - const promise = Promise.reject(error); - http.delete.mockReturnValue(promise); + itShowsServerErrorAsFlashMessage(http.delete, () => { SourceLogic.actions.removeContentSource(contentSource.id); - await expectedAsyncError(promise); - - expect(flashAPIErrors).toHaveBeenCalledWith(error); }); }); @@ -441,19 +397,8 @@ describe('SourceLogic', () => { expect(initializeSourceSpy).toHaveBeenCalledWith(contentSource.id); }); - it('handles error', async () => { - const error = { - response: { - error: 'this is an error', - status: 400, - }, - }; - const promise = Promise.reject(error); - http.post.mockReturnValue(promise); + itShowsServerErrorAsFlashMessage(http.post, () => { SourceLogic.actions.initializeSourceSynchronization(contentSource.id); - await expectedAsyncError(promise); - - expect(flashAPIErrors).toHaveBeenCalledWith(error); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.test.ts index 8518485c98b24..f7e41f6512017 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.test.ts @@ -12,11 +12,10 @@ import { } from '../../../__mocks__/kea_logic'; import { configuredSources, contentSources } from '../../__mocks__/content_sources.mock'; -import { expectedAsyncError } from '../../../test_helpers'; - jest.mock('../../app_logic', () => ({ AppLogic: { values: { isOrganization: true } }, })); +import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; import { AppLogic } from '../../app_logic'; import { SourcesLogic, fetchSourceStatuses, POLLING_INTERVAL } from './sources_logic'; @@ -185,19 +184,8 @@ describe('SourcesLogic', () => { expect(http.get).toHaveBeenCalledWith('/internal/workplace_search/account/sources'); }); - it('handles error', async () => { - const error = { - response: { - error: 'this is an error', - status: 400, - }, - }; - const promise = Promise.reject(error); - http.get.mockReturnValue(promise); + itShowsServerErrorAsFlashMessage(http.get, () => { SourcesLogic.actions.initializeSources(); - await expectedAsyncError(promise); - - expect(flashAPIErrors).toHaveBeenCalledWith(error); }); it('handles early logic unmount gracefully in org context', async () => { @@ -259,19 +247,8 @@ describe('SourcesLogic', () => { ); }); - it('handles error', async () => { - const error = { - response: { - error: 'this is an error', - status: 400, - }, - }; - const promise = Promise.reject(error); - http.put.mockReturnValue(promise); + itShowsServerErrorAsFlashMessage(http.put, () => { SourcesLogic.actions.setSourceSearchability(id, true); - await expectedAsyncError(promise); - - expect(flashAPIErrors).toHaveBeenCalledWith(error); }); }); @@ -367,19 +344,8 @@ describe('SourcesLogic', () => { expect(http.get).toHaveBeenCalledWith('/internal/workplace_search/account/sources/status'); }); - it('handles error', async () => { - const error = { - response: { - error: 'this is an error', - status: 400, - }, - }; - const promise = Promise.reject(error); - http.get.mockReturnValue(promise); + itShowsServerErrorAsFlashMessage(http.get, () => { fetchSourceStatuses(true, mockBreakpoint); - await expectedAsyncError(promise); - - expect(flashAPIErrors).toHaveBeenCalledWith(error); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.test.ts index 6f811ce364290..3048dcedef26f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.test.ts @@ -16,6 +16,7 @@ import { mockGroupValues } from './__mocks__/group_logic.mock'; import { nextTick } from '@kbn/test/jest'; +import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; import { GROUPS_PATH } from '../../routes'; import { GroupLogic } from './group_logic'; @@ -24,8 +25,7 @@ describe('GroupLogic', () => { const { mount } = new LogicMounter(GroupLogic); const { http } = mockHttpValues; const { navigateToUrl } = mockKibanaValues; - const { clearFlashMessages, flashAPIErrors, flashSuccessToast, setQueuedErrorMessage } = - mockFlashMessageHelpers; + const { clearFlashMessages, flashSuccessToast, setQueuedErrorMessage } = mockFlashMessageHelpers; const group = groups[0]; const sourceIds = ['123', '124']; @@ -222,13 +222,8 @@ describe('GroupLogic', () => { expect(flashSuccessToast).toHaveBeenCalledWith('Group "group" was successfully deleted.'); }); - it('handles error', async () => { - http.delete.mockReturnValue(Promise.reject('this is an error')); - + itShowsServerErrorAsFlashMessage(http.delete, () => { GroupLogic.actions.deleteGroup(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); @@ -253,13 +248,8 @@ describe('GroupLogic', () => { ); }); - it('handles error', async () => { - http.put.mockReturnValue(Promise.reject('this is an error')); - + itShowsServerErrorAsFlashMessage(http.put, () => { GroupLogic.actions.updateGroupName(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); @@ -284,13 +274,8 @@ describe('GroupLogic', () => { ); }); - it('handles error', async () => { - http.post.mockReturnValue(Promise.reject('this is an error')); - + itShowsServerErrorAsFlashMessage(http.post, () => { GroupLogic.actions.saveGroupSources(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); @@ -322,13 +307,8 @@ describe('GroupLogic', () => { expect(onGroupPrioritiesChangedSpy).toHaveBeenCalledWith(group); }); - it('handles error', async () => { - http.put.mockReturnValue(Promise.reject('this is an error')); - + itShowsServerErrorAsFlashMessage(http.put, () => { GroupLogic.actions.saveGroupSourcePrioritization(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_logic.test.ts index c8b725f7131a6..15951a9f8b9ca 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_logic.test.ts @@ -20,6 +20,8 @@ import { nextTick } from '@kbn/test/jest'; import { JSON_HEADER as headers } from '../../../../../common/constants'; import { DEFAULT_META } from '../../../shared/constants'; +import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; + import { GroupsLogic } from './groups_logic'; // We need to mock out the debounced functionality @@ -227,13 +229,8 @@ describe('GroupsLogic', () => { expect(onInitializeGroupsSpy).toHaveBeenCalledWith(groupsResponse); }); - it('handles error', async () => { - http.get.mockReturnValue(Promise.reject('this is an error')); - + itShowsServerErrorAsFlashMessage(http.get, () => { GroupsLogic.actions.initializeGroups(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); @@ -310,13 +307,8 @@ describe('GroupsLogic', () => { expect(setGroupUsersSpy).toHaveBeenCalledWith(users); }); - it('handles error', async () => { - http.get.mockReturnValue(Promise.reject('this is an error')); - + itShowsServerErrorAsFlashMessage(http.get, () => { GroupsLogic.actions.fetchGroupUsers('123'); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); @@ -336,13 +328,8 @@ describe('GroupsLogic', () => { expect(setNewGroupSpy).toHaveBeenCalledWith(groups[0]); }); - it('handles error', async () => { - http.post.mockReturnValue(Promise.reject('this is an error')); - + itShowsServerErrorAsFlashMessage(http.post, () => { GroupsLogic.actions.saveNewGroup(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.test.ts index 3f5a63275f05d..b70039636bba0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.test.ts @@ -23,6 +23,8 @@ import { } from '../../../shared/role_mapping/__mocks__/roles'; import { ANY_AUTH_PROVIDER } from '../../../shared/role_mapping/constants'; +import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; + import { RoleMappingsLogic } from './role_mappings_logic'; const emptyUser = { username: '', email: '' }; @@ -349,12 +351,8 @@ describe('RoleMappingsLogic', () => { expect(setRoleMappingsSpy).toHaveBeenCalledWith(mappingsServerProps); }); - it('handles error', async () => { - http.post.mockReturnValue(Promise.reject('this is an error')); + itShowsServerErrorAsFlashMessage(http.post, () => { RoleMappingsLogic.actions.enableRoleBasedAccess(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); @@ -369,12 +367,8 @@ describe('RoleMappingsLogic', () => { expect(setRoleMappingsDataSpy).toHaveBeenCalledWith(mappingsServerProps); }); - it('handles error', async () => { - http.get.mockReturnValue(Promise.reject('this is an error')); + itShowsServerErrorAsFlashMessage(http.get, () => { RoleMappingsLogic.actions.initializeRoleMappings(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); it('resets roleMapping state', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security_logic.test.ts index bc45609e9e83d..df9035d57e56b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security_logic.test.ts @@ -5,19 +5,16 @@ * 2.0. */ -import { - LogicMounter, - mockHttpValues, - mockFlashMessageHelpers, -} from '../../../__mocks__/kea_logic'; +import { LogicMounter, mockHttpValues } from '../../../__mocks__/kea_logic'; import { nextTick } from '@kbn/test/jest'; +import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; + import { SecurityLogic } from './security_logic'; describe('SecurityLogic', () => { const { http } = mockHttpValues; - const { flashAPIErrors } = mockFlashMessageHelpers; const { mount } = new LogicMounter(SecurityLogic); beforeEach(() => { @@ -124,15 +121,8 @@ describe('SecurityLogic', () => { expect(setServerPropsSpy).toHaveBeenCalledWith(serverProps); }); - it('handles error', async () => { - http.get.mockReturnValue(Promise.reject('this is an error')); - + itShowsServerErrorAsFlashMessage(http.get, () => { SecurityLogic.actions.initializeSourceRestrictions(); - try { - await nextTick(); - } catch { - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); - } }); }); @@ -150,15 +140,8 @@ describe('SecurityLogic', () => { ); }); - it('handles error', async () => { - http.patch.mockReturnValue(Promise.reject('this is an error')); - + itShowsServerErrorAsFlashMessage(http.patch, () => { SecurityLogic.actions.saveSourceRestrictions(); - try { - await nextTick(); - } catch { - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); - } }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_logic.test.ts index ebb790b59c1fa..d98c9efe04d8c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_logic.test.ts @@ -15,6 +15,7 @@ import { configuredSources, oauthApplication } from '../../__mocks__/content_sou import { nextTick } from '@kbn/test/jest'; +import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; import { ORG_UPDATED_MESSAGE, OAUTH_APP_UPDATED_MESSAGE } from '../../constants'; import { SettingsLogic } from './settings_logic'; @@ -22,7 +23,7 @@ import { SettingsLogic } from './settings_logic'; describe('SettingsLogic', () => { const { http } = mockHttpValues; const { navigateToUrl } = mockKibanaValues; - const { clearFlashMessages, flashAPIErrors, flashSuccessToast } = mockFlashMessageHelpers; + const { clearFlashMessages, flashSuccessToast } = mockFlashMessageHelpers; const { mount } = new LogicMounter(SettingsLogic); const ORG_NAME = 'myOrg'; const defaultValues = { @@ -127,12 +128,8 @@ describe('SettingsLogic', () => { expect(setServerPropsSpy).toHaveBeenCalledWith(configuredSources); }); - it('handles error', async () => { - http.get.mockReturnValue(Promise.reject('this is an error')); + itShowsServerErrorAsFlashMessage(http.get, () => { SettingsLogic.actions.initializeSettings(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); @@ -150,12 +147,8 @@ describe('SettingsLogic', () => { expect(onInitializeConnectorsSpy).toHaveBeenCalledWith(serverProps); }); - it('handles error', async () => { - http.get.mockReturnValue(Promise.reject('this is an error')); + itShowsServerErrorAsFlashMessage(http.get, () => { SettingsLogic.actions.initializeConnectors(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); @@ -176,12 +169,8 @@ describe('SettingsLogic', () => { expect(setUpdatedNameSpy).toHaveBeenCalledWith({ organizationName: NAME }); }); - it('handles error', async () => { - http.put.mockReturnValue(Promise.reject('this is an error')); + itShowsServerErrorAsFlashMessage(http.put, () => { SettingsLogic.actions.updateOrgName(); - - await nextTick(); - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); @@ -205,12 +194,8 @@ describe('SettingsLogic', () => { expect(setIconSpy).toHaveBeenCalledWith(ICON); }); - it('handles error', async () => { - http.put.mockReturnValue(Promise.reject('this is an error')); + itShowsServerErrorAsFlashMessage(http.put, () => { SettingsLogic.actions.updateOrgIcon(); - - await nextTick(); - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); @@ -234,12 +219,8 @@ describe('SettingsLogic', () => { expect(setLogoSpy).toHaveBeenCalledWith(LOGO); }); - it('handles error', async () => { - http.put.mockReturnValue(Promise.reject('this is an error')); + itShowsServerErrorAsFlashMessage(http.put, () => { SettingsLogic.actions.updateOrgLogo(); - - await nextTick(); - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); @@ -291,12 +272,8 @@ describe('SettingsLogic', () => { expect(flashSuccessToast).toHaveBeenCalledWith(OAUTH_APP_UPDATED_MESSAGE); }); - it('handles error', async () => { - http.put.mockReturnValue(Promise.reject('this is an error')); + itShowsServerErrorAsFlashMessage(http.put, () => { SettingsLogic.actions.updateOauthApplication(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); @@ -313,12 +290,8 @@ describe('SettingsLogic', () => { expect(flashSuccessToast).toHaveBeenCalled(); }); - it('handles error', async () => { - http.delete.mockReturnValue(Promise.reject('this is an error')); + itShowsServerErrorAsFlashMessage(http.delete, () => { SettingsLogic.actions.deleteSourceConfig(SERVICE_TYPE, NAME); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); diff --git a/x-pack/plugins/fleet/common/constants/agent.ts b/x-pack/plugins/fleet/common/constants/agent.ts index e38b7a6b5832b..832a9027c6517 100644 --- a/x-pack/plugins/fleet/common/constants/agent.ts +++ b/x-pack/plugins/fleet/common/constants/agent.ts @@ -5,8 +5,7 @@ * 2.0. */ -export const AGENT_SAVED_OBJECT_TYPE = 'fleet-agents'; -export const AGENT_ACTION_SAVED_OBJECT_TYPE = 'fleet-agent-actions'; +export const AGENTS_PREFIX = 'fleet-agents'; export const AGENT_TYPE_PERMANENT = 'PERMANENT'; export const AGENT_TYPE_EPHEMERAL = 'EPHEMERAL'; diff --git a/x-pack/plugins/fleet/common/constants/enrollment_api_key.ts b/x-pack/plugins/fleet/common/constants/enrollment_api_key.ts index 25d395893aaec..c9a449939ef3f 100644 --- a/x-pack/plugins/fleet/common/constants/enrollment_api_key.ts +++ b/x-pack/plugins/fleet/common/constants/enrollment_api_key.ts @@ -5,6 +5,4 @@ * 2.0. */ -export const ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE = 'fleet-enrollment-api-keys'; - export const ENROLLMENT_API_KEYS_INDEX = '.fleet-enrollment-api-keys'; diff --git a/x-pack/plugins/fleet/common/constants/routes.ts b/x-pack/plugins/fleet/common/constants/routes.ts index aa5e0dbcd5ed1..be4103c549f1a 100644 --- a/x-pack/plugins/fleet/common/constants/routes.ts +++ b/x-pack/plugins/fleet/common/constants/routes.ts @@ -12,7 +12,6 @@ export const EPM_API_ROOT = `${API_ROOT}/epm`; export const DATA_STREAM_API_ROOT = `${API_ROOT}/data_streams`; export const PACKAGE_POLICY_API_ROOT = `${API_ROOT}/package_policies`; export const AGENT_POLICY_API_ROOT = `${API_ROOT}/agent_policies`; -export const FLEET_API_ROOT_7_9 = `/api/ingest_manager/fleet`; export const LIMITED_CONCURRENCY_ROUTE_TAG = 'ingest:limited-concurrency'; diff --git a/x-pack/plugins/fleet/common/types/models/agent.ts b/x-pack/plugins/fleet/common/types/models/agent.ts index 6913fc52d8c62..018f591fef79c 100644 --- a/x-pack/plugins/fleet/common/types/models/agent.ts +++ b/x-pack/plugins/fleet/common/types/models/agent.ts @@ -66,15 +66,6 @@ export interface AgentPolicyAction extends NewAgentAction { ack_data?: any; } -// Make policy change action renaming BWC with agent version <= 7.9 -// eslint-disable-next-line @typescript-eslint/naming-convention -export type AgentPolicyActionV7_9 = Omit & { - type: 'CONFIG_CHANGE'; - data: { - config: FullAgentPolicy; - }; -}; - interface CommonAgentActionSOAttributes { type: AgentActionType; sent_at?: string; diff --git a/x-pack/plugins/fleet/dev_docs/data_model.md b/x-pack/plugins/fleet/dev_docs/data_model.md index ec9fa031d09d3..be0e06e5439dc 100644 --- a/x-pack/plugins/fleet/dev_docs/data_model.md +++ b/x-pack/plugins/fleet/dev_docs/data_model.md @@ -37,8 +37,6 @@ All of the code that interacts with this index is currently located in [`x-pack/plugins/fleet/server/services/agents/crud.ts`](../server/services/agents/crud.ts) and the schema of these documents is maintained by the `FleetServerAgent` TypeScript interface. -Prior to Fleet Server, this data was stored in the `fleet-agents` Saved Object type which is now obsolete. - ### `.fleet-actions` index Each document in this index represents an action that was initiated by a user and needs to be processed by Fleet Server @@ -167,46 +165,3 @@ represents the relative file path of the file from the package contents Used as "tombstone record" to indicate that a package that was installed by default through preconfiguration was explicitly deleted by user. Used to avoid recreating a preconfiguration policy that a user explicitly does not want. - -### `fleet-agents` - -**DEPRECATED in favor of `.fleet-agents` index.** - -- Constant in code: `AGENT_SAVED_OBJECT_TYPE` -- Introduced in ? -- [Code Link](../server/saved_objects/index.ts#76) -- Migrations: 7.10.0, 7.12.0 -- References to other objects: - - `policy_id` - ID that points to the policy (`ingest-agent-policies`) this agent is assigned to. - - `access_api_key_id` - - `default_api_key_id` - -Tracks an individual Elastic Agent's enrollment in the Fleet, which policy it is current assigned to, its check in -status, which packages are currently installed, and other metadata about the Agent. - -### `fleet-agent-actions` - -**DEPRECATED in favor of `.fleet-agent-actions` index.** - -- Constant in code: `AGENT_ACTION_SAVED_OBJECT_TYPE` -- Introduced in ? -- [Code Link](../server/saved_objects/index.ts#113) -- Migrations: 7.10.0 -- References to other objects: - - `agent_id` - ID that points to the agent for this action (`fleet-agents`) - - `policy_id`- ID that points to the policy for this action (`ingest-agent-policies`) - - -### `fleet-enrollment-api-keys` - -**DEPRECATED in favor of `.fleet-enrollment-api-keys` index.** - -- Constant in code: `ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE` -- Introduced in ? -- [Code Link](../server/saved_objects/index.ts#166) -- Migrations: 7.10.0 -- References to other objects: - - `api_key_id` - - `policy_id` - ID that points to an agent policy (`ingest-agent-policies`) - -Contains an enrollment key that can be used to enroll a new agent in a specific agent policy. \ No newline at end of file diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx index 70becfe40d8e2..f1a23ea759def 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx @@ -12,9 +12,9 @@ import { fromKueryExpression } from '@kbn/es-query'; import type { IFieldType } from '../../../../../../../src/plugins/data/public'; import { QueryStringInput } from '../../../../../../../src/plugins/data/public'; import { useStartServices } from '../hooks'; -import { INDEX_NAME, AGENT_SAVED_OBJECT_TYPE } from '../constants'; +import { INDEX_NAME, AGENTS_PREFIX } from '../constants'; -const HIDDEN_FIELDS = [`${AGENT_SAVED_OBJECT_TYPE}.actions`, '_id', '_index']; +const HIDDEN_FIELDS = [`${AGENTS_PREFIX}.actions`, '_id', '_index']; interface Props { value: string; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_delete_provider.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_delete_provider.tsx index c5d0e5279220e..b8d8f212a5451 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_delete_provider.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_delete_provider.tsx @@ -10,7 +10,7 @@ import { EuiConfirmModal, EuiCallOut } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { AGENT_SAVED_OBJECT_TYPE } from '../../../constants'; +import { AGENTS_PREFIX } from '../../../constants'; import { sendDeleteAgentPolicy, useStartServices, useConfig, sendRequest } from '../../../hooks'; interface Props { @@ -98,7 +98,7 @@ export const AgentPolicyDeleteProvider: React.FunctionComponent = ({ chil path: `/api/fleet/agents`, method: 'get', query: { - kuery: `${AGENT_SAVED_OBJECT_TYPE}.policy_id : ${agentPolicyToCheck}`, + kuery: `${AGENTS_PREFIX}.policy_id : ${agentPolicyToCheck}`, }, }); setAgentsCount(data?.total || 0); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx index c6e92cbce8d18..9ba63475aaaad 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx @@ -42,7 +42,7 @@ import { ContextMenuActions, } from '../../../components'; import { AgentStatusKueryHelper, isAgentUpgradeable } from '../../../services'; -import { AGENT_SAVED_OBJECT_TYPE, FLEET_SERVER_PACKAGE } from '../../../constants'; +import { AGENTS_PREFIX, FLEET_SERVER_PACKAGE } from '../../../constants'; import { AgentReassignAgentPolicyModal, AgentHealth, @@ -207,7 +207,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { if (kueryBuilder) { kueryBuilder = `(${kueryBuilder}) and`; } - kueryBuilder = `${kueryBuilder} ${AGENT_SAVED_OBJECT_TYPE}.policy_id : (${selectedAgentPolicies + kueryBuilder = `${kueryBuilder} ${AGENTS_PREFIX}.policy_id : (${selectedAgentPolicies .map((agentPolicy) => `"${agentPolicy}"`) .join(' or ')})`; } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.test.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.test.ts index b4e7982c52f7b..62580a1445f06 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.test.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.test.ts @@ -17,9 +17,9 @@ describe('getInstallCommandForPlatform', () => { ); expect(res).toMatchInlineSnapshot(` - "sudo ./elastic-agent install -f \\\\ - --fleet-server-es=http://elasticsearch:9200 \\\\ - --fleet-server-service-token=service-token-1" + "sudo ./elastic-agent install \\\\ + --fleet-server-es=http://elasticsearch:9200 \\\\ + --fleet-server-service-token=service-token-1" `); }); @@ -31,9 +31,9 @@ describe('getInstallCommandForPlatform', () => { ); expect(res).toMatchInlineSnapshot(` - ".\\\\elastic-agent.exe install -f \` - --fleet-server-es=http://elasticsearch:9200 \` - --fleet-server-service-token=service-token-1" + ".\\\\elastic-agent.exe install \` + --fleet-server-es=http://elasticsearch:9200 \` + --fleet-server-service-token=service-token-1" `); }); @@ -45,9 +45,9 @@ describe('getInstallCommandForPlatform', () => { ); expect(res).toMatchInlineSnapshot(` - "sudo elastic-agent enroll -f \\\\ - --fleet-server-es=http://elasticsearch:9200 \\\\ - --fleet-server-service-token=service-token-1" + "sudo elastic-agent enroll \\\\ + --fleet-server-es=http://elasticsearch:9200 \\\\ + --fleet-server-service-token=service-token-1" `); }); }); @@ -62,9 +62,9 @@ describe('getInstallCommandForPlatform', () => { ); expect(res).toMatchInlineSnapshot(` - "sudo ./elastic-agent install -f \\\\ - --fleet-server-es=http://elasticsearch:9200 \\\\ - --fleet-server-service-token=service-token-1 \\\\ + "sudo ./elastic-agent install \\\\ + --fleet-server-es=http://elasticsearch:9200 \\\\ + --fleet-server-service-token=service-token-1 \\\\ --fleet-server-policy=policy-1" `); }); @@ -78,9 +78,9 @@ describe('getInstallCommandForPlatform', () => { ); expect(res).toMatchInlineSnapshot(` - ".\\\\elastic-agent.exe install -f \` - --fleet-server-es=http://elasticsearch:9200 \` - --fleet-server-service-token=service-token-1 \` + ".\\\\elastic-agent.exe install \` + --fleet-server-es=http://elasticsearch:9200 \` + --fleet-server-service-token=service-token-1 \` --fleet-server-policy=policy-1" `); }); @@ -94,9 +94,9 @@ describe('getInstallCommandForPlatform', () => { ); expect(res).toMatchInlineSnapshot(` - "sudo elastic-agent enroll -f \\\\ - --fleet-server-es=http://elasticsearch:9200 \\\\ - --fleet-server-service-token=service-token-1 \\\\ + "sudo elastic-agent enroll \\\\ + --fleet-server-es=http://elasticsearch:9200 \\\\ + --fleet-server-service-token=service-token-1 \\\\ --fleet-server-policy=policy-1" `); }); @@ -115,9 +115,8 @@ describe('getInstallCommandForPlatform', () => { expect(res).toMatchInlineSnapshot(` "sudo ./elastic-agent install --url=http://fleetserver:8220 \\\\ - -f \\\\ - --fleet-server-es=http://elasticsearch:9200 \\\\ - --fleet-server-service-token=service-token-1 \\\\ + --fleet-server-es=http://elasticsearch:9200 \\\\ + --fleet-server-service-token=service-token-1 \\\\ --fleet-server-policy=policy-1 \\\\ --certificate-authorities= \\\\ --fleet-server-es-ca= \\\\ @@ -138,9 +137,8 @@ describe('getInstallCommandForPlatform', () => { expect(res).toMatchInlineSnapshot(` ".\\\\elastic-agent.exe install --url=http://fleetserver:8220 \` - -f \` - --fleet-server-es=http://elasticsearch:9200 \` - --fleet-server-service-token=service-token-1 \` + --fleet-server-es=http://elasticsearch:9200 \` + --fleet-server-service-token=service-token-1 \` --fleet-server-policy=policy-1 \` --certificate-authorities= \` --fleet-server-es-ca= \` @@ -161,9 +159,8 @@ describe('getInstallCommandForPlatform', () => { expect(res).toMatchInlineSnapshot(` "sudo elastic-agent enroll --url=http://fleetserver:8220 \\\\ - -f \\\\ - --fleet-server-es=http://elasticsearch:9200 \\\\ - --fleet-server-service-token=service-token-1 \\\\ + --fleet-server-es=http://elasticsearch:9200 \\\\ + --fleet-server-service-token=service-token-1 \\\\ --fleet-server-policy=policy-1 \\\\ --certificate-authorities= \\\\ --fleet-server-es-ca= \\\\ @@ -181,9 +178,9 @@ describe('getInstallCommandForPlatform', () => { ); expect(res).toMatchInlineSnapshot(` - "sudo elastic-agent enroll -f \\\\ - --fleet-server-es=http://elasticsearch:9200 \\\\ - --fleet-server-service-token=service-token-1" + "sudo elastic-agent enroll \\\\ + --fleet-server-es=http://elasticsearch:9200 \\\\ + --fleet-server-service-token=service-token-1" `); }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.ts index e129d7a4d5b4e..f5c40e8071691 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.ts @@ -20,10 +20,12 @@ export function getInstallCommandForPlatform( if (isProductionDeployment && fleetServerHost) { commandArguments += `--url=${fleetServerHost} ${newLineSeparator}\n`; + } else { + commandArguments += ` ${newLineSeparator}\n`; } - commandArguments += ` -f ${newLineSeparator}\n --fleet-server-es=${esHost}`; - commandArguments += ` ${newLineSeparator}\n --fleet-server-service-token=${serviceToken}`; + commandArguments += ` --fleet-server-es=${esHost}`; + commandArguments += ` ${newLineSeparator}\n --fleet-server-service-token=${serviceToken}`; if (policyId) { commandArguments += ` ${newLineSeparator}\n --fleet-server-policy=${policyId}`; } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx index fbac6ad74906d..701d68c0e29e3 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { FormattedMessage, FormattedRelative } from '@kbn/i18n/react'; import { EuiBadge, EuiToolTip } from '@elastic/eui'; -import * as euiVars from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars as euiVars } from '@kbn/ui-shared-deps-src/theme'; import type { Agent } from '../../../types'; @@ -29,7 +29,7 @@ const Status = { ), Inactive: ( - + ), diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/services/agent_status.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/services/agent_status.tsx index 74e9879936d42..8eafcef0dc6de 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/services/agent_status.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/services/agent_status.tsx @@ -7,20 +7,20 @@ import { euiPaletteColorBlindBehindText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import * as euiVars from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars } from '@kbn/ui-shared-deps-src/theme'; import type { SimplifiedAgentStatus } from '../../../types'; const visColors = euiPaletteColorBlindBehindText(); const colorToHexMap = { // using variables as mentioned here https://elastic.github.io/eui/#/guidelines/getting-started - default: euiVars.default.euiColorLightShade, + default: euiLightVars.euiColorLightShade, primary: visColors[1], secondary: visColors[0], accent: visColors[2], warning: visColors[5], danger: visColors[9], - inactive: euiVars.default.euiColorDarkShade, + inactive: euiLightVars.euiColorDarkShade, }; export const AGENT_STATUSES: SimplifiedAgentStatus[] = [ diff --git a/x-pack/plugins/fleet/public/components/enrollment_instructions/manual/index.tsx b/x-pack/plugins/fleet/public/components/enrollment_instructions/manual/index.tsx index 6d4d6a7172534..425fe6c6bd67e 100644 --- a/x-pack/plugins/fleet/public/components/enrollment_instructions/manual/index.tsx +++ b/x-pack/plugins/fleet/public/components/enrollment_instructions/manual/index.tsx @@ -38,9 +38,9 @@ export const ManualInstructions: React.FunctionComponent = ({ const enrollArgs = getfleetServerHostsEnrollArgs(apiKey, fleetServerHosts); - const linuxMacCommand = `sudo ./elastic-agent install -f ${enrollArgs}`; + const linuxMacCommand = `sudo ./elastic-agent install ${enrollArgs}`; - const windowsCommand = `.\\elastic-agent.exe install -f ${enrollArgs}`; + const windowsCommand = `.\\elastic-agent.exe install ${enrollArgs}`; return ( <> diff --git a/x-pack/plugins/fleet/public/components/linked_agent_count.tsx b/x-pack/plugins/fleet/public/components/linked_agent_count.tsx index f9b2727f48935..dcbda2e1445c7 100644 --- a/x-pack/plugins/fleet/public/components/linked_agent_count.tsx +++ b/x-pack/plugins/fleet/public/components/linked_agent_count.tsx @@ -11,7 +11,7 @@ import type { EuiLinkAnchorProps } from '@elastic/eui'; import { EuiLink } from '@elastic/eui'; import { useLink } from '../hooks'; -import { AGENT_SAVED_OBJECT_TYPE } from '../constants'; +import { AGENTS_PREFIX } from '../constants'; /** * Displays the provided `count` number as a link to the Agents list if it is greater than zero @@ -37,7 +37,7 @@ export const LinkedAgentCount = memo< {displayValue} diff --git a/x-pack/plugins/fleet/public/components/package_policy_delete_provider.tsx b/x-pack/plugins/fleet/public/components/package_policy_delete_provider.tsx index e2522f40ef966..7c2703ec8437b 100644 --- a/x-pack/plugins/fleet/public/components/package_policy_delete_provider.tsx +++ b/x-pack/plugins/fleet/public/components/package_policy_delete_provider.tsx @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { useStartServices, sendRequest, sendDeletePackagePolicy, useConfig } from '../hooks'; -import { AGENT_API_ROUTES, AGENT_SAVED_OBJECT_TYPE } from '../../common/constants'; +import { AGENT_API_ROUTES, AGENTS_PREFIX } from '../../common/constants'; import type { AgentPolicy } from '../types'; interface Props { @@ -53,7 +53,7 @@ export const PackagePolicyDeleteProvider: React.FunctionComponent = ({ query: { page: 1, perPage: 1, - kuery: `${AGENT_SAVED_OBJECT_TYPE}.policy_id : ${agentPolicy.id}`, + kuery: `${AGENTS_PREFIX}.policy_id : ${agentPolicy.id}`, }, }); setAgentsCount(data?.total || 0); diff --git a/x-pack/plugins/fleet/public/constants/index.ts b/x-pack/plugins/fleet/public/constants/index.ts index 32dd732c53dec..38b7875c93b3b 100644 --- a/x-pack/plugins/fleet/public/constants/index.ts +++ b/x-pack/plugins/fleet/public/constants/index.ts @@ -12,8 +12,7 @@ export { AGENT_API_ROUTES, SO_SEARCH_LIMIT, AGENT_POLICY_SAVED_OBJECT_TYPE, - AGENT_SAVED_OBJECT_TYPE, - ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, + AGENTS_PREFIX, PACKAGE_POLICY_SAVED_OBJECT_TYPE, FLEET_SERVER_PACKAGE, // Fleet Server index diff --git a/x-pack/plugins/fleet/server/collectors/agent_collectors.ts b/x-pack/plugins/fleet/server/collectors/agent_collectors.ts index 43c15e603a87a..9b7d48328467d 100644 --- a/x-pack/plugins/fleet/server/collectors/agent_collectors.ts +++ b/x-pack/plugins/fleet/server/collectors/agent_collectors.ts @@ -9,7 +9,6 @@ import type { SavedObjectsClient, ElasticsearchClient } from 'kibana/server'; import type { FleetConfigType } from '../../common/types'; import * as AgentService from '../services/agents'; -import { isFleetServerSetup } from '../services/fleet_server'; export interface AgentUsage { total_enrolled: number; @@ -26,7 +25,7 @@ export const getAgentUsage = async ( esClient?: ElasticsearchClient ): Promise => { // TODO: unsure if this case is possible at all. - if (!soClient || !esClient || !(await isFleetServerSetup())) { + if (!soClient || !esClient) { return { total_enrolled: 0, healthy: 0, diff --git a/x-pack/plugins/fleet/server/collectors/fleet_server_collector.ts b/x-pack/plugins/fleet/server/collectors/fleet_server_collector.ts index 47440e791747c..a08ed450b5b30 100644 --- a/x-pack/plugins/fleet/server/collectors/fleet_server_collector.ts +++ b/x-pack/plugins/fleet/server/collectors/fleet_server_collector.ts @@ -10,7 +10,6 @@ import type { SavedObjectsClient, ElasticsearchClient } from 'kibana/server'; import { packagePolicyService, settingsService } from '../services'; import { getAgentStatusForAgentPolicy } from '../services/agents'; -import { isFleetServerSetup } from '../services/fleet_server'; const DEFAULT_USAGE = { total_all_statuses: 0, @@ -36,7 +35,7 @@ export const getFleetServerUsage = async ( soClient?: SavedObjectsClient, esClient?: ElasticsearchClient ): Promise => { - if (!soClient || !esClient || !(await isFleetServerSetup())) { + if (!soClient || !esClient) { return DEFAULT_USAGE; } diff --git a/x-pack/plugins/fleet/server/constants/index.ts b/x-pack/plugins/fleet/server/constants/index.ts index bfb1f3ec433f2..633390c368957 100644 --- a/x-pack/plugins/fleet/server/constants/index.ts +++ b/x-pack/plugins/fleet/server/constants/index.ts @@ -35,14 +35,12 @@ export { PRECONFIGURATION_API_ROUTES, // Saved object types SO_SEARCH_LIMIT, - AGENT_SAVED_OBJECT_TYPE, - AGENT_ACTION_SAVED_OBJECT_TYPE, + AGENTS_PREFIX, AGENT_POLICY_SAVED_OBJECT_TYPE, PACKAGE_POLICY_SAVED_OBJECT_TYPE, OUTPUT_SAVED_OBJECT_TYPE, PACKAGES_SAVED_OBJECT_TYPE, ASSETS_SAVED_OBJECT_TYPE, - ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, // Defaults DEFAULT_AGENT_POLICY, diff --git a/x-pack/plugins/fleet/server/index.test.ts b/x-pack/plugins/fleet/server/index.test.ts deleted file mode 100644 index 724bb9ad91ab6..0000000000000 --- a/x-pack/plugins/fleet/server/index.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { applyDeprecations, configDeprecationFactory } from '@kbn/config'; - -import { configDeprecationsMock } from '../../../../src/core/server/mocks'; - -import { config } from '.'; - -const deprecationContext = configDeprecationsMock.createContext(); - -const applyConfigDeprecations = (settings: Record = {}) => { - if (!config.deprecations) { - throw new Error('Config is not valid no deprecations'); - } - const deprecations = config.deprecations(configDeprecationFactory); - const deprecationMessages: string[] = []; - const migrated = applyDeprecations( - settings, - deprecations.map((deprecation) => ({ - deprecation, - path: '', - context: deprecationContext, - })), - () => - ({ message }) => - deprecationMessages.push(message) - ); - return { - messages: deprecationMessages, - migrated: migrated.config, - }; -}; - -describe('Config depreciation test', () => { - it('should migrate old xpack.ingestManager.fleet settings to xpack.fleet.agents', () => { - const { migrated } = applyConfigDeprecations({ - xpack: { - ingestManager: { - fleet: { enabled: true, elasticsearch: { host: 'http://testes.fr:9200' } }, - }, - }, - }); - - expect(migrated).toMatchInlineSnapshot(` - Object { - "xpack": Object { - "fleet": Object { - "agents": Object { - "elasticsearch": Object { - "hosts": Array [ - "http://testes.fr:9200", - ], - }, - "enabled": true, - }, - }, - }, - } - `); - }); - - it('should support mixing xpack.ingestManager config and xpack.fleet config', () => { - const { migrated } = applyConfigDeprecations({ - xpack: { - ingestManager: { registryUrl: 'http://registrytest.fr' }, - fleet: { registryProxyUrl: 'http://registryProxy.fr' }, - }, - }); - - expect(migrated).toMatchInlineSnapshot(` - Object { - "xpack": Object { - "fleet": Object { - "registryProxyUrl": "http://registryProxy.fr", - "registryUrl": "http://registrytest.fr", - }, - }, - } - `); - }); -}); diff --git a/x-pack/plugins/fleet/server/index.ts b/x-pack/plugins/fleet/server/index.ts index e1ee2652594cc..c3dd408925cf0 100644 --- a/x-pack/plugins/fleet/server/index.ts +++ b/x-pack/plugins/fleet/server/index.ts @@ -44,52 +44,6 @@ export const config: PluginConfigDescriptor = { agents: true, }, deprecations: ({ renameFromRoot, unused, unusedFromRoot }) => [ - // Fleet plugin was named ingestManager before - renameFromRoot('xpack.ingestManager.enabled', 'xpack.fleet.enabled', { level: 'critical' }), - renameFromRoot('xpack.ingestManager.registryUrl', 'xpack.fleet.registryUrl', { - level: 'critical', - }), - renameFromRoot('xpack.ingestManager.registryProxyUrl', 'xpack.fleet.registryProxyUrl', { - level: 'critical', - }), - renameFromRoot('xpack.ingestManager.fleet', 'xpack.ingestManager.agents', { - level: 'critical', - }), - renameFromRoot('xpack.ingestManager.agents.enabled', 'xpack.fleet.agents.enabled', { - level: 'critical', - }), - renameFromRoot('xpack.ingestManager.agents.elasticsearch', 'xpack.fleet.agents.elasticsearch', { - level: 'critical', - }), - renameFromRoot( - 'xpack.ingestManager.agents.tlsCheckDisabled', - 'xpack.fleet.agents.tlsCheckDisabled', - { level: 'critical' } - ), - renameFromRoot( - 'xpack.ingestManager.agents.pollingRequestTimeout', - 'xpack.fleet.agents.pollingRequestTimeout', - { level: 'critical' } - ), - renameFromRoot( - 'xpack.ingestManager.agents.maxConcurrentConnections', - 'xpack.fleet.agents.maxConcurrentConnections', - { level: 'critical' } - ), - renameFromRoot('xpack.ingestManager.agents.kibana', 'xpack.fleet.agents.kibana', { - level: 'critical', - }), - renameFromRoot( - 'xpack.ingestManager.agents.agentPolicyRolloutRateLimitIntervalMs', - 'xpack.fleet.agents.agentPolicyRolloutRateLimitIntervalMs', - { level: 'critical' } - ), - renameFromRoot( - 'xpack.ingestManager.agents.agentPolicyRolloutRateLimitRequestPerInterval', - 'xpack.fleet.agents.agentPolicyRolloutRateLimitRequestPerInterval', - { level: 'critical' } - ), - unusedFromRoot('xpack.ingestManager', { level: 'critical' }), // Unused settings before Fleet server exists unused('agents.kibana', { level: 'critical' }), unused('agents.maxConcurrentConnections', { level: 'critical' }), diff --git a/x-pack/plugins/fleet/server/integration_tests/router.test.ts b/x-pack/plugins/fleet/server/integration_tests/router.test.ts index 55518923e65f2..eb002f5d731d8 100644 --- a/x-pack/plugins/fleet/server/integration_tests/router.test.ts +++ b/x-pack/plugins/fleet/server/integration_tests/router.test.ts @@ -30,7 +30,7 @@ function createXPackRoot(config: {} = {}) { }); } -describe('ingestManager', () => { +describe('fleet', () => { describe('default. manager, EPM, and Fleet all disabled', () => { let root: ReturnType; @@ -64,11 +64,11 @@ describe('ingestManager', () => { let root: ReturnType; beforeAll(async () => { - const ingestManagerConfig = { + const fleetConfig = { enabled: true, }; root = createXPackRoot({ - ingestManager: ingestManagerConfig, + fleet: fleetConfig, }); await root.preboot(); await root.setup(); @@ -103,12 +103,12 @@ describe('ingestManager', () => { let root: ReturnType; beforeAll(async () => { - const ingestManagerConfig = { + const fleetConfig = { enabled: true, epm: { enabled: true }, }; root = createXPackRoot({ - ingestManager: ingestManagerConfig, + fleet: fleetConfig, }); await root.preboot(); await root.setup(); @@ -138,12 +138,12 @@ describe('ingestManager', () => { let root: ReturnType; beforeAll(async () => { - const ingestManagerConfig = { + const fleetConfig = { enabled: true, fleet: { enabled: true }, }; root = createXPackRoot({ - ingestManager: ingestManagerConfig, + fleet: fleetConfig, }); await root.preboot(); await root.setup(); @@ -173,13 +173,13 @@ describe('ingestManager', () => { let root: ReturnType; beforeAll(async () => { - const ingestManagerConfig = { + const fleetConfig = { enabled: true, epm: { enabled: true }, fleet: { enabled: true }, }; root = createXPackRoot({ - ingestManager: ingestManagerConfig, + fleet: fleetConfig, }); await root.preboot(); await root.setup(); diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index 7cc1b8b1cfcc9..d0c73a0fe42a7 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -40,8 +40,6 @@ import { PACKAGE_POLICY_SAVED_OBJECT_TYPE, PACKAGES_SAVED_OBJECT_TYPE, ASSETS_SAVED_OBJECT_TYPE, - AGENT_SAVED_OBJECT_TYPE, - ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, } from './constants'; import { registerSavedObjects, registerEncryptedSavedObjects } from './saved_objects'; @@ -82,7 +80,6 @@ import { import { registerFleetUsageCollector } from './collectors/register'; import { getInstallation, ensureInstalledPackage } from './services/epm/packages'; import { RouterWrappers } from './routes/security'; -import { startFleetServerSetup } from './services/fleet_server'; import { FleetArtifactsClient } from './services/artifacts'; import type { FleetRouter } from './types/request_context'; import { TelemetryEventsSender } from './telemetry/sender'; @@ -131,8 +128,6 @@ const allSavedObjectTypes = [ PACKAGE_POLICY_SAVED_OBJECT_TYPE, PACKAGES_SAVED_OBJECT_TYPE, ASSETS_SAVED_OBJECT_TYPE, - AGENT_SAVED_OBJECT_TYPE, - ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, ]; @@ -335,15 +330,10 @@ export class FleetPlugin }); licenseService.start(this.licensing$); - const fleetServerSetup = startFleetServerSetup(); - this.telemetryEventsSender.start(plugins.telemetry, core); return { - fleetSetupCompleted: () => - new Promise((resolve) => { - Promise.all([fleetServerSetup]).finally(() => resolve()); - }), + fleetSetupCompleted: () => Promise.resolve(), esIndexPatternService: new ESIndexPatternSavedObjectService(), packageService: { getInstallation, diff --git a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts index c3da75183f581..5f9ff51dadf65 100644 --- a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts @@ -7,13 +7,13 @@ import type { TypeOf } from '@kbn/config-schema'; import type { RequestHandler, ResponseHeaders } from 'src/core/server'; -import bluebird from 'bluebird'; +import pMap from 'p-map'; import { safeDump } from 'js-yaml'; import { fullAgentPolicyToYaml } from '../../../common/services'; import { appContextService, agentPolicyService, packagePolicyService } from '../../services'; import { getAgentsByKuery } from '../../services/agents'; -import { AGENT_SAVED_OBJECT_TYPE } from '../../constants'; +import { AGENTS_PREFIX } from '../../constants'; import type { GetAgentPoliciesRequestSchema, GetOneAgentPolicyRequestSchema, @@ -57,14 +57,14 @@ export const getAgentPoliciesHandler: RequestHandler< perPage, }; - await bluebird.map( + await pMap( items, (agentPolicy: GetAgentPoliciesResponseItem) => getAgentsByKuery(esClient, { showInactive: false, perPage: 0, page: 1, - kuery: `${AGENT_SAVED_OBJECT_TYPE}.policy_id:${agentPolicy.id}`, + kuery: `${AGENTS_PREFIX}.policy_id:${agentPolicy.id}`, }).then(({ total: agentTotal }) => (agentPolicy.agents = agentTotal)), { concurrency: 10 } ); diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index 19998c8d8bdbb..3b459c938b5f0 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -14,29 +14,19 @@ import { PACKAGE_POLICY_SAVED_OBJECT_TYPE, PACKAGES_SAVED_OBJECT_TYPE, ASSETS_SAVED_OBJECT_TYPE, - AGENT_SAVED_OBJECT_TYPE, - AGENT_ACTION_SAVED_OBJECT_TYPE, - ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, } from '../constants'; import { - migrateAgentActionToV7100, migrateAgentPolicyToV7100, - migrateAgentToV7100, - migrateEnrollmentApiKeysToV7100, migratePackagePolicyToV7100, migrateSettingsToV7100, } from './migrations/to_v7_10_0'; import { migratePackagePolicyToV7110 } from './migrations/to_v7_11_0'; -import { - migrateAgentPolicyToV7120, - migrateAgentToV7120, - migratePackagePolicyToV7120, -} from './migrations/to_v7_12_0'; +import { migrateAgentPolicyToV7120, migratePackagePolicyToV7120 } from './migrations/to_v7_12_0'; import { migratePackagePolicyToV7130, migrateSettingsToV7130, @@ -75,66 +65,6 @@ const getSavedObjectTypes = ( '7.13.0': migrateSettingsToV7130, }, }, - [AGENT_SAVED_OBJECT_TYPE]: { - name: AGENT_SAVED_OBJECT_TYPE, - hidden: false, - namespaceType: 'agnostic', - management: { - importableAndExportable: false, - }, - mappings: { - properties: { - type: { type: 'keyword' }, - active: { type: 'boolean' }, - enrolled_at: { type: 'date' }, - unenrolled_at: { type: 'date' }, - unenrollment_started_at: { type: 'date' }, - upgraded_at: { type: 'date' }, - upgrade_started_at: { type: 'date' }, - access_api_key_id: { type: 'keyword' }, - version: { type: 'keyword' }, - user_provided_metadata: { type: 'flattened' }, - local_metadata: { type: 'flattened' }, - policy_id: { type: 'keyword' }, - policy_revision: { type: 'integer' }, - last_updated: { type: 'date' }, - last_checkin: { type: 'date' }, - last_checkin_status: { type: 'keyword' }, - default_api_key_id: { type: 'keyword' }, - default_api_key: { type: 'binary' }, - updated_at: { type: 'date' }, - current_error_events: { type: 'text', index: false }, - packages: { type: 'keyword' }, - }, - }, - migrations: { - '7.10.0': migrateAgentToV7100, - '7.12.0': migrateAgentToV7120, - }, - }, - [AGENT_ACTION_SAVED_OBJECT_TYPE]: { - name: AGENT_ACTION_SAVED_OBJECT_TYPE, - hidden: false, - namespaceType: 'agnostic', - management: { - importableAndExportable: false, - }, - mappings: { - properties: { - agent_id: { type: 'keyword' }, - policy_id: { type: 'keyword' }, - policy_revision: { type: 'integer' }, - type: { type: 'keyword' }, - data: { type: 'binary' }, - ack_data: { type: 'text' }, - sent_at: { type: 'date' }, - created_at: { type: 'date' }, - }, - }, - migrations: { - '7.10.0': migrateAgentActionToV7100(encryptedSavedObjects), - }, - }, [AGENT_POLICY_SAVED_OBJECT_TYPE]: { name: AGENT_POLICY_SAVED_OBJECT_TYPE, hidden: false, @@ -167,30 +97,6 @@ const getSavedObjectTypes = ( '7.12.0': migrateAgentPolicyToV7120, }, }, - [ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE]: { - name: ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, - hidden: false, - namespaceType: 'agnostic', - management: { - importableAndExportable: false, - }, - mappings: { - properties: { - name: { type: 'keyword' }, - type: { type: 'keyword' }, - api_key: { type: 'binary' }, - api_key_id: { type: 'keyword' }, - policy_id: { type: 'keyword' }, - created_at: { type: 'date' }, - updated_at: { type: 'date' }, - expire_at: { type: 'date' }, - active: { type: 'boolean' }, - }, - }, - migrations: { - '7.10.0': migrateEnrollmentApiKeysToV7100, - }, - }, [OUTPUT_SAVED_OBJECT_TYPE]: { name: OUTPUT_SAVED_OBJECT_TYPE, hidden: false, @@ -399,48 +305,4 @@ export function registerEncryptedSavedObjects( encryptedSavedObjects: EncryptedSavedObjectsPluginSetup ) { // Encrypted saved objects - encryptedSavedObjects.registerType({ - type: ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, - attributesToEncrypt: new Set(['api_key']), - attributesToExcludeFromAAD: new Set([ - 'name', - 'type', - 'api_key_id', - 'policy_id', - 'created_at', - 'updated_at', - 'expire_at', - 'active', - ]), - }); - encryptedSavedObjects.registerType({ - type: AGENT_SAVED_OBJECT_TYPE, - attributesToEncrypt: new Set(['default_api_key']), - attributesToExcludeFromAAD: new Set([ - 'type', - 'active', - 'enrolled_at', - 'access_api_key_id', - 'version', - 'user_provided_metadata', - 'local_metadata', - 'policy_id', - 'policy_revision', - 'last_updated', - 'last_checkin', - 'last_checkin_status', - 'updated_at', - 'current_error_events', - 'unenrolled_at', - 'unenrollment_started_at', - 'packages', - 'upgraded_at', - 'upgrade_started_at', - ]), - }); - encryptedSavedObjects.registerType({ - type: AGENT_ACTION_SAVED_OBJECT_TYPE, - attributesToEncrypt: new Set(['data']), - attributesToExcludeFromAAD: new Set(['agent_id', 'type', 'sent_at', 'created_at']), - }); } diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_10_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_10_0.ts index 64338690977c9..bb54c55ac75a6 100644 --- a/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_10_0.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_10_0.ts @@ -5,33 +5,9 @@ * 2.0. */ -import type { SavedObjectMigrationFn, SavedObjectUnsanitizedDoc } from 'kibana/server'; +import type { SavedObjectMigrationFn } from 'kibana/server'; -import type { EncryptedSavedObjectsPluginSetup } from '../../../../encrypted_saved_objects/server'; -import type { - Agent, - AgentPolicy, - PackagePolicy, - EnrollmentAPIKey, - Settings, - AgentAction, -} from '../../types'; - -export const migrateAgentToV7100: SavedObjectMigrationFn< - Exclude & { - config_id?: string; - config_revision?: number | null; - }, - Agent -> = (agentDoc) => { - agentDoc.attributes.policy_id = agentDoc.attributes.config_id; - delete agentDoc.attributes.config_id; - - agentDoc.attributes.policy_revision = agentDoc.attributes.config_revision; - delete agentDoc.attributes.config_revision; - - return agentDoc; -}; +import type { AgentPolicy, PackagePolicy, Settings } from '../../types'; export const migrateAgentPolicyToV7100: SavedObjectMigrationFn< Exclude & { @@ -46,18 +22,6 @@ export const migrateAgentPolicyToV7100: SavedObjectMigrationFn< return agentPolicyDoc; }; -export const migrateEnrollmentApiKeysToV7100: SavedObjectMigrationFn< - Exclude & { - config_id?: string; - }, - EnrollmentAPIKey -> = (enrollmentApiKeyDoc) => { - enrollmentApiKeyDoc.attributes.policy_id = enrollmentApiKeyDoc.attributes.config_id; - delete enrollmentApiKeyDoc.attributes.config_id; - - return enrollmentApiKeyDoc; -}; - export const migratePackagePolicyToV7100: SavedObjectMigrationFn< Exclude & { config_id: string; @@ -84,45 +48,3 @@ export const migrateSettingsToV7100: SavedObjectMigrationFn< return settingsDoc; }; - -export const migrateAgentActionToV7100 = ( - encryptedSavedObjects: EncryptedSavedObjectsPluginSetup -): SavedObjectMigrationFn => { - return encryptedSavedObjects.createMigration({ - isMigrationNeededPredicate: ( - agentActionDoc - ): agentActionDoc is SavedObjectUnsanitizedDoc => { - // @ts-expect-error - return agentActionDoc.attributes.type === 'CONFIG_CHANGE'; - }, - migration: (agentActionDoc) => { - let agentActionData; - try { - agentActionData = agentActionDoc.attributes.data - ? JSON.parse(agentActionDoc.attributes.data) - : undefined; - } catch (e) { - // Silently swallow JSON parsing error - } - if (agentActionData && agentActionData.config) { - const { - attributes: { data, ...restOfAttributes }, - } = agentActionDoc; - const { config, ...restOfData } = agentActionData; - return { - ...agentActionDoc, - attributes: { - ...restOfAttributes, - type: 'POLICY_CHANGE', - data: JSON.stringify({ - ...restOfData, - policy: config, - }), - }, - }; - } else { - return agentActionDoc; - } - }, - }); -}; diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_12_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_12_0.ts index ad7a179f50766..fde71388cbbde 100644 --- a/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_12_0.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_12_0.ts @@ -7,18 +7,10 @@ import type { SavedObjectMigrationFn } from 'kibana/server'; -import type { Agent, AgentPolicy } from '../../types'; +import type { AgentPolicy } from '../../types'; export { migratePackagePolicyToV7120 } from './security_solution/to_v7_12_0'; -export const migrateAgentToV7120: SavedObjectMigrationFn = ( - agentDoc -) => { - delete agentDoc.attributes.shared_id; - - return agentDoc; -}; - export const migrateAgentPolicyToV7120: SavedObjectMigrationFn< Exclude, AgentPolicy diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index b1a45b5a92421..bb9360b834b37 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -20,7 +20,7 @@ import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; import type { AuthenticatedUser } from '../../../security/server'; import { AGENT_POLICY_SAVED_OBJECT_TYPE, - AGENT_SAVED_OBJECT_TYPE, + AGENTS_PREFIX, PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, } from '../constants'; import type { @@ -626,7 +626,7 @@ class AgentPolicyService { showInactive: false, perPage: 0, page: 1, - kuery: `${AGENT_SAVED_OBJECT_TYPE}.policy_id:${id}`, + kuery: `${AGENTS_PREFIX}.policy_id:${id}`, }); if (total > 0) { diff --git a/x-pack/plugins/fleet/server/services/agents/crud.ts b/x-pack/plugins/fleet/server/services/agents/crud.ts index b8d7c284309df..516acf5a120de 100644 --- a/x-pack/plugins/fleet/server/services/agents/crud.ts +++ b/x-pack/plugins/fleet/server/services/agents/crud.ts @@ -16,7 +16,7 @@ import type { AgentSOAttributes, Agent, BulkActionResult, ListWithKuery } from ' import { appContextService, agentPolicyService } from '../../services'; import type { FleetServerAgent } from '../../../common'; import { isAgentUpgradeable, SO_SEARCH_LIMIT } from '../../../common'; -import { AGENT_SAVED_OBJECT_TYPE, AGENTS_INDEX } from '../../constants'; +import { AGENTS_PREFIX, AGENTS_INDEX } from '../../constants'; import { escapeSearchQueryPhrase, normalizeKuery } from '../saved_object'; import { IngestManagerError, isESClientError, AgentNotFoundError } from '../../errors'; @@ -176,7 +176,7 @@ export async function countInactiveAgents( const filters = [INACTIVE_AGENT_CONDITION]; if (kuery && kuery !== '') { - filters.push(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery)); + filters.push(normalizeKuery(AGENTS_PREFIX, kuery)); } const kueryNode = _joinFilters(filters); diff --git a/x-pack/plugins/fleet/server/services/agents/crud_so.ts b/x-pack/plugins/fleet/server/services/agents/crud_so.ts deleted file mode 100644 index aa3cb4e4ec1a7..0000000000000 --- a/x-pack/plugins/fleet/server/services/agents/crud_so.ts +++ /dev/null @@ -1,255 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import Boom from '@hapi/boom'; -import type { SavedObjectsBulkUpdateObject, SavedObjectsClientContract } from 'src/core/server'; - -import type { KueryNode } from '@kbn/es-query'; -import { fromKueryExpression } from '@kbn/es-query'; - -import { isAgentUpgradeable } from '../../../common'; -import { AGENT_SAVED_OBJECT_TYPE } from '../../constants'; -import type { AgentSOAttributes, Agent, ListWithKuery } from '../../types'; -import { escapeSearchQueryPhrase, normalizeKuery, findAllSOs } from '../saved_object'; -import { appContextService } from '../../services'; - -import { savedObjectToAgent } from './saved_objects'; - -const ACTIVE_AGENT_CONDITION = `${AGENT_SAVED_OBJECT_TYPE}.attributes.active:true`; -const INACTIVE_AGENT_CONDITION = `NOT (${ACTIVE_AGENT_CONDITION})`; - -function _joinFilters(filters: Array) { - return filters - .filter((filter) => filter !== undefined) - .reduce( - ( - acc: KueryNode | undefined, - kuery: string | KueryNode | undefined - ): KueryNode | undefined => { - if (kuery === undefined) { - return acc; - } - const kueryNode: KueryNode = - typeof kuery === 'string' - ? fromKueryExpression(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery)) - : kuery; - - if (!acc) { - return kueryNode; - } - - return { - type: 'function', - function: 'and', - arguments: [acc, kueryNode], - }; - }, - undefined as KueryNode | undefined - ); -} - -export async function listAgents( - soClient: SavedObjectsClientContract, - options: ListWithKuery & { - showInactive: boolean; - } -): Promise<{ - agents: Agent[]; - total: number; - page: number; - perPage: number; -}> { - const { - page = 1, - perPage = 20, - sortField = 'enrolled_at', - sortOrder = 'desc', - kuery, - showInactive = false, - showUpgradeable, - } = options; - const filters: Array = []; - - if (kuery && kuery !== '') { - filters.push(kuery); - } - - if (showInactive === false) { - filters.push(ACTIVE_AGENT_CONDITION); - } - try { - let { saved_objects: agentSOs, total } = await soClient.find({ - type: AGENT_SAVED_OBJECT_TYPE, - filter: _joinFilters(filters) || '', - sortField, - sortOrder, - page, - perPage, - }); - // filtering for a range on the version string will not work, - // nor does filtering on a flattened field (local_metadata), so filter here - if (showUpgradeable) { - agentSOs = agentSOs.filter((agent) => - isAgentUpgradeable(savedObjectToAgent(agent), appContextService.getKibanaVersion()) - ); - total = agentSOs.length; - } - - return { - agents: agentSOs.map(savedObjectToAgent), - total, - page, - perPage, - }; - } catch (e) { - if (e.output?.payload?.message?.startsWith('The key is empty')) { - return { - agents: [], - total: 0, - page: 0, - perPage: 0, - }; - } else { - throw e; - } - } -} - -export async function listAllAgents( - soClient: SavedObjectsClientContract, - options: Omit & { - showInactive: boolean; - } -): Promise<{ - agents: Agent[]; - total: number; -}> { - const { sortField = 'enrolled_at', sortOrder = 'desc', kuery, showInactive = false } = options; - const filters = []; - - if (kuery && kuery !== '') { - filters.push(kuery); - } - - if (showInactive === false) { - filters.push(ACTIVE_AGENT_CONDITION); - } - - const { saved_objects: agentSOs, total } = await findAllSOs(soClient, { - type: AGENT_SAVED_OBJECT_TYPE, - kuery: _joinFilters(filters), - sortField, - sortOrder, - }); - - return { - agents: agentSOs.map(savedObjectToAgent), - total, - }; -} - -export async function countInactiveAgents( - soClient: SavedObjectsClientContract, - options: Pick -): Promise { - const { kuery } = options; - const filters = [INACTIVE_AGENT_CONDITION]; - - if (kuery && kuery !== '') { - filters.push(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery)); - } - - const { total } = await soClient.find({ - type: AGENT_SAVED_OBJECT_TYPE, - filter: _joinFilters(filters), - perPage: 0, - }); - - return total; -} - -export async function getAgent(soClient: SavedObjectsClientContract, agentId: string) { - const agent = savedObjectToAgent( - await soClient.get(AGENT_SAVED_OBJECT_TYPE, agentId) - ); - return agent; -} - -export async function getAgents(soClient: SavedObjectsClientContract, agentIds: string[]) { - const agentSOs = await soClient.bulkGet( - agentIds.map((agentId) => ({ - id: agentId, - type: AGENT_SAVED_OBJECT_TYPE, - })) - ); - const agents = agentSOs.saved_objects.map(savedObjectToAgent); - return agents; -} - -export async function getAgentByAccessAPIKeyId( - soClient: SavedObjectsClientContract, - accessAPIKeyId: string -): Promise { - const response = await soClient.find({ - type: AGENT_SAVED_OBJECT_TYPE, - searchFields: ['access_api_key_id'], - search: escapeSearchQueryPhrase(accessAPIKeyId), - }); - const [agent] = response.saved_objects.map(savedObjectToAgent); - - if (!agent) { - throw Boom.notFound('Agent not found'); - } - if (agent.access_api_key_id !== accessAPIKeyId) { - throw new Error('Agent api key id is not matching'); - } - if (!agent.active) { - throw Boom.forbidden('Agent inactive'); - } - - return agent; -} - -export async function updateAgent( - soClient: SavedObjectsClientContract, - agentId: string, - data: Partial -) { - await soClient.update(AGENT_SAVED_OBJECT_TYPE, agentId, data); -} - -export async function bulkUpdateAgents( - soClient: SavedObjectsClientContract, - updateData: Array<{ - agentId: string; - data: Partial; - }> -) { - const updates: Array> = updateData.map( - ({ agentId, data }) => ({ - type: AGENT_SAVED_OBJECT_TYPE, - id: agentId, - attributes: data, - }) - ); - - const res = await soClient.bulkUpdate(updates); - - return { - items: res.saved_objects.map((so) => ({ - id: so.id, - success: !so.error, - error: so.error, - })), - }; -} - -export async function deleteAgent(soClient: SavedObjectsClientContract, agentId: string) { - await soClient.update(AGENT_SAVED_OBJECT_TYPE, agentId, { - active: false, - }); -} diff --git a/x-pack/plugins/fleet/server/services/agents/status.ts b/x-pack/plugins/fleet/server/services/agents/status.ts index ee30843e74e1a..5c5176ec41352 100644 --- a/x-pack/plugins/fleet/server/services/agents/status.ts +++ b/x-pack/plugins/fleet/server/services/agents/status.ts @@ -11,7 +11,7 @@ import pMap from 'p-map'; import type { KueryNode } from '@kbn/es-query'; import { fromKueryExpression } from '@kbn/es-query'; -import { AGENT_SAVED_OBJECT_TYPE } from '../../constants'; +import { AGENTS_PREFIX } from '../../constants'; import type { AgentStatus } from '../../types'; import { AgentStatusKueryHelper } from '../../../common/services'; @@ -70,8 +70,8 @@ export async function getAgentStatusForAgentPolicy( ...[ kuery, filterKuery, - `${AGENT_SAVED_OBJECT_TYPE}.attributes.active:true`, - agentPolicyId ? `${AGENT_SAVED_OBJECT_TYPE}.policy_id:"${agentPolicyId}"` : undefined, + `${AGENTS_PREFIX}.attributes.active:true`, + agentPolicyId ? `${AGENTS_PREFIX}.policy_id:"${agentPolicyId}"` : undefined, ] ), }), diff --git a/x-pack/plugins/fleet/server/services/agents/update.ts b/x-pack/plugins/fleet/server/services/agents/update.ts index 74386efe65613..cbe7853425fa6 100644 --- a/x-pack/plugins/fleet/server/services/agents/update.ts +++ b/x-pack/plugins/fleet/server/services/agents/update.ts @@ -7,7 +7,7 @@ import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server'; -import { AGENT_SAVED_OBJECT_TYPE } from '../../constants'; +import { AGENTS_PREFIX } from '../../constants'; import { getAgentsByKuery } from './crud'; import { unenrollAgent } from './unenroll'; @@ -21,7 +21,7 @@ export async function unenrollForAgentPolicyId( let page = 1; while (hasMore) { const { agents } = await getAgentsByKuery(esClient, { - kuery: `${AGENT_SAVED_OBJECT_TYPE}.policy_id:"${policyId}"`, + kuery: `${AGENTS_PREFIX}.policy_id:"${policyId}"`, page: page++, perPage: 1000, showInactive: false, diff --git a/x-pack/plugins/fleet/server/services/agents/upgrade.ts b/x-pack/plugins/fleet/server/services/agents/upgrade.ts index 988d3c63223f4..ce5536df359ba 100644 --- a/x-pack/plugins/fleet/server/services/agents/upgrade.ts +++ b/x-pack/plugins/fleet/server/services/agents/upgrade.ts @@ -7,8 +7,7 @@ import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server'; -import type { Agent, AgentAction, AgentActionSOAttributes, BulkActionResult } from '../../types'; -import { AGENT_ACTION_SAVED_OBJECT_TYPE } from '../../constants'; +import type { Agent, BulkActionResult } from '../../types'; import { agentPolicyService } from '../../services'; import { AgentReassignmentError, @@ -68,23 +67,6 @@ export async function sendUpgradeAgentAction({ }); } -export async function ackAgentUpgraded( - soClient: SavedObjectsClientContract, - esClient: ElasticsearchClient, - agentAction: AgentAction -) { - const { - attributes: { ack_data: ackData }, - } = await soClient.get(AGENT_ACTION_SAVED_OBJECT_TYPE, agentAction.id); - if (!ackData) throw new Error('data missing from UPGRADE action'); - const { version } = JSON.parse(ackData); - if (!version) throw new Error('version missing from UPGRADE action'); - await updateAgent(esClient, agentAction.agent_id, { - upgraded_at: new Date().toISOString(), - upgrade_started_at: null, - }); -} - export async function sendUpgradeAgentsActions( soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, diff --git a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key_so.ts b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key_so.ts deleted file mode 100644 index a2b40200fe136..0000000000000 --- a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key_so.ts +++ /dev/null @@ -1,72 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { SavedObjectsClientContract, SavedObject } from 'src/core/server'; - -import type { EnrollmentAPIKey, EnrollmentAPIKeySOAttributes } from '../../types'; -import { ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE } from '../../constants'; -import { appContextService } from '../app_context'; -import { normalizeKuery } from '../saved_object'; - -export async function listEnrollmentApiKeys( - soClient: SavedObjectsClientContract, - options: { - page?: number; - perPage?: number; - kuery?: string; - showInactive?: boolean; - } -): Promise<{ items: EnrollmentAPIKey[]; total: any; page: any; perPage: any }> { - const { page = 1, perPage = 20, kuery } = options; - - // eslint-disable-next-line @typescript-eslint/naming-convention - const { saved_objects, total } = await soClient.find({ - type: ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, - page, - perPage, - sortField: 'created_at', - sortOrder: 'desc', - filter: - kuery && kuery !== '' - ? normalizeKuery(ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, kuery) - : undefined, - }); - - const items = saved_objects.map(savedObjectToEnrollmentApiKey); - - return { - items, - total, - page, - perPage, - }; -} - -export async function getEnrollmentAPIKey(soClient: SavedObjectsClientContract, id: string) { - const so = await appContextService - .getEncryptedSavedObjects() - .getDecryptedAsInternalUser( - ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, - id - ); - return savedObjectToEnrollmentApiKey(so); -} - -function savedObjectToEnrollmentApiKey({ - error, - attributes, - id, -}: SavedObject): EnrollmentAPIKey { - if (error) { - throw new Error(error.message); - } - - return { - id, - ...attributes, - }; -} diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts new file mode 100644 index 0000000000000..a9bb235c22cb8 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts @@ -0,0 +1,252 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { savedObjectsClientMock } from 'src/core/server/mocks'; + +import type { ElasticsearchClient } from 'kibana/server'; + +import * as Registry from '../registry'; + +import { sendTelemetryEvents } from '../../upgrade_sender'; + +import { licenseService } from '../../license'; + +import { installPackage } from './install'; +import * as install from './_install_package'; +import * as obj from './index'; + +jest.mock('../../app_context', () => { + return { + appContextService: { + getLogger: jest.fn(() => { + return { error: jest.fn(), debug: jest.fn(), warn: jest.fn() }; + }), + getTelemetryEventsSender: jest.fn(), + }, + }; +}); +jest.mock('./index'); +jest.mock('../registry'); +jest.mock('../../upgrade_sender'); +jest.mock('../../license'); +jest.mock('../../upgrade_sender'); +jest.mock('./cleanup'); +jest.mock('./_install_package', () => { + return { + _installPackage: jest.fn(() => Promise.resolve()), + }; +}); +jest.mock('../kibana/index_pattern/install', () => { + return { + installIndexPatterns: jest.fn(() => Promise.resolve()), + }; +}); +jest.mock('../archive', () => { + return { + parseAndVerifyArchiveEntries: jest.fn(() => + Promise.resolve({ packageInfo: { name: 'apache', version: '1.3.0' } }) + ), + unpackBufferToCache: jest.fn(), + setPackageInfo: jest.fn(), + }; +}); + +describe('install', () => { + beforeEach(() => { + jest.spyOn(Registry, 'splitPkgKey').mockImplementation((pkgKey: string) => { + const [pkgName, pkgVersion] = pkgKey.split('-'); + return { pkgName, pkgVersion }; + }); + jest + .spyOn(Registry, 'fetchFindLatestPackage') + .mockImplementation(() => Promise.resolve({ version: '1.3.0' } as any)); + jest + .spyOn(Registry, 'getRegistryPackage') + .mockImplementation(() => Promise.resolve({ packageInfo: { license: 'basic' } } as any)); + }); + + describe('registry', () => { + it('should send telemetry on install failure, out of date', async () => { + await installPackage({ + installSource: 'registry', + pkgkey: 'apache-1.1.0', + savedObjectsClient: savedObjectsClientMock.create(), + esClient: {} as ElasticsearchClient, + }); + + expect(sendTelemetryEvents).toHaveBeenCalledWith(expect.anything(), undefined, { + currentVersion: 'not_installed', + dryRun: false, + errorMessage: 'apache-1.1.0 is out-of-date and cannot be installed or updated', + eventType: 'package-install', + installType: 'install', + newVersion: '1.1.0', + packageName: 'apache', + status: 'failure', + }); + }); + + it('should send telemetry on install failure, license error', async () => { + jest.spyOn(licenseService, 'hasAtLeast').mockReturnValue(false); + await installPackage({ + installSource: 'registry', + pkgkey: 'apache-1.3.0', + savedObjectsClient: savedObjectsClientMock.create(), + esClient: {} as ElasticsearchClient, + }); + + expect(sendTelemetryEvents).toHaveBeenCalledWith(expect.anything(), undefined, { + currentVersion: 'not_installed', + dryRun: false, + errorMessage: 'Requires basic license', + eventType: 'package-install', + installType: 'install', + newVersion: '1.3.0', + packageName: 'apache', + status: 'failure', + }); + }); + + it('should send telemetry on install success', async () => { + jest.spyOn(licenseService, 'hasAtLeast').mockReturnValue(true); + await installPackage({ + installSource: 'registry', + pkgkey: 'apache-1.3.0', + savedObjectsClient: savedObjectsClientMock.create(), + esClient: {} as ElasticsearchClient, + }); + + expect(sendTelemetryEvents).toHaveBeenCalledWith(expect.anything(), undefined, { + currentVersion: 'not_installed', + dryRun: false, + eventType: 'package-install', + installType: 'install', + newVersion: '1.3.0', + packageName: 'apache', + status: 'success', + }); + }); + + it('should send telemetry on update success', async () => { + jest + .spyOn(obj, 'getInstallationObject') + .mockImplementationOnce(() => Promise.resolve({ attributes: { version: '1.2.0' } } as any)); + jest.spyOn(licenseService, 'hasAtLeast').mockReturnValue(true); + await installPackage({ + installSource: 'registry', + pkgkey: 'apache-1.3.0', + savedObjectsClient: savedObjectsClientMock.create(), + esClient: {} as ElasticsearchClient, + }); + + expect(sendTelemetryEvents).toHaveBeenCalledWith(expect.anything(), undefined, { + currentVersion: '1.2.0', + dryRun: false, + eventType: 'package-install', + installType: 'update', + newVersion: '1.3.0', + packageName: 'apache', + status: 'success', + }); + }); + + it('should send telemetry on install failure, async error', async () => { + jest + .spyOn(install, '_installPackage') + .mockImplementation(() => Promise.reject(new Error('error'))); + jest.spyOn(licenseService, 'hasAtLeast').mockReturnValue(true); + await installPackage({ + installSource: 'registry', + pkgkey: 'apache-1.3.0', + savedObjectsClient: savedObjectsClientMock.create(), + esClient: {} as ElasticsearchClient, + }); + + expect(sendTelemetryEvents).toHaveBeenCalledWith(expect.anything(), undefined, { + currentVersion: 'not_installed', + dryRun: false, + errorMessage: 'error', + eventType: 'package-install', + installType: 'install', + newVersion: '1.3.0', + packageName: 'apache', + status: 'failure', + }); + }); + }); + + describe('upload', () => { + it('should send telemetry on install failure', async () => { + jest + .spyOn(obj, 'getInstallationObject') + .mockImplementationOnce(() => Promise.resolve({ attributes: { version: '1.2.0' } } as any)); + await installPackage({ + installSource: 'upload', + archiveBuffer: {} as Buffer, + contentType: '', + savedObjectsClient: savedObjectsClientMock.create(), + esClient: {} as ElasticsearchClient, + }); + + expect(sendTelemetryEvents).toHaveBeenCalledWith(expect.anything(), undefined, { + currentVersion: '1.2.0', + dryRun: false, + errorMessage: + 'Package upload only supports fresh installations. Package apache is already installed, please uninstall first.', + eventType: 'package-install', + installType: 'update', + newVersion: '1.3.0', + packageName: 'apache', + status: 'failure', + }); + }); + + it('should send telemetry on install success', async () => { + await installPackage({ + installSource: 'upload', + archiveBuffer: {} as Buffer, + contentType: '', + savedObjectsClient: savedObjectsClientMock.create(), + esClient: {} as ElasticsearchClient, + }); + + expect(sendTelemetryEvents).toHaveBeenCalledWith(expect.anything(), undefined, { + currentVersion: 'not_installed', + dryRun: false, + eventType: 'package-install', + installType: 'install', + newVersion: '1.3.0', + packageName: 'apache', + status: 'success', + }); + }); + + it('should send telemetry on install failure, async error', async () => { + jest + .spyOn(install, '_installPackage') + .mockImplementation(() => Promise.reject(new Error('error'))); + await installPackage({ + installSource: 'upload', + archiveBuffer: {} as Buffer, + contentType: '', + savedObjectsClient: savedObjectsClientMock.create(), + esClient: {} as ElasticsearchClient, + }); + + expect(sendTelemetryEvents).toHaveBeenCalledWith(expect.anything(), undefined, { + currentVersion: 'not_installed', + dryRun: false, + errorMessage: 'error', + eventType: 'package-install', + installType: 'install', + newVersion: '1.3.0', + packageName: 'apache', + status: 'failure', + }); + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.ts index f57965614adc6..42f4663dc21e3 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -41,6 +41,9 @@ import { toAssetReference } from '../kibana/assets/install'; import type { ArchiveAsset } from '../kibana/assets/install'; import { installIndexPatterns } from '../kibana/index_pattern/install'; +import type { PackageUpdateEvent } from '../../upgrade_sender'; +import { sendTelemetryEvents, UpdateEventType } from '../../upgrade_sender'; + import { isUnremovablePackage, getInstallation, getInstallationObject } from './index'; import { removeInstallation } from './remove'; import { getPackageSavedObjects } from './get'; @@ -203,6 +206,26 @@ interface InstallRegistryPackageParams { force?: boolean; } +function getTelemetryEvent(pkgName: string, pkgVersion: string): PackageUpdateEvent { + return { + packageName: pkgName, + currentVersion: 'unknown', + newVersion: pkgVersion, + status: 'failure', + dryRun: false, + eventType: UpdateEventType.PACKAGE_INSTALL, + installType: 'unknown', + }; +} + +function sendEvent(telemetryEvent: PackageUpdateEvent) { + sendTelemetryEvents( + appContextService.getLogger(), + appContextService.getTelemetryEventsSender(), + telemetryEvent + ); +} + async function installPackageFromRegistry({ savedObjectsClient, pkgkey, @@ -216,6 +239,8 @@ async function installPackageFromRegistry({ // if an error happens during getInstallType, report that we don't know let installType: InstallType = 'unknown'; + const telemetryEvent: PackageUpdateEvent = getTelemetryEvent(pkgName, pkgVersion); + try { // get the currently installed package const installedPkg = await getInstallationObject({ savedObjectsClient, pkgName }); @@ -248,6 +273,9 @@ async function installPackageFromRegistry({ } } + telemetryEvent.installType = installType; + telemetryEvent.currentVersion = installedPkg?.attributes.version || 'not_installed'; + // if the requested version is out-of-date of the latest package version, check if we allow it // if we don't allow it, return an error if (semverLt(pkgVersion, latestPackage.version)) { @@ -267,7 +295,12 @@ async function installPackageFromRegistry({ const { paths, packageInfo } = await Registry.getRegistryPackage(pkgName, pkgVersion); if (!licenseService.hasAtLeast(packageInfo.license || 'basic')) { - return { error: new Error(`Requires ${packageInfo.license} license`), installType }; + const err = new Error(`Requires ${packageInfo.license} license`); + sendEvent({ + ...telemetryEvent, + errorMessage: err.message, + }); + return { error: err, installType }; } // try installing the package, if there was an error, call error handler and rethrow @@ -287,6 +320,10 @@ async function installPackageFromRegistry({ pkgName: packageInfo.name, currentVersion: packageInfo.version, }); + sendEvent({ + ...telemetryEvent, + status: 'success', + }); return { assets, status: 'installed', installType }; }) .catch(async (err: Error) => { @@ -299,9 +336,17 @@ async function installPackageFromRegistry({ installedPkg, esClient, }); + sendEvent({ + ...telemetryEvent, + errorMessage: err.message, + }); return { error: err, installType }; }); } catch (e) { + sendEvent({ + ...telemetryEvent, + errorMessage: e.message, + }); return { error: e, installType, @@ -324,6 +369,7 @@ async function installPackageByUpload({ }: InstallUploadedArchiveParams): Promise { // if an error happens during getInstallType, report that we don't know let installType: InstallType = 'unknown'; + const telemetryEvent: PackageUpdateEvent = getTelemetryEvent('', ''); try { const { packageInfo } = await parseAndVerifyArchiveEntries(archiveBuffer, contentType); @@ -333,6 +379,12 @@ async function installPackageByUpload({ }); installType = getInstallType({ pkgVersion: packageInfo.version, installedPkg }); + + telemetryEvent.packageName = packageInfo.name; + telemetryEvent.newVersion = packageInfo.version; + telemetryEvent.installType = installType; + telemetryEvent.currentVersion = installedPkg?.attributes.version || 'not_installed'; + if (installType !== 'install') { throw new PackageOperationNotSupportedError( `Package upload only supports fresh installations. Package ${packageInfo.name} is already installed, please uninstall first.` @@ -364,12 +416,24 @@ async function installPackageByUpload({ installSource, }) .then((assets) => { + sendEvent({ + ...telemetryEvent, + status: 'success', + }); return { assets, status: 'installed', installType }; }) .catch(async (err: Error) => { + sendEvent({ + ...telemetryEvent, + errorMessage: err.message, + }); return { error: err, installType }; }); } catch (e) { + sendEvent({ + ...telemetryEvent, + errorMessage: e.message, + }); return { error: e, installType }; } } diff --git a/x-pack/plugins/fleet/server/services/fleet_server/index.ts b/x-pack/plugins/fleet/server/services/fleet_server/index.ts index 0d386b9ba4995..55b0fb0dff225 100644 --- a/x-pack/plugins/fleet/server/services/fleet_server/index.ts +++ b/x-pack/plugins/fleet/server/services/fleet_server/index.ts @@ -6,23 +6,9 @@ */ import type { ElasticsearchClient } from 'kibana/server'; -import { first } from 'rxjs/operators'; -import { appContextService } from '../app_context'; -import { licenseService } from '../license'; import { FLEET_SERVER_SERVERS_INDEX } from '../../constants'; -import { runFleetServerMigration } from './saved_object_migrations'; - -let _isFleetServerSetup = false; -let _isPending = false; -let _status: Promise | undefined; -let _onResolve: (arg?: any) => void; - -export function isFleetServerSetup() { - return _isFleetServerSetup; -} - /** * Check if at least one fleet server is connected */ @@ -35,48 +21,3 @@ export async function hasFleetServers(esClient: ElasticsearchClient) { // @ts-expect-error value is number | TotalHits return res.body.hits.total.value > 0; } - -export async function awaitIfFleetServerSetupPending() { - if (!_isPending) { - return; - } - - return _status; -} - -export async function startFleetServerSetup() { - _isPending = true; - _status = new Promise((resolve) => { - _onResolve = resolve; - }); - const logger = appContextService.getLogger(); - - // Check for security - if (!appContextService.hasSecurity()) { - // Fleet will not work if security is not enabled - logger?.warn('Fleet requires the security plugin to be enabled.'); - return; - } - - // Log information about custom registry URL - const customUrl = appContextService.getConfig()?.registryUrl; - if (customUrl) { - logger.info( - `Custom registry url is an experimental feature and is unsupported. Using custom registry at ${customUrl}` - ); - } - - try { - // We need licence to be initialized before using the SO service. - await licenseService.getLicenseInformation$()?.pipe(first())?.toPromise(); - await runFleetServerMigration(); - _isFleetServerSetup = true; - } catch (err) { - logger?.error('Setup for central management of agents failed.'); - logger?.error(err); - } - _isPending = false; - if (_onResolve) { - _onResolve(); - } -} diff --git a/x-pack/plugins/fleet/server/services/fleet_server/saved_object_migrations.ts b/x-pack/plugins/fleet/server/services/fleet_server/saved_object_migrations.ts deleted file mode 100644 index bbaf9c9479eb4..0000000000000 --- a/x-pack/plugins/fleet/server/services/fleet_server/saved_object_migrations.ts +++ /dev/null @@ -1,201 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { isBoom } from '@hapi/boom'; -import type { KibanaRequest } from 'src/core/server'; - -import { - ENROLLMENT_API_KEYS_INDEX, - ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, - AGENT_POLICY_INDEX, - AGENTS_INDEX, - AGENT_SAVED_OBJECT_TYPE, - SO_SEARCH_LIMIT, -} from '../../../common'; -import type { - FleetServerEnrollmentAPIKey, - AgentSOAttributes, - FleetServerAgent, -} from '../../../common'; -import { listEnrollmentApiKeys, getEnrollmentAPIKey } from '../api_keys/enrollment_api_key_so'; -import { appContextService } from '../app_context'; -import { agentPolicyService } from '../agent_policy'; -import { invalidateAPIKeys } from '../api_keys'; -import { settingsService } from '..'; - -export async function runFleetServerMigration() { - await settingsService.settingsSetup(getInternalUserSOClient()); - await Promise.all([migrateEnrollmentApiKeys(), migrateAgentPolicies(), migrateAgents()]); -} - -function getInternalUserSOClient() { - const fakeRequest = { - headers: {}, - getBasePath: () => '', - path: '/', - route: { settings: {} }, - url: { - href: '/', - }, - raw: { - req: { - url: '/', - }, - }, - } as unknown as KibanaRequest; - - return appContextService.getInternalUserSOClient(fakeRequest); -} - -async function migrateAgents() { - const esClient = appContextService.getInternalUserESClient(); - const soClient = getInternalUserSOClient(); - const logger = appContextService.getLogger(); - let hasMore = true; - - let hasAgents = false; - - while (hasMore) { - const res = await soClient.find({ - type: AGENT_SAVED_OBJECT_TYPE, - page: 1, - perPage: 100, - }); - - if (res.total === 0) { - hasMore = false; - } else { - hasAgents = true; - } - - for (const so of res.saved_objects) { - try { - const { attributes } = await appContextService - .getEncryptedSavedObjects() - .getDecryptedAsInternalUser(AGENT_SAVED_OBJECT_TYPE, so.id); - - await invalidateAPIKeys( - [attributes.access_api_key_id, attributes.default_api_key_id].filter( - (keyId): keyId is string => keyId !== undefined - ) - ).catch((error) => { - logger.error(`Invalidating API keys for agent ${so.id} failed: ${error.message}`); - }); - - const body: FleetServerAgent = { - type: attributes.type, - active: false, - enrolled_at: attributes.enrolled_at, - unenrolled_at: new Date().toISOString(), - unenrollment_started_at: attributes.unenrollment_started_at, - upgraded_at: attributes.upgraded_at, - upgrade_started_at: attributes.upgrade_started_at, - access_api_key_id: attributes.access_api_key_id, - user_provided_metadata: attributes.user_provided_metadata, - local_metadata: attributes.local_metadata, - policy_id: attributes.policy_id, - policy_revision_idx: attributes.policy_revision || undefined, - last_checkin: attributes.last_checkin, - last_checkin_status: attributes.last_checkin_status, - default_api_key_id: attributes.default_api_key_id, - default_api_key: attributes.default_api_key, - packages: attributes.packages, - }; - await esClient.create({ - index: AGENTS_INDEX, - body, - id: so.id, - refresh: 'wait_for', - }); - - await soClient.delete(AGENT_SAVED_OBJECT_TYPE, so.id); - } catch (error) { - // swallow 404 error has multiple Kibana can run the migration at the same time - if (!is404Error(error)) { - throw error; - } - } - } - } - - // Update settings to show migration modal - if (hasAgents) { - await settingsService.saveSettings(soClient, { - has_seen_fleet_migration_notice: false, - }); - } -} - -async function migrateEnrollmentApiKeys() { - const esClient = appContextService.getInternalUserESClient(); - const soClient = getInternalUserSOClient(); - let hasMore = true; - while (hasMore) { - const res = await listEnrollmentApiKeys(soClient, { - page: 1, - perPage: 100, - }); - if (res.total === 0) { - hasMore = false; - } - for (const item of res.items) { - try { - const key = await getEnrollmentAPIKey(soClient, item.id); - - const body: FleetServerEnrollmentAPIKey = { - api_key: key.api_key, - api_key_id: key.api_key_id, - active: key.active, - created_at: key.created_at, - name: key.name, - policy_id: key.policy_id, - }; - await esClient.create({ - index: ENROLLMENT_API_KEYS_INDEX, - body, - id: key.id, - refresh: 'wait_for', - }); - - await soClient.delete(ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, key.id); - } catch (error) { - // swallow 404 error has multiple Kibana can run the migration at the same time - if (!is404Error(error)) { - throw error; - } - } - } - } -} - -async function migrateAgentPolicies() { - const esClient = appContextService.getInternalUserESClient(); - const soClient = getInternalUserSOClient(); - const { items: agentPolicies } = await agentPolicyService.list(soClient, { - perPage: SO_SEARCH_LIMIT, - }); - - await Promise.all( - agentPolicies.map(async (agentPolicy) => { - const res = await esClient.search({ - index: AGENT_POLICY_INDEX, - q: `policy_id:${agentPolicy.id}`, - track_total_hits: true, - ignore_unavailable: true, - }); - - // @ts-expect-error value is number | TotalHits - if (res.body.hits.total.value === 0) { - return agentPolicyService.createFleetServerPolicy(soClient, agentPolicy.id); - } - }) - ); -} - -function is404Error(error: any) { - return isBoom(error) && error.output.statusCode === 404; -} diff --git a/x-pack/plugins/fleet/server/services/package_policy.test.ts b/x-pack/plugins/fleet/server/services/package_policy.test.ts index dcc00251e70f4..36976bea4a970 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.test.ts @@ -134,7 +134,7 @@ jest.mock('./epm/packages/cleanup', () => { }; }); -jest.mock('./upgrade_usage', () => { +jest.mock('./upgrade_sender', () => { return { sendTelemetryEvents: jest.fn(), }; diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index af5596964740a..20434e8290457 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -67,8 +67,8 @@ import { compileTemplate } from './epm/agent/agent'; import { normalizeKuery } from './saved_object'; import { appContextService } from '.'; import { removeOldAssets } from './epm/packages/cleanup'; -import type { PackagePolicyUpgradeUsage } from './upgrade_usage'; -import { sendTelemetryEvents } from './upgrade_usage'; +import type { PackageUpdateEvent, UpdateEventType } from './upgrade_sender'; +import { sendTelemetryEvents } from './upgrade_sender'; export type InputsOverride = Partial & { vars?: Array; @@ -423,12 +423,13 @@ class PackagePolicyService { }); if (packagePolicy.package.version !== currentVersion) { - const upgradeTelemetry: PackagePolicyUpgradeUsage = { - package_name: packagePolicy.package.name, - current_version: currentVersion || 'unknown', - new_version: packagePolicy.package.version, + const upgradeTelemetry: PackageUpdateEvent = { + packageName: packagePolicy.package.name, + currentVersion: currentVersion || 'unknown', + newVersion: packagePolicy.package.version, status: 'success', dryRun: false, + eventType: 'package-policy-upgrade' as UpdateEventType, }; sendTelemetryEvents( appContextService.getLogger(), @@ -668,13 +669,14 @@ class PackagePolicyService { const hasErrors = 'errors' in updatedPackagePolicy; if (packagePolicy.package.version !== packageInfo.version) { - const upgradeTelemetry: PackagePolicyUpgradeUsage = { - package_name: packageInfo.name, - current_version: packagePolicy.package.version, - new_version: packageInfo.version, + const upgradeTelemetry: PackageUpdateEvent = { + packageName: packageInfo.name, + currentVersion: packagePolicy.package.version, + newVersion: packageInfo.version, status: hasErrors ? 'failure' : 'success', error: hasErrors ? updatedPackagePolicy.errors : undefined, dryRun: true, + eventType: 'package-policy-upgrade' as UpdateEventType, }; sendTelemetryEvents( appContextService.getLogger(), diff --git a/x-pack/plugins/fleet/server/services/setup.ts b/x-pack/plugins/fleet/server/services/setup.ts index 37d79c1bb691d..7cde9c4c052d6 100644 --- a/x-pack/plugins/fleet/server/services/setup.ts +++ b/x-pack/plugins/fleet/server/services/setup.ts @@ -26,7 +26,6 @@ import { generateEnrollmentAPIKey, hasEnrollementAPIKeysForPolicy } from './api_ import { settingsService } from '.'; import { awaitIfPending } from './setup_utils'; import { ensureFleetServerAgentPoliciesExists } from './agents'; -import { awaitIfFleetServerSetupPending } from './fleet_server'; import { ensureFleetFinalPipelineIsInstalled } from './epm/elasticsearch/ingest_pipeline/install'; import { ensureDefaultComponentTemplate } from './epm/elasticsearch/template/install'; import { getInstallations, installPackage } from './epm/packages'; @@ -68,7 +67,6 @@ async function createSetupSideEffects( const defaultOutput = await outputService.ensureDefaultOutput(soClient); - await awaitIfFleetServerSetupPending(); if (appContextService.getConfig()?.agentIdVerificationEnabled) { await ensureFleetGlobalEsAssets(soClient, esClient); } diff --git a/x-pack/plugins/fleet/server/services/upgrade_usage.test.ts b/x-pack/plugins/fleet/server/services/upgrade_sender.test.ts similarity index 73% rename from x-pack/plugins/fleet/server/services/upgrade_usage.test.ts rename to x-pack/plugins/fleet/server/services/upgrade_sender.test.ts index 5445ad233eddc..c8a64a7172b39 100644 --- a/x-pack/plugins/fleet/server/services/upgrade_usage.test.ts +++ b/x-pack/plugins/fleet/server/services/upgrade_sender.test.ts @@ -11,8 +11,8 @@ import { loggingSystemMock } from 'src/core/server/mocks'; import type { TelemetryEventsSender } from '../telemetry/sender'; import { createMockTelemetryEventsSender } from '../telemetry/__mocks__'; -import { sendTelemetryEvents, capErrorSize } from './upgrade_usage'; -import type { PackagePolicyUpgradeUsage } from './upgrade_usage'; +import { sendTelemetryEvents, capErrorSize, UpdateEventType } from './upgrade_sender'; +import type { PackageUpdateEvent } from './upgrade_sender'; describe('sendTelemetryEvents', () => { let eventsTelemetryMock: jest.Mocked; @@ -24,23 +24,24 @@ describe('sendTelemetryEvents', () => { }); it('should queue telemetry events with generic error', () => { - const upgardeMessage: PackagePolicyUpgradeUsage = { - package_name: 'aws', - current_version: '0.6.1', - new_version: '1.3.0', + const upgradeMessage: PackageUpdateEvent = { + packageName: 'aws', + currentVersion: '0.6.1', + newVersion: '1.3.0', status: 'failure', error: [ { key: 'queueUrl', message: ['Queue URL is required'] }, { message: 'Invalid format' }, ], dryRun: true, + eventType: UpdateEventType.PACKAGE_POLICY_UPGRADE, }; - sendTelemetryEvents(loggerMock, eventsTelemetryMock, upgardeMessage); + sendTelemetryEvents(loggerMock, eventsTelemetryMock, upgradeMessage); expect(eventsTelemetryMock.queueTelemetryEvents).toHaveBeenCalledWith('fleet-upgrades', [ { - current_version: '0.6.1', + currentVersion: '0.6.1', error: [ { key: 'queueUrl', @@ -50,11 +51,12 @@ describe('sendTelemetryEvents', () => { message: 'Invalid format', }, ], - error_message: ['Field is required', 'Invalid format'], - new_version: '1.3.0', - package_name: 'aws', + errorMessage: ['Field is required', 'Invalid format'], + newVersion: '1.3.0', + packageName: 'aws', status: 'failure', dryRun: true, + eventType: 'package-policy-upgrade', }, ]); }); diff --git a/x-pack/plugins/fleet/server/services/upgrade_usage.ts b/x-pack/plugins/fleet/server/services/upgrade_sender.ts similarity index 69% rename from x-pack/plugins/fleet/server/services/upgrade_usage.ts rename to x-pack/plugins/fleet/server/services/upgrade_sender.ts index 68bb126496e01..9069ab68b55a3 100644 --- a/x-pack/plugins/fleet/server/services/upgrade_usage.ts +++ b/x-pack/plugins/fleet/server/services/upgrade_sender.ts @@ -8,15 +8,23 @@ import type { Logger } from 'src/core/server'; import type { TelemetryEventsSender } from '../telemetry/sender'; +import type { InstallType } from '../types'; -export interface PackagePolicyUpgradeUsage { - package_name: string; - current_version: string; - new_version: string; +export interface PackageUpdateEvent { + packageName: string; + currentVersion: string; + newVersion: string; status: 'success' | 'failure'; - error?: UpgradeError[]; dryRun?: boolean; - error_message?: string[]; + errorMessage?: string[] | string; + error?: UpgradeError[]; + eventType: UpdateEventType; + installType?: InstallType; +} + +export enum UpdateEventType { + PACKAGE_POLICY_UPGRADE = 'package-policy-upgrade', + PACKAGE_INSTALL = 'package-install', } export interface UpgradeError { @@ -30,19 +38,19 @@ export const FLEET_UPGRADES_CHANNEL_NAME = 'fleet-upgrades'; export function sendTelemetryEvents( logger: Logger, eventsTelemetry: TelemetryEventsSender | undefined, - upgradeUsage: PackagePolicyUpgradeUsage + upgradeEvent: PackageUpdateEvent ) { if (eventsTelemetry === undefined) { return; } try { - const cappedErrors = capErrorSize(upgradeUsage.error || [], MAX_ERROR_SIZE); + const cappedErrors = capErrorSize(upgradeEvent.error || [], MAX_ERROR_SIZE); eventsTelemetry.queueTelemetryEvents(FLEET_UPGRADES_CHANNEL_NAME, [ { - ...upgradeUsage, - error: upgradeUsage.error ? cappedErrors : undefined, - error_message: makeErrorGeneric(cappedErrors), + ...upgradeEvent, + error: upgradeEvent.error ? cappedErrors : undefined, + errorMessage: upgradeEvent.errorMessage || makeErrorGeneric(cappedErrors), }, ]); } catch (exc) { diff --git a/x-pack/plugins/fleet/server/telemetry/sender.test.ts b/x-pack/plugins/fleet/server/telemetry/sender.test.ts index 8fe4c6e150ff9..a1ba0693bf3f3 100644 --- a/x-pack/plugins/fleet/server/telemetry/sender.test.ts +++ b/x-pack/plugins/fleet/server/telemetry/sender.test.ts @@ -15,6 +15,8 @@ import type { InfoResponse } from '@elastic/elasticsearch/lib/api/types'; import { loggingSystemMock } from 'src/core/server/mocks'; +import { UpdateEventType } from '../services/upgrade_sender'; + import { TelemetryEventsSender } from './sender'; jest.mock('axios', () => { @@ -38,7 +40,13 @@ describe('TelemetryEventsSender', () => { describe('queueTelemetryEvents', () => { it('queues two events', () => { sender.queueTelemetryEvents('fleet-upgrades', [ - { package_name: 'system', current_version: '0.3', new_version: '1.0', status: 'success' }, + { + packageName: 'system', + currentVersion: '0.3', + newVersion: '1.0', + status: 'success', + eventType: UpdateEventType.PACKAGE_POLICY_UPGRADE, + }, ]); expect(sender['queuesPerChannel']['fleet-upgrades']).toBeDefined(); }); @@ -54,7 +62,13 @@ describe('TelemetryEventsSender', () => { }; sender.queueTelemetryEvents('fleet-upgrades', [ - { package_name: 'apache', current_version: '0.3', new_version: '1.0', status: 'success' }, + { + packageName: 'apache', + currentVersion: '0.3', + newVersion: '1.0', + status: 'success', + eventType: UpdateEventType.PACKAGE_POLICY_UPGRADE, + }, ]); sender['sendEvents'] = jest.fn(); @@ -74,7 +88,13 @@ describe('TelemetryEventsSender', () => { sender['telemetryStart'] = telemetryStart; sender.queueTelemetryEvents('fleet-upgrades', [ - { package_name: 'system', current_version: '0.3', new_version: '1.0', status: 'success' }, + { + packageName: 'system', + currentVersion: '0.3', + newVersion: '1.0', + status: 'success', + eventType: UpdateEventType.PACKAGE_POLICY_UPGRADE, + }, ]); sender['sendEvents'] = jest.fn(); diff --git a/x-pack/plugins/fleet/server/telemetry/sender.ts b/x-pack/plugins/fleet/server/telemetry/sender.ts index 3bda17fbd1d79..e7413872b6245 100644 --- a/x-pack/plugins/fleet/server/telemetry/sender.ts +++ b/x-pack/plugins/fleet/server/telemetry/sender.ts @@ -138,7 +138,7 @@ export class TelemetryEventsSender { clusterInfo?.version?.number ); } catch (err) { - this.logger.warn(`Error sending telemetry events data: ${err}`); + this.logger.debug(`Error sending telemetry events data: ${err}`); queue.clearEvents(); } } @@ -175,7 +175,7 @@ export class TelemetryEventsSender { }); this.logger.debug(`Events sent!. Response: ${resp.status} ${JSON.stringify(resp.data)}`); } catch (err) { - this.logger.warn( + this.logger.debug( `Error sending events: ${err.response.status} ${JSON.stringify(err.response.data)}` ); } diff --git a/x-pack/plugins/fleet/server/telemetry/types.ts b/x-pack/plugins/fleet/server/telemetry/types.ts index 4351546ecdf02..3b6478d68fba7 100644 --- a/x-pack/plugins/fleet/server/telemetry/types.ts +++ b/x-pack/plugins/fleet/server/telemetry/types.ts @@ -5,11 +5,11 @@ * 2.0. */ -import type { PackagePolicyUpgradeUsage } from '../services/upgrade_usage'; +import type { PackageUpdateEvent } from '../services/upgrade_sender'; export interface FleetTelemetryChannelEvents { // channel name => event type - 'fleet-upgrades': PackagePolicyUpgradeUsage; + 'fleet-upgrades': PackageUpdateEvent; } export type FleetTelemetryChannel = keyof FleetTelemetryChannelEvents; diff --git a/x-pack/plugins/fleet/server/types/index.tsx b/x-pack/plugins/fleet/server/types/index.tsx index 174aac03d6a3c..9d3e912864785 100644 --- a/x-pack/plugins/fleet/server/types/index.tsx +++ b/x-pack/plugins/fleet/server/types/index.tsx @@ -13,7 +13,6 @@ export type { AgentType, AgentAction, AgentPolicyAction, - AgentPolicyActionV7_9, BaseAgentActionSOAttributes, AgentActionSOAttributes, AgentPolicyActionSOAttributes, diff --git a/x-pack/plugins/graph/server/routes/search.ts b/x-pack/plugins/graph/server/routes/search.ts index 2f792dd399ccf..92f3d7a02b072 100644 --- a/x-pack/plugins/graph/server/routes/search.ts +++ b/x-pack/plugins/graph/server/routes/search.ts @@ -49,7 +49,7 @@ export function registerSearchRoute({ index: request.body.index, body: request.body.body, track_total_hits: true, - ignore_throttled: !includeFrozen, + ...(includeFrozen ? { ignore_throttled: false } : {}), }) ).body, }, diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts index 311acb13d3f06..e3184cadbdc49 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts @@ -9,7 +9,7 @@ import { act } from 'react-dom/test-utils'; import { ReactWrapper } from 'enzyme'; import { EuiDescriptionListDescription } from '@elastic/eui'; -import { registerTestBed, TestBed, TestBedConfig, findTestSubject } from '@kbn/test/jest'; +import { registerTestBed, TestBed, AsyncTestBedConfig, findTestSubject } from '@kbn/test/jest'; import { DataStream } from '../../../common'; import { IndexManagementHome } from '../../../public/application/sections/home'; import { indexManagementStore } from '../../../public/application/store'; @@ -42,7 +42,7 @@ export interface DataStreamsTabTestBed extends TestBed { } export const setup = async (overridingDependencies: any = {}): Promise => { - const testBedConfig: TestBedConfig = { + const testBedConfig: AsyncTestBedConfig = { store: () => indexManagementStore(services as any), memoryRouter: { initialEntries: [`/indices`], diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/home.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/home.helpers.ts index a15e4f2a613d3..ad8aceb7d56b8 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/home.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/home.helpers.ts @@ -5,12 +5,12 @@ * 2.0. */ -import { registerTestBed, TestBed, TestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test/jest'; import { IndexManagementHome } from '../../../public/application/sections/home'; import { indexManagementStore } from '../../../public/application/store'; import { WithAppDependencies, services, TestSubjects } from '../helpers'; -const testBedConfig: TestBedConfig = { +const testBedConfig: AsyncTestBedConfig = { store: () => indexManagementStore(services as any), memoryRouter: { initialEntries: [`/indices?includeHidden=true`], diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts index 7431686c02bbf..4ddd14562577a 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts @@ -7,12 +7,12 @@ import { act } from 'react-dom/test-utils'; -import { registerTestBed, TestBed, TestBedConfig, findTestSubject } from '@kbn/test/jest'; +import { registerTestBed, TestBed, AsyncTestBedConfig, findTestSubject } from '@kbn/test/jest'; import { TemplateList } from '../../../public/application/sections/home/template_list'; import { TemplateDeserialized } from '../../../common'; import { WithAppDependencies, TestSubjects } from '../helpers'; -const testBedConfig: TestBedConfig = { +const testBedConfig: AsyncTestBedConfig = { memoryRouter: { initialEntries: [`/templates`], componentRoutePath: `/templates/:templateName?`, diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts index 2576b5f92b7b2..0e4564163c553 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts @@ -8,12 +8,12 @@ import { act } from 'react-dom/test-utils'; import { ReactWrapper } from 'enzyme'; -import { registerTestBed, TestBed, TestBedConfig, findTestSubject } from '@kbn/test/jest'; +import { registerTestBed, TestBed, AsyncTestBedConfig, findTestSubject } from '@kbn/test/jest'; import { IndexManagementHome } from '../../../public/application/sections/home'; import { indexManagementStore } from '../../../public/application/store'; import { WithAppDependencies, services, TestSubjects } from '../helpers'; -const testBedConfig: TestBedConfig = { +const testBedConfig: AsyncTestBedConfig = { store: () => indexManagementStore(services as any), memoryRouter: { initialEntries: [`/indices?includeHiddenIndices=true`], diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.helpers.ts index 222bee28aef4b..dffa6fee19d06 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.helpers.ts @@ -5,14 +5,14 @@ * 2.0. */ -import { registerTestBed, TestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, AsyncTestBedConfig } from '@kbn/test/jest'; import { TemplateClone } from '../../../public/application/sections/template_clone'; import { WithAppDependencies } from '../helpers'; import { formSetup } from './template_form.helpers'; import { TEMPLATE_NAME } from './constants'; -const testBedConfig: TestBedConfig = { +const testBedConfig: AsyncTestBedConfig = { memoryRouter: { initialEntries: [`/clone_template/${TEMPLATE_NAME}`], componentRoutePath: `/clone_template/:name`, diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.helpers.ts index 7d3b34a6b8238..450d2c524b445 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed, TestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, AsyncTestBedConfig } from '@kbn/test/jest'; import { TemplateCreate } from '../../../public/application/sections/template_create'; import { WithAppDependencies } from '../helpers'; @@ -16,7 +16,7 @@ export const setup: any = (isLegacy: boolean = false) => { ? { pathname: '/create_template', search: '?legacy=true' } : { pathname: '/create_template' }; - const testBedConfig: TestBedConfig = { + const testBedConfig: AsyncTestBedConfig = { memoryRouter: { initialEntries: [route], componentRoutePath: route, diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.helpers.ts index e087c9432c4c2..6c73da3b3379d 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.helpers.ts @@ -5,14 +5,14 @@ * 2.0. */ -import { registerTestBed, TestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, AsyncTestBedConfig } from '@kbn/test/jest'; import { TemplateEdit } from '../../../public/application/sections/template_edit'; import { WithAppDependencies } from '../helpers'; import { formSetup, TestSubjects } from './template_form.helpers'; import { TEMPLATE_NAME } from './constants'; -const testBedConfig: TestBedConfig = { +const testBedConfig: AsyncTestBedConfig = { memoryRouter: { initialEntries: [`/edit_template/${TEMPLATE_NAME}`], componentRoutePath: `/edit_template/:name`, diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_create.helpers.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_create.helpers.ts index 9d28d57e531cb..06f0036cc5c77 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_create.helpers.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_create.helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed, TestBed, TestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test/jest'; import { BASE_PATH } from '../../../../../../../common'; import { ComponentTemplateCreate } from '../../../component_template_wizard'; @@ -19,7 +19,7 @@ export type ComponentTemplateCreateTestBed = TestBed; }; -const testBedConfig: TestBedConfig = { +const testBedConfig: AsyncTestBedConfig = { memoryRouter: { initialEntries: [`${BASE_PATH}/create_component_template`], componentRoutePath: `${BASE_PATH}/create_component_template`, diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_edit.helpers.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_edit.helpers.ts index 093a01d8db41c..e7b8df245aaa9 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_edit.helpers.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_edit.helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed, TestBed, TestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test/jest'; import { BASE_PATH } from '../../../../../../../common'; import { ComponentTemplateEdit } from '../../../component_template_wizard'; @@ -19,7 +19,7 @@ export type ComponentTemplateEditTestBed = TestBed; }; -const testBedConfig: TestBedConfig = { +const testBedConfig: AsyncTestBedConfig = { memoryRouter: { initialEntries: [`${BASE_PATH}/edit_component_template/comp-1`], componentRoutePath: `${BASE_PATH}/edit_component_template/:name`, diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_list.helpers.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_list.helpers.ts index a8d548a9bf2b8..680550d16096b 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_list.helpers.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_list.helpers.ts @@ -7,12 +7,18 @@ import { act } from 'react-dom/test-utils'; -import { registerTestBed, TestBed, TestBedConfig, findTestSubject, nextTick } from '@kbn/test/jest'; +import { + registerTestBed, + TestBed, + AsyncTestBedConfig, + findTestSubject, + nextTick, +} from '@kbn/test/jest'; import { BASE_PATH } from '../../../../../../../common'; import { WithAppDependencies } from './setup_environment'; import { ComponentTemplateList } from '../../../component_template_list/component_template_list'; -const testBedConfig: TestBedConfig = { +const testBedConfig: AsyncTestBedConfig = { memoryRouter: { initialEntries: [`${BASE_PATH}component_templates`], componentRoutePath: `${BASE_PATH}component_templates`, diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_clone.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_clone.helpers.ts index cf30870cefbbd..51f6d9bd96bd6 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_clone.helpers.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_clone.helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed, TestBedConfig, TestBed } from '@kbn/test/jest'; +import { registerTestBed, AsyncTestBedConfig, TestBed } from '@kbn/test/jest'; import { PipelinesClone } from '../../../public/application/sections/pipelines_clone'; import { getFormActions, PipelineFormTestSubjects } from './pipeline_form.helpers'; import { WithAppDependencies } from './setup_environment'; @@ -28,7 +28,7 @@ export const PIPELINE_TO_CLONE = { ], }; -const testBedConfig: TestBedConfig = { +const testBedConfig: AsyncTestBedConfig = { memoryRouter: { initialEntries: [getClonePath({ clonedPipelineName: PIPELINE_TO_CLONE.name })], componentRoutePath: ROUTES.clone, diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create.helpers.ts index 06c880bbceda4..faf1b42042ec1 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create.helpers.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create.helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed, TestBedConfig, TestBed } from '@kbn/test/jest'; +import { registerTestBed, AsyncTestBedConfig, TestBed } from '@kbn/test/jest'; import { PipelinesCreate } from '../../../public/application/sections/pipelines_create'; import { getFormActions, PipelineFormTestSubjects } from './pipeline_form.helpers'; import { WithAppDependencies } from './setup_environment'; @@ -15,7 +15,7 @@ export type PipelinesCreateTestBed = TestBed & { actions: ReturnType; }; -const testBedConfig: TestBedConfig = { +const testBedConfig: AsyncTestBedConfig = { memoryRouter: { initialEntries: [getCreatePath()], componentRoutePath: ROUTES.create, diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_edit.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_edit.helpers.ts index 913eb1355a6d7..9a3c41196653f 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_edit.helpers.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_edit.helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed, TestBedConfig, TestBed } from '@kbn/test/jest'; +import { registerTestBed, AsyncTestBedConfig, TestBed } from '@kbn/test/jest'; import { PipelinesEdit } from '../../../public/application/sections/pipelines_edit'; import { getFormActions, PipelineFormTestSubjects } from './pipeline_form.helpers'; import { WithAppDependencies } from './setup_environment'; @@ -28,7 +28,7 @@ export const PIPELINE_TO_EDIT = { ], }; -const testBedConfig: TestBedConfig = { +const testBedConfig: AsyncTestBedConfig = { memoryRouter: { initialEntries: [getEditPath({ pipelineName: PIPELINE_TO_EDIT.name })], componentRoutePath: ROUTES.edit, diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_list.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_list.helpers.ts index 5f340b645f954..3cd768104203a 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_list.helpers.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_list.helpers.ts @@ -7,12 +7,12 @@ import { act } from 'react-dom/test-utils'; -import { registerTestBed, TestBed, TestBedConfig, findTestSubject } from '@kbn/test/jest'; +import { registerTestBed, TestBed, AsyncTestBedConfig, findTestSubject } from '@kbn/test/jest'; import { PipelinesList } from '../../../public/application/sections/pipelines_list'; import { WithAppDependencies } from './setup_environment'; import { getListPath, ROUTES } from '../../../public/application/services/navigation'; -const testBedConfig: TestBedConfig = { +const testBedConfig: AsyncTestBedConfig = { memoryRouter: { initialEntries: [getListPath()], componentRoutePath: ROUTES.list, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts index 1dfc7d40f6f3e..8f180d4a021e0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts @@ -1345,8 +1345,11 @@ describe('IndexPattern Data Source', () => { }); describe('#getWarningMessages', () => { - it('should return mismatched time shifts', () => { - const state: IndexPatternPrivateState = { + let state: IndexPatternPrivateState; + let framePublicAPI: FramePublicAPI; + + beforeEach(() => { + state = { indexPatternRefs: [], existingFields: {}, isFirstExistenceFetch: false, @@ -1410,7 +1413,8 @@ describe('IndexPattern Data Source', () => { }, currentIndexPatternId: '1', }; - const warnings = indexPatternDatasource.getWarningMessages!(state, { + + framePublicAPI = { activeData: { first: { type: 'datatable', @@ -1433,20 +1437,39 @@ describe('IndexPattern Data Source', () => { ], }, }, - } as unknown as FramePublicAPI); - expect(warnings!.length).toBe(2); - expect((warnings![0] as React.ReactElement).props.id).toEqual( - 'xpack.lens.indexPattern.timeShiftSmallWarning' - ); - expect((warnings![1] as React.ReactElement).props.id).toEqual( - 'xpack.lens.indexPattern.timeShiftMultipleWarning' - ); + } as unknown as FramePublicAPI; + }); + + it('should return mismatched time shifts', () => { + const warnings = indexPatternDatasource.getWarningMessages!(state, framePublicAPI); + + expect(warnings!.map((item) => (item as React.ReactElement).props.id)).toMatchInlineSnapshot(` + Array [ + "xpack.lens.indexPattern.timeShiftSmallWarning", + "xpack.lens.indexPattern.timeShiftMultipleWarning", + ] + `); + }); + + it('should show different types of warning messages', () => { + framePublicAPI.activeData!.first.columns[0].meta.sourceParams!.hasPrecisionError = true; + + const warnings = indexPatternDatasource.getWarningMessages!(state, framePublicAPI); + + expect(warnings!.map((item) => (item as React.ReactElement).props.id)).toMatchInlineSnapshot(` + Array [ + "xpack.lens.indexPattern.timeShiftSmallWarning", + "xpack.lens.indexPattern.timeShiftMultipleWarning", + "xpack.lens.indexPattern.precisionErrorWarning", + ] + `); }); it('should prepend each error with its layer number on multi-layer chart', () => { (getErrorMessages as jest.Mock).mockClear(); (getErrorMessages as jest.Mock).mockReturnValueOnce(['error 1', 'error 2']); - const state: IndexPatternPrivateState = { + + state = { indexPatternRefs: [], existingFields: {}, isFirstExistenceFetch: false, @@ -1465,6 +1488,7 @@ describe('IndexPattern Data Source', () => { }, currentIndexPatternId: '1', }; + expect(indexPatternDatasource.getErrorMessages(state)).toEqual([ { longMessage: 'Layer 1 error: error 1', shortMessage: '' }, { longMessage: 'Layer 1 error: error 2', shortMessage: '' }, @@ -1696,7 +1720,7 @@ describe('IndexPattern Data Source', () => { isBucketed: false, label: 'Static value: 0', operationType: 'static_value', - params: { value: 0 }, + params: { value: '0' }, references: [], scale: 'ratio', }, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index bdcc0e621cc36..b970ad092c7f4 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -57,7 +57,7 @@ import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; import { GeoFieldWorkspacePanel } from '../editor_frame_service/editor_frame/workspace_panel/geo_field_workspace_panel'; import { DraggingIdentifier } from '../drag_drop'; import { getStateTimeShiftWarningMessages } from './time_shift_utils'; - +import { getPrecisionErrorWarningMessages } from './utils'; export type { OperationType, IndexPatternColumn } from './operations'; export { deleteColumn } from './operations'; @@ -502,7 +502,12 @@ export function getIndexPatternDatasource({ }); return messages.length ? messages : undefined; }, - getWarningMessages: getStateTimeShiftWarningMessages, + getWarningMessages: (state, frame) => { + return [ + ...(getStateTimeShiftWarningMessages(state, frame) || []), + ...getPrecisionErrorWarningMessages(state, frame, core.docLinks), + ]; + }, checkIntegrity: (state) => { const ids = Object.values(state.layers || {}).map(({ indexPatternId }) => indexPatternId); return ids.filter((id) => !state.indexPatterns[id]); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.test.tsx index 1c574fe69611c..816324f9f8fb5 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.test.tsx @@ -338,6 +338,36 @@ describe('static_value', () => { expect(input.prop('value')).toEqual('23'); }); + it('should allow 0 as initial value', () => { + const updateLayerSpy = jest.fn(); + const zeroLayer = { + ...layer, + columns: { + ...layer.columns, + col2: { + ...layer.columns.col2, + operationType: 'static_value', + references: [], + params: { + value: '0', + }, + }, + }, + } as IndexPatternLayer; + const instance = shallow( + + ); + + const input = instance.find('[data-test-subj="lns-indexPattern-static_value-input"]'); + expect(input.prop('value')).toEqual('0'); + }); + it('should update state on change', async () => { const updateLayerSpy = jest.fn(); const instance = mount( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.tsx index b759ebe46fb33..b66092e8a48c3 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.tsx @@ -95,7 +95,7 @@ export const staticValueOperation: OperationDefinition< arguments: { id: [columnId], name: [label || defaultLabel], - expression: [isValidNumber(params.value) ? params.value! : String(defaultValue)], + expression: [String(isValidNumber(params.value) ? params.value! : defaultValue)], }, }, ]; @@ -118,7 +118,7 @@ export const staticValueOperation: OperationDefinition< operationType: 'static_value', isBucketed: false, scale: 'ratio', - params: { ...previousParams, value: previousParams.value ?? String(defaultValue) }, + params: { ...previousParams, value: String(previousParams.value ?? defaultValue) }, references: [], }; }, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/utils.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/utils.test.tsx new file mode 100644 index 0000000000000..05be5c66fe5da --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/utils.test.tsx @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getPrecisionErrorWarningMessages } from './utils'; +import type { IndexPatternPrivateState } from './types'; +import type { FramePublicAPI } from '../types'; +import type { DocLinksStart } from 'kibana/public'; + +describe('indexpattern_datasource utils', () => { + describe('getPrecisionErrorWarningMessages', () => { + let state: IndexPatternPrivateState; + let framePublicAPI: FramePublicAPI; + let docLinks: DocLinksStart; + + beforeEach(() => { + state = {} as IndexPatternPrivateState; + framePublicAPI = { + activeData: { + id: { + columns: [ + { + meta: { + sourceParams: { + hasPrecisionError: false, + }, + }, + }, + ], + }, + }, + } as unknown as FramePublicAPI; + + docLinks = { + links: { + aggs: { + terms_doc_count_error: 'http://terms_doc_count_error', + }, + }, + } as DocLinksStart; + }); + test('should not show precisionError if hasPrecisionError is false', () => { + expect(getPrecisionErrorWarningMessages(state, framePublicAPI, docLinks)).toHaveLength(0); + }); + + test('should not show precisionError if hasPrecisionError is not defined', () => { + delete framePublicAPI.activeData!.id.columns[0].meta.sourceParams!.hasPrecisionError; + + expect(getPrecisionErrorWarningMessages(state, framePublicAPI, docLinks)).toHaveLength(0); + }); + + test('should show precisionError if hasPrecisionError is true', () => { + framePublicAPI.activeData!.id.columns[0].meta.sourceParams!.hasPrecisionError = true; + + expect(getPrecisionErrorWarningMessages(state, framePublicAPI, docLinks)).toHaveLength(1); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/utils.ts b/x-pack/plugins/lens/public/indexpattern_datasource/utils.tsx similarity index 55% rename from x-pack/plugins/lens/public/indexpattern_datasource/utils.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/utils.tsx index a4e36367cef47..6d3f75a403dd7 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/utils.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/utils.tsx @@ -5,17 +5,30 @@ * 2.0. */ -import { DataType } from '../types'; -import { IndexPattern, IndexPatternLayer, DraggedField } from './types'; +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import type { DocLinksStart } from 'kibana/public'; +import { EuiLink, EuiTextColor } from '@elastic/eui'; + +import { DatatableColumn } from 'src/plugins/expressions'; +import type { DataType, FramePublicAPI } from '../types'; +import type { + IndexPattern, + IndexPatternLayer, + DraggedField, + IndexPatternPrivateState, +} from './types'; import type { BaseIndexPatternColumn, FieldBasedIndexPatternColumn, ReferenceBasedIndexPatternColumn, } from './operations/definitions/column_types'; + import { operationDefinitionMap, IndexPatternColumn } from './operations'; import { getInvalidFieldMessage } from './operations/definitions/helpers'; import { isQueryValid } from './operations/definitions/filters'; +import { checkColumnForPrecisionError } from '../../../../../src/plugins/data/common'; /** * Normalizes the specified operation type. (e.g. document operations @@ -101,3 +114,60 @@ export function fieldIsInvalid(column: IndexPatternColumn | undefined, indexPatt } return !!getInvalidFieldMessage(column, indexPattern)?.length; } + +export function getPrecisionErrorWarningMessages( + state: IndexPatternPrivateState, + { activeData }: FramePublicAPI, + docLinks: DocLinksStart +) { + const warningMessages: React.ReactNode[] = []; + + if (state && activeData) { + Object.values(activeData) + .reduce((acc: DatatableColumn[], { columns }) => [...acc, ...columns], []) + .forEach((column) => { + if (checkColumnForPrecisionError(column)) { + warningMessages.push( + {column.name}, + topValues: ( + + + + ), + filters: ( + + + + ), + link: ( + + + + ), + }} + /> + ); + } + }); + } + + return warningMessages; +} diff --git a/x-pack/plugins/lens/public/xy_visualization/reference_line_helpers.test.ts b/x-pack/plugins/lens/public/xy_visualization/reference_line_helpers.test.ts index 42caca7fa2e09..9f48b8c8c36e4 100644 --- a/x-pack/plugins/lens/public/xy_visualization/reference_line_helpers.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/reference_line_helpers.test.ts @@ -120,6 +120,32 @@ describe('reference_line helpers', () => { ).toBe(100); }); + it('should return 0 as result of calculation', () => { + expect( + getStaticValue( + [ + { + layerId: 'id-a', + seriesType: 'area', + layerType: 'data', + accessors: ['a'], + yConfig: [{ forAccessor: 'a', axisMode: 'right' }], + } as XYLayerConfig, + ], + 'yRight', + { + activeData: getActiveData([ + { + id: 'id-a', + rows: [{ a: -30 }, { a: 10 }], + }, + ]), + }, + hasAllNumberHistogram + ) + ).toBe(0); + }); + it('should work for no yConfig defined and fallback to left axis', () => { expect( getStaticValue( diff --git a/x-pack/plugins/lens/public/xy_visualization/reference_line_helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/reference_line_helpers.tsx index 53a2d4bcc7222..127bf02b81f89 100644 --- a/x-pack/plugins/lens/public/xy_visualization/reference_line_helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/reference_line_helpers.tsx @@ -104,15 +104,14 @@ export function getStaticValue( ) { return fallbackValue; } - return ( - computeStaticValueForGroup( - filteredLayers, - accessors, - activeData, - groupId !== 'x', // histogram axis should compute the min based on the current data - groupId !== 'x' - ) || fallbackValue + const computedValue = computeStaticValueForGroup( + filteredLayers, + accessors, + activeData, + groupId !== 'x', // histogram axis should compute the min based on the current data + groupId !== 'x' ); + return computedValue ?? fallbackValue; } function getAccessorCriteriaForGroup( diff --git a/x-pack/plugins/ml/common/util/group_color_utils.ts b/x-pack/plugins/ml/common/util/group_color_utils.ts index bb3b347e25334..63f0e13676d58 100644 --- a/x-pack/plugins/ml/common/util/group_color_utils.ts +++ b/x-pack/plugins/ml/common/util/group_color_utils.ts @@ -5,7 +5,7 @@ * 2.0. */ -import euiVars from '@elastic/eui/dist/eui_theme_dark.json'; +import { euiDarkVars as euiVars } from '@kbn/ui-shared-deps-src/theme'; import { stringHash } from './string_utils'; diff --git a/x-pack/plugins/ml/public/application/components/color_range_legend/use_color_range.ts b/x-pack/plugins/ml/public/application/components/color_range_legend/use_color_range.ts index 2809a4321e7bb..2ccc687d145d0 100644 --- a/x-pack/plugins/ml/public/application/components/color_range_legend/use_color_range.ts +++ b/x-pack/plugins/ml/public/application/components/color_range_legend/use_color_range.ts @@ -7,8 +7,10 @@ import d3 from 'd3'; import { useMemo } from 'react'; -import euiThemeLight from '@elastic/eui/dist/eui_theme_light.json'; -import euiThemeDark from '@elastic/eui/dist/eui_theme_dark.json'; +import { + euiLightVars as euiThemeLight, + euiDarkVars as euiThemeDark, +} from '@kbn/ui-shared-deps-src/theme'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx b/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx index 2311807b6bbe6..facef2c02d578 100644 --- a/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx +++ b/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx @@ -17,7 +17,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars as theme } from '@kbn/ui-shared-deps-src/theme'; import { JobMessage } from '../../../../common/types/audit_message'; import { JobIcon } from '../job_message_icon'; diff --git a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.test.tsx b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.test.tsx index d0e70c38c23b4..846a8da83acb0 100644 --- a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.test.tsx +++ b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.test.tsx @@ -10,7 +10,7 @@ import { render, waitFor, screen } from '@testing-library/react'; import { __IntlProvider as IntlProvider } from '@kbn/i18n/react'; -import euiThemeLight from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars as euiThemeLight } from '@kbn/ui-shared-deps-src/theme'; import { ScatterplotMatrix } from './scatterplot_matrix'; diff --git a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.test.ts b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.test.ts index e401d70abe759..ed8a49cd36f02 100644 --- a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.test.ts +++ b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.test.ts @@ -10,7 +10,7 @@ import 'jest-canvas-mock'; // @ts-ignore import { compile } from 'vega-lite/build/vega-lite'; -import euiThemeLight from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars as euiThemeLight } from '@kbn/ui-shared-deps-src/theme'; import { LEGEND_TYPES } from '../vega_chart/common'; diff --git a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.ts b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.ts index 861b3727cea1b..83525a4837dc9 100644 --- a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.ts +++ b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.ts @@ -9,7 +9,7 @@ // @ts-ignore import type { TopLevelSpec } from 'vega-lite/build/vega-lite'; -import euiThemeLight from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars as euiThemeLight } from '@kbn/ui-shared-deps-src/theme'; import { euiPaletteColorBlind, euiPaletteNegative, euiPalettePositive } from '@elastic/eui'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx index 21090ce671d02..720dcd232d2f3 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx @@ -667,17 +667,6 @@ export const ConfigurationStepForm: FC = ({ )} - - - - = ({ setUnsupportedFieldsError={setUnsupportedFieldsError} setFormState={setFormState} /> + + + + {showScatterplotMatrix && ( <> { const mlLocator = useMlLocator()!; const navigateToPath = useNavigateToPath(); - const clickHandler = useCallback(async (item: DataFrameAnalyticsListRow) => { - const path = await mlLocator.getUrl({ - page: ML_PAGES.DATA_FRAME_ANALYTICS_MAP, - pageState: { jobId: item.id }, - }); + const [globalState] = useUrlState('_g'); - await navigateToPath(path, false); - }, []); + const clickHandler = useCallback( + async (item: DataFrameAnalyticsListRow) => { + const globalStateClone = cloneDeep(globalState || {}); + delete globalStateClone.ml; + + const path = await mlLocator.getUrl({ + page: ML_PAGES.DATA_FRAME_ANALYTICS_MAP, + pageState: { + jobId: item.id, + globalState: globalStateClone, + }, + }); + + await navigateToPath(path, false); + }, + [globalState] + ); const action: DataFrameAnalyticsListAction = useMemo( () => ({ 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 8423e569a99f2..a773fffdac997 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 @@ -39,6 +39,7 @@ import { useTableSettings } from './use_table_settings'; import { RefreshAnalyticsListButton } from '../refresh_analytics_list_button'; import { ListingPageUrlState } from '../../../../../../../common/types/common'; import { JobsAwaitingNodeWarning } from '../../../../../components/jobs_awaiting_node_warning'; +import { useRefresh } from '../../../../../routing/use_refresh'; const filters: EuiSearchBarProps['filters'] = [ { @@ -119,6 +120,8 @@ export const DataFrameAnalyticsList: FC = ({ const [errorMessage, setErrorMessage] = useState(undefined); const [jobsAwaitingNodeCount, setJobsAwaitingNodeCount] = useState(0); + const refreshObs = useRefresh(); + const disabled = !checkPermission('canCreateDataFrameAnalytics') || !checkPermission('canStartStopDataFrameAnalytics'); @@ -174,6 +177,13 @@ export const DataFrameAnalyticsList: FC = ({ isManagementTable ); + useEffect( + function updateOnTimerRefresh() { + getAnalyticsCallback(); + }, + [refreshObs] + ); + const { columns, modals } = useColumns( expandedRowItemIds, setExpandedRowItemIds, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_refresh_interval.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_refresh_interval.ts index c2335e4d5d017..0f236984f587c 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_refresh_interval.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_refresh_interval.ts @@ -8,6 +8,7 @@ import React, { useEffect } from 'react'; import { useMlKibana } from '../../../../../contexts/kibana'; +import { useUrlState } from '../../../../../util/url_state'; import { DEFAULT_REFRESH_INTERVAL_MS, @@ -20,6 +21,7 @@ export const useRefreshInterval = ( setBlockRefresh: React.Dispatch> ) => { const { services } = useMlKibana(); + const [globalState] = useUrlState('_g'); const { timefilter } = services.data.query.timefilter; const { refresh } = useRefreshAnalyticsList(); @@ -35,7 +37,9 @@ export const useRefreshInterval = ( initAutoRefresh(); function initAutoRefresh() { - const { value } = timefilter.getRefreshInterval(); + const interval = globalState?.refreshInterval ?? timefilter.getRefreshInterval(); + const { value } = interval; + if (value === 0) { // the auto refresher starts in an off state // so switch it on and set the interval to 30s diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js index fe09ed45f1274..d6926950dce7d 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js @@ -220,9 +220,21 @@ export async function cloneJob(jobId) { ]); const dataViewNames = await getDataViewNames(); - const jobIndicesAvailable = dataViewNames.includes(datafeed.indices.join(',')); + const dataViewTitle = datafeed.indices.join(','); + const jobIndicesAvailable = dataViewNames.includes(dataViewTitle); if (jobIndicesAvailable === false) { + const warningText = i18n.translate( + 'xpack.ml.jobsList.managementActions.noSourceDataViewForClone', + { + defaultMessage: + 'Unable to clone the anomaly detection job {jobId}. No data view exists for index {dataViewTitle}.', + values: { jobId, dataViewTitle }, + } + ); + getToastNotificationService().displayDangerToast(warningText, { + 'data-test-subj': 'mlCloneJobNoDataViewExistsWarningToast', + }); return; } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/settings.ts b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/settings.ts index 861b72a5a58b7..3d386073849f4 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/settings.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/settings.ts @@ -5,8 +5,10 @@ * 2.0. */ -import darkTheme from '@elastic/eui/dist/eui_theme_dark.json'; -import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; +import { + euiLightVars as lightTheme, + euiDarkVars as darkTheme, +} from '@kbn/ui-shared-deps-src/theme'; import { JobCreatorType, isMultiMetricJobCreator, diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/manifest.json index f8feaef3be5f8..3332fad66b3e2 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/manifest.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/manifest.json @@ -1,29 +1,29 @@ { "id": "apm_transaction", "title": "APM", - "description": "Detect anomalies in transactions from your APM services.", + "description": "Detect anomalies in transaction latency, throughput and failure rate from your APM services for metric data.", "type": "Transaction data", "logoFile": "logo.json", - "defaultIndexPattern": "apm-*-transaction", + "defaultIndexPattern": "apm-*-metric,metrics-apm*", "query": { "bool": { "filter": [ - { "term": { "processor.event": "transaction" } }, - { "exists": { "field": "transaction.duration" } } + { "term": { "processor.event": "metric" } }, + { "term": { "metricset.name": "transaction" } } ] } }, "jobs": [ { - "id": "high_mean_transaction_duration", - "file": "high_mean_transaction_duration.json" + "id": "apm_tx_metrics", + "file": "apm_tx_metrics.json" } ], "datafeeds": [ { - "id": "datafeed-high_mean_transaction_duration", - "file": "datafeed_high_mean_transaction_duration.json", - "job_id": "high_mean_transaction_duration" + "id": "datafeed-apm_tx_metrics", + "file": "datafeed_apm_tx_metrics.json", + "job_id": "apm_tx_metrics" } ] } diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/apm_tx_metrics.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/apm_tx_metrics.json new file mode 100644 index 0000000000000..f93b4fb009a14 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/apm_tx_metrics.json @@ -0,0 +1,53 @@ +{ + "job_type": "anomaly_detector", + "groups": [ + "apm" + ], + "description": "Detects anomalies in transaction latency, throughput and error percentage for metric data.", + "analysis_config": { + "bucket_span": "15m", + "summary_count_field_name" : "doc_count", + "detectors" : [ + { + "detector_description" : "high latency by transaction type for an APM service", + "function" : "high_mean", + "field_name" : "transaction_latency", + "by_field_name" : "transaction.type", + "partition_field_name" : "service.name" + }, + { + "detector_description" : "transaction throughput for an APM service", + "function" : "mean", + "field_name" : "transaction_throughput", + "by_field_name" : "transaction.type", + "partition_field_name" : "service.name" + }, + { + "detector_description" : "failed transaction rate for an APM service", + "function" : "high_mean", + "field_name" : "failed_transaction_rate", + "by_field_name" : "transaction.type", + "partition_field_name" : "service.name" + } + ], + "influencers" : [ + "transaction.type", + "service.name" + ] + }, + "analysis_limits": { + "model_memory_limit": "32mb" + }, + "data_description": { + "time_field" : "@timestamp", + "time_format" : "epoch_ms" + }, + "model_plot_config": { + "enabled" : true, + "annotations_enabled" : true + }, + "results_index_name" : "custom-apm", + "custom_settings": { + "created_by": "ml-module-apm-transaction" + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_apm_tx_metrics.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_apm_tx_metrics.json new file mode 100644 index 0000000000000..4d19cdc9f533d --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_apm_tx_metrics.json @@ -0,0 +1,98 @@ +{ + "job_id": "JOB_ID", + "indices": [ + "INDEX_PATTERN_NAME" + ], + "chunking_config" : { + "mode" : "off" + }, + "query": { + "bool": { + "filter": [ + { "term": { "processor.event": "metric" } }, + { "term": { "metricset.name": "transaction" } } + ] + } + }, + "aggregations" : { + "buckets" : { + "composite" : { + "size" : 5000, + "sources" : [ + { + "date" : { + "date_histogram" : { + "field" : "@timestamp", + "fixed_interval" : "90s" + } + } + }, + { + "transaction.type" : { + "terms" : { + "field" : "transaction.type" + } + } + }, + { + "service.name" : { + "terms" : { + "field" : "service.name" + } + } + } + ] + }, + "aggs" : { + "@timestamp" : { + "max" : { + "field" : "@timestamp" + } + }, + "transaction_throughput" : { + "rate" : { + "unit" : "minute" + } + }, + "transaction_latency" : { + "avg" : { + "field" : "transaction.duration.histogram" + } + }, + "error_count" : { + "filter" : { + "term" : { + "event.outcome" : "failure" + } + }, + "aggs" : { + "actual_error_count" : { + "value_count" : { + "field" : "event.outcome" + } + } + } + }, + "success_count" : { + "filter" : { + "term" : { + "event.outcome" : "success" + } + } + }, + "failed_transaction_rate" : { + "bucket_script" : { + "buckets_path" : { + "failure_count" : "error_count>_count", + "success_count" : "success_count>_count" + }, + "script" : "if ((params.failure_count + params.success_count)==0){return 0;}else{return 100 * (params.failure_count/(params.failure_count + params.success_count));}" + } + } + } + } + }, + "indices_options": { + "ignore_unavailable": true + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_high_mean_transaction_duration.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_high_mean_transaction_duration.json deleted file mode 100644 index 882bd93fd937d..0000000000000 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_high_mean_transaction_duration.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "job_id": "JOB_ID", - "indices": [ - "INDEX_PATTERN_NAME" - ], - "query": { - "bool": { - "filter": [ - { "term": { "processor.event": "transaction" } }, - { "exists": { "field": "transaction.duration.us" } } - ] - } - }, - "indices_options": { - "ignore_unavailable": true - } -} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/high_mean_transaction_duration.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/high_mean_transaction_duration.json deleted file mode 100644 index 77284cb275cd8..0000000000000 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/high_mean_transaction_duration.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "job_type": "anomaly_detector", - "groups": [ - "apm" - ], - "description": "Detect transaction duration anomalies across transaction types for your APM services.", - "analysis_config": { - "bucket_span": "15m", - "detectors": [ - { - "detector_description": "high duration by transaction type for an APM service", - "function": "high_mean", - "field_name": "transaction.duration.us", - "by_field_name": "transaction.type", - "partition_field_name": "service.name" - } - ], - "influencers": [ - "transaction.type", - "service.name" - ] - }, - "analysis_limits": { - "model_memory_limit": "32mb" - }, - "data_description": { - "time_field": "@timestamp" - }, - "model_plot_config": { - "enabled": true - }, - "custom_settings": { - "created_by": "ml-module-apm-transaction" - } -} diff --git a/x-pack/plugins/monitoring/public/alerts/lib/alerts_toast.tsx b/x-pack/plugins/monitoring/public/alerts/lib/alerts_toast.tsx index d752ec154089b..10c8f7155134b 100644 --- a/x-pack/plugins/monitoring/public/alerts/lib/alerts_toast.tsx +++ b/x-pack/plugins/monitoring/public/alerts/lib/alerts_toast.tsx @@ -18,7 +18,7 @@ export interface EnableAlertResponse { disabledWatcherClusterAlerts?: boolean; } -const showTlsAndEncryptionError = () => { +const showApiKeyAndEncryptionError = () => { const settingsUrl = Legacy.shims.docLinks.links.alerting.generalSettings; Legacy.shims.toastNotifications.addWarning({ @@ -32,7 +32,7 @@ const showTlsAndEncryptionError = () => {

{i18n.translate('xpack.monitoring.healthCheck.tlsAndEncryptionError', { - defaultMessage: `Stack monitoring alerts require Transport Layer Security between Kibana and Elasticsearch, and an encryption key in your kibana.yml file.`, + defaultMessage: `Stack Monitoring rules require API keys to be enabled and an encryption key to be configured.`, })}

@@ -97,7 +97,7 @@ export const showAlertsToast = (response: EnableAlertResponse) => { response; if (isSufficientlySecure === false || hasPermanentEncryptionKey === false) { - showTlsAndEncryptionError(); + showApiKeyAndEncryptionError(); } else if (disabledWatcherClusterAlerts === false) { showUnableToDisableWatcherClusterAlertsError(); } else if (disabledWatcherClusterAlerts === true) { diff --git a/x-pack/plugins/monitoring/server/lib/details/get_metrics.ts b/x-pack/plugins/monitoring/server/lib/details/get_metrics.ts index 83bb18169ae1e..a8de5529d8ca6 100644 --- a/x-pack/plugins/monitoring/server/lib/details/get_metrics.ts +++ b/x-pack/plugins/monitoring/server/lib/details/get_metrics.ts @@ -6,7 +6,6 @@ */ import moment from 'moment'; -import Bluebird from 'bluebird'; import { checkParam } from '../error_missing_required'; import { getSeries } from './get_series'; import { calculateTimeseriesInterval } from '../calculate_timeseries_interval'; @@ -40,25 +39,29 @@ export async function getMetrics( min = max - numOfBuckets * bucketSize * 1000; } - return Bluebird.map(metricSet, (metric: Metric) => { - // metric names match the literal metric name, but they can be supplied in groups or individually - let metricNames; + return Promise.all( + metricSet.map((metric: Metric) => { + // metric names match the literal metric name, but they can be supplied in groups or individually + let metricNames; - if (typeof metric !== 'string') { - metricNames = metric.keys; - } else { - metricNames = [metric]; - } + if (typeof metric !== 'string') { + metricNames = typeof metric.keys === 'string' ? [metric.keys] : metric.keys; + } else { + metricNames = [metric]; + } - return Bluebird.map(metricNames, (metricName) => { - return getSeries(req, indexPattern, metricName, metricOptions, filters, groupBy, { - min, - max, - bucketSize, - timezone, - }); - }); - }).then((rows) => { + return Promise.all( + metricNames.map((metricName) => { + return getSeries(req, indexPattern, metricName, metricOptions, filters, groupBy, { + min, + max, + bucketSize, + timezone, + }); + }) + ); + }) + ).then((rows) => { const data: Record = {}; metricSet.forEach((key, index) => { // keyName must match the value stored in the html template diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/verify_alerting_security.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/verify_alerting_security.ts deleted file mode 100644 index f5f9c80e0e4d3..0000000000000 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/verify_alerting_security.ts +++ /dev/null @@ -1,49 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { RequestHandlerContext } from 'kibana/server'; -import { EncryptedSavedObjectsPluginSetup } from '../../../../encrypted_saved_objects/server'; - -export interface AlertingFrameworkHealth { - isSufficientlySecure: boolean; - hasPermanentEncryptionKey: boolean; -} - -export interface XPackUsageSecurity { - security?: { - enabled?: boolean; - ssl?: { - http?: { - enabled?: boolean; - }; - }; - }; -} - -export class AlertingSecurity { - public static readonly getSecurityHealth = async ( - context: RequestHandlerContext, - encryptedSavedObjects?: EncryptedSavedObjectsPluginSetup - ): Promise => { - const { - security: { - enabled: isSecurityEnabled = false, - ssl: { http: { enabled: isTLSEnabled = false } = {} } = {}, - } = {}, - } = ( - await context.core.elasticsearch.client.asInternalUser.transport.request({ - method: 'GET', - path: '/_xpack/usage', - }) - ).body as XPackUsageSecurity; - - return { - isSufficientlySecure: !isSecurityEnabled || (isSecurityEnabled && isTLSEnabled), - hasPermanentEncryptionKey: encryptedSavedObjects?.canEncrypt === true, - }; - }; -} diff --git a/x-pack/plugins/monitoring/server/lib/kibana/get_kibanas_for_clusters.ts b/x-pack/plugins/monitoring/server/lib/kibana/get_kibanas_for_clusters.ts index 4e806c07ee660..5326976ec99ac 100644 --- a/x-pack/plugins/monitoring/server/lib/kibana/get_kibanas_for_clusters.ts +++ b/x-pack/plugins/monitoring/server/lib/kibana/get_kibanas_for_clusters.ts @@ -5,7 +5,6 @@ * 2.0. */ -import Bluebird from 'bluebird'; import { chain, find } from 'lodash'; import { LegacyRequest, Cluster, Bucket } from '../../types'; import { checkParam } from '../error_missing_required'; @@ -36,182 +35,184 @@ export function getKibanasForClusters( const start = req.payload.timeRange.min; const end = req.payload.timeRange.max; - return Bluebird.map(clusters, (cluster) => { - const clusterUuid = cluster.elasticsearch?.cluster?.id ?? cluster.cluster_uuid; - const metric = KibanaClusterMetric.getMetricFields(); - const params = { - index: kbnIndexPattern, - size: 0, - ignore_unavailable: true, - body: { - query: createQuery({ - types: ['stats', 'kibana_stats'], - start, - end, - clusterUuid, - metric, - }), - aggs: { - kibana_uuids: { - terms: { - field: 'kibana_stats.kibana.uuid', - size: config.get('monitoring.ui.max_bucket_size'), - }, - aggs: { - latest_report: { - terms: { - field: 'kibana_stats.timestamp', - size: 1, - order: { - _key: 'desc', - }, - }, - aggs: { - response_time_max: { - max: { - field: 'kibana_stats.response_times.max', + return Promise.all( + clusters.map((cluster) => { + const clusterUuid = cluster.elasticsearch?.cluster?.id ?? cluster.cluster_uuid; + const metric = KibanaClusterMetric.getMetricFields(); + const params = { + index: kbnIndexPattern, + size: 0, + ignore_unavailable: true, + body: { + query: createQuery({ + types: ['stats', 'kibana_stats'], + start, + end, + clusterUuid, + metric, + }), + aggs: { + kibana_uuids: { + terms: { + field: 'kibana_stats.kibana.uuid', + size: config.get('monitoring.ui.max_bucket_size'), + }, + aggs: { + latest_report: { + terms: { + field: 'kibana_stats.timestamp', + size: 1, + order: { + _key: 'desc', }, }, - memory_rss: { - max: { - field: 'kibana_stats.process.memory.resident_set_size_in_bytes', + aggs: { + response_time_max: { + max: { + field: 'kibana_stats.response_times.max', + }, }, - }, - memory_heap_size_limit: { - max: { - field: 'kibana_stats.process.memory.heap.size_limit', + memory_rss: { + max: { + field: 'kibana_stats.process.memory.resident_set_size_in_bytes', + }, }, - }, - concurrent_connections: { - max: { - field: 'kibana_stats.concurrent_connections', + memory_heap_size_limit: { + max: { + field: 'kibana_stats.process.memory.heap.size_limit', + }, }, - }, - requests_total: { - max: { - field: 'kibana_stats.requests.total', + concurrent_connections: { + max: { + field: 'kibana_stats.concurrent_connections', + }, + }, + requests_total: { + max: { + field: 'kibana_stats.requests.total', + }, }, }, }, - }, - response_time_max_per: { - max_bucket: { - buckets_path: 'latest_report>response_time_max', + response_time_max_per: { + max_bucket: { + buckets_path: 'latest_report>response_time_max', + }, }, - }, - memory_rss_per: { - max_bucket: { - buckets_path: 'latest_report>memory_rss', + memory_rss_per: { + max_bucket: { + buckets_path: 'latest_report>memory_rss', + }, }, - }, - memory_heap_size_limit_per: { - max_bucket: { - buckets_path: 'latest_report>memory_heap_size_limit', + memory_heap_size_limit_per: { + max_bucket: { + buckets_path: 'latest_report>memory_heap_size_limit', + }, }, - }, - concurrent_connections_per: { - max_bucket: { - buckets_path: 'latest_report>concurrent_connections', + concurrent_connections_per: { + max_bucket: { + buckets_path: 'latest_report>concurrent_connections', + }, }, - }, - requests_total_per: { - max_bucket: { - buckets_path: 'latest_report>requests_total', + requests_total_per: { + max_bucket: { + buckets_path: 'latest_report>requests_total', + }, }, }, }, - }, - response_time_max: { - max_bucket: { - buckets_path: 'kibana_uuids>response_time_max_per', - }, - }, - memory_rss: { - sum_bucket: { - buckets_path: 'kibana_uuids>memory_rss_per', + response_time_max: { + max_bucket: { + buckets_path: 'kibana_uuids>response_time_max_per', + }, }, - }, - memory_heap_size_limit: { - sum_bucket: { - buckets_path: 'kibana_uuids>memory_heap_size_limit_per', + memory_rss: { + sum_bucket: { + buckets_path: 'kibana_uuids>memory_rss_per', + }, }, - }, - concurrent_connections: { - sum_bucket: { - buckets_path: 'kibana_uuids>concurrent_connections_per', + memory_heap_size_limit: { + sum_bucket: { + buckets_path: 'kibana_uuids>memory_heap_size_limit_per', + }, }, - }, - requests_total: { - sum_bucket: { - buckets_path: 'kibana_uuids>requests_total_per', + concurrent_connections: { + sum_bucket: { + buckets_path: 'kibana_uuids>concurrent_connections_per', + }, }, - }, - status: { - terms: { - field: 'kibana_stats.kibana.status', - order: { - max_timestamp: 'desc', + requests_total: { + sum_bucket: { + buckets_path: 'kibana_uuids>requests_total_per', }, }, - aggs: { - max_timestamp: { - max: { - field: 'timestamp', + status: { + terms: { + field: 'kibana_stats.kibana.status', + order: { + max_timestamp: 'desc', + }, + }, + aggs: { + max_timestamp: { + max: { + field: 'timestamp', + }, }, }, }, }, }, - }, - }; + }; - const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); - return callWithRequest(req, 'search', params).then((result) => { - const aggregations = result.aggregations ?? {}; - const kibanaUuids = aggregations.kibana_uuids?.buckets ?? []; - const statusBuckets = aggregations.status?.buckets ?? []; + const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); + return callWithRequest(req, 'search', params).then((result) => { + const aggregations = result.aggregations ?? {}; + const kibanaUuids = aggregations.kibana_uuids?.buckets ?? []; + const statusBuckets = aggregations.status?.buckets ?? []; - // everything is initialized such that it won't impact any rollup - let status = null; - let requestsTotal = 0; - let connections = 0; - let responseTime = 0; - let memorySize = 0; - let memoryLimit = 0; + // everything is initialized such that it won't impact any rollup + let status = null; + let requestsTotal = 0; + let connections = 0; + let responseTime = 0; + let memorySize = 0; + let memoryLimit = 0; - // if the cluster has kibana instances at all - if (kibanaUuids.length) { - // get instance status by finding the latest status bucket - const latestTimestamp = chain(statusBuckets) - .map((bucket) => bucket.max_timestamp.value) - .max() - .value(); - const latestBucket = find( - statusBuckets, - (bucket) => bucket.max_timestamp.value === latestTimestamp - ); - status = latestBucket.key; + // if the cluster has kibana instances at all + if (kibanaUuids.length) { + // get instance status by finding the latest status bucket + const latestTimestamp = chain(statusBuckets) + .map((bucket) => bucket.max_timestamp.value) + .max() + .value(); + const latestBucket = find( + statusBuckets, + (bucket) => bucket.max_timestamp.value === latestTimestamp + ); + status = latestBucket.key; - requestsTotal = aggregations.requests_total?.value; - connections = aggregations.concurrent_connections?.value; - responseTime = aggregations.response_time_max?.value; - memorySize = aggregations.memory_rss?.value; - memoryLimit = aggregations.memory_heap_size_limit?.value; - } + requestsTotal = aggregations.requests_total?.value; + connections = aggregations.concurrent_connections?.value; + responseTime = aggregations.response_time_max?.value; + memorySize = aggregations.memory_rss?.value; + memoryLimit = aggregations.memory_heap_size_limit?.value; + } - return { - clusterUuid, - stats: { - uuids: kibanaUuids.map(({ key }: Bucket) => key), - status, - requests_total: requestsTotal, - concurrent_connections: connections, - response_time_max: responseTime, - memory_size: memorySize, - memory_limit: memoryLimit, - count: kibanaUuids.length, - }, - }; - }); - }); + return { + clusterUuid, + stats: { + uuids: kibanaUuids.map(({ key }: Bucket) => key), + status, + requests_total: requestsTotal, + concurrent_connections: connections, + response_time_max: responseTime, + memory_size: memorySize, + memory_limit: memoryLimit, + count: kibanaUuids.length, + }, + }; + }); + }) + ); } diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_logstash_for_clusters.ts b/x-pack/plugins/monitoring/server/lib/logstash/get_logstash_for_clusters.ts index 480b7176b7aba..03c87bfdde1ac 100644 --- a/x-pack/plugins/monitoring/server/lib/logstash/get_logstash_for_clusters.ts +++ b/x-pack/plugins/monitoring/server/lib/logstash/get_logstash_for_clusters.ts @@ -5,7 +5,6 @@ * 2.0. */ -import Bluebird from 'bluebird'; import { get } from 'lodash'; import { LegacyRequest, Cluster, Bucket } from '../../types'; import { LOGSTASH } from '../../../common/constants'; @@ -48,208 +47,210 @@ export function getLogstashForClusters( const end = req.payload.timeRange.max; const config = req.server.config(); - return Bluebird.map(clusters, (cluster) => { - const clusterUuid = get(cluster, 'elasticsearch.cluster.id', cluster.cluster_uuid); - const params = { - index: lsIndexPattern, - size: 0, - ignore_unavailable: true, - body: { - query: createQuery({ - types: ['stats', 'logstash_stats'], - start, - end, - clusterUuid, - metric: LogstashClusterMetric.getMetricFields(), - }), - aggs: { - logstash_uuids: { - terms: { - field: 'logstash_stats.logstash.uuid', - size: config.get('monitoring.ui.max_bucket_size'), - }, - aggs: { - latest_report: { - terms: { - field: 'logstash_stats.timestamp', - size: 1, - order: { - _key: 'desc', - }, - }, - aggs: { - memory_used: { - max: { - field: 'logstash_stats.jvm.mem.heap_used_in_bytes', + return Promise.all( + clusters.map((cluster) => { + const clusterUuid = get(cluster, 'elasticsearch.cluster.id', cluster.cluster_uuid); + const params = { + index: lsIndexPattern, + size: 0, + ignore_unavailable: true, + body: { + query: createQuery({ + types: ['stats', 'logstash_stats'], + start, + end, + clusterUuid, + metric: LogstashClusterMetric.getMetricFields(), + }), + aggs: { + logstash_uuids: { + terms: { + field: 'logstash_stats.logstash.uuid', + size: config.get('monitoring.ui.max_bucket_size'), + }, + aggs: { + latest_report: { + terms: { + field: 'logstash_stats.timestamp', + size: 1, + order: { + _key: 'desc', }, }, - memory: { - max: { - field: 'logstash_stats.jvm.mem.heap_max_in_bytes', + aggs: { + memory_used: { + max: { + field: 'logstash_stats.jvm.mem.heap_used_in_bytes', + }, }, - }, - events_in_total: { - max: { - field: 'logstash_stats.events.in', + memory: { + max: { + field: 'logstash_stats.jvm.mem.heap_max_in_bytes', + }, }, - }, - events_out_total: { - max: { - field: 'logstash_stats.events.out', + events_in_total: { + max: { + field: 'logstash_stats.events.in', + }, + }, + events_out_total: { + max: { + field: 'logstash_stats.events.out', + }, }, }, }, - }, - memory_used_per_node: { - max_bucket: { - buckets_path: 'latest_report>memory_used', + memory_used_per_node: { + max_bucket: { + buckets_path: 'latest_report>memory_used', + }, }, - }, - memory_per_node: { - max_bucket: { - buckets_path: 'latest_report>memory', + memory_per_node: { + max_bucket: { + buckets_path: 'latest_report>memory', + }, }, - }, - events_in_total_per_node: { - max_bucket: { - buckets_path: 'latest_report>events_in_total', + events_in_total_per_node: { + max_bucket: { + buckets_path: 'latest_report>events_in_total', + }, }, - }, - events_out_total_per_node: { - max_bucket: { - buckets_path: 'latest_report>events_out_total', + events_out_total_per_node: { + max_bucket: { + buckets_path: 'latest_report>events_out_total', + }, }, }, }, - }, - logstash_versions: { - terms: { - field: 'logstash_stats.logstash.version', - size: config.get('monitoring.ui.max_bucket_size'), - }, - }, - pipelines_nested: { - nested: { - path: 'logstash_stats.pipelines', + logstash_versions: { + terms: { + field: 'logstash_stats.logstash.version', + size: config.get('monitoring.ui.max_bucket_size'), + }, }, - aggs: { - pipelines: { - sum_bucket: { - buckets_path: 'queue_types>num_pipelines', - }, + pipelines_nested: { + nested: { + path: 'logstash_stats.pipelines', }, - queue_types: { - terms: { - field: 'logstash_stats.pipelines.queue.type', - size: config.get('monitoring.ui.max_bucket_size'), + aggs: { + pipelines: { + sum_bucket: { + buckets_path: 'queue_types>num_pipelines', + }, }, - aggs: { - num_pipelines: { - cardinality: { - field: 'logstash_stats.pipelines.id', + queue_types: { + terms: { + field: 'logstash_stats.pipelines.queue.type', + size: config.get('monitoring.ui.max_bucket_size'), + }, + aggs: { + num_pipelines: { + cardinality: { + field: 'logstash_stats.pipelines.id', + }, }, }, }, }, }, - }, - pipelines_nested_mb: { - nested: { - path: 'logstash.node.stats.pipelines', - }, - aggs: { - pipelines: { - sum_bucket: { - buckets_path: 'queue_types>num_pipelines', - }, + pipelines_nested_mb: { + nested: { + path: 'logstash.node.stats.pipelines', }, - queue_types: { - terms: { - field: 'logstash.node.stats.pipelines.queue.type', - size: config.get('monitoring.ui.max_bucket_size'), + aggs: { + pipelines: { + sum_bucket: { + buckets_path: 'queue_types>num_pipelines', + }, }, - aggs: { - num_pipelines: { - cardinality: { - field: 'logstash.node.stats.pipelines.id', + queue_types: { + terms: { + field: 'logstash.node.stats.pipelines.queue.type', + size: config.get('monitoring.ui.max_bucket_size'), + }, + aggs: { + num_pipelines: { + cardinality: { + field: 'logstash.node.stats.pipelines.id', + }, }, }, }, }, }, - }, - events_in_total: { - sum_bucket: { - buckets_path: 'logstash_uuids>events_in_total_per_node', + events_in_total: { + sum_bucket: { + buckets_path: 'logstash_uuids>events_in_total_per_node', + }, }, - }, - events_out_total: { - sum_bucket: { - buckets_path: 'logstash_uuids>events_out_total_per_node', + events_out_total: { + sum_bucket: { + buckets_path: 'logstash_uuids>events_out_total_per_node', + }, }, - }, - memory_used: { - sum_bucket: { - buckets_path: 'logstash_uuids>memory_used_per_node', + memory_used: { + sum_bucket: { + buckets_path: 'logstash_uuids>memory_used_per_node', + }, }, - }, - memory: { - sum_bucket: { - buckets_path: 'logstash_uuids>memory_per_node', + memory: { + sum_bucket: { + buckets_path: 'logstash_uuids>memory_per_node', + }, }, - }, - max_uptime: { - max: { - field: 'logstash_stats.jvm.uptime_in_millis', + max_uptime: { + max: { + field: 'logstash_stats.jvm.uptime_in_millis', + }, }, }, }, - }, - }; + }; - const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); - return callWithRequest(req, 'search', params).then((result) => { - const aggregations = get(result, 'aggregations', {}); - const logstashUuids = get(aggregations, 'logstash_uuids.buckets', []); - const logstashVersions = get(aggregations, 'logstash_versions.buckets', []); + const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); + return callWithRequest(req, 'search', params).then((result) => { + const aggregations = get(result, 'aggregations', {}); + const logstashUuids = get(aggregations, 'logstash_uuids.buckets', []); + const logstashVersions = get(aggregations, 'logstash_versions.buckets', []); - // everything is initialized such that it won't impact any rollup - let eventsInTotal = 0; - let eventsOutTotal = 0; - let memory = 0; - let memoryUsed = 0; - let maxUptime = 0; + // everything is initialized such that it won't impact any rollup + let eventsInTotal = 0; + let eventsOutTotal = 0; + let memory = 0; + let memoryUsed = 0; + let maxUptime = 0; - // if the cluster has logstash instances at all - if (logstashUuids.length) { - eventsInTotal = get(aggregations, 'events_in_total.value'); - eventsOutTotal = get(aggregations, 'events_out_total.value'); - memory = get(aggregations, 'memory.value'); - memoryUsed = get(aggregations, 'memory_used.value'); - maxUptime = get(aggregations, 'max_uptime.value'); - } + // if the cluster has logstash instances at all + if (logstashUuids.length) { + eventsInTotal = get(aggregations, 'events_in_total.value'); + eventsOutTotal = get(aggregations, 'events_out_total.value'); + memory = get(aggregations, 'memory.value'); + memoryUsed = get(aggregations, 'memory_used.value'); + maxUptime = get(aggregations, 'max_uptime.value'); + } - let types = get(aggregations, 'pipelines_nested_mb.queue_types.buckets', []); - if (!types || types.length === 0) { - types = aggregations.pipelines_nested?.queue_types.buckets ?? []; - } + let types = get(aggregations, 'pipelines_nested_mb.queue_types.buckets', []); + if (!types || types.length === 0) { + types = aggregations.pipelines_nested?.queue_types.buckets ?? []; + } - return { - clusterUuid, - stats: { - node_count: logstashUuids.length, - events_in_total: eventsInTotal, - events_out_total: eventsOutTotal, - avg_memory: memory, - avg_memory_used: memoryUsed, - max_uptime: maxUptime, - pipeline_count: - get(aggregations, 'pipelines_nested_mb.pipelines.value') || - get(aggregations, 'pipelines_nested.pipelines.value', 0), - queue_types: getQueueTypes(types), - versions: logstashVersions.map((versionBucket: Bucket) => versionBucket.key), - }, - }; - }); - }); + return { + clusterUuid, + stats: { + node_count: logstashUuids.length, + events_in_total: eventsInTotal, + events_out_total: eventsOutTotal, + avg_memory: memory, + avg_memory_used: memoryUsed, + max_uptime: maxUptime, + pipeline_count: + get(aggregations, 'pipelines_nested_mb.pipelines.value') || + get(aggregations, 'pipelines_nested.pipelines.value', 0), + queue_types: getQueueTypes(types), + versions: logstashVersions.map((versionBucket: Bucket) => versionBucket.key), + }, + }; + }); + }) + ); } diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index 557a9b5e2a3d2..ff07ea0f4a26d 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -202,6 +202,7 @@ export class MonitoringPlugin router, licenseService: this.licenseService, encryptedSavedObjects: plugins.encryptedSavedObjects, + alerting: plugins.alerting, logger: this.log, }); initInfraSource(config, plugins.infra); diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/enable.ts b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/enable.ts index 6724819c30d56..7185d399b3534 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/enable.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/enable.ts @@ -11,7 +11,6 @@ import { AlertsFactory } from '../../../../alerts'; import { LegacyServer, RouteDependencies } from '../../../../types'; import { ALERT_ACTION_TYPE_LOG } from '../../../../../common/constants'; import { ActionResult } from '../../../../../../actions/common'; -import { AlertingSecurity } from '../../../../lib/elasticsearch/verify_alerting_security'; import { disableWatcherClusterAlerts } from '../../../../lib/alerts/disable_watcher_cluster_alerts'; import { AlertTypeParams, SanitizedAlert } from '../../../../../../alerting/common'; @@ -38,12 +37,14 @@ export function enableAlertsRoute(server: LegacyServer, npRoute: RouteDependenci const alerts = AlertsFactory.getAll(); if (alerts.length) { - const { isSufficientlySecure, hasPermanentEncryptionKey } = - await AlertingSecurity.getSecurityHealth(context, npRoute.encryptedSavedObjects); + const { isSufficientlySecure, hasPermanentEncryptionKey } = npRoute.alerting + ?.getSecurityHealth + ? await npRoute.alerting?.getSecurityHealth() + : { isSufficientlySecure: false, hasPermanentEncryptionKey: false }; if (!isSufficientlySecure || !hasPermanentEncryptionKey) { server.log.info( - `Skipping alert creation for "${context.infra.spaceId}" space; Stack monitoring alerts require Transport Layer Security between Kibana and Elasticsearch, and an encryption key in your kibana.yml file.` + `Skipping rule creation for "${context.infra.spaceId}" space; Stack Monitoring rules require API keys to be enabled and an encryption key to be configured.` ); return response.ok({ body: { diff --git a/x-pack/plugins/monitoring/server/types.ts b/x-pack/plugins/monitoring/server/types.ts index 14071aafaea12..14023ccce41ae 100644 --- a/x-pack/plugins/monitoring/server/types.ts +++ b/x-pack/plugins/monitoring/server/types.ts @@ -28,6 +28,7 @@ import { PluginSetupContract as AlertingPluginSetupContract, } from '../../alerting/server'; import { InfraPluginSetup, InfraRequestHandlerContext } from '../../infra/server'; +import { PluginSetupContract as AlertingPluginSetup } from '../../alerting/server'; import { LicensingPluginStart } from '../../licensing/server'; import { PluginSetupContract as FeaturesPluginSetupContract } from '../../features/server'; import { EncryptedSavedObjectsPluginSetup } from '../../encrypted_saved_objects/server'; @@ -80,6 +81,7 @@ export interface RouteDependencies { router: IRouter; licenseService: MonitoringLicenseService; encryptedSavedObjects?: EncryptedSavedObjectsPluginSetup; + alerting?: AlertingPluginSetup; logger: Logger; } diff --git a/x-pack/plugins/observability/public/components/app/cases/all_cases/index.tsx b/x-pack/plugins/observability/public/components/app/cases/all_cases/index.tsx index bc0151052434a..4b57475343605 100644 --- a/x-pack/plugins/observability/public/components/app/cases/all_cases/index.tsx +++ b/x-pack/plugins/observability/public/components/app/cases/all_cases/index.tsx @@ -61,7 +61,7 @@ export const AllCases = React.memo(({ userCanCrud }) => { }, }, disableAlerts: true, - showTitle: false, + showTitle: true, userCanCrud, owner: [CASES_OWNER], }); diff --git a/x-pack/plugins/observability/public/components/app/cases/callout/callout.test.tsx b/x-pack/plugins/observability/public/components/app/cases/callout/callout.test.tsx index b0b6fc0e3b793..25d32d0cae884 100644 --- a/x-pack/plugins/observability/public/components/app/cases/callout/callout.test.tsx +++ b/x-pack/plugins/observability/public/components/app/cases/callout/callout.test.tsx @@ -32,25 +32,25 @@ describe('Callout', () => { it('renders the callout', () => { const wrapper = mount(); - expect(wrapper.find(`[data-test-subj="case-callout-md5-hex"]`).exists()).toBeTruthy(); - expect(wrapper.find(`[data-test-subj="callout-messages-md5-hex"]`).exists()).toBeTruthy(); - expect(wrapper.find(`[data-test-subj="callout-dismiss-md5-hex"]`).exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="caseCallout-md5-hex"]`).exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="calloutMessages-md5-hex"]`).exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="calloutDismiss-md5-hex"]`).exists()).toBeTruthy(); }); it('hides the callout', () => { const wrapper = mount(); - expect(wrapper.find(`[data-test-subj="case-callout-md5-hex"]`).exists()).toBeFalsy(); + expect(wrapper.find(`[data-test-subj="caseCallout-md5-hex"]`).exists()).toBeFalsy(); }); it('does not show any messages when the list is empty', () => { const wrapper = mount(); - expect(wrapper.find(`[data-test-subj="callout-messages-md5-hex"]`).exists()).toBeFalsy(); + expect(wrapper.find(`[data-test-subj="calloutMessages-md5-hex"]`).exists()).toBeFalsy(); }); it('transform the button color correctly - primary', () => { const wrapper = mount(); const className = - wrapper.find(`button[data-test-subj="callout-dismiss-md5-hex"]`).first().prop('className') ?? + wrapper.find(`button[data-test-subj="calloutDismiss-md5-hex"]`).first().prop('className') ?? ''; expect(className.includes('euiButton--primary')).toBeTruthy(); }); @@ -58,7 +58,7 @@ describe('Callout', () => { it('transform the button color correctly - success', () => { const wrapper = mount(); const className = - wrapper.find(`button[data-test-subj="callout-dismiss-md5-hex"]`).first().prop('className') ?? + wrapper.find(`button[data-test-subj="calloutDismiss-md5-hex"]`).first().prop('className') ?? ''; expect(className.includes('euiButton--secondary')).toBeTruthy(); }); @@ -66,7 +66,7 @@ describe('Callout', () => { it('transform the button color correctly - warning', () => { const wrapper = mount(); const className = - wrapper.find(`button[data-test-subj="callout-dismiss-md5-hex"]`).first().prop('className') ?? + wrapper.find(`button[data-test-subj="calloutDismiss-md5-hex"]`).first().prop('className') ?? ''; expect(className.includes('euiButton--warning')).toBeTruthy(); }); @@ -74,15 +74,15 @@ describe('Callout', () => { it('transform the button color correctly - danger', () => { const wrapper = mount(); const className = - wrapper.find(`button[data-test-subj="callout-dismiss-md5-hex"]`).first().prop('className') ?? + wrapper.find(`button[data-test-subj="calloutDismiss-md5-hex"]`).first().prop('className') ?? ''; expect(className.includes('euiButton--danger')).toBeTruthy(); }); it('dismiss the callout correctly', () => { const wrapper = mount(); - expect(wrapper.find(`[data-test-subj="callout-dismiss-md5-hex"]`).exists()).toBeTruthy(); - wrapper.find(`button[data-test-subj="callout-dismiss-md5-hex"]`).simulate('click'); + expect(wrapper.find(`[data-test-subj="calloutDismiss-md5-hex"]`).exists()).toBeTruthy(); + wrapper.find(`button[data-test-subj="calloutDismiss-md5-hex"]`).simulate('click'); wrapper.update(); expect(defaultProps.handleDismissCallout).toHaveBeenCalledWith('md5-hex', 'primary'); diff --git a/x-pack/plugins/observability/public/components/app/cases/callout/callout.tsx b/x-pack/plugins/observability/public/components/app/cases/callout/callout.tsx index 5aa637c8f806d..15bd250c6ceb6 100644 --- a/x-pack/plugins/observability/public/components/app/cases/callout/callout.tsx +++ b/x-pack/plugins/observability/public/components/app/cases/callout/callout.tsx @@ -35,10 +35,10 @@ function CallOutComponent({ ); return showCallOut && !isEmpty(messages) ? ( - - + + diff --git a/x-pack/plugins/observability/public/components/app/cases/callout/index.test.tsx b/x-pack/plugins/observability/public/components/app/cases/callout/index.test.tsx index 18d4dee45b9d5..bb0284398f4b3 100644 --- a/x-pack/plugins/observability/public/components/app/cases/callout/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/cases/callout/index.test.tsx @@ -42,7 +42,7 @@ describe('CaseCallOut ', () => { ); const id = createCalloutId(['message-one', 'message-two']); - expect(wrapper.find(`[data-test-subj="callout-messages-${id}"]`).last().exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="calloutMessages-${id}"]`).last().exists()).toBeTruthy(); }); it('groups the messages correctly', () => { @@ -69,11 +69,9 @@ describe('CaseCallOut ', () => { const idPrimary = createCalloutId(['message-two']); expect( - wrapper.find(`[data-test-subj="case-callout-${idPrimary}"]`).last().exists() - ).toBeTruthy(); - expect( - wrapper.find(`[data-test-subj="case-callout-${idDanger}"]`).last().exists() + wrapper.find(`[data-test-subj="caseCallout-${idPrimary}"]`).last().exists() ).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="caseCallout-${idDanger}"]`).last().exists()).toBeTruthy(); }); it('dismisses the callout correctly', () => { @@ -91,9 +89,9 @@ describe('CaseCallOut ', () => { const id = createCalloutId(['message-one']); - expect(wrapper.find(`[data-test-subj="case-callout-${id}"]`).last().exists()).toBeTruthy(); - wrapper.find(`[data-test-subj="callout-dismiss-${id}"]`).last().simulate('click'); - expect(wrapper.find(`[data-test-subj="case-callout-${id}"]`).exists()).toBeFalsy(); + expect(wrapper.find(`[data-test-subj="caseCallout-${id}"]`).last().exists()).toBeTruthy(); + wrapper.find(`[data-test-subj="calloutDismiss-${id}"]`).last().simulate('click'); + expect(wrapper.find(`[data-test-subj="caseCallout-${id}"]`).exists()).toBeFalsy(); }); it('persist the callout of type primary when dismissed', () => { @@ -112,7 +110,7 @@ describe('CaseCallOut ', () => { const id = createCalloutId(['message-one']); expect(securityLocalStorageMock.getMessages).toHaveBeenCalledWith(observabilityAppId); - wrapper.find(`[data-test-subj="callout-dismiss-${id}"]`).last().simulate('click'); + wrapper.find(`[data-test-subj="calloutDismiss-${id}"]`).last().simulate('click'); expect(securityLocalStorageMock.addMessage).toHaveBeenCalledWith(observabilityAppId, id); }); @@ -137,7 +135,7 @@ describe('CaseCallOut ', () => { ); - expect(wrapper.find(`[data-test-subj="case-callout-${id}"]`).last().exists()).toBeFalsy(); + expect(wrapper.find(`[data-test-subj="caseCallout-${id}"]`).last().exists()).toBeFalsy(); }); it('do not persist a callout of type danger', () => { @@ -160,7 +158,7 @@ describe('CaseCallOut ', () => { ); const id = createCalloutId(['message-one']); - wrapper.find(`button[data-test-subj="callout-dismiss-${id}"]`).simulate('click'); + wrapper.find(`button[data-test-subj="calloutDismiss-${id}"]`).simulate('click'); wrapper.update(); expect(securityLocalStorageMock.addMessage).not.toHaveBeenCalled(); }); @@ -185,7 +183,7 @@ describe('CaseCallOut ', () => { ); const id = createCalloutId(['message-one']); - wrapper.find(`button[data-test-subj="callout-dismiss-${id}"]`).simulate('click'); + wrapper.find(`button[data-test-subj="calloutDismiss-${id}"]`).simulate('click'); wrapper.update(); expect(securityLocalStorageMock.addMessage).not.toHaveBeenCalled(); }); @@ -210,7 +208,7 @@ describe('CaseCallOut ', () => { ); const id = createCalloutId(['message-one']); - wrapper.find(`button[data-test-subj="callout-dismiss-${id}"]`).simulate('click'); + wrapper.find(`button[data-test-subj="calloutDismiss-${id}"]`).simulate('click'); wrapper.update(); expect(securityLocalStorageMock.addMessage).not.toHaveBeenCalled(); }); diff --git a/x-pack/plugins/observability/public/components/app/cases/create/flyout.test.tsx b/x-pack/plugins/observability/public/components/app/cases/create/flyout.test.tsx index dc3db695a3fbf..977263a9721ea 100644 --- a/x-pack/plugins/observability/public/components/app/cases/create/flyout.test.tsx +++ b/x-pack/plugins/observability/public/components/app/cases/create/flyout.test.tsx @@ -45,7 +45,7 @@ describe('CreateCaseFlyout', () => { ); - expect(wrapper.find(`[data-test-subj='create-case-flyout']`).exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj='createCaseFlyout']`).exists()).toBeTruthy(); }); it('Closing modal calls onCloseCaseModal', () => { diff --git a/x-pack/plugins/observability/public/components/app/cases/create/flyout.tsx b/x-pack/plugins/observability/public/components/app/cases/create/flyout.tsx index 896bc27a97674..e8147ef7098ad 100644 --- a/x-pack/plugins/observability/public/components/app/cases/create/flyout.tsx +++ b/x-pack/plugins/observability/public/components/app/cases/create/flyout.tsx @@ -54,7 +54,7 @@ function CreateCaseFlyoutComponent({ }: CreateCaseModalProps) { const { cases } = useKibana().services; return ( - +

{i18n.CREATE_TITLE}

diff --git a/x-pack/plugins/observability/public/components/app/cases/translations.ts b/x-pack/plugins/observability/public/components/app/cases/translations.ts index a85b0bc744e66..af016be0182a3 100644 --- a/x-pack/plugins/observability/public/components/app/cases/translations.ts +++ b/x-pack/plugins/observability/public/components/app/cases/translations.ts @@ -92,10 +92,6 @@ export const OPTIONAL = i18n.translate('xpack.observability.cases.caseView.optio defaultMessage: 'Optional', }); -export const PAGE_TITLE = i18n.translate('xpack.observability.cases.pageTitle', { - defaultMessage: 'Cases', -}); - export const CREATE_CASE = i18n.translate('xpack.observability.cases.caseView.createCase', { defaultMessage: 'Create case', }); diff --git a/x-pack/plugins/observability/public/components/app/news_feed/index.test.tsx b/x-pack/plugins/observability/public/components/app/news_feed/index.test.tsx index 97b5dbc679839..f6e641082e557 100644 --- a/x-pack/plugins/observability/public/components/app/news_feed/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/news_feed/index.test.tsx @@ -59,6 +59,6 @@ describe('News', () => { ); expect(getByText("What's new")).toBeInTheDocument(); expect(getAllByText('Read full story').length).toEqual(3); - expect(queryAllByTestId('news_image').length).toEqual(1); + expect(queryAllByTestId('newsImage').length).toEqual(1); }); }); diff --git a/x-pack/plugins/observability/public/components/app/news_feed/index.tsx b/x-pack/plugins/observability/public/components/app/news_feed/index.tsx index 68039f80b0b94..6f1a5f33b9ba7 100644 --- a/x-pack/plugins/observability/public/components/app/news_feed/index.tsx +++ b/x-pack/plugins/observability/public/components/app/news_feed/index.tsx @@ -85,7 +85,7 @@ function NewsItem({ item }: { item: INewsItem }) { {item.image_url?.en && (
- + ); } diff --git a/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx b/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx index 44f699c6c390b..cf3ac2b6c7be5 100644 --- a/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx @@ -24,6 +24,7 @@ import { EuiSelect } from '@elastic/eui'; import { uniqBy } from 'lodash'; import { Alert } from '../../../../../../alerting/common'; import { usePluginContext } from '../../../../hooks/use_plugin_context'; +import { paths } from '../../../../config'; const ALL_TYPES = 'ALL_TYPES'; const allTypes = { @@ -41,8 +42,8 @@ export function AlertsSection({ alerts }: Props) { const { config, core } = usePluginContext(); const [filter, setFilter] = useState(ALL_TYPES); const manageLink = config.unsafe.alertingExperience.enabled - ? core.http.basePath.prepend(`/app/observability/alerts`) - : core.http.basePath.prepend(`/app/management/insightsAndAlerting/triggersActions/rules`); + ? core.http.basePath.prepend(paths.observability.alerts) + : core.http.basePath.prepend(paths.management.rules); const filterOptions = uniqBy(alerts, (alert) => alert.consumer).map(({ consumer }) => ({ value: consumer, text: consumer, @@ -89,9 +90,7 @@ export function AlertsSection({ alerts }: Props) { {alert.name} diff --git a/x-pack/plugins/observability/public/components/shared/core_web_vitals/palette_legends.tsx b/x-pack/plugins/observability/public/components/shared/core_web_vitals/palette_legends.tsx index 840702c744379..70ae61b5e0d74 100644 --- a/x-pack/plugins/observability/public/components/shared/core_web_vitals/palette_legends.tsx +++ b/x-pack/plugins/observability/public/components/shared/core_web_vitals/palette_legends.tsx @@ -16,8 +16,7 @@ import { } from '@elastic/eui'; import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n/react'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; -import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; +import { euiLightVars, euiDarkVars } from '@kbn/ui-shared-deps-src/theme'; import { getCoreVitalTooltipMessage, Thresholds } from './core_vital_item'; import { useUiSetting$ } from '../../../../../../../src/plugins/kibana_react/public'; import { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/action_menu.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/action_menu.tsx index 08b4a3b948c57..512a6389bbf72 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/action_menu.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/action_menu/action_menu.tsx @@ -36,9 +36,11 @@ export function ExpViewActionMenuContent({ responsive={false} style={{ paddingRight: 20 }} > - - - + {timeRange && ( + + + + )} + ); + expect(await findByText('Add to case')).toBeInTheDocument(); + + expect(useAddToCaseHook).toHaveBeenCalledWith( + expect.objectContaining({ + lensAttributes: { + title: 'Performance distribution', + }, + timeRange: { + from: '2021-11-10T10:52:06.091Z', + to: '2021-11-10T10:52:06.091Z', + }, + }) + ); + }); + it('should be able to click add to case button', async function () { const initSeries = { data: [ diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/add_to_case_action.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/add_to_case_action.tsx index bc813a4980e78..1d230c765edae 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/add_to_case_action.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/add_to_case_action.tsx @@ -15,9 +15,10 @@ import { TypedLensByValueInput } from '../../../../../../lens/public'; import { useAddToCase } from '../hooks/use_add_to_case'; import { Case, SubCase } from '../../../../../../cases/common'; import { observabilityFeatureId } from '../../../../../common'; +import { parseRelativeDate } from '../components/date_range_picker'; export interface AddToCaseProps { - timeRange?: { from: string; to: string }; + timeRange: { from: string; to: string }; lensAttributes: TypedLensByValueInput['attributes'] | null; } @@ -31,11 +32,14 @@ export function AddToCaseAction({ lensAttributes, timeRange }: AddToCaseProps) { [http.basePath] ); + const absoluteFromDate = parseRelativeDate(timeRange.from); + const absoluteToDate = parseRelativeDate(timeRange.to, { roundUp: true }); + const { createCaseUrl, goToCreateCase, onCaseClicked, isCasesOpen, setIsCasesOpen, isSaving } = useAddToCase({ lensAttributes, getToastText, - timeRange, + timeRange: { from: absoluteFromDate.toISOString(), to: absoluteToDate.toISOString() }, }); const getAllCasesSelectorModalProps: AllCasesSelectorModalProps = { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx index 8a766075ef8d2..9bd611c05e956 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx @@ -6,6 +6,7 @@ */ import React, { createContext, useContext, Context, useState, useCallback, useMemo } from 'react'; +import { HttpFetchError } from 'kibana/public'; import { IndexPattern } from '../../../../../../../../src/plugins/data/common'; import { AppDataType } from '../types'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; @@ -16,6 +17,7 @@ import { getDataHandler } from '../../../../data_handler'; export interface IndexPatternContext { loading: boolean; indexPatterns: IndexPatternState; + indexPatternErrors: IndexPatternErrors; hasAppData: HasAppDataState; loadIndexPattern: (params: { dataType: AppDataType }) => void; } @@ -28,11 +30,15 @@ interface ProviderProps { type HasAppDataState = Record; export type IndexPatternState = Record; +export type IndexPatternErrors = Record; type LoadingState = Record; export function IndexPatternContextProvider({ children }: ProviderProps) { const [loading, setLoading] = useState({} as LoadingState); const [indexPatterns, setIndexPatterns] = useState({} as IndexPatternState); + const [indexPatternErrors, setIndexPatternErrors] = useState( + {} as IndexPatternErrors + ); const [hasAppData, setHasAppData] = useState({ infra_metrics: null, infra_logs: null, @@ -78,6 +84,9 @@ export function IndexPatternContextProvider({ children }: ProviderProps) { } setLoading((prevState) => ({ ...prevState, [dataType]: false })); } catch (e) { + if ((e as HttpFetchError).body.error === 'Forbidden') { + setIndexPatternErrors((prevState) => ({ ...prevState, [dataType]: e })); + } setLoading((prevState) => ({ ...prevState, [dataType]: false })); } } @@ -91,6 +100,7 @@ export function IndexPatternContextProvider({ children }: ProviderProps) { hasAppData, indexPatterns, loadIndexPattern, + indexPatternErrors, loading: !!Object.values(loading).find((loadingT) => loadingT), }} > @@ -100,7 +110,7 @@ export function IndexPatternContextProvider({ children }: ProviderProps) { } export const useAppIndexPatternContext = (dataType?: AppDataType) => { - const { loading, hasAppData, loadIndexPattern, indexPatterns } = useContext( + const { loading, hasAppData, loadIndexPattern, indexPatterns, indexPatternErrors } = useContext( IndexPatternContext as unknown as Context ); @@ -113,9 +123,10 @@ export const useAppIndexPatternContext = (dataType?: AppDataType) => { hasAppData, loading, indexPatterns, + indexPatternErrors, indexPattern: dataType ? indexPatterns?.[dataType] : undefined, hasData: dataType ? hasAppData?.[dataType] : undefined, loadIndexPattern, }; - }, [dataType, hasAppData, indexPatterns, loadIndexPattern, loading]); + }, [dataType, hasAppData, indexPatternErrors, indexPatterns, loadIndexPattern, loading]); }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx index 3de29b02853e8..1fc38ab79de7f 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/index.tsx @@ -36,7 +36,7 @@ export function ExploratoryViewPage({ useBreadcrumbs([ { text: i18n.translate('xpack.observability.overview.exploratoryView', { - defaultMessage: 'Analyze data', + defaultMessage: 'Explore data', }), }, ]); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx index efca1152e175d..612cbfcc4bfdf 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx @@ -243,6 +243,7 @@ export const mockAppIndexPattern = () => { hasAppData: { ux: true } as any, loadIndexPattern, indexPatterns: { ux: mockIndexPattern } as unknown as Record, + indexPatternErrors: {} as any, }); return { spy, loadIndexPattern }; }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/report_metric_options.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/report_metric_options.tsx index eca18f0eb0dd4..410356d0078d8 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/report_metric_options.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/report_metric_options.tsx @@ -35,7 +35,7 @@ export function ReportMetricOptions({ seriesId, series, seriesConfig }: Props) { const [showOptions, setShowOptions] = useState(false); const metricOptions = seriesConfig?.metricOptions; - const { indexPatterns, loading } = useAppIndexPatternContext(); + const { indexPatterns, indexPatternErrors, loading } = useAppIndexPatternContext(); const onChange = (value?: string) => { setSeries(seriesId, { @@ -49,6 +49,7 @@ export function ReportMetricOptions({ seriesId, series, seriesConfig }: Props) { } const indexPattern = indexPatterns?.[series.dataType]; + const indexPatternError = indexPatternErrors?.[series.dataType]; const options = (metricOptions ?? []).map(({ label, field, id }) => { let disabled = false; @@ -80,6 +81,17 @@ export function ReportMetricOptions({ seriesId, series, seriesConfig }: Props) { }; }); + if (indexPatternError && !indexPattern && !loading) { + // TODO: Add a link to docs to explain how to add index patterns + return ( + + {indexPatternError.body.error === 'Forbidden' + ? NO_PERMISSIONS + : indexPatternError.body.message} + + ); + } + if (!indexPattern && !loading) { return {NO_DATA_AVAILABLE}; } @@ -152,3 +164,8 @@ const REMOVE_REPORT_METRIC_LABEL = i18n.translate( const NO_DATA_AVAILABLE = i18n.translate('xpack.observability.expView.seriesEditor.noData', { defaultMessage: 'No data available', }); + +const NO_PERMISSIONS = i18n.translate('xpack.observability.expView.seriesEditor.noPermissions', { + defaultMessage: + "Unable to create Index Pattern. You don't have the required permission, please contact your admin.", +}); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx index afb8baac0eaf3..4d77c04fc7805 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx @@ -7,14 +7,7 @@ import React, { useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { - EuiSpacer, - EuiFormRow, - EuiFlexItem, - EuiFlexGroup, - EuiButtonEmpty, - EuiHorizontalRule, -} from '@elastic/eui'; +import { EuiSpacer, EuiFormRow, EuiFlexItem, EuiFlexGroup, EuiHorizontalRule } from '@elastic/eui'; import { rgba } from 'polished'; import { euiStyled } from './../../../../../../../../src/plugins/kibana_react/common'; import { AppDataType, ReportViewType, BuilderItem } from '../types'; @@ -62,7 +55,7 @@ export const getSeriesToEdit = ({ export const SeriesEditor = React.memo(function () { const [editorItems, setEditorItems] = useState([]); - const { getSeries, allSeries, reportType, removeSeries } = useSeriesStorage(); + const { getSeries, allSeries, reportType } = useSeriesStorage(); const { loading, indexPatterns } = useAppIndexPatternContext(); @@ -120,15 +113,6 @@ export const SeriesEditor = React.memo(function () { setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); }; - const resetView = () => { - const totalSeries = allSeries.length; - for (let i = totalSeries; i >= 0; i--) { - removeSeries(i); - } - setEditorItems([]); - setItemIdToExpandedRowMap({}); - }; - return (
@@ -138,13 +122,6 @@ export const SeriesEditor = React.memo(function () { - {reportType && ( - - resetView()} color="text"> - {RESET_LABEL} - - - )} setItemIdToExpandedRowMap({})} /> diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts index b61af3a61c3dc..f591ef63a61fb 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts @@ -86,7 +86,6 @@ export class ObservabilityIndexPatterns { } const appIndicesPattern = getAppIndicesWithPattern(app, indices); - return await this.data.indexPatterns.createAndSave({ title: appIndicesPattern, id: getAppIndexPatternId(app, indices), diff --git a/x-pack/plugins/observability/public/config/index.ts b/x-pack/plugins/observability/public/config/index.ts new file mode 100644 index 0000000000000..fc6300acc4716 --- /dev/null +++ b/x-pack/plugins/observability/public/config/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { paths } from './paths'; +export { translations } from './translations'; diff --git a/x-pack/plugins/observability/public/config/paths.ts b/x-pack/plugins/observability/public/config/paths.ts new file mode 100644 index 0000000000000..57bbc95fef40b --- /dev/null +++ b/x-pack/plugins/observability/public/config/paths.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const paths = { + observability: { + alerts: '/app/observability/alerts', + }, + management: { + rules: '/app/management/insightsAndAlerting/triggersActions/rules', + ruleDetails: (ruleId: string) => + `/app/management/insightsAndAlerting/triggersActions/rule/${encodeURI(ruleId)}`, + alertDetails: (alertId: string) => + `/app/management/insightsAndAlerting/triggersActions/alert/${encodeURI(alertId)}`, + }, +}; diff --git a/x-pack/plugins/observability/public/config/translations.ts b/x-pack/plugins/observability/public/config/translations.ts new file mode 100644 index 0000000000000..265787ede4473 --- /dev/null +++ b/x-pack/plugins/observability/public/config/translations.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const translations = { + alertsTable: { + viewDetailsTextLabel: i18n.translate('xpack.observability.alertsTable.viewDetailsTextLabel', { + defaultMessage: 'View details', + }), + viewInAppTextLabel: i18n.translate('xpack.observability.alertsTable.viewInAppTextLabel', { + defaultMessage: 'View in app', + }), + moreActionsTextLabel: i18n.translate('xpack.observability.alertsTable.moreActionsTextLabel', { + defaultMessage: 'More actions', + }), + notEnoughPermissions: i18n.translate('xpack.observability.alertsTable.notEnoughPermissions', { + defaultMessage: 'Additional privileges required', + }), + statusColumnDescription: i18n.translate( + 'xpack.observability.alertsTGrid.statusColumnDescription', + { + defaultMessage: 'Alert Status', + } + ), + lastUpdatedColumnDescription: i18n.translate( + 'xpack.observability.alertsTGrid.lastUpdatedColumnDescription', + { + defaultMessage: 'Last updated', + } + ), + durationColumnDescription: i18n.translate( + 'xpack.observability.alertsTGrid.durationColumnDescription', + { + defaultMessage: 'Duration', + } + ), + reasonColumnDescription: i18n.translate( + 'xpack.observability.alertsTGrid.reasonColumnDescription', + { + defaultMessage: 'Reason', + } + ), + actionsTextLabel: i18n.translate('xpack.observability.alertsTable.actionsTextLabel', { + defaultMessage: 'Actions', + }), + loadingTextLabel: i18n.translate('xpack.observability.alertsTable.loadingTextLabel', { + defaultMessage: 'loading alerts', + }), + footerTextLabel: i18n.translate('xpack.observability.alertsTable.footerTextLabel', { + defaultMessage: 'alerts', + }), + showingAlertsTitle: (totalAlerts: number) => + i18n.translate('xpack.observability.alertsTable.showingAlertsTitle', { + values: { totalAlerts }, + defaultMessage: '{totalAlerts, plural, =1 {alert} other {alerts}}', + }), + viewRuleDetailsButtonText: i18n.translate( + 'xpack.observability.alertsTable.viewRuleDetailsButtonText', + { + defaultMessage: 'View rule details', + } + ), + }, + alertsFlyout: { + statusLabel: i18n.translate('xpack.observability.alertsFlyout.statusLabel', { + defaultMessage: 'Status', + }), + lastUpdatedLabel: i18n.translate('xpack.observability.alertsFlyout.lastUpdatedLabel', { + defaultMessage: 'Last updated', + }), + durationLabel: i18n.translate('xpack.observability.alertsFlyout.durationLabel', { + defaultMessage: 'Duration', + }), + expectedValueLabel: i18n.translate('xpack.observability.alertsFlyout.expectedValueLabel', { + defaultMessage: 'Expected value', + }), + actualValueLabel: i18n.translate('xpack.observability.alertsFlyout.actualValueLabel', { + defaultMessage: 'Actual value', + }), + ruleTypeLabel: i18n.translate('xpack.observability.alertsFlyout.ruleTypeLabel', { + defaultMessage: 'Rule type', + }), + reasonTitle: i18n.translate('xpack.observability.alertsFlyout.reasonTitle', { + defaultMessage: 'Reason', + }), + viewRulesDetailsLinkText: i18n.translate( + 'xpack.observability.alertsFlyout.viewRulesDetailsLinkText', + { + defaultMessage: 'View rule details', + } + ), + documentSummaryTitle: i18n.translate('xpack.observability.alertsFlyout.documentSummaryTitle', { + defaultMessage: 'Document Summary', + }), + viewInAppButtonText: i18n.translate('xpack.observability.alertsFlyout.viewInAppButtonText', { + defaultMessage: 'View in app', + }), + }, +}; diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_disclaimer.tsx b/x-pack/plugins/observability/public/pages/alerts/alerts_disclaimer.tsx index 4bf71574ea7f9..1d1aaf12cf785 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts_disclaimer.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/alerts_disclaimer.tsx @@ -33,7 +33,7 @@ export function AlertsDisclaimer() { <> {!experimentalMsgAck && ( { const parseObservabilityAlert = parseAlert(observabilityRuleTypeRegistry); return (alerts ?? []).map(parseObservabilityAlert); @@ -90,11 +93,12 @@ export function AlertsFlyout({ return null; } + const ruleId = alertData.fields['kibana.alert.rule.uuid'] ?? null; + const linkToRule = ruleId && prepend ? prepend(paths.management.ruleDetails(ruleId)) : null; + const overviewListItems = [ { - title: i18n.translate('xpack.observability.alertsFlyout.statusLabel', { - defaultMessage: 'Status', - }), + title: translations.alertsFlyout.statusLabel, description: ( {moment(alertData.start).format(dateFormat)} ), }, { - title: i18n.translate('xpack.observability.alertsFlyout.durationLabel', { - defaultMessage: 'Duration', - }), + title: translations.alertsFlyout.durationLabel, description: asDuration(alertData.fields[ALERT_DURATION], { extended: true }), }, { - title: i18n.translate('xpack.observability.alertsFlyout.expectedValueLabel', { - defaultMessage: 'Expected value', - }), + title: translations.alertsFlyout.expectedValueLabel, description: alertData.fields[ALERT_EVALUATION_THRESHOLD] ?? '-', }, { - title: i18n.translate('xpack.observability.alertsFlyout.actualValueLabel', { - defaultMessage: 'Actual value', - }), + title: translations.alertsFlyout.actualValueLabel, description: alertData.fields[ALERT_EVALUATION_VALUE] ?? '-', }, { - title: i18n.translate('xpack.observability.alertsFlyout.ruleTypeLabel', { - defaultMessage: 'Rule type', - }), + title: translations.alertsFlyout.ruleTypeLabel, description: alertData.fields[ALERT_RULE_CATEGORY] ?? '-', }, ]; return ( - +

{alertData.fields[ALERT_RULE_NAME]}

- - {alertData.reason}
+ +

{translations.alertsFlyout.reasonTitle}

+
+ + {alertData.reason} + {!!linkToRule && ( + + {translations.alertsFlyout.viewRulesDetailsLinkText} + + )} + + +

{translations.alertsFlyout.documentSummaryTitle}

+
+ - View in app + {translations.alertsFlyout.viewInAppButtonText}
diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx b/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx index 8c973dfb730f4..523d0f19be2be 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx @@ -34,10 +34,12 @@ import { EuiDataGridColumn, EuiFlexGroup, EuiFlexItem, + EuiContextMenuItem, EuiContextMenuPanel, EuiPopover, EuiToolTip, } from '@elastic/eui'; + import styled from 'styled-components'; import React, { Suspense, useMemo, useState, useCallback, useEffect } from 'react'; import usePrevious from 'react-use/lib/usePrevious'; @@ -65,7 +67,7 @@ import { getDefaultCellActions } from './default_cell_actions'; import { LazyAlertsFlyout } from '../..'; import { parseAlert } from './parse_alert'; import { CoreStart } from '../../../../../../src/core/public'; -import { translations } from './translations'; +import { translations, paths } from '../../config'; const ALERT_DURATION: typeof ALERT_DURATION_TYPED = ALERT_DURATION_NON_TYPED; const ALERT_REASON: typeof ALERT_REASON_TYPED = ALERT_REASON_NON_TYPED; @@ -115,25 +117,25 @@ export const columns: Array< > = [ { columnHeaderType: 'not-filtered', - displayAsText: translations.statusColumnDescription, + displayAsText: translations.alertsTable.statusColumnDescription, id: ALERT_STATUS, initialWidth: 110, }, { columnHeaderType: 'not-filtered', - displayAsText: translations.lastUpdatedColumnDescription, + displayAsText: translations.alertsTable.lastUpdatedColumnDescription, id: TIMESTAMP, initialWidth: 230, }, { columnHeaderType: 'not-filtered', - displayAsText: translations.durationColumnDescription, + displayAsText: translations.alertsTable.durationColumnDescription, id: ALERT_DURATION, initialWidth: 116, }, { columnHeaderType: 'not-filtered', - displayAsText: translations.reasonColumnDescription, + displayAsText: translations.alertsTable.reasonColumnDescription, id: ALERT_REASON, linkField: '*', }, @@ -188,6 +190,7 @@ function ObservabilityActions({ const toggleActionsPopover = useCallback((id) => { setActionsPopover((current) => (current ? null : id)); }, []); + const casePermissions = useGetUserCasesPermissions(); const event = useMemo(() => { return { @@ -219,6 +222,9 @@ function ObservabilityActions({ onUpdateFailure: onAlertStatusUpdated, }); + const ruleId = alert.fields['kibana.alert.rule.uuid'] ?? null; + const linkToRule = ruleId ? prepend(paths.management.ruleDetails(ruleId)) : null; + const actionsMenuItems = useMemo(() => { return [ ...(casePermissions?.crud @@ -240,37 +246,56 @@ function ObservabilityActions({ ] : []), ...(alertPermissions.crud ? statusActionItems : []), + ...(!!linkToRule + ? [ + + {translations.alertsTable.viewRuleDetailsButtonText} + , + ] + : []), ]; - }, [afterCaseSelection, casePermissions, timelines, event, statusActionItems, alertPermissions]); + }, [ + afterCaseSelection, + casePermissions, + timelines, + event, + statusActionItems, + alertPermissions, + linkToRule, + ]); const actionsToolTip = actionsMenuItems.length <= 0 - ? translations.notEnoughPermissions - : translations.moreActionsTextLabel; + ? translations.alertsTable.notEnoughPermissions + : translations.alertsTable.moreActionsTextLabel; return ( <> - + setFlyoutAlert(alert)} data-test-subj="openFlyoutButton" - aria-label={translations.viewDetailsTextLabel} + aria-label={translations.alertsTable.viewDetailsTextLabel} /> - + @@ -280,13 +305,12 @@ function ObservabilityActions({ toggleActionsPopover(eventId)} - data-test-subj="alerts-table-row-action-more" + data-test-subj="alertsTableRowActionMore" /> } @@ -345,7 +369,7 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) { id: 'expand', width: 120, headerCellRender: () => { - return {translations.actionsTextLabel}; + return {translations.alertsTable.actionsTextLabel}; }, rowCellRender: (actionProps: ActionProps) => { return ( @@ -377,8 +401,8 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) { hasAlertsCrudPermissions, indexNames, itemsPerPageOptions: [10, 25, 50], - loadingText: translations.loadingTextLabel, - footerText: translations.footerTextLabel, + loadingText: translations.alertsTable.loadingTextLabel, + footerText: translations.alertsTable.footerTextLabel, query: { query: `${ALERT_WORKFLOW_STATUS}: ${workflowStatus}${kuery !== '' ? ` and ${kuery}` : ''}`, language: 'kuery', @@ -399,7 +423,7 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) { filterStatus: workflowStatus as AlertWorkflowStatus, leadingControlColumns, trailingControlColumns, - unit: (totalAlerts: number) => translations.showingAlertsTitle(totalAlerts), + unit: (totalAlerts: number) => translations.alertsTable.showingAlertsTitle(totalAlerts), }; }, [ casePermissions, diff --git a/x-pack/plugins/observability/public/pages/alerts/filter_for_value.tsx b/x-pack/plugins/observability/public/pages/alerts/filter_for_value.tsx index f75ae488c9b28..7017f573415da 100644 --- a/x-pack/plugins/observability/public/pages/alerts/filter_for_value.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/filter_for_value.tsx @@ -35,7 +35,7 @@ const FilterForValueButton: React.FC = React.memo( Component ? ( = React.memo( - i18n.translate('xpack.observability.alertsTable.showingAlertsTitle', { - values: { totalAlerts }, - defaultMessage: '{totalAlerts, plural, =1 {alert} other {alerts}}', - }), -}; diff --git a/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.test.tsx b/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.test.tsx index f7441742ff387..29c5e88788a89 100644 --- a/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.test.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.test.tsx @@ -28,7 +28,7 @@ describe('StatusFilter', () => { const props = { onChange, status }; const { getByTestId } = render(); - const button = getByTestId(`workflow-status-filter-${status}-button`); + const button = getByTestId(`workflowStatusFilterButton-${status}`); const input = button.querySelector('input') as Element; Simulate.change(input); diff --git a/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.tsx b/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.tsx index 20073e9937b4f..d857b9d6bd650 100644 --- a/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.tsx @@ -21,7 +21,7 @@ const options: Array = label: i18n.translate('xpack.observability.alerts.workflowStatusFilter.openButtonLabel', { defaultMessage: 'Open', }), - 'data-test-subj': 'workflow-status-filter-open-button', + 'data-test-subj': 'workflowStatusFilterButton-open', }, { id: 'acknowledged', @@ -31,14 +31,14 @@ const options: Array = defaultMessage: 'Acknowledged', } ), - 'data-test-subj': 'workflow-status-filter-acknowledged-button', + 'data-test-subj': 'workflowStatusFilterButton-acknowledged', }, { id: 'closed', label: i18n.translate('xpack.observability.alerts.workflowStatusFilter.closedButtonLabel', { defaultMessage: 'Closed', }), - 'data-test-subj': 'workflow-status-filter-closed-button', + 'data-test-subj': 'workflowStatusFilterButton-closed', }, ]; diff --git a/x-pack/plugins/observability/public/pages/cases/all_cases.tsx b/x-pack/plugins/observability/public/pages/cases/all_cases.tsx index 4ac7c4cfd92a5..76c54a470ff83 100644 --- a/x-pack/plugins/observability/public/pages/cases/all_cases.tsx +++ b/x-pack/plugins/observability/public/pages/cases/all_cases.tsx @@ -8,7 +8,6 @@ import React from 'react'; import { AllCases } from '../../components/app/cases/all_cases'; -import * as i18n from '../../components/app/cases/translations'; import { CaseFeatureNoPermissions } from './feature_no_permissions'; import { useGetUserCasesPermissions } from '../../hooks/use_get_user_cases_permissions'; @@ -45,9 +44,6 @@ export const AllCasesPage = React.memo(() => { {i18n.PAGE_TITLE}, - }} > diff --git a/x-pack/plugins/observability/public/pages/cases/empty_page.tsx b/x-pack/plugins/observability/public/pages/cases/empty_page.tsx index c6fc4b59ef77c..faeafa6b4730f 100644 --- a/x-pack/plugins/observability/public/pages/cases/empty_page.tsx +++ b/x-pack/plugins/observability/public/pages/cases/empty_page.tsx @@ -59,7 +59,7 @@ const EmptyPageComponent = React.memo(({ actions, message, title (({ actions, message, title iconType={icon} target={target} fill={fill} - data-test-subj={`empty-page-${titles[idx]}-action`} + data-test-subj={`emptyPageAction-${titles[idx]}`} > {label} @@ -83,7 +83,7 @@ const EmptyPageComponent = React.memo(({ actions, message, title {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} (({ actions, message, title onClick={onClick} iconType={icon} target={target} - data-test-subj={`empty-page-${titles[idx]}-action`} + data-test-subj={`emptyPageAction-${titles[idx]}`} > {label} diff --git a/x-pack/plugins/observability/public/pages/cases/feature_no_permissions.tsx b/x-pack/plugins/observability/public/pages/cases/feature_no_permissions.tsx index 5075570c15b3e..2d8631a94e04c 100644 --- a/x-pack/plugins/observability/public/pages/cases/feature_no_permissions.tsx +++ b/x-pack/plugins/observability/public/pages/cases/feature_no_permissions.tsx @@ -29,7 +29,7 @@ export const CaseFeatureNoPermissions = React.memo(() => { ); diff --git a/x-pack/plugins/osquery/public/agents/use_agent_policy_agent_ids.ts b/x-pack/plugins/osquery/public/agents/use_agent_policy_agent_ids.ts index 65a2520e07d0b..77ca08e284182 100644 --- a/x-pack/plugins/osquery/public/agents/use_agent_policy_agent_ids.ts +++ b/x-pack/plugins/osquery/public/agents/use_agent_policy_agent_ids.ts @@ -9,7 +9,7 @@ import { map } from 'lodash'; import { i18n } from '@kbn/i18n'; import { useQuery } from 'react-query'; -import { AGENT_SAVED_OBJECT_TYPE, Agent } from '../../../fleet/common'; +import { AGENTS_PREFIX, Agent } from '../../../fleet/common'; import { useErrorToast } from '../common/hooks/use_error_toast'; import { useKibana } from '../common/lib/kibana'; @@ -30,7 +30,7 @@ export const useAgentPolicyAgentIds = ({ return useQuery<{ agents: Agent[] }, unknown, string[]>( ['agentPolicyAgentIds', agentPolicyId], () => { - const kuery = `${AGENT_SAVED_OBJECT_TYPE}.policy_id:${agentPolicyId}`; + const kuery = `${AGENTS_PREFIX}.policy_id:${agentPolicyId}`; return http.get(`/internal/osquery/fleet_wrapper/agents`, { query: { diff --git a/x-pack/plugins/osquery/public/application.tsx b/x-pack/plugins/osquery/public/application.tsx index 3e959132e21a8..3e046a138cd4b 100644 --- a/x-pack/plugins/osquery/public/application.tsx +++ b/x-pack/plugins/osquery/public/application.tsx @@ -6,8 +6,7 @@ */ import { EuiErrorBoundary } from '@elastic/eui'; -import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars, euiDarkVars } from '@kbn/ui-shared-deps-src/theme'; import React, { useMemo } from 'react'; import ReactDOM from 'react-dom'; import { Router } from 'react-router-dom'; diff --git a/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_policies.ts b/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_policies.ts index accfc2d9ef4da..06641cc60e13d 100644 --- a/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_policies.ts +++ b/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_policies.ts @@ -5,7 +5,7 @@ * 2.0. */ -import bluebird from 'bluebird'; +import pMap from 'p-map'; import { schema } from '@kbn/config-schema'; import { filter, uniq, map } from 'lodash'; import { satisfies } from 'semver'; @@ -47,7 +47,7 @@ export const getAgentPoliciesRoute = (router: IRouter, osqueryContext: OsqueryAp const agentPolicies = await agentPolicyService?.getByIds(soClient, agentPolicyIds); if (agentPolicies?.length) { - await bluebird.map( + await pMap( agentPolicies, (agentPolicy: GetAgentPoliciesResponseItem) => agentService diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.test.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.test.ts new file mode 100644 index 0000000000000..dae692fae8825 --- /dev/null +++ b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.test.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import puppeteer from 'puppeteer'; +import * as Rx from 'rxjs'; +import { take } from 'rxjs/operators'; +import { HeadlessChromiumDriverFactory } from '.'; +import type { ReportingCore } from '../../..'; +import { + createMockConfigSchema, + createMockLevelLogger, + createMockReportingCore, +} from '../../../test_helpers'; + +jest.mock('puppeteer'); + +const mock = (browserDriverFactory: HeadlessChromiumDriverFactory) => { + browserDriverFactory.getBrowserLogger = jest.fn(() => new Rx.Observable()); + browserDriverFactory.getProcessLogger = jest.fn(() => new Rx.Observable()); + browserDriverFactory.getPageExit = jest.fn(() => new Rx.Observable()); + return browserDriverFactory; +}; + +describe('class HeadlessChromiumDriverFactory', () => { + let reporting: ReportingCore; + const logger = createMockLevelLogger(); + const path = 'path/to/headless_shell'; + + beforeEach(async () => { + (puppeteer as jest.Mocked).launch.mockResolvedValue({ + newPage: jest.fn().mockResolvedValue({ + target: jest.fn(() => ({ + createCDPSession: jest.fn().mockResolvedValue({ + send: jest.fn(), + }), + })), + emulateTimezone: jest.fn(), + setDefaultTimeout: jest.fn(), + }), + close: jest.fn(), + process: jest.fn(), + } as unknown as puppeteer.Browser); + + reporting = await createMockReportingCore( + createMockConfigSchema({ + capture: { + browser: { chromium: { proxy: {} } }, + timeouts: { openUrl: 50000 }, + }, + }) + ); + }); + + it('createPage returns browser driver and process exit observable', async () => { + const factory = mock(new HeadlessChromiumDriverFactory(reporting, path, logger)); + const utils = await factory.createPage({}).pipe(take(1)).toPromise(); + expect(utils).toHaveProperty('driver'); + expect(utils).toHaveProperty('exit$'); + }); + + it('createPage rejects if Puppeteer launch fails', async () => { + (puppeteer as jest.Mocked).launch.mockRejectedValue( + `Puppeteer Launch mock fail.` + ); + const factory = mock(new HeadlessChromiumDriverFactory(reporting, path, logger)); + expect(() => + factory.createPage({}).pipe(take(1)).toPromise() + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Error spawning Chromium browser! Puppeteer Launch mock fail."` + ); + }); +}); diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts index 264e673d2bf74..2aef62f59985b 100644 --- a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts +++ b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts @@ -23,7 +23,7 @@ import { LevelLogger } from '../../../lib'; import { safeChildProcess } from '../../safe_child_process'; import { HeadlessChromiumDriver } from '../driver'; import { args } from './args'; -import { getMetrics, Metrics } from './metrics'; +import { getMetrics } from './metrics'; type BrowserConfig = CaptureConfig['browser']['chromium']; @@ -35,7 +35,7 @@ export class HeadlessChromiumDriverFactory { private getChromiumArgs: () => string[]; private core: ReportingCore; - constructor(core: ReportingCore, binaryPath: string, logger: LevelLogger) { + constructor(core: ReportingCore, binaryPath: string, private logger: LevelLogger) { this.core = core; this.binaryPath = binaryPath; const config = core.getConfig(); @@ -62,7 +62,7 @@ export class HeadlessChromiumDriverFactory { */ createPage( { browserTimezone }: { browserTimezone?: string }, - pLogger: LevelLogger + pLogger = this.logger ): Rx.Observable<{ driver: HeadlessChromiumDriver; exit$: Rx.Observable }> { // FIXME: 'create' is deprecated return Rx.Observable.create(async (observer: InnerSubscriber) => { @@ -72,10 +72,7 @@ export class HeadlessChromiumDriverFactory { const chromiumArgs = this.getChromiumArgs(); logger.debug(`Chromium launch args set to: ${chromiumArgs}`); - let browser: puppeteer.Browser; - let page: puppeteer.Page; - let devTools: puppeteer.CDPSession | undefined; - let startMetrics: Metrics | undefined; + let browser: puppeteer.Browser | null = null; try { browser = await puppeteer.launch({ @@ -89,29 +86,28 @@ export class HeadlessChromiumDriverFactory { TZ: browserTimezone, }, }); + } catch (err) { + observer.error(new Error(`Error spawning Chromium browser! ${err}`)); + return; + } - page = await browser.newPage(); - devTools = await page.target().createCDPSession(); + const page = await browser.newPage(); + const devTools = await page.target().createCDPSession(); - await devTools.send('Performance.enable', { timeDomain: 'timeTicks' }); - startMetrics = await devTools.send('Performance.getMetrics'); + await devTools.send('Performance.enable', { timeDomain: 'timeTicks' }); + const startMetrics = await devTools.send('Performance.getMetrics'); - // Log version info for debugging / maintenance - const versionInfo = await devTools.send('Browser.getVersion'); - logger.debug(`Browser version: ${JSON.stringify(versionInfo)}`); + // Log version info for debugging / maintenance + const versionInfo = await devTools.send('Browser.getVersion'); + logger.debug(`Browser version: ${JSON.stringify(versionInfo)}`); - await page.emulateTimezone(browserTimezone); + await page.emulateTimezone(browserTimezone); - // Set the default timeout for all navigation methods to the openUrl timeout (30 seconds) - // All waitFor methods have their own timeout config passed in to them - page.setDefaultTimeout(durationToNumber(this.captureConfig.timeouts.openUrl)); + // Set the default timeout for all navigation methods to the openUrl timeout + // All waitFor methods have their own timeout config passed in to them + page.setDefaultTimeout(durationToNumber(this.captureConfig.timeouts.openUrl)); - logger.debug(`Browser page driver created`); - } catch (err) { - observer.error(new Error(`Error spawning Chromium browser!`)); - observer.error(err); - throw err; - } + logger.debug(`Browser page driver created`); const childProcess = { async kill() { @@ -134,7 +130,7 @@ export class HeadlessChromiumDriverFactory { } try { - await browser.close(); + await browser?.close(); } catch (err) { // do not throw logger.error(err); diff --git a/x-pack/plugins/reporting/server/core.ts b/x-pack/plugins/reporting/server/core.ts index e89ba6af3e28f..bc74f5463ba33 100644 --- a/x-pack/plugins/reporting/server/core.ts +++ b/x-pack/plugins/reporting/server/core.ts @@ -7,7 +7,7 @@ import Hapi from '@hapi/hapi'; import * as Rx from 'rxjs'; -import { first, map, take } from 'rxjs/operators'; +import { filter, first, map, take } from 'rxjs/operators'; import { ScreenshotModePluginSetup } from 'src/plugins/screenshot_mode/server'; import { BasePath, @@ -17,6 +17,8 @@ import { PluginInitializerContext, SavedObjectsClientContract, SavedObjectsServiceStart, + ServiceStatusLevels, + StatusServiceSetup, UiSettingsServiceStart, } from '../../../../src/core/server'; import { PluginStart as DataPluginStart } from '../../../../src/plugins/data/server'; @@ -44,6 +46,7 @@ export interface ReportingInternalSetup { taskManager: TaskManagerSetupContract; screenshotMode: ScreenshotModePluginSetup; logger: LevelLogger; + status: StatusServiceSetup; } export interface ReportingInternalStart { @@ -111,12 +114,25 @@ export class ReportingCore { this.pluginStart$.next(startDeps); // trigger the observer this.pluginStartDeps = startDeps; // cache + await this.assertKibanaIsAvailable(); + const { taskManager } = startDeps; const { executeTask, monitorTask } = this; // enable this instance to generate reports and to monitor for pending reports await Promise.all([executeTask.init(taskManager), monitorTask.init(taskManager)]); } + private async assertKibanaIsAvailable(): Promise { + const { status } = this.getPluginSetupDeps(); + + await status.overall$ + .pipe( + filter((current) => current.level === ServiceStatusLevels.available), + first() + ) + .toPromise(); + } + /* * Blocks the caller until setup is done */ diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts index 3dc06996f0f04..3071ecb54dc26 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts +++ b/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts @@ -353,7 +353,7 @@ describe('Screenshot Observable Pipeline', () => { }, }, ], - "error": [Error: An error occurred when trying to read the page for visualization panel info: Error: Mock error!], + "error": [Error: The "wait for elements" phase encountered an error: Error: An error occurred when trying to read the page for visualization panel info: Error: Mock error!], "screenshots": Array [ Object { "data": Object { diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable.ts index 48802eb5e5fbe..d400c423c5e04 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/observable.ts +++ b/x-pack/plugins/reporting/server/lib/screenshots/observable.ts @@ -58,9 +58,8 @@ export function getScreenshots$( const screen = new ScreenshotObservableHandler(driver, opts, getTimeouts(captureConfig)); return Rx.from(opts.urlsOrUrlLocatorTuples).pipe( - concatMap((urlOrUrlLocatorTuple, index) => { - return Rx.of(1).pipe( - screen.setupPage(index, urlOrUrlLocatorTuple, apmTrans), + concatMap((urlOrUrlLocatorTuple, index) => + screen.setupPage(index, urlOrUrlLocatorTuple, apmTrans).pipe( catchError((err) => { screen.checkPageIsOpen(); // this fails the job if the browser has closed @@ -69,8 +68,8 @@ export function getScreenshots$( }), takeUntil(exit$), screen.getScreenshots() - ); - }), + ) + ), take(opts.urlsOrUrlLocatorTuples.length), toArray() ); diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.test.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.test.ts index 25a8bed370d86..cb0a513992722 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.test.ts +++ b/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.test.ts @@ -95,7 +95,7 @@ describe('ScreenshotObservableHandler', () => { const testPipeline = () => test$.toPromise(); await expect(testPipeline).rejects.toMatchInlineSnapshot( - `[Error: The "Test Config" phase took longer than 0.2 seconds. You may need to increase "test.config.value": TimeoutError: Timeout has occurred]` + `[Error: The "Test Config" phase took longer than 0.2 seconds. You may need to increase "test.config.value"]` ); }); diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts index 87c247273ef04..1db313b091025 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts +++ b/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts @@ -7,7 +7,7 @@ import apm from 'elastic-apm-node'; import * as Rx from 'rxjs'; -import { catchError, mergeMap, timeout } from 'rxjs/operators'; +import { catchError, mergeMap, switchMapTo, timeoutWith } from 'rxjs/operators'; import { numberToDuration } from '../../../common/schema_utils'; import { UrlOrUrlLocatorTuple } from '../../../common/types'; import { HeadlessChromiumDriver } from '../../browsers'; @@ -33,7 +33,6 @@ export class ScreenshotObservableHandler { private conditionalHeaders: ScreenshotObservableOpts['conditionalHeaders']; private layout: ScreenshotObservableOpts['layout']; private logger: ScreenshotObservableOpts['logger']; - private waitErrorRegistered = false; constructor( private readonly driver: HeadlessChromiumDriver, @@ -50,35 +49,27 @@ export class ScreenshotObservableHandler { */ public waitUntil(phase: PhaseInstance) { const { timeoutValue, label, configValue } = phase; - return (source: Rx.Observable) => { - return source.pipe( - timeout(timeoutValue), - catchError((error: string | Error) => { - if (this.waitErrorRegistered) { - throw error; // do not create a stack of errors within the error - } - - this.logger.error(error); - let throwError = new Error(`The "${label}" phase encountered an error: ${error}`); - - if (error instanceof Rx.TimeoutError) { - throwError = new Error( - `The "${label}" phase took longer than` + - ` ${numberToDuration(timeoutValue).asSeconds()} seconds.` + - ` You may need to increase "${configValue}": ${error}` - ); - } - - this.waitErrorRegistered = true; - this.logger.error(throwError); - throw throwError; - }) + + return (source: Rx.Observable) => + source.pipe( + catchError((error) => { + throw new Error(`The "${label}" phase encountered an error: ${error}`); + }), + timeoutWith( + timeoutValue, + Rx.throwError( + new Error( + `The "${label}" phase took longer than ${numberToDuration( + timeoutValue + ).asSeconds()} seconds. You may need to increase "${configValue}"` + ) + ) + ) ); - }; } private openUrl(index: number, urlOrUrlLocatorTuple: UrlOrUrlLocatorTuple) { - return mergeMap(() => + return Rx.defer(() => openUrl( this.timeouts.openUrl.timeoutValue, this.driver, @@ -87,24 +78,25 @@ export class ScreenshotObservableHandler { this.conditionalHeaders, this.logger ) - ); + ).pipe(this.waitUntil(this.timeouts.openUrl)); } private waitForElements() { const driver = this.driver; const waitTimeout = this.timeouts.waitForElements.timeoutValue; - return (withPageOpen: Rx.Observable) => - withPageOpen.pipe( - mergeMap(() => getNumberOfItems(waitTimeout, driver, this.layout, this.logger)), - mergeMap(async (itemsCount) => { - // set the viewport to the dimentions from the job, to allow elements to flow into the expected layout - const viewport = this.layout.getViewport(itemsCount) || getDefaultViewPort(); - await Promise.all([ - driver.setViewport(viewport, this.logger), - waitForVisualizations(waitTimeout, driver, itemsCount, this.layout, this.logger), - ]); - }) - ); + + return Rx.defer(() => getNumberOfItems(waitTimeout, driver, this.layout, this.logger)).pipe( + mergeMap((itemsCount) => { + // set the viewport to the dimentions from the job, to allow elements to flow into the expected layout + const viewport = this.layout.getViewport(itemsCount) || getDefaultViewPort(); + + return Rx.forkJoin([ + driver.setViewport(viewport, this.logger), + waitForVisualizations(waitTimeout, driver, itemsCount, this.layout, this.logger), + ]); + }), + this.waitUntil(this.timeouts.waitForElements) + ); } private completeRender(apmTrans: apm.Transaction | null) { @@ -112,32 +104,27 @@ export class ScreenshotObservableHandler { const layout = this.layout; const logger = this.logger; - return (withElements: Rx.Observable) => - withElements.pipe( - mergeMap(async () => { - // Waiting till _after_ elements have rendered before injecting our CSS - // allows for them to be displayed properly in many cases - await injectCustomCss(driver, layout, logger); - - const apmPositionElements = apmTrans?.startSpan('position_elements', 'correction'); - // position panel elements for print layout - await layout.positionElements?.(driver, logger); - apmPositionElements?.end(); - - await waitForRenderComplete(this.timeouts.loadDelay, driver, layout, logger); - }), - mergeMap(() => - Promise.all([ - getTimeRange(driver, layout, logger), - getElementPositionAndAttributes(driver, layout, logger), - getRenderErrors(driver, layout, logger), - ]).then(([timeRange, elementsPositionAndAttributes, renderErrors]) => ({ - elementsPositionAndAttributes, - timeRange, - renderErrors, - })) - ) - ); + return Rx.defer(async () => { + // Waiting till _after_ elements have rendered before injecting our CSS + // allows for them to be displayed properly in many cases + await injectCustomCss(driver, layout, logger); + + const apmPositionElements = apmTrans?.startSpan('position_elements', 'correction'); + // position panel elements for print layout + await layout.positionElements?.(driver, logger); + apmPositionElements?.end(); + + await waitForRenderComplete(this.timeouts.loadDelay, driver, layout, logger); + }).pipe( + mergeMap(() => + Rx.forkJoin({ + timeRange: getTimeRange(driver, layout, logger), + elementsPositionAndAttributes: getElementPositionAndAttributes(driver, layout, logger), + renderErrors: getRenderErrors(driver, layout, logger), + }) + ), + this.waitUntil(this.timeouts.renderComplete) + ); } public setupPage( @@ -145,15 +132,10 @@ export class ScreenshotObservableHandler { urlOrUrlLocatorTuple: UrlOrUrlLocatorTuple, apmTrans: apm.Transaction | null ) { - return (initial: Rx.Observable) => - initial.pipe( - this.openUrl(index, urlOrUrlLocatorTuple), - this.waitUntil(this.timeouts.openUrl), - this.waitForElements(), - this.waitUntil(this.timeouts.waitForElements), - this.completeRender(apmTrans), - this.waitUntil(this.timeouts.renderComplete) - ); + return this.openUrl(index, urlOrUrlLocatorTuple).pipe( + switchMapTo(this.waitForElements()), + switchMapTo(this.completeRender(apmTrans)) + ); } public getScreenshots() { diff --git a/x-pack/plugins/reporting/server/plugin.ts b/x-pack/plugins/reporting/server/plugin.ts index 07d61ff1630fc..8969a698a8ce4 100644 --- a/x-pack/plugins/reporting/server/plugin.ts +++ b/x-pack/plugins/reporting/server/plugin.ts @@ -52,7 +52,6 @@ export class ReportingPlugin const router = http.createRouter(); const basePath = http.basePath; - reportingCore.pluginSetup({ screenshotMode, features, @@ -63,6 +62,7 @@ export class ReportingPlugin spaces, taskManager, logger: this.logger, + status: core.status, }); registerUiSettings(core); diff --git a/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts b/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts index 7b4cc2008a676..a27ce6a49b1a2 100644 --- a/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts +++ b/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts @@ -10,6 +10,7 @@ import { spawn } from 'child_process'; import { createInterface } from 'readline'; import { setupServer } from 'src/core/server/test_utils'; import supertest from 'supertest'; +import * as Rx from 'rxjs'; import { ReportingCore } from '../..'; import { createMockConfigSchema, @@ -28,8 +29,10 @@ type SetupServerReturn = UnwrapPromise>; const devtoolMessage = 'DevTools listening on (ws://localhost:4000)'; const fontNotFoundMessage = 'Could not find the default font'; -// FLAKY: https://github.com/elastic/kibana/issues/89369 -describe.skip('POST /diagnose/browser', () => { +const wait = (ms: number): Rx.Observable<0> => + Rx.from(new Promise<0>((resolve) => setTimeout(() => resolve(0), ms))); + +describe('POST /diagnose/browser', () => { jest.setTimeout(6000); const reportingSymbol = Symbol('reporting'); const mockLogger = createMockLevelLogger(); @@ -53,6 +56,9 @@ describe.skip('POST /diagnose/browser', () => { () => ({ usesUiCapabilities: () => false }) ); + // Make all uses of 'Rx.timer' return an observable that completes in 50ms + jest.spyOn(Rx, 'timer').mockImplementation(() => wait(50)); + core = await createMockReportingCore( config, createMockPluginSetup({ @@ -79,6 +85,7 @@ describe.skip('POST /diagnose/browser', () => { }); afterEach(async () => { + jest.restoreAllMocks(); await server.stop(); }); diff --git a/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts index d62cc750ccfcc..c05b2c54aeabf 100644 --- a/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts +++ b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts @@ -11,7 +11,7 @@ jest.mock('../browsers'); import _ from 'lodash'; import * as Rx from 'rxjs'; -import { coreMock, elasticsearchServiceMock } from 'src/core/server/mocks'; +import { coreMock, elasticsearchServiceMock, statusServiceMock } from 'src/core/server/mocks'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { dataPluginMock } from 'src/plugins/data/server/mocks'; import { FieldFormatsRegistry } from 'src/plugins/field_formats/common'; @@ -45,6 +45,7 @@ export const createMockPluginSetup = (setupMock?: any): ReportingInternalSetup = licensing: { license$: Rx.of({ isAvailable: true, isActive: true, type: 'basic' }) } as any, taskManager: taskManagerMock.createSetup(), logger: createMockLevelLogger(), + status: statusServiceMock.createSetupContract(), ...setupMock, }; }; diff --git a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts index 3798506eeacd1..bfdec28a50987 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts @@ -309,6 +309,7 @@ export class ResourceInstaller { template: { settings: { + hidden: true, 'index.lifecycle': { name: ilmPolicyName, // TODO: fix the types in the ES package, they don't include rollover_alias??? diff --git a/x-pack/plugins/security/public/components/breadcrumb.test.tsx b/x-pack/plugins/security/public/components/breadcrumb.test.tsx new file mode 100644 index 0000000000000..00cd4be90a780 --- /dev/null +++ b/x-pack/plugins/security/public/components/breadcrumb.test.tsx @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { render } from '@testing-library/react'; +import React from 'react'; + +import { coreMock } from 'src/core/public/mocks'; + +import { Breadcrumb, BreadcrumbsProvider, createBreadcrumbsChangeHandler } from './breadcrumb'; + +describe('security breadcrumbs', () => { + const setBreadcrumbs = jest.fn(); + const { chrome } = coreMock.createStart(); + + beforeEach(() => { + setBreadcrumbs.mockReset(); + chrome.docTitle.reset.mockReset(); + chrome.docTitle.change.mockReset(); + }); + + it('rendering one breadcrumb and it should NOT have an href attributes', async () => { + render( + + +
{'Find'}
+
+
+ ); + + expect(setBreadcrumbs).toHaveBeenCalledTimes(1); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ text: 'Find' }]); + }); + + it('rendering two breadcrumb and our last breadcrumb should NOT have an href attributes', async () => { + render( + + +
{'Find'}
+ +
{'Sandy is a sweet dog'}
+
+
+
+ ); + + expect(setBreadcrumbs).toHaveBeenCalledTimes(1); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: '/', text: 'Find' }, { text: 'Sandy' }]); + }); + + it('rendering three breadcrumb and our last breadcrumb should NOT have an href attributes', async () => { + render( + + +
{'Find'}
+ +
{'Sandy is a sweet dog'}
+ +
{'Sandy is a mutts'}
+
+
+
+
+ ); + + expect(setBreadcrumbs).toHaveBeenCalledTimes(1); + expect(setBreadcrumbs).toHaveBeenCalledWith([ + { href: '/', text: 'Find' }, + { href: '/sandy', text: 'Sandy' }, + { text: 'Breed' }, + ]); + }); +}); diff --git a/x-pack/plugins/security/public/components/breadcrumb.tsx b/x-pack/plugins/security/public/components/breadcrumb.tsx index 353f738501cbe..4706f60712ad5 100644 --- a/x-pack/plugins/security/public/components/breadcrumb.tsx +++ b/x-pack/plugins/security/public/components/breadcrumb.tsx @@ -80,11 +80,17 @@ export const BreadcrumbsProvider: FunctionComponent = const breadcrumbsRef = useRef([]); const handleChange = (breadcrumbs: BreadcrumbProps[]) => { + const newBreadcrumbs = breadcrumbs.map((item, index) => { + if (index === breadcrumbs.length - 1) { + return { ...item, href: undefined }; + } + return item; + }); if (onChange) { - onChange(breadcrumbs); + onChange(newBreadcrumbs); } else if (services.chrome) { const setBreadcrumbs = createBreadcrumbsChangeHandler(services.chrome); - setBreadcrumbs(breadcrumbs); + setBreadcrumbs(newBreadcrumbs); } }; diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.test.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.test.tsx index d2611864e77a2..922fd59c56d1b 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.test.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.test.tsx @@ -56,7 +56,7 @@ describe('apiKeysManagementApp', () => { }); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: '/', text: 'API keys' }]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ text: 'API keys' }]); expect(docTitle.change).toHaveBeenCalledWith(['API keys']); expect(docTitle.reset).not.toHaveBeenCalled(); expect(container).toMatchInlineSnapshot(` diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.test.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.test.tsx index e73abc3b1eeaf..f6d17327b7118 100644 --- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.test.tsx @@ -5,6 +5,14 @@ * 2.0. */ +import { act } from '@testing-library/react'; +import { noop } from 'lodash'; + +import { coreMock, scopedHistoryMock } from 'src/core/public/mocks'; +import type { Unmount } from 'src/plugins/management/public/types'; + +import { roleMappingsManagementApp } from './role_mappings_management_app'; + jest.mock('./role_mappings_grid', () => ({ RoleMappingsGridPage: (props: any) => // `docLinks` object is too big to include into test snapshot, so we just check its existence. @@ -23,24 +31,23 @@ jest.mock('./edit_role_mapping', () => ({ })}`, })); -import { coreMock, scopedHistoryMock } from 'src/core/public/mocks'; - -import { roleMappingsManagementApp } from './role_mappings_management_app'; - async function mountApp(basePath: string, pathname: string) { const container = document.createElement('div'); const setBreadcrumbs = jest.fn(); const startServices = await coreMock.createSetup().getStartServices(); - const unmount = await roleMappingsManagementApp - .create({ getStartServices: () => Promise.resolve(startServices) as any }) - .mount({ - basePath, - element: container, - setBreadcrumbs, - history: scopedHistoryMock.create({ pathname }), - }); + let unmount: Unmount = noop; + await act(async () => { + unmount = await roleMappingsManagementApp + .create({ getStartServices: () => Promise.resolve(startServices) as any }) + .mount({ + basePath, + element: container, + setBreadcrumbs, + history: scopedHistoryMock.create({ pathname }), + }); + }); return { unmount, container, setBreadcrumbs, docTitle: startServices[0].chrome.docTitle }; } @@ -65,7 +72,7 @@ describe('roleMappingsManagementApp', () => { const { setBreadcrumbs, container, unmount, docTitle } = await mountApp('/', '/'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Role Mappings' }]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ text: 'Role Mappings' }]); expect(docTitle.change).toHaveBeenCalledWith('Role Mappings'); expect(docTitle.reset).not.toHaveBeenCalled(); expect(container).toMatchInlineSnapshot(` @@ -114,8 +121,8 @@ describe('roleMappingsManagementApp', () => { expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `/`, text: 'Role Mappings' }, - { href: `/edit/${encodeURIComponent(roleMappingName)}`, text: roleMappingName }, + { href: '/', text: 'Role Mappings' }, + { text: roleMappingName }, ]); expect(docTitle.change).toHaveBeenCalledWith('Role Mappings'); expect(docTitle.reset).not.toHaveBeenCalled(); @@ -139,9 +146,8 @@ describe('roleMappingsManagementApp', () => { expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `/`, text: 'Role Mappings' }, + { href: '/', text: 'Role Mappings' }, { - href: '/edit/some%20%E5%AE%89%E5%85%A8%E6%80%A7%20role%20mapping', text: roleMappingName, }, ]); diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx index 4dfc9b43642bf..22d09e9e2a678 100644 --- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx @@ -7,13 +7,18 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { Route, Router, Switch, useParams } from 'react-router-dom'; +import { Route, Router, useParams } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import type { StartServicesAccessor } from 'src/core/public'; import type { RegisterManagementAppArgs } from 'src/plugins/management/public'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; +import { + Breadcrumb, + BreadcrumbsProvider, + createBreadcrumbsChangeHandler, +} from '../../components/breadcrumb'; import type { PluginStartDependencies } from '../../plugin'; import { tryDecodeURIComponent } from '../url_utils'; @@ -27,21 +32,12 @@ export const roleMappingsManagementApp = Object.freeze({ const title = i18n.translate('xpack.security.management.roleMappingsTitle', { defaultMessage: 'Role Mappings', }); + return { id: this.id, order: 40, title, async mount({ element, setBreadcrumbs, history }) { - const [coreStart] = await getStartServices(); - const roleMappingsBreadcrumbs = [ - { - text: title, - href: `/`, - }, - ]; - - coreStart.chrome.docTitle.change(title); - const [ [core], { RoleMappingsGridPage }, @@ -56,20 +52,9 @@ export const roleMappingsManagementApp = Object.freeze({ import('../roles'), ]); + core.chrome.docTitle.change(title); + const roleMappingsAPIClient = new RoleMappingsAPIClient(core.http); - const RoleMappingsGridPageWithBreadcrumbs = () => { - setBreadcrumbs(roleMappingsBreadcrumbs); - return ( - - ); - }; const EditRoleMappingsPageWithBreadcrumbs = () => { const { name } = useParams<{ name?: string }>(); @@ -78,26 +63,26 @@ export const roleMappingsManagementApp = Object.freeze({ // See https://github.com/elastic/kibana/issues/82440 const decodedName = name ? tryDecodeURIComponent(name) : undefined; - setBreadcrumbs([ - ...roleMappingsBreadcrumbs, - name + const breadcrumbObj = + name && decodedName ? { text: decodedName, href: `/edit/${encodeURIComponent(name)}` } : { text: i18n.translate('xpack.security.roleMappings.createBreadcrumb', { defaultMessage: 'Create', }), - }, - ]); + }; return ( - + + + ); }; @@ -105,14 +90,25 @@ export const roleMappingsManagementApp = Object.freeze({ - - - - - - - - + + + + + + + + + + , @@ -120,7 +116,6 @@ export const roleMappingsManagementApp = Object.freeze({ ); return () => { - coreStart.chrome.docTitle.reset(); unmountComponentAtNode(element); }; }, diff --git a/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx b/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx index 4b10c2d5cf13b..faab47a858d67 100644 --- a/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx @@ -5,7 +5,11 @@ * 2.0. */ +import { act } from '@testing-library/react'; +import { noop } from 'lodash'; + import { coreMock, scopedHistoryMock } from 'src/core/public/mocks'; +import type { Unmount } from 'src/plugins/management/public/types'; import { featuresPluginMock } from '../../../../features/public/mocks'; import { licenseMock } from '../../../common/licensing/index.mock'; @@ -29,20 +33,23 @@ async function mountApp(basePath: string, pathname: string) { const featuresStart = featuresPluginMock.createStart(); const coreStart = coreMock.createStart(); - const unmount = await rolesManagementApp - .create({ - license: licenseMock.create(), - fatalErrors, - getStartServices: jest - .fn() - .mockResolvedValue([coreStart, { data: {}, features: featuresStart }]), - }) - .mount({ - basePath, - element: container, - setBreadcrumbs, - history: scopedHistoryMock.create({ pathname }), - }); + let unmount: Unmount = noop; + await act(async () => { + unmount = await rolesManagementApp + .create({ + license: licenseMock.create(), + fatalErrors, + getStartServices: jest + .fn() + .mockResolvedValue([coreStart, { data: {}, features: featuresStart }]), + }) + .mount({ + basePath, + element: container, + setBreadcrumbs, + history: scopedHistoryMock.create({ pathname }), + }); + }); return { unmount, container, setBreadcrumbs, docTitle: coreStart.chrome.docTitle }; } @@ -71,7 +78,7 @@ describe('rolesManagementApp', () => { const { setBreadcrumbs, container, unmount, docTitle } = await mountApp('/', '/'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Roles' }]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ text: 'Roles' }]); expect(docTitle.change).toHaveBeenCalledWith('Roles'); expect(docTitle.reset).not.toHaveBeenCalled(); expect(container).toMatchInlineSnapshot(` @@ -116,10 +123,7 @@ describe('rolesManagementApp', () => { ); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `/`, text: 'Roles' }, - { href: `/edit/${encodeURIComponent(roleName)}`, text: roleName }, - ]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Roles' }, { text: roleName }]); expect(docTitle.change).toHaveBeenCalledWith('Roles'); expect(docTitle.reset).not.toHaveBeenCalled(); expect(container).toMatchInlineSnapshot(` @@ -169,7 +173,6 @@ describe('rolesManagementApp', () => { expect(setBreadcrumbs).toHaveBeenCalledWith([ { href: `/`, text: 'Roles' }, { - href: '/edit/some%20%E5%AE%89%E5%85%A8%E6%80%A7%20role', text: roleName, }, ]); diff --git a/x-pack/plugins/security/public/management/roles/roles_management_app.tsx b/x-pack/plugins/security/public/management/roles/roles_management_app.tsx index 8936f506066e0..fcd037a861ed0 100644 --- a/x-pack/plugins/security/public/management/roles/roles_management_app.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_management_app.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { Route, Router, Switch, useParams } from 'react-router-dom'; +import { Route, Router, useParams } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import type { FatalErrorsSetup, StartServicesAccessor } from 'src/core/public'; @@ -15,6 +15,11 @@ import type { RegisterManagementAppArgs } from 'src/plugins/management/public'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import type { SecurityLicense } from '../../../common/licensing'; +import { + Breadcrumb, + BreadcrumbsProvider, + createBreadcrumbsChangeHandler, +} from '../../components/breadcrumb'; import type { PluginStartDependencies } from '../../plugin'; import { tryDecodeURIComponent } from '../url_utils'; @@ -35,13 +40,6 @@ export const rolesManagementApp = Object.freeze({ order: 20, title, async mount({ element, setBreadcrumbs, history }) { - const rolesBreadcrumbs = [ - { - text: title, - href: `/`, - }, - ]; - const [ [startServices, { data, features, spaces }], { RolesGridPage }, @@ -72,16 +70,6 @@ export const rolesManagementApp = Object.freeze({ chrome.docTitle.change(title); const rolesAPIClient = new RolesAPIClient(http); - const RolesGridPageWithBreadcrumbs = () => { - setBreadcrumbs(rolesBreadcrumbs); - return ( - - ); - }; const EditRolePageWithBreadcrumbs = ({ action }: { action: 'edit' | 'clone' }) => { const { roleName } = useParams<{ roleName?: string }>(); @@ -90,38 +78,38 @@ export const rolesManagementApp = Object.freeze({ // See https://github.com/elastic/kibana/issues/82440 const decodedRoleName = roleName ? tryDecodeURIComponent(roleName) : undefined; - setBreadcrumbs([ - ...rolesBreadcrumbs, - action === 'edit' && roleName + const breadcrumbObj = + action === 'edit' && roleName && decodedRoleName ? { text: decodedRoleName, href: `/edit/${encodeURIComponent(roleName)}` } : { text: i18n.translate('xpack.security.roles.createBreadcrumb', { defaultMessage: 'Create', }), - }, - ]); + }; const spacesApiUi = spaces?.ui; return ( - + + + ); }; @@ -129,26 +117,32 @@ export const rolesManagementApp = Object.freeze({ - - - - - - - - - - - + + + + + + + + + + + + + , - element ); return () => { - chrome.docTitle.reset(); unmountComponentAtNode(element); }; }, diff --git a/x-pack/plugins/security/public/management/users/users_management_app.test.tsx b/x-pack/plugins/security/public/management/users/users_management_app.test.tsx index f4fed153d9975..f25fb211cb9de 100644 --- a/x-pack/plugins/security/public/management/users/users_management_app.test.tsx +++ b/x-pack/plugins/security/public/management/users/users_management_app.test.tsx @@ -5,7 +5,11 @@ * 2.0. */ +import { act } from '@testing-library/react'; +import { noop } from 'lodash'; + import { coreMock, scopedHistoryMock } from 'src/core/public/mocks'; +import type { Unmount } from 'src/plugins/management/public/types'; import { securityMock } from '../../mocks'; import { usersManagementApp } from './users_management_app'; @@ -22,16 +26,19 @@ describe('usersManagementApp', () => { const setBreadcrumbs = jest.fn(); const history = scopedHistoryMock.create({ pathname: '/create' }); - const unmount = await usersManagementApp.create({ authc, getStartServices }).mount({ - basePath: '/', - element, - setBreadcrumbs, - history, + let unmount: Unmount = noop; + await act(async () => { + unmount = await usersManagementApp.create({ authc, getStartServices }).mount({ + basePath: '/', + element, + setBreadcrumbs, + history, + }); }); expect(setBreadcrumbs).toHaveBeenLastCalledWith([ { href: '/', text: 'Users' }, - { href: '/create', text: 'Create' }, + { text: 'Create' }, ]); unmount(); diff --git a/x-pack/plugins/security/public/management/users/users_management_app.tsx b/x-pack/plugins/security/public/management/users/users_management_app.tsx index f6a2956c7ad43..7957599da7f57 100644 --- a/x-pack/plugins/security/public/management/users/users_management_app.tsx +++ b/x-pack/plugins/security/public/management/users/users_management_app.tsx @@ -119,7 +119,6 @@ export const usersManagementApp = Object.freeze({ ); return () => { - coreStart.chrome.docTitle.reset(); unmountComponentAtNode(element); }; }, diff --git a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_hosts.ts b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_hosts.ts index de564019db6d0..ed75823cd30d3 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_hosts.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_hosts.ts @@ -8,6 +8,7 @@ import { Client } from '@elastic/elasticsearch'; import { cloneDeep, merge } from 'lodash'; import { AxiosResponse } from 'axios'; +import uuid from 'uuid'; // eslint-disable-next-line import/no-extraneous-dependencies import { KbnClient } from '@kbn/test'; import { DeleteByQueryResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; @@ -139,12 +140,13 @@ export async function indexEndpointHostDocs({ if (enrollFleet) { const { id: appliedPolicyId, name: appliedPolicyName } = hostMetadata.Endpoint.policy.applied; + const uniqueAppliedPolicyName = `${appliedPolicyName}-${uuid.v4()}`; // If we don't yet have a "real" policy record, then create it now in ingest (package config) if (!realPolicies[appliedPolicyId]) { const createdPolicies = await indexFleetEndpointPolicy( kbnClient, - appliedPolicyName, + uniqueAppliedPolicyName, epmEndpointPackage.version ); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts index 033a12dd9de3e..116ee4d3820f9 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts @@ -5,7 +5,13 @@ * 2.0. */ -import { ALERT_FLYOUT, CELL_TEXT, JSON_TEXT, TABLE_ROWS } from '../../screens/alerts_details'; +import { + ALERT_FLYOUT, + CELL_TEXT, + JSON_TEXT, + TABLE_CONTAINER, + TABLE_ROWS, +} from '../../screens/alerts_details'; import { expandFirstAlert, @@ -65,4 +71,20 @@ describe('Alert details with unmapped fields', () => { cy.get(CELL_TEXT).eq(4).should('have.text', expectedUnmmappedField.text); }); }); + + // This test makes sure that the table does not overflow horizontally + it('Table does not scroll horizontally', () => { + openTable(); + + cy.get(ALERT_FLYOUT) + .find(TABLE_CONTAINER) + .within(($tableContainer) => { + expect($tableContainer[0].scrollLeft).to.equal(0); + + // Try to scroll left and make sure that the table hasn't actually scrolled + $tableContainer[0].scroll({ left: 1000 }); + + expect($tableContainer[0].scrollLeft).to.equal(0); + }); + }); }); diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts b/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts index 584fba05452f0..85a4fa257a957 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts @@ -34,6 +34,8 @@ export const TABLE_CELL = '.euiTableRowCell'; export const TABLE_TAB = '[data-test-subj="tableTab"]'; +export const TABLE_CONTAINER = '[data-test-subj="event-fields-browser"]'; + export const TABLE_ROWS = '.euiTableRow'; export const THREAT_DETAILS_ACCORDION = '.euiAccordion__triggerWrapper'; diff --git a/x-pack/plugins/security_solution/cypress/screens/timeline.ts b/x-pack/plugins/security_solution/cypress/screens/timeline.ts index 619e7d01f10e2..81e025fa6db8f 100644 --- a/x-pack/plugins/security_solution/cypress/screens/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/screens/timeline.ts @@ -149,6 +149,8 @@ export const TIMELINE_ADD_FIELD_BUTTON = '[data-test-subj="addField"]'; export const TIMELINE_DATA_PROVIDER_FIELD = '[data-test-subj="field"]'; +export const TIMELINE_DATA_PROVIDER_FIELD_INPUT = '[data-test-subj="comboBoxSearchInput"]'; + export const TIMELINE_DATA_PROVIDER_OPERATOR = `[data-test-subj="operator"]`; export const TIMELINE_DATA_PROVIDER_VALUE = `[data-test-subj="value"]`; diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts index 01a3b6f18be80..03b931bc74d77 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts @@ -69,6 +69,7 @@ import { TIMELINE_TAB_CONTENT_EQL, TIMESTAMP_HOVER_ACTION_OVERFLOW_BTN, PINNED_TAB_BUTTON, + TIMELINE_DATA_PROVIDER_FIELD_INPUT, } from '../screens/timeline'; import { REFRESH_BUTTON, TIMELINE } from '../screens/timelines'; @@ -176,8 +177,9 @@ export const addFilter = (filter: TimelineFilter): Cypress.Chainable> => { cy.get(TIMELINE_ADD_FIELD_BUTTON).click(); cy.get(LOADING_INDICATOR).should('not.exist'); - cy.get(TIMELINE_DATA_PROVIDER_VALUE).should('have.focus'); // make sure the focus is ready before start typing - + cy.get(TIMELINE_DATA_PROVIDER_FIELD) + .find(TIMELINE_DATA_PROVIDER_FIELD_INPUT) + .should('have.focus'); // make sure the focus is ready before start typing cy.get(TIMELINE_DATA_PROVIDER_FIELD) .find(COMBO_BOX_INPUT) .type(`${filter.field}{downarrow}{enter}`); diff --git a/x-pack/plugins/security_solution/jest.config.dev.js b/x-pack/plugins/security_solution/jest.config.dev.js new file mode 100644 index 0000000000000..2162d85f43367 --- /dev/null +++ b/x-pack/plugins/security_solution/jest.config.dev.js @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../', + projects: [ + '/x-pack/plugins/security_solution/*/jest.config.js', + + '/x-pack/plugins/security_solution/server/*/jest.config.js', + '/x-pack/plugins/security_solution/public/*/jest.config.js', + ], +}; diff --git a/x-pack/plugins/security_solution/public/app/app.tsx b/x-pack/plugins/security_solution/public/app/app.tsx index 8abe19ed26d8d..78a340d6bbca0 100644 --- a/x-pack/plugins/security_solution/public/app/app.tsx +++ b/x-pack/plugins/security_solution/public/app/app.tsx @@ -11,6 +11,7 @@ import { Store, Action } from 'redux'; import { Provider as ReduxStoreProvider } from 'react-redux'; import { EuiErrorBoundary } from '@elastic/eui'; +import { QueryClient, QueryClientProvider } from 'react-query'; import { AppLeaveHandler, AppMountParameters } from '../../../../../src/core/public'; import { ManageUserInfo } from '../detections/components/user_info'; @@ -34,6 +35,8 @@ interface StartAppComponent { store: Store; } +const queryClient = new QueryClient(); + const StartAppComponent: FC = ({ children, history, @@ -56,13 +59,15 @@ const StartAppComponent: FC = ({ - - {children} - + + + {children} + + diff --git a/x-pack/plugins/security_solution/public/app/home/global_header/index.test.tsx b/x-pack/plugins/security_solution/public/app/home/global_header/index.test.tsx new file mode 100644 index 0000000000000..c16e77e9182f2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/app/home/global_header/index.test.tsx @@ -0,0 +1,121 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { render } from '@testing-library/react'; +import { useLocation } from 'react-router-dom'; +import { GlobalHeader } from '.'; +import { SecurityPageName } from '../../../../common/constants'; +import { + createSecuritySolutionStorageMock, + mockGlobalState, + SUB_PLUGINS_REDUCER, + TestProviders, +} from '../../../common/mock'; +import { TimelineId } from '../../../../common/types/timeline'; +import { createStore } from '../../../common/store'; +import { kibanaObservable } from '../../../../../timelines/public/mock'; +import { sourcererPaths } from '../../../common/containers/sourcerer'; + +jest.mock('react-router-dom', () => { + const actual = jest.requireActual('react-router-dom'); + return { ...actual, useLocation: jest.fn().mockReturnValue({ pathname: '' }) }; +}); + +jest.mock('../../../common/lib/kibana', () => { + const originalModule = jest.requireActual('../../../common/lib/kibana'); + return { + ...originalModule, + useKibana: jest + .fn() + .mockReturnValue({ services: { http: { basePath: { prepend: jest.fn() } } } }), + useUiSetting$: jest.fn().mockReturnValue([]), + }; +}); + +jest.mock('react-reverse-portal', () => ({ + InPortal: ({ children }: { children: React.ReactNode }) => <>{children}, + OutPortal: ({ children }: { children: React.ReactNode }) => <>{children}, + createPortalNode: () => ({ unmount: jest.fn() }), +})); + +describe('global header', () => { + const mockSetHeaderActionMenu = jest.fn(); + const state = { + ...mockGlobalState, + timeline: { + ...mockGlobalState.timeline, + timelineById: { + [TimelineId.active]: { + ...mockGlobalState.timeline.timelineById.test, + show: false, + }, + }, + }, + }; + const { storage } = createSecuritySolutionStorageMock(); + const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + + it('has add data link', () => { + (useLocation as jest.Mock).mockReturnValue([ + { pageName: SecurityPageName.overview, detailName: undefined }, + ]); + const { getByText } = render( + + + + ); + expect(getByText('Add integrations')).toBeInTheDocument(); + }); + + it.each(sourcererPaths)('shows sourcerer on %s page', (pathname) => { + (useLocation as jest.Mock).mockReturnValue({ pathname }); + + const { getByTestId } = render( + + + + ); + expect(getByTestId('sourcerer-trigger')).toBeInTheDocument(); + }); + + it('shows sourcerer on rule details page', () => { + (useLocation as jest.Mock).mockReturnValue({ pathname: sourcererPaths[2] }); + + const { getByTestId } = render( + + + + ); + expect(getByTestId('sourcerer-trigger')).toBeInTheDocument(); + }); + + it('shows no sourcerer if timeline is open', () => { + const mockstate = { + ...mockGlobalState, + timeline: { + ...mockGlobalState.timeline, + timelineById: { + [TimelineId.active]: { + ...mockGlobalState.timeline.timelineById.test, + show: true, + }, + }, + }, + }; + const mockStore = createStore(mockstate, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + + (useLocation as jest.Mock).mockReturnValue({ pathname: sourcererPaths[2] }); + + const { queryByTestId } = render( + + + + ); + + expect(queryByTestId('sourcerer-trigger')).not.toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/app/home/global_header/index.tsx b/x-pack/plugins/security_solution/public/app/home/global_header/index.tsx index 41e441fd4110f..6afcc649da5f3 100644 --- a/x-pack/plugins/security_solution/public/app/home/global_header/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/global_header/index.tsx @@ -5,14 +5,14 @@ * 2.0. */ import { - EuiHeaderSection, - EuiHeaderLinks, EuiHeaderLink, + EuiHeaderLinks, + EuiHeaderSection, EuiHeaderSectionItem, } from '@elastic/eui'; import React, { useEffect, useMemo } from 'react'; import { useLocation } from 'react-router-dom'; -import { createPortalNode, OutPortal, InPortal } from 'react-reverse-portal'; +import { createPortalNode, InPortal, OutPortal } from 'react-reverse-portal'; import { i18n } from '@kbn/i18n'; import { AppMountParameters } from '../../../../../../../src/core/public'; @@ -21,6 +21,12 @@ import { MlPopover } from '../../../common/components/ml_popover/ml_popover'; import { useKibana } from '../../../common/lib/kibana'; import { ADD_DATA_PATH } from '../../../../common/constants'; import { isDetectionsPath } from '../../../../public/helpers'; +import { Sourcerer } from '../../../common/components/sourcerer'; +import { TimelineId } from '../../../../common/types/timeline'; +import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; +import { timelineSelectors } from '../../../timelines/store/timeline'; +import { useShallowEqualSelector } from '../../../common/hooks/use_selector'; +import { getScopeFromPath, showSourcererByPath } from '../../../common/containers/sourcerer'; const BUTTON_ADD_DATA = i18n.translate('xpack.securitySolution.globalHeader.buttonAddData', { defaultMessage: 'Add integrations', @@ -40,6 +46,16 @@ export const GlobalHeader = React.memo( } = useKibana().services; const { pathname } = useLocation(); + const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); + const showTimeline = useShallowEqualSelector( + (state) => (getTimeline(state, TimelineId.active) ?? timelineDefaults).show + ); + + const sourcererScope = getScopeFromPath(pathname); + const showSourcerer = showSourcererByPath(pathname); + + const href = useMemo(() => prepend(ADD_DATA_PATH), [prepend]); + useEffect(() => { setHeaderActionMenu((element) => { const mount = toMountPoint(); @@ -65,11 +81,14 @@ export const GlobalHeader = React.memo( {BUTTON_ADD_DATA} + {showSourcerer && !showTimeline && ( + + )} diff --git a/x-pack/plugins/security_solution/public/cases/components/case_header_page/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_header_page/index.tsx index 53bc20af5e491..c1eb11ea5182d 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_header_page/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_header_page/index.tsx @@ -9,8 +9,6 @@ import React from 'react'; import { HeaderPage, HeaderPageProps } from '../../../common/components/header_page'; -const CaseHeaderPageComponent: React.FC = (props) => ( - -); +const CaseHeaderPageComponent: React.FC = (props) => ; export const CaseHeaderPage = React.memo(CaseHeaderPageComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/and_or_badge/__examples__/index.stories.tsx b/x-pack/plugins/security_solution/public/common/components/and_or_badge/__examples__/index.stories.tsx index 03a6a2653c1de..231f93e896df9 100644 --- a/x-pack/plugins/security_solution/public/common/components/and_or_badge/__examples__/index.stories.tsx +++ b/x-pack/plugins/security_solution/public/common/components/and_or_badge/__examples__/index.stories.tsx @@ -8,7 +8,7 @@ import { storiesOf } from '@storybook/react'; import React, { ReactNode } from 'react'; import { ThemeProvider } from 'styled-components'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars } from '@kbn/ui-shared-deps-src/theme'; import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; import { AndOrBadge } from '..'; diff --git a/x-pack/plugins/security_solution/public/common/components/conditions_table/index.stories.tsx b/x-pack/plugins/security_solution/public/common/components/conditions_table/index.stories.tsx index 6fe0e6851a098..9efbbc7a3211d 100644 --- a/x-pack/plugins/security_solution/public/common/components/conditions_table/index.stories.tsx +++ b/x-pack/plugins/security_solution/public/common/components/conditions_table/index.stories.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { ThemeProvider } from 'styled-components'; import { storiesOf, addDecorator } from '@storybook/react'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars } from '@kbn/ui-shared-deps-src/theme'; import { createItems, TEST_COLUMNS } from './test_utils'; import { ConditionsTable } from '.'; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx index b0e818d08678e..384e9d72b0787 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx @@ -281,6 +281,7 @@ export const EventFieldsBrowser = React.memo( rowProps={onSetRowProps} search={search} sorting={false} + data-test-subj="event-fields-browser" /> ); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.tsx index 0685582b33882..62aad51785206 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.tsx @@ -72,6 +72,7 @@ export const FieldValueCell = React.memo( isObjectArray={data.isObjectArray} value={value} linkValue={(getLinkValue && getLinkValue(data.field)) ?? linkValue} + truncate={false} /> )}
diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.stories.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.stories.tsx index dd7f0f7a13e26..f8697b2f3db79 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.stories.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.stories.tsx @@ -9,7 +9,7 @@ import { storiesOf, addDecorator } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import React from 'react'; import { ThemeProvider } from 'styled-components'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars } from '@kbn/ui-shared-deps-src/theme'; import { ExceptionItem } from './'; import { getExceptionListItemSchemaMock } from '../../../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exceptions_viewer_header.stories.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exceptions_viewer_header.stories.tsx index 4f78b49ea266c..de56e0eefc1ac 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exceptions_viewer_header.stories.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exceptions_viewer_header.stories.tsx @@ -9,7 +9,7 @@ import { storiesOf, addDecorator } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import React from 'react'; import { ThemeProvider } from 'styled-components'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars } from '@kbn/ui-shared-deps-src/theme'; import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { ExceptionsViewerHeader } from './exceptions_viewer_header'; diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/header_page/__snapshots__/index.test.tsx.snap index d00bd7040c164..9e5b265c187cf 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_page/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/header_page/__snapshots__/index.test.tsx.snap @@ -33,9 +33,6 @@ exports[`HeaderPage it renders 1`] = ` Test supplement

- = ({ border, children, draggableArguments, - hideSourcerer = false, isLoading, - sourcererScope = SourcererScopeName.default, subtitle, subtitle2, title, @@ -149,7 +143,6 @@ const HeaderPageComponent: React.FC = ({ {children} )} - {!hideSourcerer && } {/* Manually add a 'padding-bottom' to header */} diff --git a/x-pack/plugins/security_solution/public/common/components/header_section/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/header_section/index.test.tsx index cc0ac3e6c2b0c..07a5ad475aed2 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_section/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_section/index.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; +import { euiDarkVars } from '@kbn/ui-shared-deps-src/theme'; import { mount, shallow } from 'enzyme'; import React from 'react'; diff --git a/x-pack/plugins/security_solution/public/common/components/item_details_card/index.stories.tsx b/x-pack/plugins/security_solution/public/common/components/item_details_card/index.stories.tsx index d910d258e7bfe..513ba8ccdc462 100644 --- a/x-pack/plugins/security_solution/public/common/components/item_details_card/index.stories.tsx +++ b/x-pack/plugins/security_solution/public/common/components/item_details_card/index.stories.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { ThemeProvider } from 'styled-components'; import { storiesOf, addDecorator } from '@storybook/react'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars } from '@kbn/ui-shared-deps-src/theme'; import { ItemDetailsAction, ItemDetailsCard, ItemDetailsPropertySummary } from '.'; diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/sourcerer/helpers.tsx new file mode 100644 index 0000000000000..af21a018ee47a --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/helpers.tsx @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { + EuiSuperSelectOption, + EuiIcon, + EuiBadge, + EuiButtonEmpty, + EuiFormRow, + EuiFormRowProps, +} from '@elastic/eui'; +import styled from 'styled-components'; + +import { sourcererModel } from '../../store/sourcerer'; + +import * as i18n from './translations'; + +export const FormRow = styled(EuiFormRow)` + display: ${({ $expandAdvancedOptions }) => ($expandAdvancedOptions ? 'flex' : 'none')}; + max-width: none; +`; + +export const StyledFormRow = styled(EuiFormRow)` + max-width: none; +`; + +export const StyledButton = styled(EuiButtonEmpty)` + &:enabled:focus, + &:focus { + background-color: transparent; + } +`; + +export const ResetButton = styled(EuiButtonEmpty)` + width: fit-content; + &:enabled:focus, + &:focus { + background-color: transparent; + } +`; + +export const PopoverContent = styled.div` + width: 600px; +`; + +export const StyledBadge = styled(EuiBadge)` + margin-left: 8px; +`; + +interface GetDataViewSelectOptionsProps { + dataViewId: string; + defaultDataView: sourcererModel.KibanaDataView; + isModified: boolean; + isOnlyDetectionAlerts: boolean; + kibanaDataViews: sourcererModel.KibanaDataView[]; +} + +export const getDataViewSelectOptions = ({ + dataViewId, + defaultDataView, + isModified, + isOnlyDetectionAlerts, + kibanaDataViews, +}: GetDataViewSelectOptionsProps): Array> => + isOnlyDetectionAlerts + ? [ + { + inputDisplay: ( + + {i18n.SIEM_SECURITY_DATA_VIEW_LABEL} + + {i18n.ALERTS_BADGE_TITLE} + + + ), + value: defaultDataView.id, + }, + ] + : kibanaDataViews.map(({ title, id }) => ({ + inputDisplay: + id === defaultDataView.id ? ( + + {i18n.SECURITY_DEFAULT_DATA_VIEW_LABEL} + {isModified && id === dataViewId && ( + {i18n.MODIFIED_BADGE_TITLE} + )} + + ) : ( + + {title} + {isModified && id === dataViewId && ( + {i18n.MODIFIED_BADGE_TITLE} + )} + + ), + value: id, + })); + +interface GetTooltipContent { + isOnlyDetectionAlerts: boolean; + isPopoverOpen: boolean; + selectedPatterns: string[]; + signalIndexName: string | null; +} + +export const getTooltipContent = ({ + isOnlyDetectionAlerts, + isPopoverOpen, + selectedPatterns, + signalIndexName, +}: GetTooltipContent): string | null => { + if (isPopoverOpen || (isOnlyDetectionAlerts && !signalIndexName)) { + return null; + } + return (isOnlyDetectionAlerts ? [signalIndexName] : selectedPatterns).join(', '); +}; + +export const getPatternListWithoutSignals = ( + patternList: string[], + signalIndexName: string | null +): string[] => patternList.filter((p) => p !== signalIndexName); diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx index 1b23d23c5eb62..c2da7e78d64e0 100644 --- a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx @@ -17,7 +17,7 @@ import { SUB_PLUGINS_REDUCER, TestProviders, } from '../../mock'; -import { createStore, State } from '../../store'; +import { createStore } from '../../store'; import { EuiSuperSelectOption } from '@elastic/eui/src/components/form/super_select/super_select_control'; const mockDispatch = jest.fn(); @@ -45,31 +45,55 @@ const defaultProps = { scope: sourcererModel.SourcererScopeName.default, }; -describe('Sourcerer component', () => { - const state: State = mockGlobalState; - const { id, patternList, title } = state.sourcerer.defaultDataView; - const patternListNoSignals = patternList - .filter((p) => p !== state.sourcerer.signalIndexName) - .sort(); - const checkOptionsAndSelections = (wrapper: ReactWrapper, patterns: string[]) => ({ - availableOptionCount: wrapper.find(`[data-test-subj="sourcerer-combo-option"]`).length, - optionsSelected: patterns.every((pattern) => - wrapper - .find(`[data-test-subj="sourcerer-combo-box"] span[title="${pattern}"]`) - .first() - .exists() - ), - }); +const checkOptionsAndSelections = (wrapper: ReactWrapper, patterns: string[]) => ({ + availableOptionCount: wrapper.find(`[data-test-subj="sourcerer-combo-option"]`).length, + optionsSelected: patterns.every((pattern) => + wrapper.find(`[data-test-subj="sourcerer-combo-box"] span[title="${pattern}"]`).first().exists() + ), +}); +const { id, patternList, title } = mockGlobalState.sourcerer.defaultDataView; +const patternListNoSignals = patternList + .filter((p) => p !== mockGlobalState.sourcerer.signalIndexName) + .sort(); +let store: ReturnType; +describe('Sourcerer component', () => { const { storage } = createSecuritySolutionStorageMock(); - let store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); beforeEach(() => { - store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage); jest.clearAllMocks(); jest.restoreAllMocks(); }); + it('renders data view title', () => { + const wrapper = mount( + + + + ); + wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); + expect(wrapper.find(`[data-test-subj="sourcerer-title"]`).first().text()).toEqual( + 'Data view selection' + ); + }); + + it('renders a toggle for advanced options', () => { + const testProps = { + ...defaultProps, + showAlertsOnlyCheckbox: true, + }; + const wrapper = mount( + + + + ); + wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); + expect( + wrapper.find(`[data-test-subj="sourcerer-advanced-options-toggle"]`).first().text() + ).toEqual('Advanced options'); + }); + it('renders tooltip', () => { const wrapper = mount( @@ -119,25 +143,25 @@ describe('Sourcerer component', () => { it('Removes duplicate options from title', () => { store = createStore( { - ...state, + ...mockGlobalState, sourcerer: { - ...state.sourcerer, + ...mockGlobalState.sourcerer, defaultDataView: { - ...state.sourcerer.defaultDataView, + ...mockGlobalState.sourcerer.defaultDataView, id: '1234', title: 'filebeat-*,auditbeat-*,auditbeat-*,auditbeat-*,auditbeat-*', patternList: ['filebeat-*', 'auditbeat-*'], }, kibanaDataViews: [ { - ...state.sourcerer.defaultDataView, + ...mockGlobalState.sourcerer.defaultDataView, id: '1234', title: 'filebeat-*,auditbeat-*,auditbeat-*,auditbeat-*,auditbeat-*', patternList: ['filebeat-*', 'auditbeat-*'], }, ], sourcererScopes: { - ...state.sourcerer.sourcererScopes, + ...mockGlobalState.sourcerer.sourcererScopes, [SourcererScopeName.default]: { ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], loading: false, @@ -170,25 +194,25 @@ describe('Sourcerer component', () => { it('Disables options with no data', () => { store = createStore( { - ...state, + ...mockGlobalState, sourcerer: { - ...state.sourcerer, + ...mockGlobalState.sourcerer, defaultDataView: { - ...state.sourcerer.defaultDataView, + ...mockGlobalState.sourcerer.defaultDataView, id: '1234', title: 'filebeat-*,auditbeat-*,fakebeat-*', patternList: ['filebeat-*', 'auditbeat-*'], }, kibanaDataViews: [ { - ...state.sourcerer.defaultDataView, + ...mockGlobalState.sourcerer.defaultDataView, id: '1234', title: 'filebeat-*,auditbeat-*,fakebeat-*', patternList: ['filebeat-*', 'auditbeat-*'], }, ], sourcererScopes: { - ...state.sourcerer.sourcererScopes, + ...mockGlobalState.sourcerer.sourcererScopes, [SourcererScopeName.default]: { ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], loading: false, @@ -225,15 +249,15 @@ describe('Sourcerer component', () => { sourcerer: { ...mockGlobalState.sourcerer, kibanaDataViews: [ - state.sourcerer.defaultDataView, + mockGlobalState.sourcerer.defaultDataView, { - ...state.sourcerer.defaultDataView, + ...mockGlobalState.sourcerer.defaultDataView, id: '1234', title: 'auditbeat-*', patternList: ['auditbeat-*'], }, { - ...state.sourcerer.defaultDataView, + ...mockGlobalState.sourcerer.defaultDataView, id: '12347', title: 'packetbeat-*', patternList: ['packetbeat-*'], @@ -244,9 +268,8 @@ describe('Sourcerer component', () => { [SourcererScopeName.default]: { ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], loading: false, - patternList, selectedDataViewId: id, - selectedPatterns: patternList.slice(0, 2), + selectedPatterns: patternListNoSignals.slice(0, 2), }, }, }, @@ -260,7 +283,7 @@ describe('Sourcerer component', () => { ); wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); wrapper.find(`[data-test-subj="comboBoxInput"]`).first().simulate('click'); - expect(checkOptionsAndSelections(wrapper, patternList.slice(0, 2))).toEqual({ + expect(checkOptionsAndSelections(wrapper, patternListNoSignals.slice(0, 2))).toEqual({ // should hide signal index availableOptionCount: title.split(',').length - 3, optionsSelected: true, @@ -272,15 +295,15 @@ describe('Sourcerer component', () => { sourcerer: { ...mockGlobalState.sourcerer, kibanaDataViews: [ - state.sourcerer.defaultDataView, + mockGlobalState.sourcerer.defaultDataView, { - ...state.sourcerer.defaultDataView, + ...mockGlobalState.sourcerer.defaultDataView, id: '1234', title: 'auditbeat-*', patternList: ['auditbeat-*'], }, { - ...state.sourcerer.defaultDataView, + ...mockGlobalState.sourcerer.defaultDataView, id: '12347', title: 'packetbeat-*', patternList: ['packetbeat-*'], @@ -305,7 +328,7 @@ describe('Sourcerer component', () => { ); - wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); + wrapper.find(`[data-test-subj="timeline-sourcerer-trigger"]`).first().simulate('click'); wrapper.find(`[data-test-subj="comboBoxInput"]`).first().simulate('click'); expect(checkOptionsAndSelections(wrapper, patternList.slice(0, 2))).toEqual({ // should show every option except fakebeat-* @@ -316,25 +339,25 @@ describe('Sourcerer component', () => { it('onSave dispatches setSelectedDataView', async () => { store = createStore( { - ...state, + ...mockGlobalState, sourcerer: { - ...state.sourcerer, + ...mockGlobalState.sourcerer, kibanaDataViews: [ - state.sourcerer.defaultDataView, + mockGlobalState.sourcerer.defaultDataView, { - ...state.sourcerer.defaultDataView, + ...mockGlobalState.sourcerer.defaultDataView, id: '1234', title: 'filebeat-*', patternList: ['filebeat-*'], }, ], sourcererScopes: { - ...state.sourcerer.sourcererScopes, + ...mockGlobalState.sourcerer.sourcererScopes, [SourcererScopeName.default]: { ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], loading: false, selectedDataViewId: id, - selectedPatterns: patternList.slice(0, 2), + selectedPatterns: patternListNoSignals.slice(0, 2), }, }, }, @@ -350,13 +373,12 @@ describe('Sourcerer component', () => { ); wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); wrapper.find(`[data-test-subj="comboBoxInput"]`).first().simulate('click'); - expect(checkOptionsAndSelections(wrapper, patternList.slice(0, 2))).toEqual({ + expect(checkOptionsAndSelections(wrapper, patternListNoSignals.slice(0, 2))).toEqual({ availableOptionCount: title.split(',').length - 3, optionsSelected: true, }); - wrapper.find(`[data-test-subj="sourcerer-combo-option"]`).first().simulate('click'); - expect(checkOptionsAndSelections(wrapper, patternList.slice(0, 3))).toEqual({ + expect(checkOptionsAndSelections(wrapper, patternListNoSignals.slice(0, 3))).toEqual({ availableOptionCount: title.split(',').length - 4, optionsSelected: true, }); @@ -367,7 +389,7 @@ describe('Sourcerer component', () => { sourcererActions.setSelectedDataView({ id: SourcererScopeName.default, selectedDataViewId: id, - selectedPatterns: patternList.slice(0, 3), + selectedPatterns: patternListNoSignals.slice(0, 3), }) ); }); @@ -387,7 +409,7 @@ describe('Sourcerer component', () => { wrapper .find( - `[data-test-subj="sourcerer-combo-box"] [title="${patternList[0]}"] button.euiBadge__iconButton` + `[data-test-subj="sourcerer-combo-box"] [title="${patternListNoSignals[0]}"] button.euiBadge__iconButton` ) .first() .simulate('click'); @@ -407,13 +429,13 @@ describe('Sourcerer component', () => { it('disables saving when no index patterns are selected', () => { store = createStore( { - ...state, + ...mockGlobalState, sourcerer: { - ...state.sourcerer, + ...mockGlobalState.sourcerer, kibanaDataViews: [ - state.sourcerer.defaultDataView, + mockGlobalState.sourcerer.defaultDataView, { - ...state.sourcerer.defaultDataView, + ...mockGlobalState.sourcerer.defaultDataView, id: '1234', title: 'auditbeat-*', patternList: ['auditbeat-*'], @@ -434,72 +456,21 @@ describe('Sourcerer component', () => { wrapper.find('[data-test-subj="comboBoxClearButton"]').first().simulate('click'); expect(wrapper.find('[data-test-subj="sourcerer-save"]').first().prop('disabled')).toBeTruthy(); }); - it('Selects a different index pattern', async () => { - const state2 = { - ...mockGlobalState, - sourcerer: { - ...mockGlobalState.sourcerer, - kibanaDataViews: [ - state.sourcerer.defaultDataView, - { - ...state.sourcerer.defaultDataView, - id: '1234', - title: 'fakebeat-*,neatbeat-*', - patternList: ['fakebeat-*'], - }, - ], - sourcererScopes: { - ...mockGlobalState.sourcerer.sourcererScopes, - [SourcererScopeName.default]: { - ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], - loading: false, - patternList, - selectedDataViewId: id, - selectedPatterns: patternList.slice(0, 2), - }, - }, - }, - }; - - store = createStore(state2, SUB_PLUGINS_REDUCER, kibanaObservable, storage); - const wrapper = mount( - - - - ); - wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); - wrapper.find(`button[data-test-subj="sourcerer-select"]`).first().simulate('click'); - - wrapper.find(`[data-test-subj="dataView-option-super"]`).first().simulate('click'); - expect(checkOptionsAndSelections(wrapper, ['fakebeat-*'])).toEqual({ - availableOptionCount: 0, - optionsSelected: true, - }); - wrapper.find(`[data-test-subj="sourcerer-save"]`).first().simulate('click'); - - expect(mockDispatch).toHaveBeenCalledWith( - sourcererActions.setSelectedDataView({ - id: SourcererScopeName.default, - selectedDataViewId: '1234', - selectedPatterns: ['fakebeat-*'], - }) - ); - }); it('Does display signals index on timeline sourcerer', () => { const state2 = { ...mockGlobalState, sourcerer: { ...mockGlobalState.sourcerer, kibanaDataViews: [ - state.sourcerer.defaultDataView, + mockGlobalState.sourcerer.defaultDataView, { - ...state.sourcerer.defaultDataView, + ...mockGlobalState.sourcerer.defaultDataView, id: '1234', title: 'auditbeat-*', patternList: ['auditbeat-*'], }, { - ...state.sourcerer.defaultDataView, + ...mockGlobalState.sourcerer.defaultDataView, id: '12347', title: 'packetbeat-*', patternList: ['packetbeat-*'], @@ -510,9 +481,8 @@ describe('Sourcerer component', () => { [SourcererScopeName.timeline]: { ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.timeline], loading: false, - patternList, selectedDataViewId: id, - selectedPatterns: patternList.slice(0, 2), + selectedPatterns: patternListNoSignals.slice(0, 2), }, }, }, @@ -524,9 +494,9 @@ describe('Sourcerer component', () => { ); - wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); + wrapper.find(`[data-test-subj="timeline-sourcerer-trigger"]`).first().simulate('click'); wrapper.find(`[data-test-subj="comboBoxToggleListButton"]`).first().simulate('click'); - expect(wrapper.find(`[data-test-subj="sourcerer-combo-option"]`).at(6).text()).toEqual( + expect(wrapper.find(`[data-test-subj="sourcerer-combo-option"]`).at(0).text()).toEqual( mockGlobalState.sourcerer.signalIndexName ); }); @@ -536,15 +506,15 @@ describe('Sourcerer component', () => { sourcerer: { ...mockGlobalState.sourcerer, kibanaDataViews: [ - state.sourcerer.defaultDataView, + mockGlobalState.sourcerer.defaultDataView, { - ...state.sourcerer.defaultDataView, + ...mockGlobalState.sourcerer.defaultDataView, id: '1234', title: 'auditbeat-*', patternList: ['auditbeat-*'], }, { - ...state.sourcerer.defaultDataView, + ...mockGlobalState.sourcerer.defaultDataView, id: '12347', title: 'packetbeat-*', patternList: ['packetbeat-*'], @@ -555,9 +525,8 @@ describe('Sourcerer component', () => { [SourcererScopeName.default]: { ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], loading: false, - patternList, selectedDataViewId: id, - selectedPatterns: patternList.slice(0, 2), + selectedPatterns: patternListNoSignals.slice(0, 2), }, }, }, @@ -581,3 +550,204 @@ describe('Sourcerer component', () => { ).toBeFalsy(); }); }); + +describe('sourcerer on alerts page or rules details page', () => { + let wrapper: ReactWrapper; + const { storage } = createSecuritySolutionStorageMock(); + store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + const testProps = { + scope: sourcererModel.SourcererScopeName.detections, + }; + + beforeAll(() => { + wrapper = mount( + + + + ); + wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); + wrapper.find(`[data-test-subj="sourcerer-advanced-options-toggle"]`).first().simulate('click'); + }); + + it('renders an alerts badge in sourcerer button', () => { + expect(wrapper.find(`[data-test-subj="sourcerer-alerts-badge"]`).first().text()).toEqual( + 'Alerts' + ); + }); + + it('renders a callout', () => { + expect(wrapper.find(`[data-test-subj="sourcerer-callout"]`).first().text()).toEqual( + 'Data view cannot be modified on this page' + ); + }); + + it('disable data view selector', () => { + expect( + wrapper.find(`[data-test-subj="sourcerer-select"]`).first().prop('disabled') + ).toBeTruthy(); + }); + + it('data view selector is default to Security Data View', () => { + expect( + wrapper.find(`[data-test-subj="sourcerer-select"]`).first().prop('valueOfSelected') + ).toEqual('security-solution'); + }); + + it('renders an alert badge in data view selector', () => { + expect(wrapper.find(`[data-test-subj="security-alerts-option-badge"]`).first().text()).toEqual( + 'Alerts' + ); + }); + + it('disable index pattern selector', () => { + expect( + wrapper.find(`[data-test-subj="sourcerer-combo-box"]`).first().prop('disabled') + ).toBeTruthy(); + }); + + it('shows signal index as index pattern option', () => { + expect(wrapper.find(`[data-test-subj="sourcerer-combo-box"]`).first().prop('options')).toEqual([ + { disabled: false, label: '.siem-signals-spacename', value: '.siem-signals-spacename' }, + ]); + }); + + it('does not render reset button', () => { + expect(wrapper.find(`[data-test-subj="sourcerer-reset"]`).exists()).toBeFalsy(); + }); + + it('does not render save button', () => { + expect(wrapper.find(`[data-test-subj="sourcerer-save"]`).exists()).toBeFalsy(); + }); +}); + +describe('timeline sourcerer', () => { + let wrapper: ReactWrapper; + const { storage } = createSecuritySolutionStorageMock(); + store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + const testProps = { + scope: sourcererModel.SourcererScopeName.timeline, + }; + + beforeAll(() => { + wrapper = mount( + + + + ); + wrapper.find(`[data-test-subj="timeline-sourcerer-trigger"]`).first().simulate('click'); + wrapper + .find( + `[data-test-subj="timeline-sourcerer-popover"] [data-test-subj="sourcerer-advanced-options-toggle"]` + ) + .first() + .simulate('click'); + }); + + it('renders "alerts only" checkbox', () => { + wrapper + .find( + `[data-test-subj="timeline-sourcerer-popover"] [data-test-subj="sourcerer-alert-only-checkbox"]` + ) + .first() + .simulate('click'); + expect(wrapper.find(`[data-test-subj="sourcerer-alert-only-checkbox"]`).first().text()).toEqual( + 'Show only detection alerts' + ); + }); + + it('data view selector is enabled', () => { + expect( + wrapper + .find(`[data-test-subj="timeline-sourcerer-popover"] [data-test-subj="sourcerer-select"]`) + .first() + .prop('disabled') + ).toBeFalsy(); + }); + + it('data view selector is default to Security Default Data View', () => { + expect( + wrapper + .find(`[data-test-subj="timeline-sourcerer-popover"] [data-test-subj="sourcerer-select"]`) + .first() + .prop('valueOfSelected') + ).toEqual('security-solution'); + }); + + it('index pattern selector is enabled', () => { + expect( + wrapper + .find( + `[data-test-subj="timeline-sourcerer-popover"] [data-test-subj="sourcerer-combo-box"]` + ) + .first() + .prop('disabled') + ).toBeFalsy(); + }); + + it('render reset button', () => { + expect(wrapper.find(`[data-test-subj="sourcerer-reset"]`).exists()).toBeTruthy(); + }); + + it('render save button', () => { + expect(wrapper.find(`[data-test-subj="sourcerer-save"]`).exists()).toBeTruthy(); + }); +}); + +describe('Sourcerer integration tests', () => { + const state = { + ...mockGlobalState, + sourcerer: { + ...mockGlobalState.sourcerer, + kibanaDataViews: [ + mockGlobalState.sourcerer.defaultDataView, + { + ...mockGlobalState.sourcerer.defaultDataView, + id: '1234', + title: 'fakebeat-*,neatbeat-*', + patternList: ['fakebeat-*'], + }, + ], + sourcererScopes: { + ...mockGlobalState.sourcerer.sourcererScopes, + [SourcererScopeName.default]: { + ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], + loading: false, + selectedDataViewId: id, + selectedPatterns: patternListNoSignals.slice(0, 2), + }, + }, + }, + }; + + const { storage } = createSecuritySolutionStorageMock(); + + beforeEach(() => { + store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + it('Selects a different index pattern', async () => { + const wrapper = mount( + + + + ); + wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); + wrapper.find(`button[data-test-subj="sourcerer-select"]`).first().simulate('click'); + + wrapper.find(`[data-test-subj="dataView-option-super"]`).first().simulate('click'); + expect(checkOptionsAndSelections(wrapper, ['fakebeat-*'])).toEqual({ + availableOptionCount: 0, + optionsSelected: true, + }); + wrapper.find(`[data-test-subj="sourcerer-save"]`).first().simulate('click'); + + expect(mockDispatch).toHaveBeenCalledWith( + sourcererActions.setSelectedDataView({ + id: SourcererScopeName.default, + selectedDataViewId: '1234', + selectedPatterns: ['fakebeat-*'], + }) + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx index 6f32282c53040..6f223cbb4aa30 100644 --- a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx @@ -7,48 +7,52 @@ import { EuiButton, - EuiButtonEmpty, + EuiCallOut, + EuiCheckbox, EuiComboBox, - EuiComboBoxOptionOption, EuiFlexGroup, EuiFlexItem, - EuiIcon, + EuiForm, EuiPopover, EuiPopoverTitle, EuiSpacer, EuiSuperSelect, - EuiText, EuiToolTip, } from '@elastic/eui'; import deepEqual from 'fast-deep-equal'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; -import styled from 'styled-components'; import * as i18n from './translations'; import { sourcererActions, sourcererModel, sourcererSelectors } from '../../store/sourcerer'; -import { getScopePatternListSelection } from '../../store/sourcerer/helpers'; import { useDeepEqualSelector } from '../../hooks/use_selector'; import { SourcererScopeName } from '../../store/sourcerer/model'; +import { usePickIndexPatterns } from './use_pick_index_patterns'; +import { + FormRow, + getDataViewSelectOptions, + getTooltipContent, + PopoverContent, + ResetButton, + StyledBadge, + StyledButton, + StyledFormRow, +} from './helpers'; -const PopoverContent = styled.div` - width: 600px; -`; - -const ResetButton = styled(EuiButtonEmpty)` - width: fit-content; -`; interface SourcererComponentProps { scope: sourcererModel.SourcererScopeName; } -const getPatternListWithoutSignals = ( - patternList: string[], - signalIndexName: string | null -): string[] => patternList.filter((p) => p !== signalIndexName); - export const Sourcerer = React.memo(({ scope: scopeId }) => { const dispatch = useDispatch(); + const isDetectionsSourcerer = scopeId === SourcererScopeName.detections; + const isTimelineSourcerer = scopeId === SourcererScopeName.timeline; + + const [isOnlyDetectionAlertsChecked, setIsOnlyDetectionAlertsChecked] = useState(false); + + const isOnlyDetectionAlerts: boolean = + isDetectionsSourcerer || (isTimelineSourcerer && isOnlyDetectionAlertsChecked); + const sourcererScopeSelector = useMemo(() => sourcererSelectors.getSourcererScopeSelector(), []); const { defaultDataView, @@ -58,55 +62,39 @@ export const Sourcerer = React.memo(({ scope: scopeId } } = useDeepEqualSelector((state) => sourcererScopeSelector(state, scopeId)); const [isPopoverOpen, setPopoverIsOpen] = useState(false); - const [dataViewId, setDataViewId] = useState(selectedDataViewId ?? defaultDataView.id); - const { patternList, selectablePatterns } = useMemo(() => { - const theDataView = kibanaDataViews.find((dataView) => dataView.id === dataViewId); - return theDataView != null - ? scopeId === SourcererScopeName.default - ? { - patternList: getPatternListWithoutSignals( - theDataView.title - .split(',') - // remove duplicates patterns from selector - .filter((pattern, i, self) => self.indexOf(pattern) === i), - signalIndexName - ), - selectablePatterns: getPatternListWithoutSignals( - theDataView.patternList, - signalIndexName - ), - } - : { - patternList: theDataView.title - .split(',') - // remove duplicates patterns from selector - .filter((pattern, i, self) => self.indexOf(pattern) === i), - selectablePatterns: theDataView.patternList, - } - : { patternList: [], selectablePatterns: [] }; - }, [kibanaDataViews, scopeId, signalIndexName, dataViewId]); - - const selectableOptions = useMemo( - () => - patternList.map((indexName) => ({ - label: indexName, - value: indexName, - disabled: !selectablePatterns.includes(indexName), - })), - [selectablePatterns, patternList] - ); - - const [selectedOptions, setSelectedOptions] = useState>>( - selectedPatterns.map((indexName) => ({ - label: indexName, - value: indexName, - })) + const { + isModified, + onChangeCombo, + renderOption, + selectableOptions, + selectedOptions, + setIndexPatternsByDataView, + } = usePickIndexPatterns({ + dataViewId, + defaultDataViewId: defaultDataView.id, + isOnlyDetectionAlerts, + kibanaDataViews, + scopeId, + selectedPatterns, + signalIndexName, + }); + const onCheckboxChanged = useCallback( + (e) => { + setIsOnlyDetectionAlertsChecked(e.target.checked); + setDataViewId(defaultDataView.id); + setIndexPatternsByDataView(defaultDataView.id, e.target.checked); + }, + [defaultDataView.id, setIndexPatternsByDataView] ); const isSavingDisabled = useMemo(() => selectedOptions.length === 0, [selectedOptions]); + const [expandAdvancedOptions, setExpandAdvancedOptions] = useState(false); - const setPopoverIsOpenCb = useCallback(() => setPopoverIsOpen((prevState) => !prevState), []); + const setPopoverIsOpenCb = useCallback(() => { + setPopoverIsOpen((prevState) => !prevState); + setExpandAdvancedOptions(false); // we always want setExpandAdvancedOptions collapsed by default when popover opened + }, []); const onChangeDataView = useCallback( (newSelectedDataView: string, newSelectedPatterns: string[]) => { dispatch( @@ -120,90 +108,64 @@ export const Sourcerer = React.memo(({ scope: scopeId } [dispatch, scopeId] ); - const renderOption = useCallback( - ({ value }) => {value}, - [] - ); - - const onChangeCombo = useCallback((newSelectedOptions) => { - setSelectedOptions(newSelectedOptions); - }, []); - const onChangeSuper = useCallback( (newSelectedOption) => { setDataViewId(newSelectedOption); - setSelectedOptions( - getScopePatternListSelection( - kibanaDataViews.find((dataView) => dataView.id === newSelectedOption), - scopeId, - signalIndexName, - newSelectedOption === defaultDataView.id - ).map((indexSelected: string) => ({ - label: indexSelected, - value: indexSelected, - })) - ); + setIndexPatternsByDataView(newSelectedOption); }, - [defaultDataView.id, kibanaDataViews, scopeId, signalIndexName] + [setIndexPatternsByDataView] ); const resetDataSources = useCallback(() => { setDataViewId(defaultDataView.id); - setSelectedOptions( - getScopePatternListSelection(defaultDataView, scopeId, signalIndexName, true).map( - (indexSelected: string) => ({ - label: indexSelected, - value: indexSelected, - }) - ) - ); - }, [defaultDataView, scopeId, signalIndexName]); + setIndexPatternsByDataView(defaultDataView.id); + setIsOnlyDetectionAlertsChecked(false); + }, [defaultDataView.id, setIndexPatternsByDataView]); const handleSaveIndices = useCallback(() => { - onChangeDataView( - dataViewId, - selectedOptions.map((so) => so.label) - ); + const patterns = selectedOptions.map((so) => so.label); + onChangeDataView(dataViewId, patterns); setPopoverIsOpen(false); }, [onChangeDataView, dataViewId, selectedOptions]); const handleClosePopOver = useCallback(() => { setPopoverIsOpen(false); + setExpandAdvancedOptions(false); }, []); const trigger = useMemo( () => ( - - {i18n.SOURCERER} - + {i18n.DATA_VIEW} + {isModified === 'modified' && {i18n.MODIFIED_BADGE_TITLE}} + {isModified === 'alerts' && ( + + {i18n.ALERTS_BADGE_TITLE} + + )} + ), - [setPopoverIsOpenCb, loading] + [isTimelineSourcerer, loading, setPopoverIsOpenCb, isModified] ); const dataViewSelectOptions = useMemo( () => - kibanaDataViews.map(({ title, id }) => ({ - inputDisplay: - id === defaultDataView.id ? ( - - {i18n.SIEM_DATA_VIEW_LABEL} - - ) : ( - - {title} - - ), - value: id, - })), - [defaultDataView.id, kibanaDataViews] + getDataViewSelectOptions({ + dataViewId, + defaultDataView, + isModified: isModified === 'modified', + isOnlyDetectionAlerts, + kibanaDataViews, + }), + [dataViewId, defaultDataView, isModified, isOnlyDetectionAlerts, kibanaDataViews] ); useEffect(() => { @@ -213,18 +175,16 @@ export const Sourcerer = React.memo(({ scope: scopeId } : prevSelectedOption ); }, [selectedDataViewId]); - useEffect(() => { - setSelectedOptions( - selectedPatterns.map((indexName) => ({ - label: indexName, - value: indexName, - })) - ); - }, [selectedPatterns]); const tooltipContent = useMemo( - () => (isPopoverOpen ? null : selectedPatterns.join(', ')), - [selectedPatterns, isPopoverOpen] + () => + getTooltipContent({ + isOnlyDetectionAlerts, + isPopoverOpen, + selectedPatterns, + signalIndexName, + }), + [isPopoverOpen, isOnlyDetectionAlerts, signalIndexName, selectedPatterns] ); const buttonWithTooptip = useMemo(() => { @@ -237,67 +197,117 @@ export const Sourcerer = React.memo(({ scope: scopeId } ); }, [trigger, tooltipContent]); + const onExpandAdvancedOptionsClicked = useCallback(() => { + setExpandAdvancedOptions((prevState) => !prevState); + }, []); + return ( - - <>{i18n.SELECT_INDEX_PATTERNS} + + <>{i18n.SELECT_DATA_VIEW} + {isOnlyDetectionAlerts && ( + + )} - {i18n.INDEX_PATTERNS_SELECTION_LABEL} - - - - - - - - - {i18n.INDEX_PATTERNS_RESET} - - - - + {isTimelineSourcerer && ( + + + + )} + + + - {i18n.SAVE_INDEX_PATTERNS} - - - + onChange={onChangeSuper} + options={dataViewSelectOptions} + placeholder={i18n.PICK_INDEX_PATTERNS} + valueOfSelected={dataViewId} + /> + + + + + + {i18n.INDEX_PATTERNS_ADVANCED_OPTIONS_TITLE} + + {expandAdvancedOptions && } + + + + + {!isDetectionsSourcerer && ( + + + + + {i18n.INDEX_PATTERNS_RESET} + + + + + {i18n.SAVE_INDEX_PATTERNS} + + + + + )} + + ); diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/translations.ts b/x-pack/plugins/security_solution/public/common/components/sourcerer/translations.ts index 03fdc5d191719..fcf465ebfc9ef 100644 --- a/x-pack/plugins/security_solution/public/common/components/sourcerer/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/translations.ts @@ -7,29 +7,85 @@ import { i18n } from '@kbn/i18n'; -export const SOURCERER = i18n.translate('xpack.securitySolution.indexPatterns.dataSourcesLabel', { - defaultMessage: 'Data sources', +export const CALL_OUT_TITLE = i18n.translate('xpack.securitySolution.indexPatterns.callOutTitle', { + defaultMessage: 'Data view cannot be modified on this page', }); -export const SIEM_DATA_VIEW_LABEL = i18n.translate( - 'xpack.securitySolution.indexPatterns.kipLabel', +export const CALL_OUT_TIMELINE_TITLE = i18n.translate( + 'xpack.securitySolution.indexPatterns.callOutTimelineTitle', { - defaultMessage: 'Default Security Data View', + defaultMessage: 'Data view cannot be modified when show only detection alerts is selected', } ); -export const SELECT_INDEX_PATTERNS = i18n.translate('xpack.securitySolution.indexPatterns.help', { - defaultMessage: 'Data sources selection', +export const DATA_VIEW = i18n.translate('xpack.securitySolution.indexPatterns.dataViewLabel', { + defaultMessage: 'Data view', }); +export const MODIFIED_BADGE_TITLE = i18n.translate( + 'xpack.securitySolution.indexPatterns.modifiedBadgeTitle', + { + defaultMessage: 'Modified', + } +); + +export const ALERTS_BADGE_TITLE = i18n.translate( + 'xpack.securitySolution.indexPatterns.alertsBadgeTitle', + { + defaultMessage: 'Alerts', + } +); + +export const SECURITY_DEFAULT_DATA_VIEW_LABEL = i18n.translate( + 'xpack.securitySolution.indexPatterns.securityDefaultDataViewLabel', + { + defaultMessage: 'Security Default Data View', + } +); + +export const SIEM_SECURITY_DATA_VIEW_LABEL = i18n.translate( + 'xpack.securitySolution.indexPatterns.securityDataViewLabel', + { + defaultMessage: 'Security Data View', + } +); + +export const SELECT_DATA_VIEW = i18n.translate( + 'xpack.securitySolution.indexPatterns.selectDataView', + { + defaultMessage: 'Data view selection', + } +); export const SAVE_INDEX_PATTERNS = i18n.translate('xpack.securitySolution.indexPatterns.save', { defaultMessage: 'Save', }); -export const INDEX_PATTERNS_SELECTION_LABEL = i18n.translate( - 'xpack.securitySolution.indexPatterns.selectionLabel', +export const INDEX_PATTERNS_CHOOSE_DATA_VIEW_LABEL = i18n.translate( + 'xpack.securitySolution.indexPatterns.chooseDataViewLabel', + { + defaultMessage: 'Choose data view', + } +); + +export const INDEX_PATTERNS_ADVANCED_OPTIONS_TITLE = i18n.translate( + 'xpack.securitySolution.indexPatterns.advancedOptionsTitle', + { + defaultMessage: 'Advanced options', + } +); + +export const INDEX_PATTERNS_LABEL = i18n.translate( + 'xpack.securitySolution.indexPatterns.indexPatternsLabel', { - defaultMessage: 'Choose the source of the data on this page', + defaultMessage: 'Index patterns', + } +); + +export const INDEX_PATTERNS_DESCRIPTIONS = i18n.translate( + 'xpack.securitySolution.indexPatterns.descriptionsLabel', + { + defaultMessage: + 'These are the index patterns currently selected. Filtering out index patterns from your data view can help improve overall performance.', } ); @@ -54,3 +110,10 @@ export const PICK_INDEX_PATTERNS = i18n.translate( defaultMessage: 'Pick index patterns', } ); + +export const ALERTS_CHECKBOX_LABEL = i18n.translate( + 'xpack.securitySolution.indexPatterns.onlyDetectionAlertsLabel', + { + defaultMessage: 'Show only detection alerts', + } +); diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/use_pick_index_patterns.tsx b/x-pack/plugins/security_solution/public/common/components/sourcerer/use_pick_index_patterns.tsx new file mode 100644 index 0000000000000..2ed2319499398 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/use_pick_index_patterns.tsx @@ -0,0 +1,167 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { EuiComboBoxOptionOption } from '@elastic/eui'; +import { getScopePatternListSelection } from '../../store/sourcerer/helpers'; +import { sourcererModel } from '../../store/sourcerer'; +import { getPatternListWithoutSignals } from './helpers'; +import { SourcererScopeName } from '../../store/sourcerer/model'; + +interface UsePickIndexPatternsProps { + dataViewId: string; + defaultDataViewId: string; + isOnlyDetectionAlerts: boolean; + kibanaDataViews: sourcererModel.SourcererModel['kibanaDataViews']; + scopeId: sourcererModel.SourcererScopeName; + selectedPatterns: string[]; + signalIndexName: string | null; +} + +export type ModifiedTypes = 'modified' | 'alerts' | ''; + +interface UsePickIndexPatterns { + isModified: ModifiedTypes; + onChangeCombo: (newSelectedDataViewId: Array>) => void; + renderOption: ({ value }: EuiComboBoxOptionOption) => React.ReactElement; + selectableOptions: Array>; + selectedOptions: Array>; + setIndexPatternsByDataView: (newSelectedDataViewId: string, isAlerts?: boolean) => void; +} + +const patternListToOptions = (patternList: string[], selectablePatterns?: string[]) => + patternList.sort().map((s) => ({ + label: s, + value: s, + ...(selectablePatterns != null ? { disabled: !selectablePatterns.includes(s) } : {}), + })); + +export const usePickIndexPatterns = ({ + dataViewId, + defaultDataViewId, + isOnlyDetectionAlerts, + kibanaDataViews, + scopeId, + selectedPatterns, + signalIndexName, +}: UsePickIndexPatternsProps): UsePickIndexPatterns => { + const alertsOptions = useMemo( + () => (signalIndexName ? patternListToOptions([signalIndexName]) : []), + [signalIndexName] + ); + + const { patternList, selectablePatterns } = useMemo(() => { + if (isOnlyDetectionAlerts && signalIndexName) { + return { + patternList: [signalIndexName], + selectablePatterns: [signalIndexName], + }; + } + const theDataView = kibanaDataViews.find((dataView) => dataView.id === dataViewId); + return theDataView != null + ? scopeId === sourcererModel.SourcererScopeName.default + ? { + patternList: getPatternListWithoutSignals( + theDataView.title + .split(',') + // remove duplicates patterns from selector + .filter((pattern, i, self) => self.indexOf(pattern) === i), + signalIndexName + ), + selectablePatterns: getPatternListWithoutSignals( + theDataView.patternList, + signalIndexName + ), + } + : { + patternList: theDataView.title + .split(',') + // remove duplicates patterns from selector + .filter((pattern, i, self) => self.indexOf(pattern) === i), + selectablePatterns: theDataView.patternList, + } + : { patternList: [], selectablePatterns: [] }; + }, [dataViewId, isOnlyDetectionAlerts, kibanaDataViews, scopeId, signalIndexName]); + + const selectableOptions = useMemo( + () => patternListToOptions(patternList, selectablePatterns), + [patternList, selectablePatterns] + ); + const [selectedOptions, setSelectedOptions] = useState>>( + isOnlyDetectionAlerts ? alertsOptions : patternListToOptions(selectedPatterns) + ); + + const getDefaultSelectedOptionsByDataView = useCallback( + (id: string, isAlerts: boolean = false): Array> => + scopeId === SourcererScopeName.detections || isAlerts + ? alertsOptions + : patternListToOptions( + getScopePatternListSelection( + kibanaDataViews.find((dataView) => dataView.id === id), + scopeId, + signalIndexName, + id === defaultDataViewId + ) + ), + [alertsOptions, kibanaDataViews, scopeId, signalIndexName, defaultDataViewId] + ); + + const defaultSelectedPatternsAsOptions = useMemo( + () => getDefaultSelectedOptionsByDataView(dataViewId), + [dataViewId, getDefaultSelectedOptionsByDataView] + ); + + const [isModified, setIsModified] = useState<'modified' | 'alerts' | ''>(''); + const onSetIsModified = useCallback( + (patterns?: string[]) => { + if (isOnlyDetectionAlerts) { + return setIsModified('alerts'); + } + const modifiedPatterns = patterns != null ? patterns : selectedPatterns; + const isPatternsModified = + defaultSelectedPatternsAsOptions.length !== modifiedPatterns.length || + !defaultSelectedPatternsAsOptions.every((option) => + modifiedPatterns.find((pattern) => option.value === pattern) + ); + return setIsModified(isPatternsModified ? 'modified' : ''); + }, + [defaultSelectedPatternsAsOptions, isOnlyDetectionAlerts, selectedPatterns] + ); + + // when scope updates, check modified to set/remove alerts label + useEffect(() => { + setSelectedOptions( + scopeId === SourcererScopeName.detections + ? alertsOptions + : patternListToOptions(selectedPatterns) + ); + onSetIsModified(selectedPatterns); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [scopeId, selectedPatterns]); + + const onChangeCombo = useCallback((newSelectedOptions) => { + setSelectedOptions(newSelectedOptions); + }, []); + + const renderOption = useCallback( + ({ value }) => {value}, + [] + ); + + const setIndexPatternsByDataView = (newSelectedDataViewId: string, isAlerts?: boolean) => { + setSelectedOptions(getDefaultSelectedOptionsByDataView(newSelectedDataViewId, isAlerts)); + }; + + return { + isModified, + onChangeCombo, + renderOption, + selectableOptions, + selectedOptions, + setIndexPatternsByDataView, + }; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/text_field_value/index.stories.tsx b/x-pack/plugins/security_solution/public/common/components/text_field_value/index.stories.tsx index b3f0de1376396..146ba8ef82505 100644 --- a/x-pack/plugins/security_solution/public/common/components/text_field_value/index.stories.tsx +++ b/x-pack/plugins/security_solution/public/common/components/text_field_value/index.stories.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { ThemeProvider } from 'styled-components'; import { storiesOf, addDecorator } from '@storybook/react'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars } from '@kbn/ui-shared-deps-src/theme'; import { TextFieldValue } from '.'; diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/logic_buttons.stories.tsx b/x-pack/plugins/security_solution/public/common/components/threat_match/logic_buttons.stories.tsx index 20a7786f6d09e..6497875ac8d4a 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/logic_buttons.stories.tsx +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/logic_buttons.stories.tsx @@ -9,7 +9,7 @@ import { storiesOf, addDecorator } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import React from 'react'; import { ThemeProvider } from 'styled-components'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars } from '@kbn/ui-shared-deps-src/theme'; import { LogicButtons } from './logic_buttons'; diff --git a/x-pack/plugins/security_solution/public/common/components/utility_bar/utility_bar.test.tsx b/x-pack/plugins/security_solution/public/common/components/utility_bar/utility_bar.test.tsx index 7a9413a92843e..73acaa48983b4 100644 --- a/x-pack/plugins/security_solution/public/common/components/utility_bar/utility_bar.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/utility_bar/utility_bar.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; +import { euiDarkVars } from '@kbn/ui-shared-deps-src/theme'; import { mount, shallow } from 'enzyme'; import React from 'react'; diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx index 4ca8bf037261a..2edfc1336269f 100644 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx @@ -13,7 +13,15 @@ import { sourcererActions, sourcererSelectors } from '../../store/sourcerer'; import { SelectedDataView, SourcererScopeName } from '../../store/sourcerer/model'; import { useUserInfo } from '../../../detections/components/user_info'; import { timelineSelectors } from '../../../timelines/store/timeline'; -import { ALERTS_PATH, CASES_PATH, RULES_PATH, UEBA_PATH } from '../../../../common/constants'; +import { + ALERTS_PATH, + CASES_PATH, + HOSTS_PATH, + NETWORK_PATH, + OVERVIEW_PATH, + RULES_PATH, + UEBA_PATH, +} from '../../../../common/constants'; import { TimelineId } from '../../../../common'; import { useDeepEqualSelector } from '../../hooks/use_selector'; import { getScopePatternListSelection } from '../../store/sourcerer/helpers'; @@ -300,3 +308,24 @@ export const getScopeFromPath = ( }) == null ? SourcererScopeName.default : SourcererScopeName.detections; + +export const sourcererPaths = [ + ALERTS_PATH, + `${RULES_PATH}/id/:id`, + HOSTS_PATH, + NETWORK_PATH, + OVERVIEW_PATH, + UEBA_PATH, +]; + +export const showSourcererByPath = (pathname: string): boolean => + matchPath(pathname, { + path: sourcererPaths, + strict: false, + }) != null; + +export const isAlertsOrRulesDetailsPage = (pathname: string): boolean => + matchPath(pathname, { + path: [ALERTS_PATH, `${RULES_PATH}/id/:id`], + strict: false, + }) != null; diff --git a/x-pack/plugins/security_solution/public/common/lib/theme/use_eui_theme.tsx b/x-pack/plugins/security_solution/public/common/lib/theme/use_eui_theme.tsx index e9b66728b9a1d..0057666ba4262 100644 --- a/x-pack/plugins/security_solution/public/common/lib/theme/use_eui_theme.tsx +++ b/x-pack/plugins/security_solution/public/common/lib/theme/use_eui_theme.tsx @@ -5,8 +5,10 @@ * 2.0. */ -import darkTheme from '@elastic/eui/dist/eui_theme_dark.json'; -import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; +import { + euiLightVars as lightTheme, + euiDarkVars as darkTheme, +} from '@kbn/ui-shared-deps-src/theme'; import { DEFAULT_DARK_MODE } from '../../../../common/constants'; import { useUiSetting$ } from '../kibana'; diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx index 56f5dc28652aa..ed207c9d76186 100644 --- a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx @@ -10,6 +10,7 @@ import { createMemoryHistory, MemoryHistory } from 'history'; import { render as reactRender, RenderOptions, RenderResult } from '@testing-library/react'; import { Action, Reducer, Store } from 'redux'; import { AppDeepLink } from 'kibana/public'; +import { QueryClient, QueryClientProvider } from 'react-query'; import { coreMock } from '../../../../../../../src/core/public/mocks'; import { StartPlugins, StartServices } from '../../../types'; import { depsStartMock } from './dependencies_start_mock'; @@ -85,6 +86,14 @@ const experimentalFeaturesReducer: Reducer { const AppWrapper: React.FC<{ children: React.ReactElement }> = ({ children }) => ( - {children} + {children} ); diff --git a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx index 7ea93bb7ce8fb..f180dd2baf5f4 100644 --- a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; +import { euiDarkVars } from '@kbn/ui-shared-deps-src/theme'; import { I18nProvider } from '@kbn/i18n/react'; import React from 'react'; diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/helpers.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/helpers.ts index 1b4efa72127f3..c99ed720c7f00 100644 --- a/x-pack/plugins/security_solution/public/common/store/sourcerer/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/helpers.ts @@ -37,15 +37,7 @@ export const getScopePatternListSelection = ( // set to signalIndexName whether or not it exists yet in the patternList return (signalIndexName != null ? [signalIndexName] : []).sort(); case SourcererScopeName.timeline: - return ( - signalIndexName != null - ? [ - // remove signalIndexName in case its already in there and add it whether or not it exists yet in the patternList - ...patternList.filter((index) => index !== signalIndexName), - signalIndexName, - ] - : patternList - ).sort(); + return patternList.sort(); } }; @@ -96,16 +88,14 @@ export const validateSelectedPatterns = ( selectedDataViewId: dataView?.id ?? null, selectedPatterns, ...(isEmpty(selectedPatterns) - ? id === SourcererScopeName.timeline - ? defaultDataViewByEventType({ state, eventType }) - : { - selectedPatterns: getScopePatternListSelection( - dataView ?? state.defaultDataView, - id, - state.signalIndexName, - (dataView ?? state.defaultDataView).id === state.defaultDataView.id - ), - } + ? { + selectedPatterns: getScopePatternListSelection( + dataView ?? state.defaultDataView, + id, + state.signalIndexName, + (dataView ?? state.defaultDataView).id === state.defaultDataView.id + ), + } : {}), loading: false, }, diff --git a/x-pack/plugins/security_solution/public/detections/components/detection_engine_header_page/index.tsx b/x-pack/plugins/security_solution/public/detections/components/detection_engine_header_page/index.tsx index 92911ab285375..44f27b690fbc7 100644 --- a/x-pack/plugins/security_solution/public/detections/components/detection_engine_header_page/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/detection_engine_header_page/index.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { HeaderPage, HeaderPageProps } from '../../../common/components/header_page'; const DetectionEngineHeaderPageComponent: React.FC = (props) => ( - + ); export const DetectionEngineHeaderPage = React.memo(DetectionEngineHeaderPageComponent); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/severity_badge/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/severity_badge/index.tsx index 32075c48c7ff3..728e0ec871e93 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/severity_badge/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/severity_badge/index.tsx @@ -8,7 +8,7 @@ import { upperFirst } from 'lodash/fp'; import React from 'react'; import { EuiHealth } from '@elastic/eui'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars } from '@kbn/ui-shared-deps-src/theme'; interface Props { value: string; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/data.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/data.tsx index 264e499d9cf86..df50946f058ba 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/data.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/data.tsx @@ -7,7 +7,7 @@ import styled from 'styled-components'; import { EuiHealth } from '@elastic/eui'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars } from '@kbn/ui-shared-deps-src/theme'; import React from 'react'; import { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts index 15ff91cac5096..ca1b1f57b8399 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts @@ -551,7 +551,8 @@ export const IMPORT_RULE_BTN_TITLE = i18n.translate( export const SELECT_RULE = i18n.translate( 'xpack.securitySolution.detectionEngine.components.importRuleModal.selectRuleDescription', { - defaultMessage: 'Select Security rules (as exported from the Detection Rules page) to import', + defaultMessage: + 'Select rules and actions (as exported from the Security > Rules page) to import', } ); diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/service.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/service.ts index 3b796d6aff0b3..2bb987271615a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/service.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/service.ts @@ -81,7 +81,7 @@ export async function createHostIsolationExceptionItem({ }); } -export async function deleteHostIsolationExceptionItems(http: HttpStart, id: string) { +export async function deleteOneHostIsolationExceptionItem(http: HttpStart, id: string) { await ensureHostIsolationExceptionsListExists(http); return http.delete(EXCEPTION_LIST_ITEM_URL, { query: { diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/action.ts index 237868ad18c50..7a9b1dc60c445 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/action.ts @@ -5,10 +5,7 @@ * 2.0. */ -import { - ExceptionListItemSchema, - UpdateExceptionListItemSchema, -} from '@kbn/securitysolution-io-ts-list-types'; +import { UpdateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import { Action } from 'redux'; import { HostIsolationExceptionsPageState } from '../types'; @@ -31,16 +28,7 @@ export type HostIsolationExceptionsCreateEntry = Action<'hostIsolationExceptions payload: HostIsolationExceptionsPageState['form']['entry']; }; -export type HostIsolationExceptionsDeleteItem = Action<'hostIsolationExceptionsMarkToDelete'> & { - payload?: ExceptionListItemSchema; -}; - -export type HostIsolationExceptionsSubmitDelete = Action<'hostIsolationExceptionsSubmitDelete'>; - -export type HostIsolationExceptionsDeleteStatusChanged = - Action<'hostIsolationExceptionsDeleteStatusChanged'> & { - payload: HostIsolationExceptionsPageState['deletion']['status']; - }; +export type HostIsolationExceptionsRefreshList = Action<'hostIsolationExceptionsRefreshList'>; export type HostIsolationExceptionsMarkToEdit = Action<'hostIsolationExceptionsMarkToEdit'> & { payload: { @@ -56,9 +44,7 @@ export type HostIsolationExceptionsPageAction = | HostIsolationExceptionsPageDataChanged | HostIsolationExceptionsCreateEntry | HostIsolationExceptionsFormStateChanged - | HostIsolationExceptionsDeleteItem - | HostIsolationExceptionsSubmitDelete - | HostIsolationExceptionsDeleteStatusChanged + | HostIsolationExceptionsRefreshList | HostIsolationExceptionsFormEntryChanged | HostIsolationExceptionsMarkToEdit | HostIsolationExceptionsSubmitEdit; diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.test.ts index 878c17a1a2757..a59a289f79be5 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.test.ts @@ -25,7 +25,6 @@ import { } from '../../../state'; import { createHostIsolationExceptionItem, - deleteHostIsolationExceptionItems, getHostIsolationExceptionItems, getOneHostIsolationExceptionItem, updateOneHostIsolationExceptionItem, @@ -39,7 +38,6 @@ import { getListFetchError } from './selector'; jest.mock('../service'); const getHostIsolationExceptionItemsMock = getHostIsolationExceptionItems as jest.Mock; -const deleteHostIsolationExceptionItemsMock = deleteHostIsolationExceptionItems as jest.Mock; const createHostIsolationExceptionItemMock = createHostIsolationExceptionItem as jest.Mock; const getOneHostIsolationExceptionItemMock = getOneHostIsolationExceptionItem as jest.Mock; const updateOneHostIsolationExceptionItemMock = updateOneHostIsolationExceptionItem as jest.Mock; @@ -319,69 +317,4 @@ describe('Host isolation exceptions middleware', () => { await waiter; }); }); - - describe('When deleting an item from host isolation exceptions', () => { - beforeEach(() => { - deleteHostIsolationExceptionItemsMock.mockReset(); - deleteHostIsolationExceptionItemsMock.mockReturnValue(undefined); - getHostIsolationExceptionItemsMock.mockReset(); - getHostIsolationExceptionItemsMock.mockImplementation(getFoundExceptionListItemSchemaMock); - store.dispatch({ - type: 'hostIsolationExceptionsMarkToDelete', - payload: { - id: '1', - }, - }); - }); - - it('should call the delete exception API when a delete is submitted and advertise a loading status', async () => { - const waiter = Promise.all([ - // delete loading action - spyMiddleware.waitForAction('hostIsolationExceptionsDeleteStatusChanged', { - validate({ payload }) { - return isLoadingResourceState(payload); - }, - }), - // delete finished action - spyMiddleware.waitForAction('hostIsolationExceptionsDeleteStatusChanged', { - validate({ payload }) { - return isLoadedResourceState(payload); - }, - }), - ]); - store.dispatch({ - type: 'hostIsolationExceptionsSubmitDelete', - }); - await waiter; - expect(deleteHostIsolationExceptionItemsMock).toHaveBeenLastCalledWith( - fakeCoreStart.http, - '1' - ); - }); - - it('should dispatch a failure if the API returns an error', async () => { - deleteHostIsolationExceptionItemsMock.mockRejectedValue({ - body: { message: 'error message', statusCode: 500, error: 'Internal Server Error' }, - }); - store.dispatch({ - type: 'hostIsolationExceptionsSubmitDelete', - }); - await spyMiddleware.waitForAction('hostIsolationExceptionsDeleteStatusChanged', { - validate({ payload }) { - return isFailedResourceState(payload); - }, - }); - }); - - it('should reload the host isolation exception lists after delete', async () => { - store.dispatch({ - type: 'hostIsolationExceptionsSubmitDelete', - }); - await spyMiddleware.waitForAction('hostIsolationExceptionsPageDataChanged', { - validate({ payload }) { - return isLoadingResourceState(payload); - }, - }); - }); - }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.ts index 3eca607f0c747..f4e49b1ea02da 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.ts @@ -26,14 +26,13 @@ import { asStaleResourceState, } from '../../../state/async_resource_builders'; import { - deleteHostIsolationExceptionItems, getHostIsolationExceptionItems, createHostIsolationExceptionItem, getOneHostIsolationExceptionItem, updateOneHostIsolationExceptionItem, } from '../service'; import { HostIsolationExceptionsPageState } from '../types'; -import { getCurrentListPageDataState, getCurrentLocation, getItemToDelete } from './selector'; +import { getCurrentListPageDataState, getCurrentLocation } from './selector'; import { HostIsolationExceptionsPageAction } from './action'; export const SEARCHABLE_FIELDS: Readonly = [`name`, `description`, `entries.value`]; @@ -52,12 +51,12 @@ export const createHostIsolationExceptionsPageMiddleware = ( loadHostIsolationExceptionsList(store, coreStart.http); } - if (action.type === 'hostIsolationExceptionsCreateEntry') { - createHostIsolationException(store, coreStart.http); + if (action.type === 'hostIsolationExceptionsRefreshList') { + loadHostIsolationExceptionsList(store, coreStart.http); } - if (action.type === 'hostIsolationExceptionsSubmitDelete') { - deleteHostIsolationExceptionsItem(store, coreStart.http); + if (action.type === 'hostIsolationExceptionsCreateEntry') { + createHostIsolationException(store, coreStart.http); } if (action.type === 'hostIsolationExceptionsMarkToEdit') { @@ -156,42 +155,6 @@ function isHostIsolationExceptionsPage(location: Immutable) { ); } -async function deleteHostIsolationExceptionsItem( - store: ImmutableMiddlewareAPI< - HostIsolationExceptionsPageState, - HostIsolationExceptionsPageAction - >, - http: HttpSetup -) { - const { dispatch } = store; - const itemToDelete = getItemToDelete(store.getState()); - if (itemToDelete === undefined) { - return; - } - - try { - dispatch({ - type: 'hostIsolationExceptionsDeleteStatusChanged', - payload: { - type: 'LoadingResourceState', - }, - }); - - await deleteHostIsolationExceptionItems(http, itemToDelete.id); - - dispatch({ - type: 'hostIsolationExceptionsDeleteStatusChanged', - payload: createLoadedResourceState(itemToDelete), - }); - loadHostIsolationExceptionsList(store, http); - } catch (error) { - dispatch({ - type: 'hostIsolationExceptionsDeleteStatusChanged', - payload: createFailedResourceState(error.body ?? error), - }); - } -} - async function loadHostIsolationExceptionsItem( store: ImmutableMiddlewareAPI< HostIsolationExceptionsPageState, diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/reducer.ts index 77a1c248d0cf0..d89e8abef5aae 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/reducer.ts @@ -73,23 +73,6 @@ export const hostIsolationExceptionsPageReducer: StateReducer = ( } case 'userChangedUrl': return userChangedUrl(state, action); - case 'hostIsolationExceptionsMarkToDelete': { - return { - ...state, - deletion: { - item: action.payload, - status: createUninitialisedResourceState(), - }, - }; - } - case 'hostIsolationExceptionsDeleteStatusChanged': - return { - ...state, - deletion: { - ...state.deletion, - status: action.payload, - }, - }; } return state; }; diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/selector.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/selector.ts index 996978f96fcb5..9e79637259941 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/selector.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/selector.ts @@ -93,9 +93,6 @@ export const showDeleteModal: HostIsolationExceptionsSelector = createS } ); -export const getItemToDelete: HostIsolationExceptionsSelector = - createSelector(getDeletionState, ({ item }) => item); - export const isDeletionInProgress: HostIsolationExceptionsSelector = createSelector( getDeletionState, ({ status }) => { diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/delete_modal.test.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/delete_modal.test.tsx index 9cca87bf61d6a..a133801bf356c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/delete_modal.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/delete_modal.test.tsx @@ -5,38 +5,34 @@ * 2.0. */ import React from 'react'; -import { act } from '@testing-library/react'; import { AppContextTestRender, createAppRootMockRenderer, } from '../../../../../common/mock/endpoint'; import { HostIsolationExceptionDeleteModal } from './delete_modal'; -import { isFailedResourceState, isLoadedResourceState } from '../../../../state'; -import { getHostIsolationExceptionItems, deleteHostIsolationExceptionItems } from '../../service'; +import { deleteOneHostIsolationExceptionItem } from '../../service'; import { getExceptionListItemSchemaMock } from '../../../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; -import { fireEvent } from '@testing-library/dom'; +import { waitFor } from '@testing-library/dom'; +import userEvent from '@testing-library/user-event'; jest.mock('../../service'); -const getHostIsolationExceptionItemsMock = getHostIsolationExceptionItems as jest.Mock; -const deleteHostIsolationExceptionItemsMock = deleteHostIsolationExceptionItems as jest.Mock; +const deleteOneHostIsolationExceptionItemMock = deleteOneHostIsolationExceptionItem as jest.Mock; describe('When on the host isolation exceptions delete modal', () => { let render: () => ReturnType; let renderResult: ReturnType; - let waitForAction: AppContextTestRender['middlewareSpy']['waitForAction']; let coreStart: AppContextTestRender['coreStart']; + let onCancel: (forceRefresh?: boolean) => void; beforeEach(() => { - const itemToDelete = getExceptionListItemSchemaMock(); - getHostIsolationExceptionItemsMock.mockReset(); - deleteHostIsolationExceptionItemsMock.mockReset(); const mockedContext = createAppRootMockRenderer(); - mockedContext.store.dispatch({ - type: 'hostIsolationExceptionsMarkToDelete', - payload: itemToDelete, - }); - render = () => (renderResult = mockedContext.render()); - waitForAction = mockedContext.middlewareSpy.waitForAction; + const itemToDelete = getExceptionListItemSchemaMock(); + deleteOneHostIsolationExceptionItemMock.mockReset(); + onCancel = jest.fn(); + render = () => + (renderResult = mockedContext.render( + + )); ({ coreStart } = mockedContext); }); @@ -51,6 +47,13 @@ describe('When on the host isolation exceptions delete modal', () => { it('should disable the buttons when confirm is pressed and show loading', async () => { render(); + // fake a delay on a response + deleteOneHostIsolationExceptionItemMock.mockImplementationOnce(() => { + return new Promise((resolve) => { + setTimeout(resolve, 300); + }); + }); + const submitButton = renderResult.baseElement.querySelector( '[data-test-subj="hostIsolationExceptionsDeleteModalConfirmButton"]' ) as HTMLButtonElement; @@ -59,77 +62,65 @@ describe('When on the host isolation exceptions delete modal', () => { '[data-test-subj="hostIsolationExceptionsDeleteModalConfirmButton"]' ) as HTMLButtonElement; - act(() => { - fireEvent.click(submitButton); - }); + userEvent.click(submitButton); + + // wait for the mock API to be called + await waitFor(expect(deleteOneHostIsolationExceptionItemMock).toHaveBeenCalled); expect(submitButton.disabled).toBe(true); expect(cancelButton.disabled).toBe(true); expect(submitButton.querySelector('.euiLoadingSpinner')).not.toBeNull(); }); - it('should clear the item marked to delete when cancel is pressed', async () => { + it('should call the onCancel callback when cancel is pressed', async () => { render(); const cancelButton = renderResult.baseElement.querySelector( '[data-test-subj="hostIsolationExceptionsDeleteModalConfirmButton"]' ) as HTMLButtonElement; - const waiter = waitForAction('hostIsolationExceptionsMarkToDelete', { - validate: ({ payload }) => { - return payload === undefined; - }, + userEvent.click(cancelButton); + await waitFor(() => { + expect(onCancel).toHaveBeenCalledTimes(1); }); - - act(() => { - fireEvent.click(cancelButton); - }); - expect(await waiter).toBeTruthy(); }); - it('should show success toast after the delete is completed', async () => { + it('should show success toast after the delete is completed and call onCancel with forceRefresh', async () => { + deleteOneHostIsolationExceptionItemMock.mockResolvedValue({}); render(); - const updateCompleted = waitForAction('hostIsolationExceptionsDeleteStatusChanged', { - validate(action) { - return isLoadedResourceState(action.payload); - }, - }); const submitButton = renderResult.baseElement.querySelector( '[data-test-subj="hostIsolationExceptionsDeleteModalConfirmButton"]' ) as HTMLButtonElement; - await act(async () => { - fireEvent.click(submitButton); - await updateCompleted; - }); + userEvent.click(submitButton); + + // wait for the mock API to be called + await waitFor(expect(deleteOneHostIsolationExceptionItemMock).toHaveBeenCalled); expect(coreStart.notifications.toasts.addSuccess).toHaveBeenCalledWith( '"some name" has been removed from the Host isolation exceptions list.' ); + expect(onCancel).toHaveBeenCalledWith(true); }); - it('should show error toast if error is encountered', async () => { - deleteHostIsolationExceptionItemsMock.mockRejectedValue( + it('should show error toast if error is encountered and call onCancel with forceRefresh', async () => { + deleteOneHostIsolationExceptionItemMock.mockRejectedValue( new Error("That's not true. That's impossible") ); render(); - const updateFailure = waitForAction('hostIsolationExceptionsDeleteStatusChanged', { - validate(action) { - return isFailedResourceState(action.payload); - }, - }); const submitButton = renderResult.baseElement.querySelector( '[data-test-subj="hostIsolationExceptionsDeleteModalConfirmButton"]' ) as HTMLButtonElement; - await act(async () => { - fireEvent.click(submitButton); - await updateFailure; - }); + userEvent.click(submitButton); + + // wait for the mock API to be called + await waitFor(expect(deleteOneHostIsolationExceptionItemMock).toHaveBeenCalled); expect(coreStart.notifications.toasts.addDanger).toHaveBeenCalledWith( 'Unable to remove "some name" from the Host isolation exceptions list. Reason: That\'s not true. That\'s impossible' ); + expect(onCancel).toHaveBeenCalledWith(true); }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/delete_modal.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/delete_modal.tsx index 51e0ab5a5a154..0b9319580a443 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/delete_modal.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/delete_modal.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { memo, useCallback, useEffect } from 'react'; +import React, { memo } from 'react'; import { EuiButton, EuiButtonEmpty, @@ -17,125 +17,122 @@ import { EuiText, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useDispatch } from 'react-redux'; -import { Dispatch } from 'redux'; import { i18n } from '@kbn/i18n'; -import { useToasts } from '../../../../../common/lib/kibana'; -import { useHostIsolationExceptionsSelector } from '../hooks'; -import { - getDeleteError, - getItemToDelete, - isDeletionInProgress, - wasDeletionSuccessful, -} from '../../store/selector'; -import { HostIsolationExceptionsPageAction } from '../../store/action'; - -export const HostIsolationExceptionDeleteModal = memo<{}>(() => { - const dispatch = useDispatch>(); - const toasts = useToasts(); +import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { useMutation } from 'react-query'; +import { useHttp, useToasts } from '../../../../../common/lib/kibana'; +import { deleteOneHostIsolationExceptionItem } from '../../service'; - const isDeleting = useHostIsolationExceptionsSelector(isDeletionInProgress); - const exception = useHostIsolationExceptionsSelector(getItemToDelete); - const wasDeleted = useHostIsolationExceptionsSelector(wasDeletionSuccessful); - const deleteError = useHostIsolationExceptionsSelector(getDeleteError); +export const HostIsolationExceptionDeleteModal = memo( + ({ + item, + onCancel, + }: { + item: ExceptionListItemSchema; + onCancel: (forceRefresh?: boolean) => void; + }) => { + const toasts = useToasts(); + const http = useHttp(); - const onCancel = useCallback(() => { - dispatch({ type: 'hostIsolationExceptionsMarkToDelete', payload: undefined }); - }, [dispatch]); + const mutation = useMutation( + () => { + return deleteOneHostIsolationExceptionItem(http, item.id); + }, + { + onError: (error: Error) => { + toasts.addDanger( + i18n.translate( + 'xpack.securitySolution.hostIsolationExceptions.deletionDialog.deleteFailure', + { + defaultMessage: + 'Unable to remove "{name}" from the Host isolation exceptions list. Reason: {message}', + values: { name: item?.name, message: error.message }, + } + ) + ); + onCancel(true); + }, + onSuccess: () => { + toasts.addSuccess( + i18n.translate( + 'xpack.securitySolution.hostIsolationExceptions.deletionDialog.deleteSuccess', + { + defaultMessage: + '"{name}" has been removed from the Host isolation exceptions list.', + values: { name: item?.name }, + } + ) + ); + onCancel(true); + }, + } + ); - const onConfirm = useCallback(() => { - dispatch({ type: 'hostIsolationExceptionsSubmitDelete' }); - }, [dispatch]); + const handleConfirmButton = () => { + mutation.mutate(); + }; - // Show toast for success - useEffect(() => { - if (wasDeleted) { - toasts.addSuccess( - i18n.translate( - 'xpack.securitySolution.hostIsolationExceptions.deletionDialog.deleteSuccess', - { - defaultMessage: '"{name}" has been removed from the Host isolation exceptions list.', - values: { name: exception?.name }, - } - ) - ); + const handleCancelButton = () => { + onCancel(); + }; - dispatch({ type: 'hostIsolationExceptionsMarkToDelete', payload: undefined }); - } - }, [dispatch, exception?.name, toasts, wasDeleted]); - - // show toast for failures - useEffect(() => { - if (deleteError) { - toasts.addDanger( - i18n.translate( - 'xpack.securitySolution.hostIsolationExceptions.deletionDialog.deleteFailure', - { - defaultMessage: - 'Unable to remove "{name}" from the Host isolation exceptions list. Reason: {message}', - values: { name: exception?.name, message: deleteError.message }, - } - ) - ); - } - }, [deleteError, exception?.name, toasts]); + return ( + onCancel()}> + + + + + - return ( - - - - - - + + +

+ {item?.name} }} + /> +

+

+ +

+
+
- - -

+ + {exception?.name} }} + id="xpack.securitySolution.hostIsolationExceptions.deletionDialog.cancel" + defaultMessage="Cancel" /> -

-

+ + + -

-
-
- - - - - - - - - - -
- ); -}); + + +
+ ); + } +); HostIsolationExceptionDeleteModal.displayName = 'HostIsolationExceptionDeleteModal'; diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx index f7429c213d2d5..14a5bae009988 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx @@ -7,15 +7,14 @@ import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import { i18n } from '@kbn/i18n'; -import React, { Dispatch, useCallback, useEffect } from 'react'; +import React, { Dispatch, useCallback, useEffect, useState } from 'react'; import { EuiButton, EuiText, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useDispatch } from 'react-redux'; import { useHistory } from 'react-router-dom'; +import { useDispatch } from 'react-redux'; import { ExceptionItem } from '../../../../common/components/exceptions/viewer/exception_item'; import { getCurrentLocation, - getItemToDelete, getListFetchError, getListIsLoading, getListItems, @@ -32,7 +31,6 @@ import { AdministrationListPage } from '../../../components/administration_list_ import { SearchExceptions } from '../../../components/search_exceptions'; import { ArtifactEntryCard, ArtifactEntryCardProps } from '../../../components/artifact_entry_card'; import { HostIsolationExceptionsEmptyState } from './components/empty'; -import { HostIsolationExceptionsPageAction } from '../store/action'; import { HostIsolationExceptionDeleteModal } from './components/delete_modal'; import { HostIsolationExceptionsFormFlyout } from './components/form_flyout'; import { @@ -41,6 +39,7 @@ import { } from './components/translations'; import { getEndpointListPath } from '../../../common/routing'; import { useEndpointPrivileges } from '../../../../common/components/user_privileges/endpoint'; +import { HostIsolationExceptionsPageAction } from '../store/action'; type HostIsolationExceptionPaginatedContent = PaginatedContentProps< Immutable, @@ -55,8 +54,10 @@ export const HostIsolationExceptionsList = () => { const fetchError = useHostIsolationExceptionsSelector(getListFetchError); const location = useHostIsolationExceptionsSelector(getCurrentLocation); const dispatch = useDispatch>(); - const itemToDelete = useHostIsolationExceptionsSelector(getItemToDelete); const navigateCallback = useHostIsolationExceptionsNavigateCallback(); + + const [itemToDelete, setItemToDelete] = useState(null); + const history = useHistory(); const privileges = useEndpointPrivileges(); const showFlyout = privileges.canIsolateHost && !!location.show; @@ -90,10 +91,7 @@ export const HostIsolationExceptionsList = () => { const deleteAction = { icon: 'trash', onClick: () => { - dispatch({ - type: 'hostIsolationExceptionsMarkToDelete', - payload: element, - }); + setItemToDelete(element); }, 'data-test-subj': 'deleteHostIsolationException', children: DELETE_HOST_ISOLATION_EXCEPTION_LABEL, @@ -125,6 +123,15 @@ export const HostIsolationExceptionsList = () => { [navigateCallback] ); + const handleCloseDeleteDialog = (forceRefresh: boolean = false) => { + if (forceRefresh) { + dispatch({ + type: 'hostIsolationExceptionsRefreshList', + }); + } + setItemToDelete(null); + }; + return ( { > {showFlyout && } - {itemToDelete ? : null} + {itemToDelete ? ( + + ) : null} {hasDataToShow ? ( <> diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/components/config_form/index.stories.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/config_form/index.stories.tsx index 796ecc30f4033..09321244e0abc 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/components/config_form/index.stories.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/config_form/index.stories.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { ThemeProvider } from 'styled-components'; import { addDecorator, storiesOf } from '@storybook/react'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars } from '@kbn/ui-shared-deps-src/theme'; import { EuiCheckbox, EuiSpacer, EuiSwitch, EuiText } from '@elastic/eui'; import { OperatingSystem } from '../../../../../../../common/endpoint/types'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.stories.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.stories.tsx index 75323f8b55174..ecc18d5d52fd9 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.stories.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.stories.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { Provider } from 'react-redux'; import { ThemeProvider } from 'styled-components'; import { storiesOf } from '@storybook/react'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars } from '@kbn/ui-shared-deps-src/theme'; import { EuiHorizontalRule } from '@elastic/eui'; import { KibanaContextProvider } from '../../../../../../../../../../src/plugins/kibana_react/public'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/view_type_toggle/index.stories.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/view_type_toggle/index.stories.tsx index b8f98ebcf78bb..484f17318f839 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/view_type_toggle/index.stories.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/view_type_toggle/index.stories.tsx @@ -8,7 +8,7 @@ import React, { useState } from 'react'; import { ThemeProvider } from 'styled-components'; import { storiesOf, addDecorator } from '@storybook/react'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars } from '@kbn/ui-shared-deps-src/theme'; import { ViewType } from '../../../state'; import { ViewTypeToggle } from '.'; diff --git a/x-pack/plugins/security_solution/public/network/components/details/index.tsx b/x-pack/plugins/security_solution/public/network/components/details/index.tsx index 0b53a4bfb3fe2..5cd2f4dfd72c8 100644 --- a/x-pack/plugins/security_solution/public/network/components/details/index.tsx +++ b/x-pack/plugins/security_solution/public/network/components/details/index.tsx @@ -5,8 +5,10 @@ * 2.0. */ -import darkTheme from '@elastic/eui/dist/eui_theme_dark.json'; -import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; +import { + euiLightVars as lightTheme, + euiDarkVars as darkTheme, +} from '@kbn/ui-shared-deps-src/theme'; import React from 'react'; import { DEFAULT_DARK_MODE } from '../../../../common/constants'; diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/tooltip_footer.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/tooltip_footer.tsx index dbb280228e504..a557ee7b8b190 100644 --- a/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/tooltip_footer.tsx +++ b/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/tooltip_footer.tsx @@ -14,7 +14,7 @@ import { EuiIcon, EuiText, } from '@elastic/eui'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars as theme } from '@kbn/ui-shared-deps-src/theme'; import styled from 'styled-components'; import * as i18n from '../translations'; diff --git a/x-pack/plugins/security_solution/public/overview/components/host_overview/index.tsx b/x-pack/plugins/security_solution/public/overview/components/host_overview/index.tsx index e73096aa3babf..0708892affe13 100644 --- a/x-pack/plugins/security_solution/public/overview/components/host_overview/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/host_overview/index.tsx @@ -6,8 +6,10 @@ */ import { EuiHorizontalRule } from '@elastic/eui'; -import darkTheme from '@elastic/eui/dist/eui_theme_dark.json'; -import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; +import { + euiLightVars as lightTheme, + euiDarkVars as darkTheme, +} from '@kbn/ui-shared-deps-src/theme'; import { getOr } from 'lodash/fp'; import React, { useCallback, useMemo } from 'react'; diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx index 3a98f062db65d..67ee6c55ac06f 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx @@ -28,8 +28,6 @@ import { EndpointNotice } from '../components/endpoint_notice'; import { useMessagesStorage } from '../../common/containers/local_storage/use_messages_storage'; import { ENDPOINT_METADATA_INDEX } from '../../../common/constants'; import { useSourcererDataView } from '../../common/containers/sourcerer'; -import { Sourcerer } from '../../common/components/sourcerer'; -import { SourcererScopeName } from '../../common/store/sourcerer/model'; import { useDeepEqualSelector } from '../../common/hooks/use_selector'; import { ThreatIntelLinkPanel } from '../components/overview_cti_links'; import { useIsThreatIntelModuleEnabled } from '../containers/overview_cti_links/use_is_threat_intel_module_enabled'; @@ -96,7 +94,6 @@ const OverviewComponent = () => { )} - diff --git a/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts b/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts index baf3eff8b391c..f52075cbe4d85 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts +++ b/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts @@ -5,10 +5,8 @@ * 2.0. */ -import euiThemeAmsterdamDark from '@elastic/eui/dist/eui_theme_amsterdam_dark.json'; -import euiThemeAmsterdamLight from '@elastic/eui/dist/eui_theme_amsterdam_light.json'; +import { darkMode, euiThemeVars } from '@kbn/ui-shared-deps-src/theme'; import { useMemo } from 'react'; -import { useUiSetting } from '../../../../../../src/plugins/kibana_react/public'; type ResolverColorNames = | 'copyableFieldBackground' @@ -31,24 +29,22 @@ type ColorMap = Record; * Get access to Kibana-theme based colors. */ export function useColors(): ColorMap { - const isDarkMode = useUiSetting('theme:darkMode'); - const theme = isDarkMode ? euiThemeAmsterdamDark : euiThemeAmsterdamLight; return useMemo(() => { return { - copyableFieldBackground: theme.euiColorLightShade, - descriptionText: theme.euiTextColor, - full: theme.euiColorFullShade, - graphControls: theme.euiColorDarkestShade, - graphControlsBackground: theme.euiColorEmptyShade, - graphControlsBorderColor: theme.euiColorLightShade, - processBackingFill: `${theme.euiColorPrimary}${isDarkMode ? '1F' : '0F'}`, // Add opacity 0F = 6% , 1F = 12% - resolverBackground: theme.euiColorEmptyShade, - resolverEdge: isDarkMode ? theme.euiColorLightShade : theme.euiColorLightestShade, - resolverBreadcrumbBackground: theme.euiColorLightestShade, - resolverEdgeText: isDarkMode ? theme.euiColorFullShade : theme.euiColorDarkShade, - triggerBackingFill: `${theme.euiColorDanger}${isDarkMode ? '1F' : '0F'}`, - pillStroke: theme.euiColorLightShade, - linkColor: theme.euiLinkColor, + copyableFieldBackground: euiThemeVars.euiColorLightShade, + descriptionText: euiThemeVars.euiTextColor, + full: euiThemeVars.euiColorFullShade, + graphControls: euiThemeVars.euiColorDarkestShade, + graphControlsBackground: euiThemeVars.euiColorEmptyShade, + graphControlsBorderColor: euiThemeVars.euiColorLightShade, + processBackingFill: `${euiThemeVars.euiColorPrimary}${darkMode ? '1F' : '0F'}`, // Add opacity 0F = 6% , 1F = 12% + resolverBackground: euiThemeVars.euiColorEmptyShade, + resolverEdge: darkMode ? euiThemeVars.euiColorLightShade : euiThemeVars.euiColorLightestShade, + resolverBreadcrumbBackground: euiThemeVars.euiColorLightestShade, + resolverEdgeText: darkMode ? euiThemeVars.euiColorFullShade : euiThemeVars.euiColorDarkShade, + triggerBackingFill: `${euiThemeVars.euiColorDanger}${darkMode ? '1F' : '0F'}`, + pillStroke: euiThemeVars.euiColorLightShade, + linkColor: euiThemeVars.euiLinkColor, }; - }, [isDarkMode, theme]); + }, []); } diff --git a/x-pack/plugins/security_solution/public/resolver/view/use_cube_assets.ts b/x-pack/plugins/security_solution/public/resolver/view/use_cube_assets.ts index 774c5f0ce1c74..f5a9c37623c47 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/use_cube_assets.ts +++ b/x-pack/plugins/security_solution/public/resolver/view/use_cube_assets.ts @@ -7,12 +7,10 @@ import { i18n } from '@kbn/i18n'; +import { euiThemeVars } from '@kbn/ui-shared-deps-src/theme'; import { ButtonColor } from '@elastic/eui'; -import euiThemeAmsterdamDark from '@elastic/eui/dist/eui_theme_amsterdam_dark.json'; -import euiThemeAmsterdamLight from '@elastic/eui/dist/eui_theme_amsterdam_light.json'; import { useMemo } from 'react'; import { ResolverProcessType, NodeDataStatus } from '../types'; -import { useUiSetting } from '../../../../../../src/plugins/kibana_react/public'; import { useSymbolIDs } from './use_symbol_ids'; import { useColors } from './use_colors'; @@ -24,8 +22,6 @@ export function useCubeAssets( isProcessTrigger: boolean ): NodeStyleConfig { const SymbolIds = useSymbolIDs(); - const isDarkMode = useUiSetting('theme:darkMode'); - const theme = isDarkMode ? euiThemeAmsterdamDark : euiThemeAmsterdamLight; const colorMap = useColors(); const nodeAssets: NodeStyleMap = useMemo( @@ -39,7 +35,7 @@ export function useCubeAssets( }), isLabelFilled: true, labelButtonFill: 'primary', - strokeColor: theme.euiColorPrimary, + strokeColor: euiThemeVars.euiColorPrimary, }, loadingCube: { backingFill: colorMap.processBackingFill, @@ -50,7 +46,7 @@ export function useCubeAssets( }), isLabelFilled: false, labelButtonFill: 'primary', - strokeColor: theme.euiColorPrimary, + strokeColor: euiThemeVars.euiColorPrimary, }, errorCube: { backingFill: colorMap.processBackingFill, @@ -61,7 +57,7 @@ export function useCubeAssets( }), isLabelFilled: false, labelButtonFill: 'primary', - strokeColor: theme.euiColorPrimary, + strokeColor: euiThemeVars.euiColorPrimary, }, runningTriggerCube: { backingFill: colorMap.triggerBackingFill, @@ -72,7 +68,7 @@ export function useCubeAssets( }), isLabelFilled: true, labelButtonFill: 'danger', - strokeColor: theme.euiColorDanger, + strokeColor: euiThemeVars.euiColorDanger, }, terminatedProcessCube: { backingFill: colorMap.processBackingFill, @@ -86,7 +82,7 @@ export function useCubeAssets( ), isLabelFilled: false, labelButtonFill: 'primary', - strokeColor: theme.euiColorPrimary, + strokeColor: euiThemeVars.euiColorPrimary, }, terminatedTriggerCube: { backingFill: colorMap.triggerBackingFill, @@ -100,10 +96,10 @@ export function useCubeAssets( ), isLabelFilled: false, labelButtonFill: 'danger', - strokeColor: theme.euiColorDanger, + strokeColor: euiThemeVars.euiColorDanger, }, }), - [SymbolIds, colorMap, theme] + [SymbolIds, colorMap] ); if (cubeType === 'terminated') { diff --git a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.tsx index 2fd9a1de1b8bc..dcf32cb585f9e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.tsx @@ -13,7 +13,6 @@ import { EuiFieldText, EuiFlexGroup, EuiFlexItem, - EuiFocusTrap, EuiFormRow, EuiPanel, EuiSpacer, @@ -181,7 +180,6 @@ export const StatefulEditDataProvider = React.memo( useEffect(() => { disableScrolling(); - focusInput(); return () => { enableScrolling(); }; @@ -189,94 +187,92 @@ export const StatefulEditDataProvider = React.memo( return ( - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - {type !== DataProviderType.template && - updatedOperator.length > 0 && - updatedOperator[0].label !== i18n.EXISTS && - updatedOperator[0].label !== i18n.DOES_NOT_EXIST ? ( - - + - ) : null} + + - - - + + + + {type !== DataProviderType.template && + updatedOperator.length > 0 && + updatedOperator[0].label !== i18n.EXISTS && + updatedOperator[0].label !== i18n.DOES_NOT_EXIST ? ( - - - - {i18n.SAVE} - - - + + + - - + ) : null} + + + + + + + + + + {i18n.SAVE} + + + + + ); } diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.tsx index 6cb0e6f2e7982..4bd963b21a7f1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.tsx @@ -42,7 +42,6 @@ import { requiredFieldsForActions } from '../../../../detections/components/aler import { ExitFullScreen } from '../../../../common/components/exit_full_screen'; import { SuperDatePicker } from '../../../../common/components/super_date_picker'; import { EventDetailsWidthProvider } from '../../../../common/components/events_viewer/event_details_width_context'; -import { PickEventType } from '../search_or_filter/pick_events'; import { inputsModel, inputsSelectors, State } from '../../../../common/store'; import { sourcererActions } from '../../../../common/store/sourcerer'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; @@ -57,6 +56,7 @@ import { DetailsPanel } from '../../side_panel'; import { EqlQueryBarTimeline } from '../query_bar/eql'; import { defaultControlColumn } from '../body/control_columns'; import { Sort } from '../body/sort'; +import { Sourcerer } from '../../../../common/components/sourcerer'; const TimelineHeaderContainer = styled.div` margin-top: 6px; @@ -283,10 +283,7 @@ export const EqlTabContentComponent: React.FC = ({
- +
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx index 5f6f2796d4ba9..6d53e7194306c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx @@ -46,7 +46,6 @@ import { import { requiredFieldsForActions } from '../../../../detections/components/alerts_table/default_config'; import { SuperDatePicker } from '../../../../common/components/super_date_picker'; import { EventDetailsWidthProvider } from '../../../../common/components/events_viewer/event_details_width_context'; -import { PickEventType } from '../search_or_filter/pick_events'; import { inputsModel, inputsSelectors, State } from '../../../../common/store'; import { sourcererActions } from '../../../../common/store/sourcerer'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; @@ -61,6 +60,7 @@ import { DetailsPanel } from '../../side_panel'; import { ExitFullScreen } from '../../../../common/components/exit_full_screen'; import { defaultControlColumn } from '../body/control_columns'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; +import { Sourcerer } from '../../../../common/components/sourcerer'; const TimelineHeaderContainer = styled.div` margin-top: 6px; @@ -358,10 +358,7 @@ export const QueryTabContentComponent: React.FC = ({ - + diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.test.tsx deleted file mode 100644 index 47ea0f781f7c3..0000000000000 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.test.tsx +++ /dev/null @@ -1,220 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { fireEvent, render, within } from '@testing-library/react'; -import { EuiToolTip } from '@elastic/eui'; - -import React from 'react'; -import { PickEventType } from './pick_events'; -import { - createSecuritySolutionStorageMock, - kibanaObservable, - mockGlobalState, - mockSourcererState, - SUB_PLUGINS_REDUCER, - TestProviders, -} from '../../../../common/mock'; -import { TimelineEventsType } from '../../../../../common'; -import { createStore } from '../../../../common/store'; -import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; - -jest.mock('@elastic/eui', () => { - const actual = jest.requireActual('@elastic/eui'); - return { - ...actual, - EuiToolTip: jest.fn(), - }; -}); - -describe('Pick Events/Timeline Sourcerer', () => { - const defaultProps = { - eventType: 'all' as TimelineEventsType, - onChangeEventTypeAndIndexesName: jest.fn(), - }; - const initialPatterns = [ - ...mockSourcererState.defaultDataView.patternList.filter( - (p) => p !== mockSourcererState.signalIndexName - ), - mockSourcererState.signalIndexName, - ]; - const { storage } = createSecuritySolutionStorageMock(); - - // const state = { - // ...mockGlobalState, - // sourcerer: { - // ...mockGlobalState.sourcerer, - // kibanaIndexPatterns: [ - // { id: '1234', title: 'auditbeat-*' }, - // { id: '9100', title: 'filebeat-*' }, - // { id: '9100', title: 'auditbeat-*,filebeat-*' }, - // { id: '5678', title: 'auditbeat-*,.siem-signals-default' }, - // ], - // configIndexPatterns: - // mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.timeline].selectedPatterns, - // signalIndexName: mockGlobalState.sourcerer.signalIndexName, - // sourcererScopes: { - // ...mockGlobalState.sourcerer.sourcererScopes, - // [SourcererScopeName.timeline]: { - // ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.timeline], - // loading: false, - // selectedPatterns: ['filebeat-*'], - // }, - // }, - // }, - // }; - // const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); - const state = { - ...mockGlobalState, - sourcerer: { - ...mockGlobalState.sourcerer, - sourcererScopes: { - ...mockGlobalState.sourcerer.sourcererScopes, - [SourcererScopeName.timeline]: { - ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.timeline], - loading: false, - selectedDataViewId: mockGlobalState.sourcerer.defaultDataView.id, - selectedPatterns: ['filebeat-*'], - }, - }, - }, - }; - const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); - const mockTooltip = ({ - tooltipContent, - children, - }: { - tooltipContent: string; - children: React.ReactElement; - }) => ( -
- {tooltipContent} - {children} -
- ); - - beforeAll(() => { - (EuiToolTip as unknown as jest.Mock).mockImplementation(mockTooltip); - }); - beforeEach(() => { - jest.clearAllMocks(); - jest.restoreAllMocks(); - }); - it('renders', () => { - const wrapper = render( - - - - ); - fireEvent.click(wrapper.getByTestId('sourcerer-timeline-trigger')); - expect(wrapper.getByTestId('timeline-sourcerer').textContent).toEqual( - initialPatterns.sort().join('') - ); - fireEvent.click(wrapper.getByTestId(`sourcerer-accordion`)); - fireEvent.click(wrapper.getByTestId('comboBoxToggleListButton')); - const optionNodes = wrapper.getAllByTestId('sourcerer-option'); - expect(optionNodes.length).toBe(1); - }); - it('Removes duplicate options from options list', () => { - const store2 = createStore( - { - ...mockGlobalState, - sourcerer: { - ...mockGlobalState.sourcerer, - defaultDataView: { - ...mockGlobalState.sourcerer.defaultDataView, - id: '1234', - title: 'filebeat-*,auditbeat-*,auditbeat-*,auditbeat-*,auditbeat-*', - patternList: ['filebeat-*', 'auditbeat-*'], - }, - kibanaDataViews: [ - { - ...mockGlobalState.sourcerer.defaultDataView, - id: '1234', - title: 'filebeat-*,auditbeat-*,auditbeat-*,auditbeat-*,auditbeat-*', - patternList: ['filebeat-*', 'auditbeat-*'], - }, - ], - sourcererScopes: { - ...mockGlobalState.sourcerer.sourcererScopes, - [SourcererScopeName.timeline]: { - ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.timeline], - loading: false, - selectedDataViewId: '1234', - selectedPatterns: ['filebeat-*'], - }, - }, - }, - }, - SUB_PLUGINS_REDUCER, - kibanaObservable, - storage - ); - const wrapper = render( - - - - ); - fireEvent.click(wrapper.getByTestId(`sourcerer-timeline-trigger`)); - fireEvent.click(wrapper.getByTestId(`sourcerer-accordion`)); - fireEvent.click(wrapper.getByTestId(`comboBoxToggleListButton`)); - expect( - wrapper.getByTestId('comboBoxOptionsList timeline-sourcerer-optionsList').textContent - ).toEqual('auditbeat-*'); - }); - - it('renders tooltip', () => { - render( - - - - ); - - expect((EuiToolTip as unknown as jest.Mock).mock.calls[0][0].content).toEqual( - initialPatterns - .filter((p) => p != null) - .sort() - .join(', ') - ); - }); - - it('renders popover button inside tooltip', () => { - const wrapper = render( - - - - ); - const tooltip = wrapper.getByTestId('timeline-sourcerer-tooltip'); - expect(within(tooltip).getByTestId('sourcerer-timeline-trigger')).toBeTruthy(); - }); - - it('correctly filters options', () => { - const wrapper = render( - - - - ); - fireEvent.click(wrapper.getByTestId('sourcerer-timeline-trigger')); - fireEvent.click(wrapper.getByTestId('comboBoxToggleListButton')); - fireEvent.click(wrapper.getByTestId('sourcerer-accordion')); - const optionNodes = wrapper.getAllByTestId('sourcerer-option'); - expect(optionNodes.length).toBe(9); - }); - it('reset button works', () => { - const wrapper = render( - - - - ); - fireEvent.click(wrapper.getByTestId('sourcerer-timeline-trigger')); - expect(wrapper.getByTestId('timeline-sourcerer').textContent).toEqual('filebeat-*'); - - fireEvent.click(wrapper.getByTestId('sourcerer-reset')); - expect(wrapper.getByTestId('timeline-sourcerer').textContent).toEqual( - initialPatterns.sort().join('') - ); - }); -}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx deleted file mode 100644 index 791993d67135d..0000000000000 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx +++ /dev/null @@ -1,465 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - EuiAccordion, - EuiButton, - EuiButtonEmpty, - EuiRadioGroup, - EuiComboBox, - EuiComboBoxOptionOption, - EuiHealth, - EuiIcon, - EuiFlexGroup, - EuiFlexItem, - EuiPopover, - EuiPopoverTitle, - EuiSpacer, - EuiText, - EuiToolTip, - EuiSuperSelect, -} from '@elastic/eui'; -import deepEqual from 'fast-deep-equal'; -import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; -import styled from 'styled-components'; - -import { sourcererSelectors } from '../../../../common/store'; -import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; -import { TimelineEventsType } from '../../../../../common'; -import * as i18n from './translations'; -import { getScopePatternListSelection } from '../../../../common/store/sourcerer/helpers'; -import { SIEM_DATA_VIEW_LABEL } from '../../../../common/components/sourcerer/translations'; -import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; - -const PopoverContent = styled.div` - width: 600px; -`; - -const ResetButton = styled(EuiButtonEmpty)` - width: fit-content; -`; - -const MyEuiButton = styled(EuiButton)` - .euiHealth { - vertical-align: middle; - } -`; - -const AllEuiHealth = styled(EuiHealth)` - margin-left: -2px; - svg { - stroke: #fff; - stroke-width: 1px; - stroke-linejoin: round; - width: 19px; - height: 19px; - margin-top: 1px; - z-index: 1; - } -`; - -const WarningEuiHealth = styled(EuiHealth)` - margin-left: -17px; - svg { - z-index: 0; - } -`; - -const AdvancedSettings = styled(EuiText)` - color: ${({ theme }) => theme.eui.euiColorPrimary}; -`; - -const ConfigHelper = styled(EuiText)` - margin-left: 4px; -`; - -const Filter = styled(EuiRadioGroup)` - margin-left: 4px; -`; - -const PickEventContainer = styled.div` - .euiSuperSelect { - width: 170px; - max-width: 170px; - button.euiSuperSelectControl { - padding-top: 3px; - } - } -`; - -const getEventTypeOptions = (isCustomDisabled: boolean = true, isDefaultPattern: boolean) => [ - { - id: 'all', - label: ( - - {i18n.ALL_EVENT} - - ), - }, - { - id: 'raw', - label: {i18n.RAW_EVENT}, - disabled: !isDefaultPattern, - }, - { - id: 'alert', - label: {i18n.DETECTION_ALERTS_EVENT}, - disabled: !isDefaultPattern, - }, - { - id: 'custom', - label: <>{i18n.CUSTOM_INDEX_PATTERNS}, - disabled: isCustomDisabled, - }, -]; - -interface PickEventTypeProps { - eventType: TimelineEventsType; - onChangeEventTypeAndIndexesName: ( - value: TimelineEventsType, - indexNames: string[], - dataViewId: string - ) => void; -} - -// AKA TimelineSourcerer -const PickEventTypeComponents: React.FC = ({ - eventType = 'all', - onChangeEventTypeAndIndexesName, -}) => { - const [isPopoverOpen, setPopover] = useState(false); - const [showAdvanceSettings, setAdvanceSettings] = useState(eventType === 'custom'); - const [filterEventType, setFilterEventType] = useState(eventType); - const sourcererScopeSelector = useMemo(() => sourcererSelectors.getSourcererScopeSelector(), []); - const { - defaultDataView, - kibanaDataViews, - signalIndexName, - sourcererScope: { loading, selectedPatterns, selectedDataViewId }, - }: sourcererSelectors.SourcererScopeSelector = useDeepEqualSelector((state) => - sourcererScopeSelector(state, SourcererScopeName.timeline) - ); - - const [dataViewId, setDataViewId] = useState(selectedDataViewId ?? ''); - const { patternList, selectablePatterns } = useMemo(() => { - const theDataView = kibanaDataViews.find((dataView) => dataView.id === dataViewId); - return theDataView != null - ? { - patternList: theDataView.title - .split(',') - // remove duplicates patterns from selector - .filter((pattern, i, self) => self.indexOf(pattern) === i), - selectablePatterns: theDataView.patternList, - } - : { patternList: [], selectablePatterns: [] }; - }, [kibanaDataViews, dataViewId]); - const [selectedOptions, setSelectedOptions] = useState>>( - selectedPatterns.map((indexName) => ({ - label: indexName, - value: indexName, - })) - ); - const isSavingDisabled = useMemo(() => selectedOptions.length === 0, [selectedOptions]); - const selectableOptions = useMemo( - () => - patternList.map((indexName) => ({ - label: indexName, - value: indexName, - 'data-test-subj': 'sourcerer-option', - disabled: !selectablePatterns.includes(indexName), - })), - [selectablePatterns, patternList] - ); - - const onChangeFilter = useCallback( - (filter) => { - setFilterEventType(filter); - if (filter === 'all' || filter === 'kibana') { - setSelectedOptions( - selectablePatterns.map((indexSelected) => ({ - label: indexSelected, - value: indexSelected, - })) - ); - } else if (filter === 'raw') { - setSelectedOptions( - (signalIndexName == null - ? selectablePatterns - : selectablePatterns.filter((index) => index !== signalIndexName) - ).map((indexSelected) => ({ - label: indexSelected, - value: indexSelected, - })) - ); - } else if (filter === 'alert') { - setSelectedOptions([ - { - label: signalIndexName ?? '', - value: signalIndexName ?? '', - }, - ]); - } - }, - [selectablePatterns, signalIndexName] - ); - - const onChangeCombo = useCallback( - (newSelectedOptions: Array>) => { - const localSelectedPatterns = newSelectedOptions - .map((nso) => nso.label) - .sort() - .join(); - if (localSelectedPatterns === selectablePatterns.sort().join()) { - setFilterEventType('all'); - } else if ( - dataViewId === defaultDataView.id && - localSelectedPatterns === - selectablePatterns - .filter((index) => index !== signalIndexName) - .sort() - .join() - ) { - setFilterEventType('raw'); - } else if (dataViewId === defaultDataView.id && localSelectedPatterns === signalIndexName) { - setFilterEventType('alert'); - } else { - setFilterEventType('custom'); - } - - setSelectedOptions(newSelectedOptions); - }, - [defaultDataView.id, dataViewId, selectablePatterns, signalIndexName] - ); - - const onChangeSuper = useCallback( - (newSelectedOption) => { - setFilterEventType('all'); - setDataViewId(newSelectedOption); - setSelectedOptions( - getScopePatternListSelection( - kibanaDataViews.find((dataView) => dataView.id === newSelectedOption), - SourcererScopeName.timeline, - signalIndexName, - newSelectedOption === defaultDataView.id - ).map((indexSelected: string) => ({ - label: indexSelected, - value: indexSelected, - })) - ); - }, - [defaultDataView.id, kibanaDataViews, signalIndexName] - ); - - const togglePopover = useCallback( - () => setPopover((prevIsPopoverOpen) => !prevIsPopoverOpen), - [] - ); - - const closePopover = useCallback(() => setPopover(false), []); - - const handleSaveIndices = useCallback(() => { - onChangeEventTypeAndIndexesName( - filterEventType, - selectedOptions.map((so) => so.label), - dataViewId - ); - setPopover(false); - }, [dataViewId, filterEventType, onChangeEventTypeAndIndexesName, selectedOptions]); - - const resetDataSources = useCallback(() => { - setDataViewId(defaultDataView.id); - setSelectedOptions( - getScopePatternListSelection( - defaultDataView, - SourcererScopeName.timeline, - signalIndexName, - true - ).map((indexSelected: string) => ({ - label: indexSelected, - value: indexSelected, - })) - ); - setFilterEventType(eventType); - }, [defaultDataView, eventType, signalIndexName]); - - const dataViewSelectOptions = useMemo( - () => - kibanaDataViews.map(({ title, id }) => ({ - inputDisplay: - id === defaultDataView.id ? ( - - {SIEM_DATA_VIEW_LABEL} - - ) : ( - - {title} - - ), - value: id, - })), - [defaultDataView.id, kibanaDataViews] - ); - - const filterOptions = useMemo( - () => getEventTypeOptions(filterEventType !== 'custom', dataViewId === defaultDataView.id), - [defaultDataView.id, filterEventType, dataViewId] - ); - - const button = useMemo(() => { - const options = getEventTypeOptions(true, dataViewId === defaultDataView.id); - return ( - - {options.find((opt) => opt.id === eventType)?.label} - - ); - }, [defaultDataView.id, eventType, dataViewId, loading, togglePopover]); - - const tooltipContent = useMemo( - () => (isPopoverOpen ? null : selectedPatterns.sort().join(', ')), - [isPopoverOpen, selectedPatterns] - ); - - const buttonWithTooptip = useMemo(() => { - return tooltipContent ? ( - - {button} - - ) : ( - button - ); - }, [button, tooltipContent]); - - const ButtonContent = useMemo( - () => ( - - {showAdvanceSettings - ? i18n.HIDE_INDEX_PATTERNS_ADVANCED_SETTINGS - : i18n.SHOW_INDEX_PATTERNS_ADVANCED_SETTINGS} - - ), - [showAdvanceSettings] - ); - - useEffect(() => { - const newSelectedOptions = selectedPatterns.map((indexSelected) => ({ - label: indexSelected, - value: indexSelected, - })); - setSelectedOptions((prevSelectedOptions) => { - if (!deepEqual(newSelectedOptions, prevSelectedOptions)) { - return newSelectedOptions; - } - return prevSelectedOptions; - }); - }, [selectedPatterns]); - - useEffect(() => { - setFilterEventType((prevFilter) => (prevFilter !== eventType ? eventType : prevFilter)); - setAdvanceSettings(eventType === 'custom'); - }, [eventType]); - - return ( - - - - - <>{i18n.SELECT_INDEX_PATTERNS} - - - - - - <> - - - - - - - {!showAdvanceSettings && ( - <> - - - {i18n.CONFIGURE_INDEX_PATTERNS} - - - )} - - - - - {i18n.DATA_SOURCES_RESET} - - - - - {i18n.SAVE_INDEX_PATTERNS} - - - - - - - ); -}; - -export const PickEventType = memo(PickEventTypeComponents); diff --git a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx index 3a9b9b0d2693e..e59af74d9a478 100644 --- a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx +++ b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx @@ -46,7 +46,7 @@ export const TimelinesPageComponent: React.FC = () => { {indicesExist ? ( <> - + {capabilitiesCanUserCRUD && ( @@ -93,6 +93,7 @@ export const TimelinesPageComponent: React.FC = () => { ) : ( + )} diff --git a/x-pack/plugins/security_solution/public/ueba/pages/details/index.tsx b/x-pack/plugins/security_solution/public/ueba/pages/details/index.tsx index 72122aba3c4aa..51c06fffb7b63 100644 --- a/x-pack/plugins/security_solution/public/ueba/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/ueba/pages/details/index.tsx @@ -99,7 +99,6 @@ const UebaDetailsComponent: React.FC = ({ detailName, uebaDeta { await ensureCreateEndpointEventFiltersList(kbn); - await bluebird.map( + await pMap( Array.from({ length: flags.count as unknown as number }), () => kbn diff --git a/x-pack/plugins/security_solution/scripts/endpoint/host_isolation_exceptions/index.ts b/x-pack/plugins/security_solution/scripts/endpoint/host_isolation_exceptions/index.ts index 15f0b2f65cb95..779aee0bb1dc1 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/host_isolation_exceptions/index.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/host_isolation_exceptions/index.ts @@ -8,7 +8,7 @@ import { run, RunFn, createFailError } from '@kbn/dev-utils'; import { KbnClient } from '@kbn/test'; import { AxiosError } from 'axios'; -import bluebird from 'bluebird'; +import pMap from 'p-map'; import type { CreateExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; import { ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_DESCRIPTION, @@ -70,7 +70,7 @@ const createHostIsolationException: RunFn = async ({ flags, log }) => { await ensureCreateEndpointHostIsolationExceptionList(kbn); - await bluebird.map( + await pMap( Array.from({ length: flags.count as unknown as number }), () => kbn diff --git a/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts b/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts index d20d29a34754c..97695ec60062c 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts @@ -8,7 +8,7 @@ import minimist from 'minimist'; import { ToolingLog } from '@kbn/dev-utils'; import { KbnClient } from '@kbn/test'; -import bluebird from 'bluebird'; +import pMap from 'p-map'; import { basename } from 'path'; import { AxiosResponse } from 'axios'; import { TRUSTED_APPS_CREATE_API, TRUSTED_APPS_LIST_API } from '../../../common/endpoint/constants'; @@ -113,7 +113,7 @@ export const run: (options?: RunOptions) => Promise = async ({ return () => policyIds[randomN(policyIds.length)]; })(); - return bluebird.map( + return pMap( Array.from({ length: count }), async () => { const body = trustedAppGenerator.generateTrustedAppForCreate(); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts index 80b77722e79b0..db4887f14108e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts @@ -89,8 +89,7 @@ export const updateRulesBulkRoute = ( rulesClient, ruleStatusClient, defaultOutputIndex: siemClient.getSignalsIndex(), - existingRule, - migratedRule, + existingRule: migratedRule, ruleUpdate: payloadRule, isRuleRegistryEnabled, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts index 1aad28d110bd9..d18171c489512 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts @@ -79,8 +79,7 @@ export const updateRulesRoute = ( isRuleRegistryEnabled, rulesClient, ruleStatusClient, - existingRule, - migratedRule, + existingRule: migratedRule, ruleUpdate: request.body, spaceId: context.securitySolution.getSpaceId(), }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts index ed0f0447ad3b0..f847e385e6477 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts @@ -268,7 +268,6 @@ export interface UpdateRulesOptions { rulesClient: RulesClient; defaultOutputIndex: string; existingRule: SanitizedAlert | null | undefined; - migratedRule: SanitizedAlert | null | undefined; ruleUpdate: UpdateRulesSchema; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts index ae16d0435e3dc..eb406cffccbb4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts @@ -16,13 +16,7 @@ import { addTags } from './add_tags'; import { typeSpecificSnakeToCamel } from '../schemas/rule_converters'; import { internalRuleUpdate, RuleParams } from '../schemas/rule_schemas'; import { enableRule } from './enable_rule'; -import { - maybeMute, - transformToAlertThrottle, - transformToNotifyWhen, - updateActions, - updateThrottleNotifyWhen, -} from './utils'; +import { maybeMute, transformToAlertThrottle, transformToNotifyWhen } from './utils'; class UpdateError extends Error { public readonly statusCode: number; @@ -38,7 +32,6 @@ export const updateRules = async ({ ruleStatusClient, defaultOutputIndex, existingRule, - migratedRule, ruleUpdate, }: UpdateRulesOptions): Promise | null> => { if (existingRule == null) { @@ -86,24 +79,9 @@ export const updateRules = async ({ ...typeSpecificParams, }, schedule: { interval: ruleUpdate.interval ?? '5m' }, - actions: updateActions( - transformRuleToAlertAction, - migratedRule?.actions, - existingRule.actions, - ruleUpdate?.actions - ), - throttle: updateThrottleNotifyWhen( - transformToAlertThrottle, - migratedRule?.throttle, - existingRule.throttle, - ruleUpdate?.throttle - ), - notifyWhen: updateThrottleNotifyWhen( - transformToNotifyWhen, - migratedRule?.notifyWhen, - existingRule.notifyWhen, - ruleUpdate?.throttle - ), + actions: ruleUpdate.actions != null ? ruleUpdate.actions.map(transformRuleToAlertAction) : [], + throttle: transformToAlertThrottle(ruleUpdate.throttle), + notifyWhen: transformToNotifyWhen(ruleUpdate.throttle), }; const [validated, errors] = validate(newInternalRule, internalRuleUpdate); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts index c9e00486dc130..ec25b45dd1597 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { pickBy, isEmpty, isEqual } from 'lodash/fp'; +import { pickBy, isEmpty } from 'lodash/fp'; import type { FromOrUndefined, MachineLearningJobIdOrUndefined, @@ -64,14 +64,10 @@ import { RulesClient } from '../../../../../alerting/server'; // eslint-disable-next-line no-restricted-imports import { LegacyRuleActions } from '../rule_actions/legacy_types'; import { FullResponseSchema } from '../../../../common/detection_engine/schemas/request'; -import { - transformAlertToRuleAction, - transformRuleToAlertAction, -} from '../../../../common/detection_engine/transform_actions'; +import { transformAlertToRuleAction } from '../../../../common/detection_engine/transform_actions'; // eslint-disable-next-line no-restricted-imports import { legacyRuleActionsSavedObjectType } from '../rule_actions/legacy_saved_object_mappings'; import { LegacyMigrateParams } from './types'; -import { RuleAlertAction } from '../../../../common/detection_engine/types'; export const calculateInterval = ( interval: string | undefined, @@ -368,35 +364,3 @@ export const legacyMigrate = async ({ } return rule; }; - -export const updateThrottleNotifyWhen = ( - transform: typeof transformToAlertThrottle | typeof transformToNotifyWhen, - migratedRuleThrottle: string | null | undefined, - existingRuleThrottle: string | null | undefined, - ruleUpdateThrottle: string | null | undefined -) => { - if (existingRuleThrottle == null && ruleUpdateThrottle == null && migratedRuleThrottle != null) { - return migratedRuleThrottle; - } - return transform(ruleUpdateThrottle); -}; - -export const updateActions = ( - transform: typeof transformRuleToAlertAction, - migratedRuleActions: AlertAction[] | null | undefined, - existingRuleActions: AlertAction[] | null | undefined, - ruleUpdateActions: RuleAlertAction[] | null | undefined -) => { - // if the existing rule actions and the rule update actions are equivalent (aka no change) - // but the migrated actions and the ruleUpdateActions (or existing rule actions, associatively) - // are not equivalent, we know that the rules' actions were migrated and we need to apply - // that change to the update request so it is not overwritten by the rule update payload - if ( - existingRuleActions?.length === 0 && - ruleUpdateActions == null && - !isEqual(existingRuleActions, migratedRuleActions) - ) { - return migratedRuleActions; - } - return ruleUpdateActions != null ? ruleUpdateActions.map(transform) : []; -}; diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts index 71930efe12953..1e16fa2a40129 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts @@ -7,12 +7,18 @@ import { act } from 'react-dom/test-utils'; -import { registerTestBed, findTestSubject, TestBed, TestBedConfig, delay } from '@kbn/test/jest'; +import { + registerTestBed, + findTestSubject, + TestBed, + AsyncTestBedConfig, + delay, +} from '@kbn/test/jest'; import { SnapshotRestoreHome } from '../../../public/application/sections/home/home'; import { BASE_PATH } from '../../../public/application/constants'; import { WithAppDependencies } from './setup_environment'; -const testBedConfig: TestBedConfig = { +const testBedConfig: AsyncTestBedConfig = { memoryRouter: { initialEntries: [`${BASE_PATH}/repositories`], componentRoutePath: `${BASE_PATH}/:section(repositories|snapshots)/:repositoryName?/:snapshotId*`, diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_add.helpers.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_add.helpers.ts index b3eeaeedbbe52..a6e7c4a4c1056 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_add.helpers.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_add.helpers.ts @@ -5,12 +5,12 @@ * 2.0. */ -import { registerTestBed, TestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, AsyncTestBedConfig } from '@kbn/test/jest'; import { PolicyAdd } from '../../../public/application/sections/policy_add'; import { formSetup, PolicyFormTestSubjects } from './policy_form.helpers'; import { WithAppDependencies } from './setup_environment'; -const testBedConfig: TestBedConfig = { +const testBedConfig: AsyncTestBedConfig = { memoryRouter: { initialEntries: ['/add_policy'], componentRoutePath: '/add_policy', diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_edit.helpers.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_edit.helpers.ts index 71b500ac73263..2014d22ffbfbc 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_edit.helpers.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_edit.helpers.ts @@ -5,13 +5,13 @@ * 2.0. */ -import { registerTestBed, TestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, AsyncTestBedConfig } from '@kbn/test/jest'; import { PolicyEdit } from '../../../public/application/sections/policy_edit'; import { WithAppDependencies } from './setup_environment'; import { POLICY_NAME } from './constant'; import { formSetup, PolicyFormTestSubjects } from './policy_form.helpers'; -const testBedConfig: TestBedConfig = { +const testBedConfig: AsyncTestBedConfig = { memoryRouter: { initialEntries: [`/edit_policy/${POLICY_NAME}`], componentRoutePath: '/edit_policy/:name', diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_edit.helpers.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_edit.helpers.ts index 9a6f0d9a76bd1..f0563f2831a98 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_edit.helpers.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_edit.helpers.ts @@ -5,12 +5,12 @@ * 2.0. */ -import { registerTestBed, TestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, AsyncTestBedConfig } from '@kbn/test/jest'; import { RepositoryEdit } from '../../../public/application/sections/repository_edit'; import { WithAppDependencies } from './setup_environment'; import { REPOSITORY_NAME } from './constant'; -const testBedConfig: TestBedConfig = { +const testBedConfig: AsyncTestBedConfig = { memoryRouter: { initialEntries: [`/${REPOSITORY_NAME}`], componentRoutePath: '/:name', diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/restore_snapshot.helpers.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/restore_snapshot.helpers.ts index 9b82c1d5b6364..123ae0cbb1c2e 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/restore_snapshot.helpers.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/restore_snapshot.helpers.ts @@ -6,11 +6,11 @@ */ import { act } from 'react-dom/test-utils'; -import { registerTestBed, TestBed, TestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test/jest'; import { RestoreSnapshot } from '../../../public/application/sections/restore_snapshot'; import { WithAppDependencies } from './setup_environment'; -const testBedConfig: TestBedConfig = { +const testBedConfig: AsyncTestBedConfig = { memoryRouter: { initialEntries: ['/add_policy'], componentRoutePath: '/add_policy', diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx index 0b6df121cb166..aefff7fb3c76a 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx @@ -77,7 +77,7 @@ describe('spacesManagementApp', () => { const { setBreadcrumbs, container, unmount, docTitle } = await mountApp('/', '/'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Spaces' }]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ text: 'Spaces' }]); expect(docTitle.change).toHaveBeenCalledWith('Spaces'); expect(docTitle.reset).not.toHaveBeenCalled(); expect(container).toMatchInlineSnapshot(` @@ -102,7 +102,7 @@ describe('spacesManagementApp', () => { expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ { href: `/`, text: 'Spaces' }, - { href: '/create', text: 'Create' }, + { text: 'Create' }, ]); expect(docTitle.change).toHaveBeenCalledWith('Spaces'); expect(docTitle.reset).not.toHaveBeenCalled(); @@ -134,7 +134,7 @@ describe('spacesManagementApp', () => { expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ { href: `/`, text: 'Spaces' }, - { href: `/edit/${spaceId}`, text: `space with id some-space` }, + { text: `space with id some-space` }, ]); expect(docTitle.change).toHaveBeenCalledWith('Spaces'); expect(docTitle.reset).not.toHaveBeenCalled(); diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx index 5d44a1cd78635..8ea947a33037d 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx @@ -43,18 +43,16 @@ export const spacesManagementApp = Object.freeze({ const [[coreStart, { features }], { SpacesGridPage }, { ManageSpacePage }] = await Promise.all([getStartServices(), import('./spaces_grid'), import('./edit_space')]); - const spacesBreadcrumbs = [ - { - text: title, - href: `/`, - }, - ]; + const spacesFirstBreadcrumb = { + text: title, + href: `/`, + }; const { notifications, i18n: i18nStart, application, chrome } = coreStart; chrome.docTitle.change(title); const SpacesGridPageWithBreadcrumbs = () => { - setBreadcrumbs(spacesBreadcrumbs); + setBreadcrumbs([{ ...spacesFirstBreadcrumb, href: undefined }]); return ( { setBreadcrumbs([ - ...spacesBreadcrumbs, + spacesFirstBreadcrumb, { text: i18n.translate('xpack.spaces.management.createSpaceBreadcrumb', { defaultMessage: 'Create', }), - href: '/create', }, ]); @@ -94,10 +91,9 @@ export const spacesManagementApp = Object.freeze({ const onLoadSpace = (space: Space) => { setBreadcrumbs([ - ...spacesBreadcrumbs, + spacesFirstBreadcrumb, { text: space.name, - href: `/edit/${encodeURIComponent(space.id)}`, }, ]); }; diff --git a/x-pack/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap b/x-pack/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap index e02e81e497806..674f5e8b37ca2 100644 --- a/x-pack/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap +++ b/x-pack/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap @@ -9,6 +9,7 @@ exports[`NavControlPopover renders without crashing 1`] = ` aria-expanded={false} aria-haspopup="true" aria-label="loading" + data-test-subj="spacesNavSelector" onClick={[Function]} title="loading" > @@ -18,7 +19,6 @@ exports[`NavControlPopover renders without crashing 1`] = ` } closePopover={[Function]} - data-test-subj="spacesNavSelector" display="inlineBlock" hasArrow={true} id="spcMenuPopover" diff --git a/x-pack/plugins/spaces/public/nav_control/components/spaces_menu.scss b/x-pack/plugins/spaces/public/nav_control/components/spaces_menu.scss index 1f272260fc9c2..f444b45192f8b 100644 --- a/x-pack/plugins/spaces/public/nav_control/components/spaces_menu.scss +++ b/x-pack/plugins/spaces/public/nav_control/components/spaces_menu.scss @@ -3,8 +3,8 @@ } .spcMenu__spacesList { + @include euiYScrollWithShadows; max-height: $euiSizeXL * 10; - overflow-y: auto; } .spcMenu__searchFieldWrapper { @@ -18,4 +18,4 @@ .spcMenu__item { margin-left: $euiSizeM; -} \ No newline at end of file +} diff --git a/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx index 41a05a38fa305..d4e7ffe510c8f 100644 --- a/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx +++ b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx @@ -98,14 +98,13 @@ export class NavControlPopover extends Component { return ( {element} @@ -154,6 +153,7 @@ export class NavControlPopover extends Component { aria-expanded={this.state.showSpaceSelector} aria-haspopup="true" aria-label={linkTitle} + data-test-subj="spacesNavSelector" title={linkTitle} onClick={this.toggleSpaceSelector} > diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index 9a43cd5b8c1d6..4e1db9c274b5e 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -159,7 +159,7 @@ export class TaskManagerPlugin const taskStore = new TaskStore({ serializer, savedObjectsRepository, - esClient: elasticsearch.createClient('taskManager').asInternalUser, + esClient: elasticsearch.client.asInternalUser, index: TASK_MANAGER_INDEX, definitions: this.definitions, taskManagerId: `kibana:${this.taskManagerId!}`, diff --git a/x-pack/plugins/timelines/public/mock/test_providers.tsx b/x-pack/plugins/timelines/public/mock/test_providers.tsx index 9fa6177cccee1..0fb1afec43627 100644 --- a/x-pack/plugins/timelines/public/mock/test_providers.tsx +++ b/x-pack/plugins/timelines/public/mock/test_providers.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; +import { euiDarkVars } from '@kbn/ui-shared-deps-src/theme'; import { I18nProvider } from '@kbn/i18n/react'; import React from 'react'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_messages_pane.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_messages_pane.tsx index fc95b818126bc..3f8b0549c219b 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_messages_pane.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_messages_pane.tsx @@ -10,7 +10,7 @@ import React, { useState } from 'react'; import { EuiSpacer, EuiBasicTable } from '@elastic/eui'; // @ts-ignore import { formatDate } from '@elastic/eui/lib/services/format'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars as theme } from '@kbn/ui-shared-deps-src/theme'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 7281a2a70fc6a..f728ec13847f9 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -5939,9 +5939,6 @@ "xpack.alerting.ruleTypeRegistry.register.customRecoveryActionGroupUsageError": "ルールタイプ [id=\"{id}\"] を登録できません。アクショングループ[{actionGroup}] は、復元とアクティブなアクショングループの両方として使用できません。", "xpack.alerting.ruleTypeRegistry.register.duplicateAlertTypeError": "ルールタイプ\"{id}\"はすでに登録されています。", "xpack.alerting.savedObjects.goToRulesButtonText": "ルールに移動", - "xpack.alerting.server.healthStatus.available": "アラートフレームワークを使用できます", - "xpack.alerting.server.healthStatus.degraded": "アラートフレームワークは劣化しました", - "xpack.alerting.server.healthStatus.unavailable": "アラートフレームワークを使用できません", "xpack.alerting.serverSideErrors.expirerdLicenseErrorMessage": "{licenseType} ライセンスの期限が切れたのでアラートタイプ {alertTypeId} は無効です。", "xpack.alerting.serverSideErrors.invalidLicenseErrorMessage": "アラート{alertTypeId}は無効です。{licenseType}ライセンスが必要です。アップグレードオプションを表示するには、[ライセンス管理]に移動してください。", "xpack.alerting.serverSideErrors.unavailableLicenseErrorMessage": "現時点でライセンス情報を入手できないため、アラートタイプ {alertTypeId} は無効です。", @@ -6619,7 +6616,6 @@ "xpack.apm.settings.schema.confirm.apmServerSettingsCloudLinkText": "クラウドでAPMサーバー設定に移動", "xpack.apm.settings.schema.confirm.cancelText": "キャンセル", "xpack.apm.settings.schema.confirm.checkboxLabel": "データストリームに切り替えることを確認する", - "xpack.apm.settings.schema.confirm.descriptionText": "現在、スタック監視はFleetで管理されたAPMではサポートされていません。", "xpack.apm.settings.schema.confirm.irreversibleWarning.message": "移行中には一時的にAPMデータ収集に影響する可能性があります。移行プロセスは数分で完了します。", "xpack.apm.settings.schema.confirm.irreversibleWarning.title": "データストリームへの切り替えは元に戻せません。", "xpack.apm.settings.schema.confirm.switchButtonText": "データストリームに切り替える", @@ -6996,7 +6992,6 @@ "xpack.observability.cases.createCase.fieldTagsHelpText": "このケースの1つ以上のカスタム識別タグを入力します。新しいタグを開始するには、各タグの後でEnterを押します。", "xpack.observability.cases.createCase.titleFieldRequiredError": "タイトルが必要です。", "xpack.observability.cases.dismissErrorsPushServiceCallOutTitle": "閉じる", - "xpack.observability.cases.pageTitle": "ケース", "xpack.observability.casesLinkTitle": "ケース", "xpack.observability.emptySection.apps.alert.description": "503 エラーが累積していますか?サービスは応答していますか?CPUとRAMの使用量が跳ね上がっていますか?このような警告を、事後にではなく、発生と同時に把握しましょう。", "xpack.observability.emptySection.apps.alert.link": "ルールを作成", @@ -22076,13 +22071,10 @@ "xpack.securitySolution.hostsTable.osTitle": "オペレーティングシステム", "xpack.securitySolution.hostsTable.versionTitle": "バージョン", "xpack.securitySolution.hoverActions.showTopTooltip": "上位の{fieldName}を表示", - "xpack.securitySolution.indexPatterns.dataSourcesLabel": "データソース", "xpack.securitySolution.indexPatterns.disabled": "このページでは無効なインデックスパターンが推奨されますが、最初にKibanaインデックスパターン設定で構成する必要があります。", - "xpack.securitySolution.indexPatterns.help": "データソース選択", "xpack.securitySolution.indexPatterns.pickIndexPatternsCombo": "インデックスパターンを選択", "xpack.securitySolution.indexPatterns.resetButton": "リセット", "xpack.securitySolution.indexPatterns.save": "保存", - "xpack.securitySolution.indexPatterns.selectionLabel": "このページでデータソースを選択", "xpack.securitySolution.insert.timeline.insertTimelineButton": "タイムラインリンクの挿入", "xpack.securitySolution.inspect.modal.closeTitle": "閉じる", "xpack.securitySolution.inspect.modal.indexPatternDescription": "Elasticsearchインデックスに接続したインデックスパターンです。これらのインデックスは Kibana > 高度な設定で構成できます。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index b6b2744160423..33927d4ffbb0b 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -5987,9 +5987,6 @@ "xpack.alerting.ruleTypeRegistry.register.reservedActionGroupUsageError": "无法注册规则类型 [id=\"{id}\"]。操作组 [{actionGroups}] 由框架保留。", "xpack.alerting.savedObjects.goToRulesButtonText": "前往规则", "xpack.alerting.savedObjects.onImportText": "导入后必须启用 {rulesSavedObjectsLength} 个{rulesSavedObjectsLength, plural,other {规则}}。", - "xpack.alerting.server.healthStatus.available": "告警框架可用", - "xpack.alerting.server.healthStatus.degraded": "告警框架已降级", - "xpack.alerting.server.healthStatus.unavailable": "告警框架不可用", "xpack.alerting.serverSideErrors.expirerdLicenseErrorMessage": "告警类型 {alertTypeId} 已禁用,因为您的{licenseType}许可证已过期。", "xpack.alerting.serverSideErrors.invalidLicenseErrorMessage": "告警 {alertTypeId} 已禁用,因为它需要{licenseType}许可证。前往“许可证管理”以查看升级选项。", "xpack.alerting.serverSideErrors.unavailableLicenseErrorMessage": "告警类型 {alertTypeId} 已禁用,因为许可证信息当前不可用。", @@ -6672,7 +6669,6 @@ "xpack.apm.settings.schema.confirm.apmServerSettingsCloudLinkText": "前往 Cloud 中的 APM Server 设置", "xpack.apm.settings.schema.confirm.cancelText": "取消", "xpack.apm.settings.schema.confirm.checkboxLabel": "我确认我想切换到数据流", - "xpack.apm.settings.schema.confirm.descriptionText": "请注意 Fleet 托管的 APM 当前不支持堆栈监测。", "xpack.apm.settings.schema.confirm.irreversibleWarning.message": "迁移在进行中时,可能会暂时影响 APM 数据收集。迁移的过程应只需花费几分钟。", "xpack.apm.settings.schema.confirm.irreversibleWarning.title": "切换到数据流是不可逆的操作", "xpack.apm.settings.schema.confirm.switchButtonText": "切换到数据流", @@ -7054,7 +7050,6 @@ "xpack.observability.cases.createCase.fieldTagsHelpText": "为此案例键入一个或多个定制识别标签。在每个标签后按 Enter 键可开始新的标签。", "xpack.observability.cases.createCase.titleFieldRequiredError": "标题必填。", "xpack.observability.cases.dismissErrorsPushServiceCallOutTitle": "关闭", - "xpack.observability.cases.pageTitle": "案例", "xpack.observability.casesLinkTitle": "案例", "xpack.observability.emptySection.apps.alert.description": "503 错误是否越来越多?服务是否响应?CPU 和 RAM 利用率是否激增?实时查看警告,而不是事后再进行剖析。", "xpack.observability.emptySection.apps.alert.link": "创建规则", @@ -22426,13 +22421,10 @@ "xpack.securitySolution.hostsTable.unit": "{totalCount, plural, other {个主机}}", "xpack.securitySolution.hostsTable.versionTitle": "版本", "xpack.securitySolution.hoverActions.showTopTooltip": "显示排名靠前的{fieldName}", - "xpack.securitySolution.indexPatterns.dataSourcesLabel": "数据源", "xpack.securitySolution.indexPatterns.disabled": "在此页面上建议使用已禁用的索引模式,但是首先需要在 Kibana 索引模式设置中配置这些模式", - "xpack.securitySolution.indexPatterns.help": "数据源的选择", "xpack.securitySolution.indexPatterns.pickIndexPatternsCombo": "选取索引模式", "xpack.securitySolution.indexPatterns.resetButton": "重置", "xpack.securitySolution.indexPatterns.save": "保存", - "xpack.securitySolution.indexPatterns.selectionLabel": "在此页面上选择数据源", "xpack.securitySolution.insert.timeline.insertTimelineButton": "插入时间线链接", "xpack.securitySolution.inspect.modal.closeTitle": "关闭", "xpack.securitySolution.inspect.modal.indexPatternDescription": "连接到 Elasticsearch 索引的索引模式。可以在“Kibana”>“高级设置”中配置这些索引。", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/center_justified_spinner.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/center_justified_spinner.tsx index e9473cbff7488..e4157b1afe0e4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/center_justified_spinner.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/center_justified_spinner.tsx @@ -15,7 +15,7 @@ interface Props { } export const CenterJustifiedSpinner: React.FunctionComponent = ({ size }) => ( - + diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx index 162f41605e91e..27623654245eb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx @@ -111,6 +111,8 @@ export const AlertsList: React.FunctionComponent = () => { } = useKibana().services; const canExecuteActions = hasExecuteActionsCapability(capabilities); + const [initialLoad, setInitialLoad] = useState(true); + const [noData, setNoData] = useState(true); const [actionTypes, setActionTypes] = useState([]); const [selectedIds, setSelectedIds] = useState([]); const [isPerformingAction, setIsPerformingAction] = useState(false); @@ -220,7 +222,8 @@ export const AlertsList: React.FunctionComponent = () => { }, []); async function loadAlertsData() { - const hasAnyAuthorizedAlertType = alertTypesState.data.size > 0; + const hasAnyAuthorizedAlertType = + alertTypesState.isInitialized && alertTypesState.data.size > 0; if (hasAnyAuthorizedAlertType) { setAlertsState({ ...alertsState, isLoading: true }); try { @@ -243,6 +246,15 @@ export const AlertsList: React.FunctionComponent = () => { if (!alertsResponse.data?.length && page.index > 0) { setPage({ ...page, index: 0 }); } + + const isFilterApplied = !( + isEmpty(searchText) && + isEmpty(typesFilter) && + isEmpty(actionTypesFilter) && + isEmpty(alertStatusesFilter) + ); + + setNoData(alertsResponse.data.length === 0 && !isFilterApplied); } catch (e) { toasts.addDanger({ title: i18n.translate( @@ -254,6 +266,7 @@ export const AlertsList: React.FunctionComponent = () => { }); setAlertsState({ ...alertsState, isLoading: false }); } + setInitialLoad(false); } } @@ -946,18 +959,22 @@ export const AlertsList: React.FunctionComponent = () => { ); - const loadedItems = convertAlertsToTableItems( - alertsState.data, - alertTypesState.data, - canExecuteActions - ); + // if initial load, show spinner + const getRulesList = () => { + if (noData && !alertsState.isLoading && !alertTypesState.isLoading) { + return authorizedToCreateAnyAlerts ? ( + setAlertFlyoutVisibility(true)} /> + ) : ( + noPermissionPrompt + ); + } - const isFilterApplied = !( - isEmpty(searchText) && - isEmpty(typesFilter) && - isEmpty(actionTypesFilter) && - isEmpty(alertStatusesFilter) - ); + if (initialLoad) { + return ; + } + + return table; + }; return (
@@ -988,15 +1005,7 @@ export const AlertsList: React.FunctionComponent = () => { }} /> - {loadedItems.length || isFilterApplied ? ( - table - ) : alertTypesState.isLoading || alertsState.isLoading ? ( - - ) : authorizedToCreateAnyAlerts ? ( - setAlertFlyoutVisibility(true)} /> - ) : ( - noPermissionPrompt - )} + {getRulesList()} {alertFlyoutVisible && ( { const { getByLabelText, getByText } = render(); const analyzeAnchor = getByLabelText( - 'Navigate to the "Analyze Data" view to visualize Synthetics/User data' + 'Navigate to the "Explore Data" view to visualize Synthetics/User data' ); expect(analyzeAnchor.getAttribute('href')).toContain('/app/observability/exploratory-view'); - expect(getByText('Analyze data')); + expect(getByText('Explore data')); }); it('renders Add Data link', () => { diff --git a/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx b/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx index 26f9e28101ea4..7b510432f773b 100644 --- a/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx +++ b/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx @@ -26,12 +26,12 @@ const ADD_DATA_LABEL = i18n.translate('xpack.uptime.addDataButtonLabel', { }); const ANALYZE_DATA = i18n.translate('xpack.uptime.analyzeDataButtonLabel', { - defaultMessage: 'Analyze data', + defaultMessage: 'Explore data', }); const ANALYZE_MESSAGE = i18n.translate('xpack.uptime.analyzeDataButtonLabel.message', { defaultMessage: - 'EXPERIMENTAL - Analyze Data allows you to select and filter result data in any dimension and look for the cause or impact of performance problems.', + 'EXPERIMENTAL - Explore Data allows you to select and filter result data in any dimension and look for the cause or impact of performance problems.', }); export function ActionMenuContent(): React.ReactElement { @@ -87,7 +87,7 @@ export function ActionMenuContent(): React.ReactElement { {ANALYZE_MESSAGE}

}> = ({ } }, [hasIntersected, isIntersecting, setHasIntersected]); - const imgSrc = basePath + `/api/uptime/journey/screenshot/${checkGroup}/${stepIndex}`; + const imgSrc = basePath + `/internal/uptime/journey/screenshot/${checkGroup}/${stepIndex}`; // When loading a legacy screenshot, set `url` to full-size screenshot path. // Otherwise, we first need to composite the image. diff --git a/x-pack/plugins/uptime/public/contexts/uptime_theme_context.tsx b/x-pack/plugins/uptime/public/contexts/uptime_theme_context.tsx index 7798aabc879a6..6df3879ef7407 100644 --- a/x-pack/plugins/uptime/public/contexts/uptime_theme_context.tsx +++ b/x-pack/plugins/uptime/public/contexts/uptime_theme_context.tsx @@ -5,9 +5,8 @@ * 2.0. */ -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { euiLightVars, euiDarkVars } from '@kbn/ui-shared-deps-src/theme'; import React, { createContext, useMemo } from 'react'; -import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme'; import { DARK_THEME, LIGHT_THEME, PartialTheme, Theme } from '@elastic/charts'; import { UptimeAppColors } from '../apps/uptime_app'; diff --git a/x-pack/plugins/uptime/public/state/api/dynamic_settings.ts b/x-pack/plugins/uptime/public/state/api/dynamic_settings.ts index 3aa300e69e3fe..a7bacfbba3462 100644 --- a/x-pack/plugins/uptime/public/state/api/dynamic_settings.ts +++ b/x-pack/plugins/uptime/public/state/api/dynamic_settings.ts @@ -12,8 +12,9 @@ import { DynamicSettingsSaveType, } from '../../../common/runtime_types'; import { apiService } from './utils'; +import { API_URLS } from '../../../common/constants'; -const apiPath = '/api/uptime/dynamic_settings'; +const apiPath = API_URLS.DYNAMIC_SETTINGS; interface SaveApiRequest { settings: DynamicSettings; diff --git a/x-pack/plugins/uptime/public/state/api/journey.ts b/x-pack/plugins/uptime/public/state/api/journey.ts index 05d4a9e356919..258d00c5d278e 100644 --- a/x-pack/plugins/uptime/public/state/api/journey.ts +++ b/x-pack/plugins/uptime/public/state/api/journey.ts @@ -18,9 +18,10 @@ import { SyntheticsJourneyApiResponse, SyntheticsJourneyApiResponseType, } from '../../../common/runtime_types/ping/synthetics'; +import { API_URLS } from '../../../common/constants'; export async function fetchScreenshotBlockSet(params: string[]): Promise { - return apiService.post('/api/uptime/journey/screenshot/block', { + return apiService.post(API_URLS.JOURNEY_SCREENSHOT_BLOCKS, { hashes: params, }); } @@ -29,7 +30,7 @@ export async function fetchJourneySteps( params: FetchJourneyStepsParams ): Promise { return apiService.get( - `/api/uptime/journey/${params.checkGroup}`, + `/internal/uptime/journey/${params.checkGroup}`, { syntheticEventTypes: params.syntheticEventTypes }, SyntheticsJourneyApiResponseType ); @@ -40,11 +41,7 @@ export async function fetchJourneysFailedSteps({ }: { checkGroups: string[]; }): Promise { - return apiService.get( - `/api/uptime/journeys/failed_steps`, - { checkGroups }, - FailedStepsApiResponseType - ); + return apiService.get(API_URLS.JOURNEY_FAILED_STEPS, { checkGroups }, FailedStepsApiResponseType); } export async function fetchLastSuccessfulStep({ @@ -59,7 +56,7 @@ export async function fetchLastSuccessfulStep({ location?: string; }): Promise { return await apiService.get( - `/api/uptime/synthetics/step/success/`, + API_URLS.SYNTHETICS_SUCCESSFUL_STEP, { monitorId, timestamp, diff --git a/x-pack/plugins/uptime/public/state/api/network_events.ts b/x-pack/plugins/uptime/public/state/api/network_events.ts index ea3bb6e5183e4..d9b3d518444a3 100644 --- a/x-pack/plugins/uptime/public/state/api/network_events.ts +++ b/x-pack/plugins/uptime/public/state/api/network_events.ts @@ -11,12 +11,13 @@ import { SyntheticsNetworkEventsApiResponse, SyntheticsNetworkEventsApiResponseType, } from '../../../common/runtime_types'; +import { API_URLS } from '../../../common/constants'; export async function fetchNetworkEvents( params: FetchNetworkEventsParams ): Promise { return (await apiService.get( - `/api/uptime/network_events`, + API_URLS.NETWORK_EVENTS, { checkGroup: params.checkGroup, stepIndex: params.stepIndex, diff --git a/x-pack/plugins/uptime/public/state/api/snapshot.test.ts b/x-pack/plugins/uptime/public/state/api/snapshot.test.ts index 38be97d74844f..01177eceb4720 100644 --- a/x-pack/plugins/uptime/public/state/api/snapshot.test.ts +++ b/x-pack/plugins/uptime/public/state/api/snapshot.test.ts @@ -8,6 +8,7 @@ import { HttpFetchError } from 'src/core/public'; import { fetchSnapshotCount } from './snapshot'; import { apiService } from './utils'; +import { API_URLS } from '../../../common/constants'; describe('snapshot API', () => { let fetchMock: jest.SpyInstance>; @@ -36,7 +37,7 @@ describe('snapshot API', () => { }); expect(fetchMock).toHaveBeenCalledWith({ asResponse: false, - path: '/api/uptime/snapshot/count', + path: API_URLS.SNAPSHOT_COUNT, query: { dateRangeEnd: 'now', dateRangeStart: 'now-15m', diff --git a/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts b/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts index 637ac72e12ee5..5d9947e23cf44 100644 --- a/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts +++ b/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts @@ -16,10 +16,11 @@ import { VALUE_MUST_BE_GREATER_THAN_ZERO, VALUE_MUST_BE_AN_INTEGER, } from '../../common/translations'; +import { API_URLS } from '../../common/constants'; export const createGetDynamicSettingsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ method: 'GET', - path: '/api/uptime/dynamic_settings', + path: API_URLS.DYNAMIC_SETTINGS, validate: false, handler: async ({ savedObjectsClient }): Promise => { return savedObjectsAdapter.getUptimeDynamicSettings(savedObjectsClient); @@ -47,7 +48,7 @@ export const validateCertsValues = ( export const createPostDynamicSettingsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ method: 'POST', - path: '/api/uptime/dynamic_settings', + path: API_URLS.DYNAMIC_SETTINGS, validate: { body: schema.object({ heartbeatIndices: schema.string(), diff --git a/x-pack/plugins/uptime/server/rest_api/network_events/get_network_events.ts b/x-pack/plugins/uptime/server/rest_api/network_events/get_network_events.ts index 21ca946e574d1..0afe21413d4fb 100644 --- a/x-pack/plugins/uptime/server/rest_api/network_events/get_network_events.ts +++ b/x-pack/plugins/uptime/server/rest_api/network_events/get_network_events.ts @@ -8,10 +8,11 @@ import { schema } from '@kbn/config-schema'; import { UMServerLibs } from '../../lib/lib'; import { UMRestApiRouteFactory } from '../types'; +import { API_URLS } from '../../../common/constants'; export const createNetworkEventsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ method: 'GET', - path: '/api/uptime/network_events', + path: API_URLS.NETWORK_EVENTS, validate: { query: schema.object({ checkGroup: schema.string(), diff --git a/x-pack/plugins/uptime/server/rest_api/pings/journey_screenshot_blocks.ts b/x-pack/plugins/uptime/server/rest_api/pings/journey_screenshot_blocks.ts index 4b06a13d29f4e..95c30b5f73689 100644 --- a/x-pack/plugins/uptime/server/rest_api/pings/journey_screenshot_blocks.ts +++ b/x-pack/plugins/uptime/server/rest_api/pings/journey_screenshot_blocks.ts @@ -10,6 +10,7 @@ import { isRight } from 'fp-ts/lib/Either'; import { schema } from '@kbn/config-schema'; import { UMServerLibs } from '../../lib/lib'; import { UMRestApiRouteFactory } from '../types'; +import { API_URLS } from '../../../common/constants'; function isStringArray(data: unknown): data is string[] { return isRight(t.array(t.string).decode(data)); @@ -17,7 +18,7 @@ function isStringArray(data: unknown): data is string[] { export const createJourneyScreenshotBlocksRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ method: 'POST', - path: '/api/uptime/journey/screenshot/block', + path: API_URLS.JOURNEY_SCREENSHOT_BLOCKS, validate: { body: schema.object({ hashes: schema.arrayOf(schema.string()), diff --git a/x-pack/plugins/uptime/server/rest_api/pings/journey_screenshots.ts b/x-pack/plugins/uptime/server/rest_api/pings/journey_screenshots.ts index 3e71051816d30..146460295f444 100644 --- a/x-pack/plugins/uptime/server/rest_api/pings/journey_screenshots.ts +++ b/x-pack/plugins/uptime/server/rest_api/pings/journey_screenshots.ts @@ -10,6 +10,7 @@ import { isRefResult, isFullScreenshot } from '../../../common/runtime_types/pin import { UMServerLibs } from '../../lib/lib'; import { ScreenshotReturnTypesUnion } from '../../lib/requests/get_journey_screenshot'; import { UMRestApiRouteFactory } from '../types'; +import { API_URLS } from '../../../common/constants'; function getSharedHeaders(stepName: string, totalSteps: number) { return { @@ -21,7 +22,7 @@ function getSharedHeaders(stepName: string, totalSteps: number) { export const createJourneyScreenshotRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ method: 'GET', - path: '/api/uptime/journey/screenshot/{checkGroup}/{stepIndex}', + path: API_URLS.JOURNEY_SCREENSHOT, validate: { params: schema.object({ checkGroup: schema.string(), diff --git a/x-pack/plugins/uptime/server/rest_api/pings/journeys.ts b/x-pack/plugins/uptime/server/rest_api/pings/journeys.ts index 7c3dcdfbe845c..bfc5a52e3e01f 100644 --- a/x-pack/plugins/uptime/server/rest_api/pings/journeys.ts +++ b/x-pack/plugins/uptime/server/rest_api/pings/journeys.ts @@ -8,10 +8,11 @@ import { schema } from '@kbn/config-schema'; import { UMServerLibs } from '../../lib/lib'; import { UMRestApiRouteFactory } from '../types'; +import { API_URLS } from '../../../common/constants'; export const createJourneyRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ method: 'GET', - path: '/api/uptime/journey/{checkGroup}', + path: API_URLS.JOURNEY_CREATE, validate: { params: schema.object({ checkGroup: schema.string(), @@ -54,7 +55,7 @@ export const createJourneyRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => export const createJourneyFailedStepsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ method: 'GET', - path: '/api/uptime/journeys/failed_steps', + path: API_URLS.JOURNEY_FAILED_STEPS, validate: { query: schema.object({ checkGroups: schema.arrayOf(schema.string()), diff --git a/x-pack/plugins/uptime/server/rest_api/synthetics/last_successful_step.ts b/x-pack/plugins/uptime/server/rest_api/synthetics/last_successful_step.ts index 81539459172cc..99695bbce09a0 100644 --- a/x-pack/plugins/uptime/server/rest_api/synthetics/last_successful_step.ts +++ b/x-pack/plugins/uptime/server/rest_api/synthetics/last_successful_step.ts @@ -13,10 +13,11 @@ import { } from '../../../common/runtime_types/ping/synthetics'; import { UMServerLibs } from '../../lib/lib'; import { UMRestApiRouteFactory } from '../types'; +import { API_URLS } from '../../../common/constants'; export const createLastSuccessfulStepRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ method: 'GET', - path: '/api/uptime/synthetics/step/success/', + path: API_URLS.SYNTHETICS_SUCCESSFUL_STEP, validate: { query: schema.object({ monitorId: schema.string(), diff --git a/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_create_json.helpers.ts b/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_create_json.helpers.ts index 696a266ca4a3e..a276b717bc544 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_create_json.helpers.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_create_json.helpers.ts @@ -5,13 +5,13 @@ * 2.0. */ -import { registerTestBed, TestBed, TestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test/jest'; import { WatchEdit } from '../../../public/application/sections/watch_edit/components/watch_edit'; import { registerRouter } from '../../../public/application/lib/navigation'; import { ROUTES, WATCH_TYPES } from '../../../common/constants'; import { withAppContext } from './app_context.mock'; -const testBedConfig: TestBedConfig = { +const testBedConfig: AsyncTestBedConfig = { memoryRouter: { onRouter: (router) => registerRouter(router), initialEntries: [`${ROUTES.API_ROOT}/watches/new-watch/${WATCH_TYPES.JSON}`], diff --git a/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts b/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts index caddf1df93d40..320f88eef2651 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts @@ -5,13 +5,13 @@ * 2.0. */ -import { registerTestBed, TestBed, TestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test/jest'; import { WatchEdit } from '../../../public/application/sections/watch_edit/components/watch_edit'; import { registerRouter } from '../../../public/application/lib/navigation'; import { ROUTES, WATCH_TYPES } from '../../../common/constants'; import { withAppContext } from './app_context.mock'; -const testBedConfig: TestBedConfig = { +const testBedConfig: AsyncTestBedConfig = { memoryRouter: { onRouter: (router) => registerRouter(router), initialEntries: [`${ROUTES.API_ROOT}/watches/new-watch/${WATCH_TYPES.THRESHOLD}`], diff --git a/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_edit.helpers.ts b/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_edit.helpers.ts index 957755c9e5361..15489fa0a864d 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_edit.helpers.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_edit.helpers.ts @@ -5,14 +5,14 @@ * 2.0. */ -import { registerTestBed, TestBed, TestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test/jest'; import { WatchEdit } from '../../../public/application/sections/watch_edit/components/watch_edit'; import { registerRouter } from '../../../public/application/lib/navigation'; import { ROUTES } from '../../../common/constants'; import { WATCH_ID } from './jest_constants'; import { withAppContext } from './app_context.mock'; -const testBedConfig: TestBedConfig = { +const testBedConfig: AsyncTestBedConfig = { memoryRouter: { onRouter: (router) => registerRouter(router), initialEntries: [`${ROUTES.API_ROOT}/watches/watch/${WATCH_ID}/edit`], diff --git a/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_list.helpers.ts b/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_list.helpers.ts index c0643e70dded9..d048a55422f6e 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_list.helpers.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_list.helpers.ts @@ -7,12 +7,12 @@ import { act } from 'react-dom/test-utils'; -import { registerTestBed, findTestSubject, TestBed, TestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, findTestSubject, TestBed, AsyncTestBedConfig } from '@kbn/test/jest'; import { WatchList } from '../../../public/application/sections/watch_list/components/watch_list'; import { ROUTES, REFRESH_INTERVALS } from '../../../common/constants'; import { withAppContext } from './app_context.mock'; -const testBedConfig: TestBedConfig = { +const testBedConfig: AsyncTestBedConfig = { memoryRouter: { initialEntries: [`${ROUTES.API_ROOT}/watches`], }, diff --git a/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_status.helpers.ts b/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_status.helpers.ts index 02b6908fc1d4c..0578f9f1092a1 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_status.helpers.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/helpers/watch_status.helpers.ts @@ -7,13 +7,13 @@ import { act } from 'react-dom/test-utils'; -import { registerTestBed, findTestSubject, TestBed, TestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, findTestSubject, TestBed, AsyncTestBedConfig } from '@kbn/test/jest'; import { WatchStatus } from '../../../public/application/sections/watch_status/components/watch_status'; import { ROUTES } from '../../../common/constants'; import { WATCH_ID } from './jest_constants'; import { withAppContext } from './app_context.mock'; -const testBedConfig: TestBedConfig = { +const testBedConfig: AsyncTestBedConfig = { memoryRouter: { initialEntries: [`${ROUTES.API_ROOT}/watches/watch/${WATCH_ID}/status`], componentRoutePath: `${ROUTES.API_ROOT}/watches/watch/:id/status`, diff --git a/x-pack/test/accessibility/apps/spaces.ts b/x-pack/test/accessibility/apps/spaces.ts index daddb9b24fe03..3dbdc1900ca35 100644 --- a/x-pack/test/accessibility/apps/spaces.ts +++ b/x-pack/test/accessibility/apps/spaces.ts @@ -96,8 +96,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); // test starts with deleting space b so we can get the space selection page instead of logging out in the test - // FLAKY: https://github.com/elastic/kibana/issues/100968 - it.skip('a11y test for space selection page', async () => { + it('a11y test for space selection page', async () => { await PageObjects.spaceSelector.confirmDeletingSpace(); await a11y.testAppSnapshot(); await PageObjects.spaceSelector.clickSpaceCard('default'); diff --git a/x-pack/test/accessibility/apps/users.ts b/x-pack/test/accessibility/apps/users.ts index bafd608aa8dee..8682cc8f0a884 100644 --- a/x-pack/test/accessibility/apps/users.ts +++ b/x-pack/test/accessibility/apps/users.ts @@ -88,11 +88,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('a11y test for edit user panel', async () => { + await PageObjects.settings.clickLinkText('Users'); await PageObjects.settings.clickLinkText('deleteA11y'); await a11y.testAppSnapshot(); }); it('a11y test for change password screen', async () => { + await PageObjects.settings.clickLinkText('Users'); await PageObjects.settings.clickLinkText('deleteA11y'); await find.clickByButtonText('Change password'); await a11y.testAppSnapshot(); @@ -100,6 +102,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('a11y test for deactivate user screen', async () => { + await PageObjects.settings.clickLinkText('Users'); await PageObjects.settings.clickLinkText('deleteA11y'); await find.clickByButtonText('Deactivate user'); await a11y.testAppSnapshot(); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts index f9cb6175e6fa6..f739d9d956cb5 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts @@ -40,6 +40,7 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC loadTestFile(require.resolve('./notify_when')); loadTestFile(require.resolve('./ephemeral')); loadTestFile(require.resolve('./event_log_alerts')); + loadTestFile(require.resolve('./scheduled_task_id')); // note that this test will destroy existing spaces loadTestFile(require.resolve('./migrations')); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/scheduled_task_id.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/scheduled_task_id.ts new file mode 100644 index 0000000000000..9f087b7392132 --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/scheduled_task_id.ts @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { getUrlPrefix, TaskManagerDoc, ObjectRemover, getTestAlertData } from '../../../common/lib'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +const MIGRATED_RULE_ID = '74f3e6d7-b7bb-477d-ac28-92ee22728e6e'; +const MIGRATED_TASK_ID = '329798f0-b0b0-11ea-9510-fdf248d5f2a4'; + +// eslint-disable-next-line import/no-default-export +export default function createScheduledTaskIdTests({ getService }: FtrProviderContext) { + const es = getService('es'); + const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const esArchiver = getService('esArchiver'); + + describe('scheduled task id', () => { + const objectRemover = new ObjectRemover(supertest); + async function getScheduledTask(id: string): Promise { + const scheduledTask = await es.get({ + id: `task:${id}`, + index: '.kibana_task_manager', + }); + return scheduledTask._source!; + } + + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/rules_scheduled_task_id'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/rules_scheduled_task_id'); + }); + + it('cannot create rule with same ID as a scheduled task ID used by another rule', async () => { + const response = await supertest.get( + `${getUrlPrefix(``)}/api/alerting/rule/${MIGRATED_RULE_ID}` + ); + expect(response.status).to.eql(200); + expect(response.body.scheduled_task_id).to.eql(MIGRATED_TASK_ID); + + await supertest + .post(`${getUrlPrefix(``)}/api/alerting/rule/${MIGRATED_TASK_ID}`) + .set('kbn-xsrf', 'foo') + .send(getTestAlertData()) + .expect(409); + }); + + it('for migrated rules - sets scheduled task id to match rule id when rule is disabled then enabled', async () => { + const response = await supertest.get( + `${getUrlPrefix(``)}/api/alerting/rule/${MIGRATED_RULE_ID}` + ); + expect(response.status).to.eql(200); + expect(response.body.scheduled_task_id).to.eql(MIGRATED_TASK_ID); + + // scheduled task id should exist + const taskRecordLoaded = await getScheduledTask(MIGRATED_TASK_ID); + expect(JSON.parse(taskRecordLoaded.task.params)).to.eql({ + alertId: MIGRATED_RULE_ID, + spaceId: 'default', + }); + + await supertestWithoutAuth + .post(`${getUrlPrefix(``)}/api/alerting/rule/${MIGRATED_RULE_ID}/_disable`) + .set('kbn-xsrf', 'foo') + .expect(204); + + await supertestWithoutAuth + .post(`${getUrlPrefix(``)}/api/alerting/rule/${MIGRATED_RULE_ID}/_enable`) + .set('kbn-xsrf', 'foo') + .expect(204); + + try { + await getScheduledTask(MIGRATED_TASK_ID); + throw new Error('Should have removed scheduled task'); + } catch (e) { + expect(e.meta.statusCode).to.eql(404); + } + + // scheduled task id that is same as rule id should exist + const taskRecordNew = await getScheduledTask(MIGRATED_RULE_ID); + expect(JSON.parse(taskRecordNew.task.params)).to.eql({ + alertId: MIGRATED_RULE_ID, + spaceId: 'default', + }); + }); + + it('sets scheduled task id to rule id when rule is created', async () => { + const response = await supertestWithoutAuth + .post(`${getUrlPrefix(``)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestAlertData()); + + expect(response.status).to.eql(200); + objectRemover.add('default', response.body.id, 'rule', 'alerting'); + + expect(response.body.scheduled_task_id).to.eql(response.body.id); + const taskRecord = await getScheduledTask(response.body.scheduled_task_id); + expect(taskRecord.type).to.eql('task'); + expect(taskRecord.task.taskType).to.eql('alerting:test.noop'); + expect(JSON.parse(taskRecord.task.params)).to.eql({ + alertId: response.body.id, + spaceId: 'default', + }); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts b/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts index 2742fbff294c0..2a7266e50d2c7 100644 --- a/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts @@ -44,7 +44,7 @@ export default ({ getService }: FtrProviderContext) => { user: USER.ML_POWERUSER, expected: { responseCode: 200, - moduleIds: ['apm_jsbase', 'apm_transaction', 'apm_nodejs'], + moduleIds: ['apm_jsbase', 'apm_nodejs'], }, }, { @@ -202,6 +202,16 @@ export default ({ getService }: FtrProviderContext) => { moduleIds: ['nginx_data_stream'], }, }, + { + testTitleSuffix: 'for apm transaction dataset', + sourceDataArchive: 'x-pack/test/functional/es_archives/ml/module_apm_transaction', + indexPattern: 'ft_module_apm_transaction', + user: USER.ML_POWERUSER, + expected: { + responseCode: 200, + moduleIds: ['apm_transaction'], + }, + }, ]; async function executeRecognizeModuleRequest(indexPattern: string, user: USER, rspCode: number) { diff --git a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts index c538fc0d9cb55..7c9c2ff185ea8 100644 --- a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts @@ -190,13 +190,13 @@ export default ({ getService }: FtrProviderContext) => { { testTitleSuffix: 'for apm_transaction with prefix, startDatafeed true and estimateModelMemory true', - sourceDataArchive: 'x-pack/test/functional/es_archives/ml/module_apm', - indexPattern: { name: 'ft_module_apm', timeField: '@timestamp' }, + sourceDataArchive: 'x-pack/test/functional/es_archives/ml/module_apm_transaction', + indexPattern: { name: 'ft_module_apm_transaction', timeField: '@timestamp' }, module: 'apm_transaction', user: USER.ML_POWERUSER, requestBody: { prefix: 'pf5_', - indexPatternName: 'ft_module_apm', + indexPatternName: 'ft_module_apm_transaction', startDatafeed: true, end: Date.now(), }, @@ -204,7 +204,7 @@ export default ({ getService }: FtrProviderContext) => { responseCode: 200, jobs: [ { - jobId: 'pf5_high_mean_transaction_duration', + jobId: 'pf5_apm_tx_metrics', jobState: JOB_STATE.CLOSED, datafeedState: DATAFEED_STATE.STOPPED, }, diff --git a/x-pack/test/api_integration/apis/uptime/get_all_pings.ts b/x-pack/test/api_integration/apis/uptime/get_all_pings.ts index cf52d72823095..2658afd0d90d2 100644 --- a/x-pack/test/api_integration/apis/uptime/get_all_pings.ts +++ b/x-pack/test/api_integration/apis/uptime/get_all_pings.ts @@ -9,6 +9,7 @@ import moment from 'moment'; import expect from '@kbn/expect'; import { PINGS_DATE_RANGE_START, PINGS_DATE_RANGE_END } from './constants'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { API_URLS } from '../../../../plugins/uptime/common/constants'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -22,9 +23,12 @@ export default function ({ getService }: FtrProviderContext) { it('should get all pings stored in index', async () => { const { body: apiResponse } = await supertest - .get( - `/api/uptime/pings?sort=desc&from=${PINGS_DATE_RANGE_START}&to=${PINGS_DATE_RANGE_END}` - ) + .get(API_URLS.PINGS) + .query({ + sort: 'desc', + from: PINGS_DATE_RANGE_START, + to: PINGS_DATE_RANGE_END, + }) .expect(200); expect(apiResponse.total).to.be(2); @@ -34,7 +38,12 @@ export default function ({ getService }: FtrProviderContext) { it('should sort pings according to timestamp', async () => { const { body: apiResponse } = await supertest - .get(`/api/uptime/pings?sort=asc&from=${PINGS_DATE_RANGE_START}&to=${PINGS_DATE_RANGE_END}`) + .get(API_URLS.PINGS) + .query({ + sort: 'asc', + from: PINGS_DATE_RANGE_START, + to: PINGS_DATE_RANGE_END, + }) .expect(200); expect(apiResponse.total).to.be(2); @@ -45,9 +54,13 @@ export default function ({ getService }: FtrProviderContext) { it('should return results of n length', async () => { const { body: apiResponse } = await supertest - .get( - `/api/uptime/pings?sort=desc&size=1&from=${PINGS_DATE_RANGE_START}&to=${PINGS_DATE_RANGE_END}` - ) + .get(API_URLS.PINGS) + .query({ + sort: 'desc', + size: 1, + from: PINGS_DATE_RANGE_START, + to: PINGS_DATE_RANGE_END, + }) .expect(200); expect(apiResponse.total).to.be(2); @@ -59,7 +72,8 @@ export default function ({ getService }: FtrProviderContext) { const from = moment('2002-01-01').valueOf(); const to = moment('2002-01-02').valueOf(); const { body: apiResponse } = await supertest - .get(`/api/uptime/pings?from=${from}&to=${to}`) + .get(API_URLS.PINGS) + .query({ from, to }) .expect(200); expect(apiResponse.total).to.be(0); diff --git a/x-pack/test/api_integration/apis/uptime/rest/dynamic_settings.ts b/x-pack/test/api_integration/apis/uptime/rest/dynamic_settings.ts index d5cf2078e377b..84e01ecef63d6 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/dynamic_settings.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/dynamic_settings.ts @@ -12,13 +12,16 @@ import { DynamicSettingsType, DynamicSettings, } from '../../../../../plugins/uptime/common/runtime_types'; -import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../../plugins/uptime/common/constants'; +import { + DYNAMIC_SETTINGS_DEFAULTS, + API_URLS, +} from '../../../../../plugins/uptime/common/constants'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); describe('dynamic settings', () => { it('returns the defaults when no user settings have been saved', async () => { - const apiResponse = await supertest.get(`/api/uptime/dynamic_settings`); + const apiResponse = await supertest.get(API_URLS.DYNAMIC_SETTINGS); expect(apiResponse.body).to.eql(DYNAMIC_SETTINGS_DEFAULTS); expect(isRight(DynamicSettingsType.decode(apiResponse.body))).to.be.ok(); }); @@ -31,14 +34,14 @@ export default function ({ getService }: FtrProviderContext) { defaultConnectors: [], }; const postResponse = await supertest - .post(`/api/uptime/dynamic_settings`) + .post(API_URLS.DYNAMIC_SETTINGS) .set('kbn-xsrf', 'true') .send(newSettings); expect(postResponse.body).to.eql({ success: true }); expect(postResponse.status).to.eql(200); - const getResponse = await supertest.get(`/api/uptime/dynamic_settings`); + const getResponse = await supertest.get(API_URLS.DYNAMIC_SETTINGS); expect(getResponse.body).to.eql(newSettings); expect(isRight(DynamicSettingsType.decode(getResponse.body))).to.be.ok(); }); diff --git a/x-pack/test/api_integration/apis/uptime/rest/filters.ts b/x-pack/test/api_integration/apis/uptime/rest/filters.ts deleted file mode 100644 index 5603d142cdb61..0000000000000 --- a/x-pack/test/api_integration/apis/uptime/rest/filters.ts +++ /dev/null @@ -1,28 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { expectFixtureEql } from './helper/expect_fixture_eql'; -import { FtrProviderContext } from '../../../ftr_provider_context'; - -const getApiPath = (dateRangeStart: string, dateRangeEnd: string, filters?: string) => - `/api/uptime/filters?dateRangeStart=${dateRangeStart}&dateRangeEnd=${dateRangeEnd}${ - filters ? `&filters=${filters}` : '' - }`; - -export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - - describe('filter group endpoint', () => { - const dateRangeStart = '2019-01-28T17:40:08.078Z'; - const dateRangeEnd = '2025-01-28T19:00:16.078Z'; - - it('returns expected filters', async () => { - const resp = await supertest.get(getApiPath(dateRangeStart, dateRangeEnd)); - expectFixtureEql(resp.body, 'filters'); - }); - }); -} diff --git a/x-pack/test/api_integration/apis/uptime/rest/monitor_duration.ts b/x-pack/test/api_integration/apis/uptime/rest/monitor_duration.ts index 92c5174c7263d..4fa0837e2e812 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/monitor_duration.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/monitor_duration.ts @@ -7,6 +7,7 @@ import { expectFixtureEql } from './helper/expect_fixture_eql'; import { FtrProviderContext } from '../../../ftr_provider_context'; +import { API_URLS } from '../../../../../plugins/uptime/common/constants'; export default function ({ getService }: FtrProviderContext) { describe('monitor duration query', () => { @@ -18,9 +19,11 @@ export default function ({ getService }: FtrProviderContext) { const monitorId = '0002-up'; - const apiResponse = await supertest.get( - `/api/uptime/monitor/duration?monitorId=${monitorId}&dateStart=${dateStart}&dateEnd=${dateEnd}` - ); + const apiResponse = await supertest.get(API_URLS.MONITOR_DURATION).query({ + monitorId, + dateStart, + dateEnd, + }); const data = apiResponse.body; expectFixtureEql(data, 'monitor_charts'); }); @@ -31,9 +34,11 @@ export default function ({ getService }: FtrProviderContext) { const monitorId = '0002-up'; - const apiResponse = await supertest.get( - `/api/uptime/monitor/duration?monitorId=${monitorId}&dateStart=${dateStart}&dateEnd=${dateEnd}` - ); + const apiResponse = await supertest.get(API_URLS.MONITOR_DURATION).query({ + monitorId, + dateStart, + dateEnd, + }); const data = apiResponse.body; expectFixtureEql(data, 'monitor_charts_empty_sets'); diff --git a/x-pack/test/api_integration/apis/uptime/rest/monitor_latest_status.ts b/x-pack/test/api_integration/apis/uptime/rest/monitor_latest_status.ts index 69b1bd0324ee6..036d3ad856f57 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/monitor_latest_status.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/monitor_latest_status.ts @@ -7,6 +7,7 @@ import { expectFixtureEql } from './helper/expect_fixture_eql'; import { FtrProviderContext } from '../../../ftr_provider_context'; +import { API_URLS } from '../../../../../plugins/uptime/common/constants'; export default function ({ getService }: FtrProviderContext) { describe('get monitor latest status API', () => { @@ -17,9 +18,11 @@ export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); it('returns the status for only the given monitor', async () => { - const apiResponse = await supertest.get( - `/api/uptime/monitor/status?monitorId=${monitorId}&dateStart=${dateStart}&dateEnd=${dateEnd}` - ); + const apiResponse = await supertest.get(API_URLS.MONITOR_STATUS).query({ + monitorId, + dateStart, + dateEnd, + }); expectFixtureEql(apiResponse.body, 'monitor_latest_status'); }); }); diff --git a/x-pack/test/api_integration/apis/uptime/rest/ping_histogram.ts b/x-pack/test/api_integration/apis/uptime/rest/ping_histogram.ts index 0d69b083d8f23..fcc0873be9a76 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/ping_histogram.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/ping_histogram.ts @@ -7,6 +7,7 @@ import { expectFixtureEql } from './helper/expect_fixture_eql'; import { FtrProviderContext } from '../../../ftr_provider_context'; +import { API_URLS } from '../../../../../plugins/uptime/common/constants'; export default function ({ getService }: FtrProviderContext) { describe('pingHistogram', () => { @@ -16,9 +17,10 @@ export default function ({ getService }: FtrProviderContext) { const dateStart = '2019-09-11T03:31:04.380Z'; const dateEnd = '2019-09-11T03:40:34.410Z'; - const apiResponse = await supertest.get( - `/api/uptime/ping/histogram?dateStart=${dateStart}&dateEnd=${dateEnd}` - ); + const apiResponse = await supertest.get(API_URLS.PING_HISTOGRAM).query({ + dateStart, + dateEnd, + }); const data = apiResponse.body; expectFixtureEql(data, 'ping_histogram'); @@ -29,9 +31,11 @@ export default function ({ getService }: FtrProviderContext) { const dateEnd = '2019-09-11T03:40:34.410Z'; const monitorId = '0002-up'; - const apiResponse = await supertest.get( - `/api/uptime/ping/histogram?monitorId=${monitorId}&dateStart=${dateStart}&dateEnd=${dateEnd}` - ); + const apiResponse = await supertest.get(API_URLS.PING_HISTOGRAM).query({ + monitorId, + dateStart, + dateEnd, + }); const data = apiResponse.body; expectFixtureEql(data, 'ping_histogram_by_id'); @@ -43,9 +47,11 @@ export default function ({ getService }: FtrProviderContext) { const filters = '{"bool":{"must":[{"match":{"monitor.status":{"query":"up","operator":"and"}}}]}}'; - const apiResponse = await supertest.get( - `/api/uptime/ping/histogram?dateStart=${dateStart}&dateEnd=${dateEnd}&filters=${filters}` - ); + const apiResponse = await supertest.get(API_URLS.PING_HISTOGRAM).query({ + dateStart, + dateEnd, + filters, + }); const data = apiResponse.body; expectFixtureEql(data, 'ping_histogram_by_filter'); diff --git a/x-pack/test/api_integration/apis/uptime/rest/ping_list.ts b/x-pack/test/api_integration/apis/uptime/rest/ping_list.ts index 860aae81703f4..7a0367728eed7 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/ping_list.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/ping_list.ts @@ -10,6 +10,7 @@ import { isLeft } from 'fp-ts/lib/Either'; import { PathReporter } from 'io-ts/lib/PathReporter'; import { PingsResponseType } from '../../../../../plugins/uptime/common/runtime_types'; import { FtrProviderContext } from '../../../ftr_provider_context'; +import { API_URLS } from '../../../../../plugins/uptime/common/constants'; function decodePingsResponseData(response: any) { const decoded = PingsResponseType.decode(response); @@ -33,7 +34,11 @@ export default function ({ getService }: FtrProviderContext) { const from = '2019-01-28T17:40:08.078Z'; const to = '2025-01-28T19:00:16.078Z'; - const apiResponse = await supertest.get(`/api/uptime/pings?from=${from}&to=${to}&size=10`); + const apiResponse = await supertest.get(API_URLS.PINGS).query({ + from, + to, + size: 10, + }); const { total, pings } = decodePingsResponseData(apiResponse.body); @@ -58,9 +63,11 @@ export default function ({ getService }: FtrProviderContext) { const to = '2025-01-28T19:00:16.078Z'; const size = 50; - const apiResponse = await supertest.get( - `/api/uptime/pings?from=${from}&to=${to}&size=${size}` - ); + const apiResponse = await supertest.get(API_URLS.PINGS).query({ + from, + to, + size, + }); const { total, pings } = decodePingsResponseData(apiResponse.body); @@ -126,9 +133,12 @@ export default function ({ getService }: FtrProviderContext) { const monitorId = '0001-up'; const size = 15; - const apiResponse = await supertest.get( - `/api/uptime/pings?from=${from}&to=${to}&monitorId=${monitorId}&size=${size}` - ); + const apiResponse = await supertest.get(API_URLS.PINGS).query({ + from, + to, + monitorId, + size, + }); const { total, pings } = decodePingsResponseData(apiResponse.body); @@ -160,9 +170,13 @@ export default function ({ getService }: FtrProviderContext) { const size = 5; const sort = 'asc'; - const apiResponse = await supertest.get( - `/api/uptime/pings?from=${from}&to=${to}&monitorId=${monitorId}&size=${size}&sort=${sort}` - ); + const apiResponse = await supertest.get(API_URLS.PINGS).query({ + from, + to, + monitorId, + size, + sort, + }); const { total, pings } = decodePingsResponseData(apiResponse.body); diff --git a/x-pack/test/api_integration/apis/uptime/rest/snapshot.ts b/x-pack/test/api_integration/apis/uptime/rest/snapshot.ts index 59393f7a4acf1..e7f604116e305 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/snapshot.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/snapshot.ts @@ -8,6 +8,7 @@ import { expectFixtureEql } from './helper/expect_fixture_eql'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { makeChecksWithStatus, getChecksDateRange } from './helper/make_checks'; +import { API_URLS } from '../../../../../plugins/uptime/common/constants'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -18,9 +19,10 @@ export default function ({ getService }: FtrProviderContext) { describe('when no data is present', async () => { it('returns a null snapshot', async () => { - const apiResponse = await supertest.get( - `/api/uptime/snapshot/count?dateRangeStart=${dateRangeStart}&dateRangeEnd=${dateRangeEnd}` - ); + const apiResponse = await supertest.get(API_URLS.SNAPSHOT_COUNT).query({ + dateRangeStart, + dateRangeEnd, + }); expectFixtureEql(apiResponse.body, 'snapshot_empty'); }); @@ -75,9 +77,10 @@ export default function ({ getService }: FtrProviderContext) { }); it('will count all statuses correctly', async () => { - const apiResponse = await supertest.get( - `/api/uptime/snapshot/count?dateRangeStart=${dateRange.start}&dateRangeEnd=${dateRange.end}` - ); + const apiResponse = await supertest.get(API_URLS.SNAPSHOT_COUNT).query({ + dateRangeStart: dateRange.start, + dateRangeEnd: dateRange.end, + }); expectFixtureEql(apiResponse.body, 'snapshot'); }); diff --git a/x-pack/test/apm_api_integration/tests/csm/csm_services.spec.ts b/x-pack/test/apm_api_integration/tests/csm/csm_services.spec.ts index 2d5f0ddf1cd4c..657e2485e1c21 100644 --- a/x-pack/test/apm_api_integration/tests/csm/csm_services.spec.ts +++ b/x-pack/test/apm_api_integration/tests/csm/csm_services.spec.ts @@ -14,9 +14,11 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) registry.when('CSM Services without data', { config: 'trial', archives: [] }, () => { it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/services?start=2020-06-28T10%3A24%3A46.055Z&end=2020-07-29T10%3A24%3A46.055Z&uiFilters=%7B%22agentName%22%3A%5B%22js-base%22%2C%22rum-js%22%5D%7D' - ); + const response = await supertest.get('/internal/apm/ux/services').query({ + start: '2020-06-28T10:24:46.055Z', + end: '2020-07-29T10:24:46.055Z', + uiFilters: '{"agentName":["js-base","rum-js"]}', + }); expect(response.status).to.be(200); expect(response.body.rumServices).to.eql([]); @@ -28,9 +30,11 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, () => { it('returns rum services list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/services?start=2020-06-28T10%3A24%3A46.055Z&end=2020-07-29T10%3A24%3A46.055Z&uiFilters=%7B%22agentName%22%3A%5B%22js-base%22%2C%22rum-js%22%5D%7D' - ); + const response = await supertest.get('/internal/apm/ux/services').query({ + start: '2020-06-28T10:24:46.055Z', + end: '2020-07-29T10:24:46.055Z', + uiFilters: '{"agentName":["js-base","rum-js"]}', + }); expect(response.status).to.be(200); diff --git a/x-pack/test/apm_api_integration/tests/csm/js_errors.spec.ts b/x-pack/test/apm_api_integration/tests/csm/js_errors.spec.ts index 5e4f306552273..b23f365348328 100644 --- a/x-pack/test/apm_api_integration/tests/csm/js_errors.spec.ts +++ b/x-pack/test/apm_api_integration/tests/csm/js_errors.spec.ts @@ -14,9 +14,13 @@ export default function rumJsErrorsApiTests({ getService }: FtrProviderContext) registry.when('CSM JS errors with data', { config: 'trial', archives: [] }, () => { it('returns no js errors', async () => { - const response = await supertest.get( - '/api/apm/rum-client/js-errors?pageSize=5&pageIndex=0&start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' - ); + const response = await supertest.get('/internal/apm/ux/js-errors').query({ + pageSize: 5, + pageIndex: 0, + start: '2020-09-07T20:35:54.654Z', + end: '2020-09-14T20:35:54.654Z', + uiFilters: '{"serviceName":["elastic-co-rum-test"]}', + }); expect(response.status).to.be(200); expectSnapshot(response.body).toMatchInline(` @@ -34,9 +38,13 @@ export default function rumJsErrorsApiTests({ getService }: FtrProviderContext) { config: 'trial', archives: ['8.0.0', 'rum_test_data'] }, () => { it('returns js errors', async () => { - const response = await supertest.get( - '/api/apm/rum-client/js-errors?start=2021-01-18T12%3A20%3A17.202Z&end=2021-01-18T12%3A25%3A17.203Z&uiFilters=%7B%22environment%22%3A%22ENVIRONMENT_ALL%22%2C%22serviceName%22%3A%5B%22elastic-co-frontend%22%5D%7D&pageSize=5&pageIndex=0' - ); + const response = await supertest.get('/internal/apm/ux/js-errors').query({ + start: '2021-01-18T12:20:17.202Z', + end: '2021-01-18T12:25:17.203Z', + uiFilters: '{"environment":"ENVIRONMENT_ALL","serviceName":["elastic-co-frontend"]}', + pageSize: 5, + pageIndex: 0, + }); expect(response.status).to.be(200); diff --git a/x-pack/test/apm_api_integration/tests/csm/long_task_metrics.spec.ts b/x-pack/test/apm_api_integration/tests/csm/long_task_metrics.spec.ts index ef1e537585b79..756d10bc4558d 100644 --- a/x-pack/test/apm_api_integration/tests/csm/long_task_metrics.spec.ts +++ b/x-pack/test/apm_api_integration/tests/csm/long_task_metrics.spec.ts @@ -14,9 +14,11 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) registry.when('CSM long task metrics without data', { config: 'trial', archives: [] }, () => { it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/long-task-metrics?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' - ); + const response = await supertest.get('/internal/apm/ux/long-task-metrics').query({ + start: '2020-09-07T20:35:54.654Z', + end: '2020-09-14T20:35:54.654Z', + uiFilters: '{"serviceName":["elastic-co-rum-test"]}', + }); expect(response.status).to.be(200); expect(response.body).to.eql({ @@ -32,9 +34,11 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, () => { it('returns web core vitals values', async () => { - const response = await supertest.get( - '/api/apm/rum-client/long-task-metrics?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D' - ); + const response = await supertest.get('/internal/apm/ux/long-task-metrics').query({ + start: '2020-09-07T20:35:54.654Z', + end: '2020-09-16T20:35:54.654Z', + uiFilters: '{"serviceName":["kibana-frontend-8_0_0"]}', + }); expect(response.status).to.be(200); diff --git a/x-pack/test/apm_api_integration/tests/csm/page_load_dist.spec.ts b/x-pack/test/apm_api_integration/tests/csm/page_load_dist.spec.ts index 1177b331c4c35..fd75a5cef33a2 100644 --- a/x-pack/test/apm_api_integration/tests/csm/page_load_dist.spec.ts +++ b/x-pack/test/apm_api_integration/tests/csm/page_load_dist.spec.ts @@ -14,18 +14,25 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) registry.when('UX page load dist without data', { config: 'trial', archives: [] }, () => { it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-load-distribution?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' - ); + const response = await supertest.get('/internal/apm/ux/page-load-distribution').query({ + start: '2020-09-07T20:35:54.654Z', + end: '2020-09-14T20:35:54.654Z', + uiFilters: '{"serviceName":["elastic-co-rum-test"]}', + }); expect(response.status).to.be(200); expectSnapshot(response.body).toMatch(); }); it('returns empty list with breakdowns', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-load-distribution/breakdown?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&breakdown=Browser' - ); + const response = await supertest + .get('/internal/apm/ux/page-load-distribution/breakdown') + .query({ + start: '2020-09-07T20:35:54.654Z', + end: '2020-09-14T20:35:54.654Z', + uiFilters: '{"serviceName":["elastic-co-rum-test"]}', + breakdown: 'Browser', + }); expect(response.status).to.be(200); expectSnapshot(response.body).toMatch(); @@ -37,18 +44,25 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, () => { it('returns page load distribution', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-load-distribution?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D' - ); + const response = await supertest.get('/internal/apm/ux/page-load-distribution').query({ + start: '2020-09-07T20:35:54.654Z', + end: '2020-09-16T20:35:54.654Z', + uiFilters: '{"serviceName":["kibana-frontend-8_0_0"]}', + }); expect(response.status).to.be(200); expectSnapshot(response.body).toMatch(); }); it('returns page load distribution with breakdown', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-load-distribution/breakdown?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&breakdown=Browser' - ); + const response = await supertest + .get('/internal/apm/ux/page-load-distribution/breakdown') + .query({ + start: '2020-09-07T20:35:54.654Z', + end: '2020-09-16T20:35:54.654Z', + uiFilters: '{"serviceName":["kibana-frontend-8_0_0"]}', + breakdown: 'Browser', + }); expect(response.status).to.be(200); diff --git a/x-pack/test/apm_api_integration/tests/csm/page_views.spec.ts b/x-pack/test/apm_api_integration/tests/csm/page_views.spec.ts index 40aa88aa5ad82..f699fc9f8a3b1 100644 --- a/x-pack/test/apm_api_integration/tests/csm/page_views.spec.ts +++ b/x-pack/test/apm_api_integration/tests/csm/page_views.spec.ts @@ -14,18 +14,23 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) registry.when('CSM page views without data', { config: 'trial', archives: [] }, () => { it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' - ); + const response = await supertest.get('/internal/apm/ux/page-view-trends').query({ + start: '2020-09-07T20:35:54.654Z', + end: '2020-09-14T20:35:54.654Z', + uiFilters: '{"serviceName":["elastic-co-rum-test"]}', + }); expect(response.status).to.be(200); expectSnapshot(response.body).toMatch(); }); it('returns empty list with breakdowns', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&breakdowns=%7B%22name%22%3A%22Browser%22%2C%22fieldName%22%3A%22user_agent.name%22%2C%22type%22%3A%22category%22%7D' - ); + const response = await supertest.get('/internal/apm/ux/page-view-trends').query({ + start: '2020-09-07T20:35:54.654Z', + end: '2020-09-14T20:35:54.654Z', + uiFilters: '{"serviceName":["elastic-co-rum-test"]}', + breakdowns: '{"name":"Browser","fieldName":"user_agent.name","type":"category"}', + }); expect(response.status).to.be(200); expectSnapshot(response.body).toMatch(); @@ -37,18 +42,23 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, () => { it('returns page views', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D' - ); + const response = await supertest.get('/internal/apm/ux/page-view-trends').query({ + start: '2020-09-07T20:35:54.654Z', + end: '2020-09-16T20:35:54.654Z', + uiFilters: '{"serviceName":["kibana-frontend-8_0_0"]}', + }); expect(response.status).to.be(200); expectSnapshot(response.body).toMatch(); }); it('returns page views with breakdown', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&breakdowns=%7B%22name%22%3A%22Browser%22%2C%22fieldName%22%3A%22user_agent.name%22%2C%22type%22%3A%22category%22%7D' - ); + const response = await supertest.get('/internal/apm/ux/page-view-trends').query({ + start: '2020-09-07T20:35:54.654Z', + end: '2020-09-16T20:35:54.654Z', + uiFilters: '{"serviceName":["kibana-frontend-8_0_0"]}', + breakdowns: '{"name":"Browser","fieldName":"user_agent.name","type":"category"}', + }); expect(response.status).to.be(200); diff --git a/x-pack/test/apm_api_integration/tests/csm/url_search.spec.ts b/x-pack/test/apm_api_integration/tests/csm/url_search.spec.ts index f45e82865983e..89e56face9343 100644 --- a/x-pack/test/apm_api_integration/tests/csm/url_search.spec.ts +++ b/x-pack/test/apm_api_integration/tests/csm/url_search.spec.ts @@ -14,9 +14,12 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) registry.when('CSM url search api without data', { config: 'trial', archives: [] }, () => { it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&percentile=50' - ); + const response = await supertest.get('/internal/apm/ux/url-search').query({ + start: '2020-09-07T20:35:54.654Z', + end: '2020-09-14T20:35:54.654Z', + uiFilters: '{"serviceName":["elastic-co-rum-test"]}', + percentile: 50, + }); expect(response.status).to.be(200); expectSnapshot(response.body).toMatchInline(` @@ -33,9 +36,12 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, () => { it('returns top urls when no query', async () => { - const response = await supertest.get( - '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&percentile=50' - ); + const response = await supertest.get('/internal/apm/ux/url-search').query({ + start: '2020-09-07T20:35:54.654Z', + end: '2020-09-16T20:35:54.654Z', + uiFilters: '{"serviceName":["kibana-frontend-8_0_0"]}', + percentile: 50, + }); expect(response.status).to.be(200); @@ -59,9 +65,13 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) }); it('returns specific results against query', async () => { - const response = await supertest.get( - '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&urlQuery=csm&percentile=50' - ); + const response = await supertest.get('/internal/apm/ux/url-search').query({ + start: '2020-09-07T20:35:54.654Z', + end: '2020-09-16T20:35:54.654Z', + uiFilters: '{"serviceName":["kibana-frontend-8_0_0"]}', + urlQuery: 'csm', + percentile: 50, + }); expect(response.status).to.be(200); diff --git a/x-pack/test/apm_api_integration/tests/csm/web_core_vitals.spec.ts b/x-pack/test/apm_api_integration/tests/csm/web_core_vitals.spec.ts index 421bafcb4064f..882e9e23a4314 100644 --- a/x-pack/test/apm_api_integration/tests/csm/web_core_vitals.spec.ts +++ b/x-pack/test/apm_api_integration/tests/csm/web_core_vitals.spec.ts @@ -14,9 +14,12 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) registry.when('CSM web core vitals without data', { config: 'trial', archives: [] }, () => { it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/web-core-vitals?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&percentile=50' - ); + const response = await supertest.get('/internal/apm/ux/web-core-vitals').query({ + start: '2020-09-07T20:35:54.654Z', + end: '2020-09-14T20:35:54.654Z', + uiFilters: '{"serviceName":["elastic-co-rum-test"]}', + percentile: 50, + }); expect(response.status).to.be(200); expect(response.body).to.eql({ @@ -35,9 +38,12 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, () => { it('returns web core vitals values', async () => { - const response = await supertest.get( - '/api/apm/rum-client/web-core-vitals?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&percentile=50' - ); + const response = await supertest.get('/internal/apm/ux/web-core-vitals').query({ + start: '2020-09-07T20:35:54.654Z', + end: '2020-09-16T20:35:54.654Z', + uiFilters: '{"serviceName":["kibana-frontend-8_0_0"]}', + percentile: 50, + }); expect(response.status).to.be(200); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules.ts index d6f69095c43c4..819683b564c68 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules.ts @@ -132,6 +132,59 @@ export default ({ getService }: FtrProviderContext) => { expect(bodyToCompare).to.eql(outputRule); }); + it('should update a single rule property and remove the action', async () => { + const [connector1] = await Promise.all([ + supertest + .post(`/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + connector_type_id: '.slack', + secrets: { + webhookUrl: 'http://localhost:1234', + }, + }), + ]); + + const action1 = { + group: 'default', + id: connector1.body.id, + action_type_id: connector1.body.connector_type_id, + params: { + message: 'message', + }, + }; + + const ruleWithConnector: ReturnType = { + ...getSimpleRule('rule-1'), + actions: [action1], + }; + const createdRule = await createRule(supertest, log, ruleWithConnector); + expect(createdRule.actions.length).to.eql(1); + + // update a simple rule's name and remove the actions + const updatedRule = getSimpleRuleUpdate('rule-1'); + updatedRule.rule_id = ruleWithConnector.rule_id; + updatedRule.name = 'some other name'; + delete updatedRule.id; + + const { body } = await supertest + .put(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(updatedRule) + .expect(200); + + const outputRule = getSimpleRuleOutputWithoutRuleId(); + outputRule.name = 'some other name'; + outputRule.version = 2; + // Expect an empty array + outputRule.actions = []; + // Expect "no_actions" + outputRule.throttle = 'no_actions'; + const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body); + expect(bodyToCompare).to.eql(outputRule); + }); + it('should update a single rule property of name using an auto-generated rule_id and migrate the actions', async () => { const rule = getSimpleRule('rule-1'); delete rule.rule_id; @@ -150,10 +203,20 @@ export default ({ getService }: FtrProviderContext) => { ]); await createLegacyRuleAction(supertest, createRuleBody.id, connector.body.id); + const action1 = { + group: 'default', + id: connector.body.id, + action_type_id: connector.body.connector_type_id, + params: { + message: 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + }; // update a simple rule's name const updatedRule = getSimpleRuleUpdate('rule-1'); updatedRule.rule_id = createRuleBody.rule_id; updatedRule.name = 'some other name'; + updatedRule.actions = [action1]; + updatedRule.throttle = '1m'; delete updatedRule.id; const { body } = await supertest diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts index 217debca079dc..b165258237b41 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; +import { FullResponseSchema } from '../../../../plugins/security_solution/common/detection_engine/schemas/request'; import { DETECTION_ENGINE_RULES_URL } from '../../../../plugins/security_solution/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -97,42 +98,50 @@ export default ({ getService }: FtrProviderContext) => { }); it('should update two rule properties of name using the two rules rule_id and migrate actions', async () => { - const [connector, rule1, rule2] = await Promise.all([ - supertest - .post(`/api/actions/connector`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'My action', - connector_type_id: '.slack', - secrets: { - webhookUrl: 'http://localhost:1234', - }, - }), - createRule(supertest, log, getSimpleRule('rule-1')), - createRule(supertest, log, getSimpleRule('rule-2')), + const connector = await supertest + .post(`/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + connector_type_id: '.slack', + secrets: { + webhookUrl: 'http://localhost:1234', + }, + }); + const action1 = { + group: 'default', + id: connector.body.id, + action_type_id: connector.body.connector_type_id, + params: { + message: 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + }; + const [rule1, rule2] = await Promise.all([ + createRule(supertest, log, { ...getSimpleRule('rule-1'), actions: [action1] }), + createRule(supertest, log, { ...getSimpleRule('rule-2'), actions: [action1] }), ]); await Promise.all([ createLegacyRuleAction(supertest, rule1.id, connector.body.id), createLegacyRuleAction(supertest, rule2.id, connector.body.id), ]); - expect(rule1.actions).to.eql([]); - expect(rule2.actions).to.eql([]); - const updatedRule1 = getSimpleRuleUpdate('rule-1'); updatedRule1.name = 'some other name'; + updatedRule1.actions = [action1]; + updatedRule1.throttle = '1m'; const updatedRule2 = getSimpleRuleUpdate('rule-2'); updatedRule2.name = 'some other name'; + updatedRule2.actions = [action1]; + updatedRule2.throttle = '1m'; // update both rule names - const { body } = await supertest + const { body }: { body: FullResponseSchema[] } = await supertest .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) .set('kbn-xsrf', 'true') .send([updatedRule1, updatedRule2]) .expect(200); - // @ts-expect-error body.forEach((response) => { const outputRule = getSimpleRuleOutput(response.rule_id); outputRule.name = 'some other name'; @@ -154,6 +163,58 @@ export default ({ getService }: FtrProviderContext) => { }); }); + it('should update two rule properties of name using the two rules rule_id and remove actions', async () => { + const connector = await supertest + .post(`/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + connector_type_id: '.slack', + secrets: { + webhookUrl: 'http://localhost:1234', + }, + }); + const action1 = { + group: 'default', + id: connector.body.id, + action_type_id: connector.body.connector_type_id, + params: { + message: 'message', + }, + }; + const [rule1, rule2] = await Promise.all([ + createRule(supertest, log, { ...getSimpleRule('rule-1'), actions: [action1] }), + createRule(supertest, log, { ...getSimpleRule('rule-2'), actions: [action1] }), + ]); + await Promise.all([ + createLegacyRuleAction(supertest, rule1.id, connector.body.id), + createLegacyRuleAction(supertest, rule2.id, connector.body.id), + ]); + + const updatedRule1 = getSimpleRuleUpdate('rule-1'); + updatedRule1.name = 'some other name'; + + const updatedRule2 = getSimpleRuleUpdate('rule-2'); + updatedRule2.name = 'some other name'; + + // update both rule names + const { body }: { body: FullResponseSchema[] } = await supertest + .put(`${DETECTION_ENGINE_RULES_URL}/_bulk_update`) + .set('kbn-xsrf', 'true') + .send([updatedRule1, updatedRule2]) + .expect(200); + + body.forEach((response) => { + const outputRule = getSimpleRuleOutput(response.rule_id); + outputRule.name = 'some other name'; + outputRule.version = 2; + outputRule.actions = []; + outputRule.throttle = 'no_actions'; + const bodyToCompare = removeServerGeneratedProperties(response); + expect(bodyToCompare).to.eql(outputRule); + }); + }); + it('should update a single rule property of name using an id', async () => { const createRuleBody = await createRule(supertest, log, getSimpleRule('rule-1')); diff --git a/x-pack/test/fleet_api_integration/apis/epm/setup.ts b/x-pack/test/fleet_api_integration/apis/epm/setup.ts index ce967160f33e1..0b669c96bb0ea 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/setup.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/setup.ts @@ -24,8 +24,10 @@ export default function (providerContext: FtrProviderContext) { describe('setup performs upgrades', async () => { const oldEndpointVersion = '0.13.0'; beforeEach(async () => { + const url = '/api/fleet/epm/packages/endpoint'; + await supertest.delete(url).set('kbn-xsrf', 'xxxx').send({ force: true }).expect(200); await supertest - .post(`/api/fleet/epm/packages/endpoint-${oldEndpointVersion}`) + .post(`${url}-${oldEndpointVersion}`) .set('kbn-xsrf', 'xxxx') .send({ force: true }) .expect(200); diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/create.ts b/x-pack/test/fleet_api_integration/apis/package_policy/create.ts index 6817289d389f3..d568e7224fd20 100644 --- a/x-pack/test/fleet_api_integration/apis/package_policy/create.ts +++ b/x-pack/test/fleet_api_integration/apis/package_policy/create.ts @@ -199,7 +199,8 @@ export default function (providerContext: FtrProviderContext) { .expect(400); }); - it('should not allow multiple limited packages on the same agent policy', async function () { + // https://github.com/elastic/kibana/issues/118257 + it.skip('should not allow multiple limited packages on the same agent policy', async function () { await supertest .post(`/api/fleet/package_policies`) .set('kbn-xsrf', 'xxxx') diff --git a/x-pack/test/fleet_api_integration/config.ts b/x-pack/test/fleet_api_integration/config.ts index d2b61a3f5c321..308c4ab66f15c 100644 --- a/x-pack/test/fleet_api_integration/config.ts +++ b/x-pack/test/fleet_api_integration/config.ts @@ -15,7 +15,7 @@ import { defineDockerServersConfig } from '@kbn/test'; // example: https://beats-ci.elastic.co/blue/organizations/jenkins/Ingest-manager%2Fpackage-storage/detail/snapshot/74/pipeline/257#step-302-log-1. // It should be updated any time there is a new Docker image published for the Snapshot Distribution of the Package Registry. export const dockerImage = - 'docker.elastic.co/package-registry/distribution@sha256:42dbdbb7fbc7ea61d0c38c0df6dad977ca2ad9cf01e247543054377aef33d377'; + 'docker.elastic.co/package-registry/distribution@sha256:13d9996dd24161624784704e080f5f5b7f0ef34ff0d9259f8f05010ccae00058'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/single_metric_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection/single_metric_job.ts index 97b340c081958..3c3d27ebd5244 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection/single_metric_job.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection/single_metric_job.ts @@ -10,6 +10,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); + const browser = getService('browser'); const jobId = `fq_single_1_${Date.now()}`; const jobIdClone = `${jobId}_clone`; @@ -201,7 +202,26 @@ export default function ({ getService }: FtrProviderContext) { await ml.api.assertDetectorResultsExist(jobId, 0); }); + it('job cloning fails in the single metric wizard if a matching data view does not exist', async () => { + await ml.testExecution.logTestStep('delete data view used by job'); + await ml.testResources.deleteIndexPatternByTitle('ft_farequote'); + + // Refresh page to ensure page has correct cache of data views + await browser.refresh(); + + await ml.testExecution.logTestStep( + 'job cloning clicks the clone action and displays an error toast' + ); + await ml.jobTable.clickCloneJobActionWhenNoDataViewExists(jobId); + }); + it('job cloning opens the existing job in the single metric wizard', async () => { + await ml.testExecution.logTestStep('recreate data view used by job'); + await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp'); + + // Refresh page to ensure page has correct cache of data views + await browser.refresh(); + await ml.testExecution.logTestStep( 'job cloning clicks the clone action and loads the single metric wizard' ); diff --git a/x-pack/test/functional/apps/ml/model_management/model_list.ts b/x-pack/test/functional/apps/ml/model_management/model_list.ts index 955639dbe60a4..aac1ad5b1e50b 100644 --- a/x-pack/test/functional/apps/ml/model_management/model_list.ts +++ b/x-pack/test/functional/apps/ml/model_management/model_list.ts @@ -10,7 +10,8 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const ml = getService('ml'); - describe('trained models', function () { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/118251 + describe.skip('trained models', function () { before(async () => { await ml.trainedModels.createTestTrainedModels('classification', 15, true); await ml.trainedModels.createTestTrainedModels('regression', 15); diff --git a/x-pack/test/functional/apps/saved_objects_management/feature_controls/saved_objects_management_security.ts b/x-pack/test/functional/apps/saved_objects_management/feature_controls/saved_objects_management_security.ts index 8b6474b0679b7..c0b81ed8443e5 100644 --- a/x-pack/test/functional/apps/saved_objects_management/feature_controls/saved_objects_management_security.ts +++ b/x-pack/test/functional/apps/saved_objects_management/feature_controls/saved_objects_management_security.ts @@ -16,7 +16,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { let version: string = ''; const find = getService('find'); - describe('feature controls saved objects management', () => { + // FLAKY: https://github.com/elastic/kibana/issues/118272 + describe.skip('feature controls saved objects management', () => { before(async () => { version = await kibanaServer.version.get(); await kibanaServer.importExport.load( diff --git a/x-pack/test/functional/apps/spaces/enter_space.ts b/x-pack/test/functional/apps/spaces/enter_space.ts index 25fdcfda395ce..7a3ffc50a4821 100644 --- a/x-pack/test/functional/apps/spaces/enter_space.ts +++ b/x-pack/test/functional/apps/spaces/enter_space.ts @@ -14,7 +14,7 @@ export default function enterSpaceFunctonalTests({ const esArchiver = getService('esArchiver'); const PageObjects = getPageObjects(['security', 'spaceSelector']); - // Failing: See https://github.com/elastic/kibana/issues/100570 + // FLAKY: https://github.com/elastic/kibana/issues/100570 describe.skip('Enter Space', function () { this.tags('includeFirefox'); before(async () => { @@ -50,15 +50,12 @@ export default function enterSpaceFunctonalTests({ }); await PageObjects.spaceSelector.clickSpaceCard(spaceId); - await PageObjects.spaceSelector.expectRoute(spaceId, '/app/canvas'); - await PageObjects.spaceSelector.openSpacesNav(); // change spaces const newSpaceId = 'default'; await PageObjects.spaceSelector.clickSpaceAvatar(newSpaceId); - await PageObjects.spaceSelector.expectHomePage(newSpaceId); }); }); diff --git a/x-pack/test/functional/es_archives/ml/module_apm_transaction/data.json.gz b/x-pack/test/functional/es_archives/ml/module_apm_transaction/data.json.gz new file mode 100644 index 0000000000000..56c3e0ebc5e17 Binary files /dev/null and b/x-pack/test/functional/es_archives/ml/module_apm_transaction/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/ml/module_apm_transaction/mappings.json b/x-pack/test/functional/es_archives/ml/module_apm_transaction/mappings.json new file mode 100644 index 0000000000000..92c1fcdb3ed4a --- /dev/null +++ b/x-pack/test/functional/es_archives/ml/module_apm_transaction/mappings.json @@ -0,0 +1,501 @@ +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "ft_module_apm_transaction", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ecs": { + "properties": { + "version": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "reason": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "url": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "hostname": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "metricset": { + "properties": { + "name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "observer": { + "properties": { + "ephemeral_id": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "hostname": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "id": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "type": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "version": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "version_major": { + "type": "long" + } + } + }, + "processor": { + "properties": { + "event": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "service": { + "dynamic": "false", + "properties": { + "environment": { + "ignore_above": 1024, + "type": "keyword" + }, + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "framework": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "language": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timeseries": { + "properties": { + "instance": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "transaction": { + "properties": { + "breakdown": { + "properties": { + "count": { + "type": "long" + } + } + }, + "duration": { + "properties": { + "count": { + "type": "long" + }, + "histogram": { + "type": "histogram" + }, + "sum": { + "properties": { + "us": { + "meta": { + "unit": "micros" + }, + "type": "long" + } + } + }, + "us": { + "type": "long" + } + } + }, + "experience": { + "properties": { + "cls": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "fid": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "longtask": { + "properties": { + "count": { + "type": "long" + }, + "max": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "sum": { + "scaling_factor": 1000000, + "type": "scaled_float" + } + } + }, + "tbt": { + "scaling_factor": 1000000, + "type": "scaled_float" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "marks": { + "dynamic": "true", + "properties": { + "*": { + "properties": { + "*": { + "dynamic": "true", + "type": "object" + } + } + } + } + }, + "message": { + "dynamic": "false", + "properties": { + "age": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "queue": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "result": { + "ignore_above": 1024, + "type": "keyword" + }, + "root": { + "type": "boolean" + }, + "sampled": { + "type": "boolean" + }, + "self_time": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "properties": { + "us": { + "meta": { + "unit": "micros" + }, + "type": "long" + } + } + } + } + }, + "span_count": { + "properties": { + "dropped": { + "type": "long" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/rules_scheduled_task_id/data.json b/x-pack/test/functional/es_archives/rules_scheduled_task_id/data.json new file mode 100644 index 0000000000000..159380a281de5 --- /dev/null +++ b/x-pack/test/functional/es_archives/rules_scheduled_task_id/data.json @@ -0,0 +1,74 @@ +{ + "type": "doc", + "value": { + "id": "alert:74f3e6d7-b7bb-477d-ac28-92ee22728e6e", + "index": ".kibana_1", + "source": { + "alert": { + "actions": [ + ], + "alertTypeId": "example.always-firing", + "apiKey": "QIUT8u0/kbOakEHSj50jDpVR90MrqOxanEscboYOoa8PxQvcA5jfHash+fqH3b+KNjJ1LpnBcisGuPkufY9j1e32gKzwGZV5Bfys87imHvygJvIM8uKiFF8bQ8Y4NTaxOJO9fAmZPrFy07ZcQMCAQz+DUTgBFqs=", + "apiKeyOwner": "elastic", + "consumer": "alerts", + "createdAt": "2020-06-17T15:35:38.497Z", + "createdBy": "elastic", + "enabled": true, + "muteAll": false, + "mutedInstanceIds": [ + ], + "name": "always-firing-alert", + "params": { + }, + "schedule": { + "interval": "1m" + }, + "scheduledTaskId": "329798f0-b0b0-11ea-9510-fdf248d5f2a4", + "tags": [ + ], + "throttle": null, + "updatedBy": "elastic" + }, + "migrationVersion": { + "alert": "7.16.0" + }, + "references": [ + ], + "type": "alert", + "updated_at": "2020-06-17T15:35:39.839Z" + } + } +} + +{ + "type": "doc", + "value": { + "id": "task:329798f0-b0b0-11ea-9510-fdf248d5f2a4", + "index": ".kibana_task_manager_1", + "source": { + "migrationVersion": { + "task": "7.16.0" + }, + "task": { + "attempts": 0, + "ownerId": null, + "params": "{\"alertId\":\"74f3e6d7-b7bb-477d-ac28-92ee22728e6e\",\"spaceId\":\"default\"}", + "retryAt": null, + "runAt": "2021-11-05T16:21:52.148Z", + "schedule": { + "interval": "1m" + }, + "scheduledAt": "2021-11-05T15:28:42.055Z", + "scope": [ + "alerting" + ], + "startedAt": null, + "status": "idle", + "taskType": "alerting:example.always-firing" + }, + "references": [], + "type": "task", + "updated_at": "2021-11-05T16:21:37.629Z" + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/rules_scheduled_task_id/mappings.json b/x-pack/test/functional/es_archives/rules_scheduled_task_id/mappings.json new file mode 100644 index 0000000000000..45adfd491a09b --- /dev/null +++ b/x-pack/test/functional/es_archives/rules_scheduled_task_id/mappings.json @@ -0,0 +1,460 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "7b44fba6773e37c806ce290ea9b7024e", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "3525d7c22c42bc80f5e6e9cb3f2b26a2", + "application_usage_totals": "c897e4310c5f24b07caaff3db53ae2c1", + "application_usage_transactional": "965839e75f809fefe04f92dc4d99722a", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "cases": "32aa96a6d3855ddda53010ae2048ac22", + "cases-comments": "c2061fb929f585df57425102fa928b4b", + "cases-configure": "42711cbb311976c0687853f4c1354572", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "ae24d22d5986d04124cc6568f771066f", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "infrastructure-ui-source": "ddc0ecb18383f6b26101a2fadb2dab0c", + "inventory-view": "88fc7e12fd1b45b6f0787323ce4f18d2", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "d33c68a69ff1e78c9888dedd2164ac22", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "23d7aa4a720d4938ccde3983f87bd58d", + "maps-telemetry": "bfd39d88aadadb4be597ea984d433dbe", + "metrics-explorer-view": "428e319af3e822c80a84cf87123ca35c", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "181661168bbadd1eff5902361e2a0d5c", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "todo": "082a2cc96a590268344d5cd74c159ac4", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "296a89039fc4260292be36b1b005d8f2", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "fcdb453a30092f022f2642db29523d80", + "url": "b675c3be8d76ecf029294d51dc7ec65d", + "visualization": "52d7a13ad68a150c4525b292d23e12cc" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "alert": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "config": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "search": { + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + ".kibana_task_manager": { + } + }, + "index": ".kibana_task_manager_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "originId": "2f4316de49999235636386fe51dc06c1", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "task": "235412e52d09e7165fac8a67a43ad6b4", + "type": "2f4316de49999235636386fe51dc06c1", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0" + } + }, + "dynamic": "strict", + "properties": { + "migrationVersion": { + "dynamic": "true", + "properties": { + "task": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "task": { + "properties": { + "attempts": { + "type": "integer" + }, + "ownerId": { + "type": "keyword" + }, + "params": { + "type": "text" + }, + "retryAt": { + "type": "date" + }, + "runAt": { + "type": "date" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledAt": { + "type": "date" + }, + "scope": { + "type": "keyword" + }, + "startedAt": { + "type": "date" + }, + "state": { + "type": "text" + }, + "status": { + "type": "keyword" + }, + "taskType": { + "type": "keyword" + }, + "user": { + "type": "keyword" + } + } + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index 247dc607c0038..7f175b6d83ab6 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -6,7 +6,7 @@ */ import expect from '@kbn/expect'; -import { delay } from 'bluebird'; +import { setTimeout as setTimeoutAsync } from 'timers/promises'; import { FtrProviderContext } from '../ftr_provider_context'; import { logWrapper } from './log_wrapper'; @@ -1094,7 +1094,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont draggedOver, dropTarget ); - await delay(150); + await setTimeoutAsync(150); }, /** diff --git a/x-pack/test/functional/page_objects/observability_page.ts b/x-pack/test/functional/page_objects/observability_page.ts index f89dafe4f3a73..07cceca4be122 100644 --- a/x-pack/test/functional/page_objects/observability_page.ts +++ b/x-pack/test/functional/page_objects/observability_page.ts @@ -28,7 +28,7 @@ export function ObservabilityPageProvider({ getService, getPageObjects }: FtrPro }, async expectNoReadOnlyCallout() { - await testSubjects.missingOrFail('case-callout-e41900b01c9ef0fa81dd6ff326083fb3'); + await testSubjects.missingOrFail('caseCallout-e41900b01c9ef0fa81dd6ff326083fb3'); }, async expectNoDataPage() { @@ -50,7 +50,7 @@ export function ObservabilityPageProvider({ getService, getPageObjects }: FtrPro }, async expectForbidden() { - const h2 = await testSubjects.find('no_feature_permissions', 20000); + const h2 = await testSubjects.find('noFeaturePermissions', 20000); const text = await h2.getVisibleText(); expect(text).to.contain('Kibana feature privileges required'); }, diff --git a/x-pack/test/functional/page_objects/rollup_page.ts b/x-pack/test/functional/page_objects/rollup_page.ts index 0740a8f015da1..28ad65f60a49b 100644 --- a/x-pack/test/functional/page_objects/rollup_page.ts +++ b/x-pack/test/functional/page_objects/rollup_page.ts @@ -6,7 +6,6 @@ */ import expect from '@kbn/expect'; -import { map as mapAsync } from 'bluebird'; import { FtrService } from '../ftr_provider_context'; export class RollupPageObject extends FtrService { @@ -111,26 +110,32 @@ export class RollupPageObject extends FtrService { async getJobList() { const jobs = await this.testSubjects.findAll('jobTableRow'); - return mapAsync(jobs, async (job) => { - const jobNameElement = await job.findByTestSubject('jobTableCell-id'); - const jobStatusElement = await job.findByTestSubject('jobTableCell-status'); - const jobIndexPatternElement = await job.findByTestSubject('jobTableCell-indexPattern'); - const jobRollUpIndexPatternElement = await job.findByTestSubject('jobTableCell-rollupIndex'); - const jobDelayElement = await job.findByTestSubject('jobTableCell-rollupDelay'); - const jobIntervalElement = await job.findByTestSubject('jobTableCell-dateHistogramInterval'); - const jobGroupElement = await job.findByTestSubject('jobTableCell-groups'); - const jobMetricsElement = await job.findByTestSubject('jobTableCell-metrics'); - - return { - jobName: await jobNameElement.getVisibleText(), - jobStatus: await jobStatusElement.getVisibleText(), - jobIndexPattern: await jobIndexPatternElement.getVisibleText(), - jobRollUpIndexPattern: await jobRollUpIndexPatternElement.getVisibleText(), - jobDelayElement: await jobDelayElement.getVisibleText(), - jobInterval: await jobIntervalElement.getVisibleText(), - jobGroup: await jobGroupElement.getVisibleText(), - jobMetrics: await jobMetricsElement.getVisibleText(), - }; - }); + return await Promise.all( + jobs.map(async (job) => { + const jobNameElement = await job.findByTestSubject('jobTableCell-id'); + const jobStatusElement = await job.findByTestSubject('jobTableCell-status'); + const jobIndexPatternElement = await job.findByTestSubject('jobTableCell-indexPattern'); + const jobRollUpIndexPatternElement = await job.findByTestSubject( + 'jobTableCell-rollupIndex' + ); + const jobDelayElement = await job.findByTestSubject('jobTableCell-rollupDelay'); + const jobIntervalElement = await job.findByTestSubject( + 'jobTableCell-dateHistogramInterval' + ); + const jobGroupElement = await job.findByTestSubject('jobTableCell-groups'); + const jobMetricsElement = await job.findByTestSubject('jobTableCell-metrics'); + + return { + jobName: await jobNameElement.getVisibleText(), + jobStatus: await jobStatusElement.getVisibleText(), + jobIndexPattern: await jobIndexPatternElement.getVisibleText(), + jobRollUpIndexPattern: await jobRollUpIndexPatternElement.getVisibleText(), + jobDelayElement: await jobDelayElement.getVisibleText(), + jobInterval: await jobIntervalElement.getVisibleText(), + jobGroup: await jobGroupElement.getVisibleText(), + jobMetrics: await jobMetricsElement.getVisibleText(), + }; + }) + ); } } diff --git a/x-pack/test/functional/page_objects/watcher_page.ts b/x-pack/test/functional/page_objects/watcher_page.ts index 5aeaddffff581..ad5153a933466 100644 --- a/x-pack/test/functional/page_objects/watcher_page.ts +++ b/x-pack/test/functional/page_objects/watcher_page.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { map as mapAsync } from 'bluebird'; import { FtrService } from '../ftr_provider_context'; export class WatcherPageObject extends FtrService { @@ -51,16 +50,18 @@ export class WatcherPageObject extends FtrService { // get all the watches in the list async getWatches() { const watches = await this.find.allByCssSelector('.euiTableRow'); - return mapAsync(watches, async (watch) => { - const checkBox = await watch.findByCssSelector('td:nth-child(1)'); - const id = await watch.findByCssSelector('td:nth-child(2)'); - const name = await watch.findByCssSelector('td:nth-child(3)'); + return await Promise.all( + watches.map(async (watch) => { + const checkBox = await watch.findByCssSelector('td:nth-child(1)'); + const id = await watch.findByCssSelector('td:nth-child(2)'); + const name = await watch.findByCssSelector('td:nth-child(3)'); - return { - checkBox: (await checkBox.getAttribute('innerHTML')).includes('input'), - id: await id.getVisibleText(), - name: (await name.getVisibleText()).split(',').map((role) => role.trim()), - }; - }); + return { + checkBox: (await checkBox.getAttribute('innerHTML')).includes('input'), + id: await id.getVisibleText(), + name: (await name.getVisibleText()).split(',').map((role) => role.trim()), + }; + }) + ); } } diff --git a/x-pack/test/functional/services/ace_editor.js b/x-pack/test/functional/services/ace_editor.js index 589f05695e065..2b2adde74ecc7 100644 --- a/x-pack/test/functional/services/ace_editor.js +++ b/x-pack/test/functional/services/ace_editor.js @@ -5,8 +5,6 @@ * 2.0. */ -import { map as mapAsync } from 'bluebird'; - export function AceEditorProvider({ getService }) { const testSubjects = getService('testSubjects'); const find = getService('find'); @@ -35,7 +33,7 @@ export function AceEditorProvider({ getService }) { return await retry.try(async () => { const editor = await testSubjects.find(testSubjectSelector); const lines = await editor.findAllByClassName('ace_line'); - const linesText = await mapAsync(lines, (line) => line.getVisibleText()); + const linesText = await Promise.all(lines.map((line) => line.getVisibleText())); return linesText.join('\n'); }); } diff --git a/x-pack/test/functional/services/ml/data_frame_analytics_table.ts b/x-pack/test/functional/services/ml/data_frame_analytics_table.ts index 0bfb37c6c94f8..18e3c4d898bd6 100644 --- a/x-pack/test/functional/services/ml/data_frame_analytics_table.ts +++ b/x-pack/test/functional/services/ml/data_frame_analytics_table.ts @@ -163,9 +163,11 @@ export function MachineLearningDataFrameAnalyticsTableProvider({ getService }: F } public async openMapView(analyticsId: string) { - await this.assertJobRowMapButtonExists(analyticsId); - await testSubjects.click(this.rowSelector(analyticsId, 'mlAnalyticsJobMapButton')); - await testSubjects.existOrFail('mlPageDataFrameAnalyticsMap', { timeout: 20 * 1000 }); + await retry.tryForTime(20 * 1000, async () => { + await this.assertJobRowMapButtonExists(analyticsId); + await testSubjects.click(this.rowSelector(analyticsId, 'mlAnalyticsJobMapButton')); + await testSubjects.existOrFail('mlPageDataFrameAnalyticsMap', { timeout: 5 * 1000 }); + }); } public async assertAnalyticsSearchInputValue(expectedSearchValue: string) { diff --git a/x-pack/test/functional/services/ml/job_table.ts b/x-pack/test/functional/services/ml/job_table.ts index 67488d88aa120..e2d50c52c55ba 100644 --- a/x-pack/test/functional/services/ml/job_table.ts +++ b/x-pack/test/functional/services/ml/job_table.ts @@ -373,6 +373,16 @@ export function MachineLearningJobTableProvider( await testSubjects.existOrFail('~mlPageJobWizard'); } + public async clickCloneJobActionWhenNoDataViewExists(jobId: string) { + await this.ensureJobActionsMenuOpen(jobId); + await testSubjects.click('mlActionButtonCloneJob'); + await this.assertNoDataViewForCloneJobWarningToastExist(); + } + + public async assertNoDataViewForCloneJobWarningToastExist() { + await testSubjects.existOrFail('mlCloneJobNoDataViewExistsWarningToast', { timeout: 5000 }); + } + public async clickEditJobAction(jobId: string) { await this.ensureJobActionsMenuOpen(jobId); await testSubjects.click('mlActionButtonEditJob'); diff --git a/x-pack/test/functional/services/monitoring/cluster_alerts.js b/x-pack/test/functional/services/monitoring/cluster_alerts.js index cbeb537b08016..4f70903bda1c2 100644 --- a/x-pack/test/functional/services/monitoring/cluster_alerts.js +++ b/x-pack/test/functional/services/monitoring/cluster_alerts.js @@ -6,7 +6,6 @@ */ import { range } from 'lodash'; -import { map as mapAsync } from 'bluebird'; export function MonitoringClusterAlertsProvider({ getService, getPageObjects }) { const testSubjects = getService('testSubjects'); @@ -61,9 +60,11 @@ export function MonitoringClusterAlertsProvider({ getService, getPageObjects }) const listingRows = await this.getOverviewAlerts(); const alertIcons = await retry.try(async () => { const elements = await find.allByCssSelector(SUBJ_OVERVIEW_ICONS); - return await mapAsync(elements, async (element) => { - return await element.getVisibleText(); - }); + return await Promise.all( + elements.map(async (element) => { + return await element.getVisibleText(); + }) + ); }); return await this._getAlertSetAll({ diff --git a/x-pack/test/functional/services/observability/alerts/common.ts b/x-pack/test/functional/services/observability/alerts/common.ts index 8a32b41e9b8e9..dd7d49af4fe5a 100644 --- a/x-pack/test/functional/services/observability/alerts/common.ts +++ b/x-pack/test/functional/services/observability/alerts/common.ts @@ -16,8 +16,11 @@ const DATE_WITH_DATA = { }; const ALERTS_FLYOUT_SELECTOR = 'alertsFlyout'; -const FILTER_FOR_VALUE_BUTTON_SELECTOR = 'filter-for-value'; +const FILTER_FOR_VALUE_BUTTON_SELECTOR = 'filterForValue'; const ALERTS_TABLE_CONTAINER_SELECTOR = 'events-viewer-panel'; +const VIEW_RULE_DETAILS_SELECTOR = 'viewRuleDetails'; +const VIEW_RULE_DETAILS_FLYOUT_SELECTOR = 'viewRuleDetailsFlyout'; + const ACTION_COLUMN_INDEX = 1; type WorkflowStatus = 'open' | 'acknowledged' | 'closed'; @@ -71,7 +74,7 @@ export function ObservabilityAlertsCommonProvider({ }; const getExperimentalDisclaimer = async () => { - return testSubjects.existOrFail('o11y-experimental-disclaimer'); + return testSubjects.existOrFail('o11yExperimentalDisclaimer'); }; const getTableCellsInRows = async () => { @@ -150,6 +153,10 @@ export function ObservabilityAlertsCommonProvider({ return await testSubjects.existOrFail('alertsFlyoutViewInAppButton'); }; + const getAlertsFlyoutViewRuleDetailsLinkOrFail = async () => { + return await testSubjects.existOrFail('viewRuleDetailsFlyout'); + }; + const getAlertsFlyoutDescriptionListTitles = async (): Promise => { const flyout = await getAlertsFlyout(); return await testSubjects.findAllDescendant('alertsFlyoutDescriptionListTitle', flyout); @@ -173,12 +180,19 @@ export function ObservabilityAlertsCommonProvider({ const openActionsMenuForRow = async (rowIndex: number) => { const rows = await getTableCellsInRows(); const actionsOverflowButton = await testSubjects.findDescendant( - 'alerts-table-row-action-more', + 'alertsTableRowActionMore', rows[rowIndex][ACTION_COLUMN_INDEX] ); await actionsOverflowButton.click(); }; + const viewRuleDetailsButtonClick = async () => { + return await (await testSubjects.find(VIEW_RULE_DETAILS_SELECTOR)).click(); + }; + const viewRuleDetailsLinkClick = async () => { + return await (await testSubjects.find(VIEW_RULE_DETAILS_FLYOUT_SELECTOR)).click(); + }; + // Workflow status const setWorkflowStatusForRow = async (rowIndex: number, workflowStatus: WorkflowStatus) => { await openActionsMenuForRow(rowIndex); @@ -196,7 +210,7 @@ export function ObservabilityAlertsCommonProvider({ const setWorkflowStatusFilter = async (workflowStatus: WorkflowStatus) => { const buttonGroupButton = await testSubjects.find( - `workflow-status-filter-${workflowStatus}-button` + `workflowStatusFilterButton-${workflowStatus}` ); await buttonGroupButton.click(); }; @@ -223,7 +237,7 @@ export function ObservabilityAlertsCommonProvider({ const getActionsButtonByIndex = async (index: number) => { const actionsOverflowButtons = await find.allByCssSelector( - '[data-test-subj="alerts-table-row-action-more"]' + '[data-test-subj="alertsTableRowActionMore"]' ); return actionsOverflowButtons[index] || null; }; @@ -259,5 +273,8 @@ export function ObservabilityAlertsCommonProvider({ navigateWithoutFilter, getExperimentalDisclaimer, getActionsButtonByIndex, + viewRuleDetailsButtonClick, + viewRuleDetailsLinkClick, + getAlertsFlyoutViewRuleDetailsLinkOrFail, }; } diff --git a/x-pack/test/functional/services/pipeline_editor.js b/x-pack/test/functional/services/pipeline_editor.js index ece7ec5b3ecbc..bb1f122b988bd 100644 --- a/x-pack/test/functional/services/pipeline_editor.js +++ b/x-pack/test/functional/services/pipeline_editor.js @@ -6,7 +6,6 @@ */ import expect from '@kbn/expect'; -import { props as propsAsync } from 'bluebird'; export function PipelineEditorProvider({ getService }) { const retry = getService('retry'); @@ -125,20 +124,27 @@ export function PipelineEditorProvider({ getService }) { * @return {Promise} */ async assertInputs(expectedValues) { - const values = await propsAsync({ - id: testSubjects.getAttribute(SUBJ_INPUT_ID, 'value'), - description: testSubjects.getAttribute(SUBJ_INPUT_DESCRIPTION, 'value'), - pipeline: aceEditor.getValue(SUBJ_UI_ACE_PIPELINE), - workers: testSubjects.getAttribute(SUBJ_INPUT_WORKERS, 'value'), - batchSize: testSubjects.getAttribute(SUBJ_INPUT_BATCH_SIZE, 'value'), - queueType: testSubjects.getAttribute(SUBJ_SELECT_QUEUE_TYPE, 'value'), - queueMaxBytesNumber: testSubjects.getAttribute(SUBJ_INPUT_QUEUE_MAX_BYTES_NUMBER, 'value'), - queueMaxBytesUnits: testSubjects.getAttribute(SUBJ_SELECT_QUEUE_MAX_BYTES_UNITS, 'value'), - queueCheckpointWrites: testSubjects.getAttribute( - SUBJ_INPUT_QUEUE_CHECKPOINT_WRITES, - 'value' - ), - }); + const values = await Promise.all([ + testSubjects.getAttribute(SUBJ_INPUT_ID, 'value'), + testSubjects.getAttribute(SUBJ_INPUT_DESCRIPTION, 'value'), + aceEditor.getValue(SUBJ_UI_ACE_PIPELINE), + testSubjects.getAttribute(SUBJ_INPUT_WORKERS, 'value'), + testSubjects.getAttribute(SUBJ_INPUT_BATCH_SIZE, 'value'), + testSubjects.getAttribute(SUBJ_SELECT_QUEUE_TYPE, 'value'), + testSubjects.getAttribute(SUBJ_INPUT_QUEUE_MAX_BYTES_NUMBER, 'value'), + testSubjects.getAttribute(SUBJ_SELECT_QUEUE_MAX_BYTES_UNITS, 'value'), + testSubjects.getAttribute(SUBJ_INPUT_QUEUE_CHECKPOINT_WRITES, 'value'), + ]).then((values) => ({ + id: values[0], + description: values[1], + pipeline: values[2], + workers: values[3], + batchSize: values[4], + queueType: values[5], + queueMaxBytesNumber: values[6], + queueMaxBytesUnits: values[7], + queueCheckpointWrites: values[8], + })); expect(values).to.eql(expectedValues); } diff --git a/x-pack/test/functional_execution_context/config.ts b/x-pack/test/functional_execution_context/config.ts index f841e8957cde3..456d31b586ad0 100644 --- a/x-pack/test/functional_execution_context/config.ts +++ b/x-pack/test/functional_execution_context/config.ts @@ -42,6 +42,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { `--elasticsearch.hosts=${servers.elasticsearch.protocol}://${servers.elasticsearch.hostname}:${servers.elasticsearch.port}`, `--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`, + '--server.requestId.allowFromAnyIp=true', '--execution_context.enabled=true', '--logging.appenders.file.type=file', `--logging.appenders.file.fileName=${logFilePath}`, diff --git a/x-pack/test/functional_execution_context/fixtures/plugins/alerts/server/ensure_apm_started.ts b/x-pack/test/functional_execution_context/fixtures/plugins/alerts/server/ensure_apm_started.ts new file mode 100644 index 0000000000000..8581ebe5183c1 --- /dev/null +++ b/x-pack/test/functional_execution_context/fixtures/plugins/alerts/server/ensure_apm_started.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import apmAgent from 'elastic-apm-node'; +import { initApm } from '@kbn/apm-config-loader'; +import { REPO_ROOT } from '@kbn/utils'; + +if (!apmAgent.isStarted()) { + initApm(process.argv, REPO_ROOT, false, 'test-plugin'); +} diff --git a/x-pack/test/functional_execution_context/fixtures/plugins/alerts/server/index.ts b/x-pack/test/functional_execution_context/fixtures/plugins/alerts/server/index.ts index 700aee6bfd49d..dbd04e32e860b 100644 --- a/x-pack/test/functional_execution_context/fixtures/plugins/alerts/server/index.ts +++ b/x-pack/test/functional_execution_context/fixtures/plugins/alerts/server/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import './ensure_apm_started'; import { FixturePlugin } from './plugin'; export const plugin = () => new FixturePlugin(); diff --git a/x-pack/test/functional_execution_context/fixtures/plugins/alerts/server/plugin.ts b/x-pack/test/functional_execution_context/fixtures/plugins/alerts/server/plugin.ts index 47a9e4edc30fc..ec4e3ef99c6df 100644 --- a/x-pack/test/functional_execution_context/fixtures/plugins/alerts/server/plugin.ts +++ b/x-pack/test/functional_execution_context/fixtures/plugins/alerts/server/plugin.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import apmAgent from 'elastic-apm-node'; import { Plugin, CoreSetup } from 'kibana/server'; import { PluginSetupContract as AlertingPluginSetup } from '../../../../../../plugins/alerting/server/plugin'; @@ -81,6 +82,32 @@ export class FixturePlugin implements Plugin { + const transaction = apmAgent.startTransaction(); + const subscription = req.events.completed$.subscribe(() => { + transaction?.end(); + subscription.unsubscribe(); + }); + + await ctx.core.elasticsearch.client.asInternalUser.ping(); + + return res.ok({ + body: { + traceId: apmAgent.currentTraceIds['trace.id'], + }, + }); + } + ); } public start() {} diff --git a/x-pack/test/functional_execution_context/tests/index.ts b/x-pack/test/functional_execution_context/tests/index.ts index 6d74a94608671..c092be9bd8bdb 100644 --- a/x-pack/test/functional_execution_context/tests/index.ts +++ b/x-pack/test/functional_execution_context/tests/index.ts @@ -12,5 +12,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { this.tags('ciGroup1'); loadTestFile(require.resolve('./browser')); loadTestFile(require.resolve('./server')); + loadTestFile(require.resolve('./log_correlation')); }); } diff --git a/x-pack/test/functional_execution_context/tests/log_correlation.ts b/x-pack/test/functional_execution_context/tests/log_correlation.ts new file mode 100644 index 0000000000000..80bb2285a665e --- /dev/null +++ b/x-pack/test/functional_execution_context/tests/log_correlation.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import expect from '@kbn/expect'; +import type { FtrProviderContext } from '../ftr_provider_context'; +import { assertLogContains } from '../test_utils'; + +export default function ({ getService }: FtrProviderContext) { + const retry = getService('retry'); + const supertest = getService('supertest'); + + describe('Log Correlation', () => { + it('Emits "trace.id" into the logs', async () => { + const response1 = await supertest + .get('/emit_log_with_trace_id') + .set('x-opaque-id', 'myheader1'); + + expect(response1.body.traceId).to.be.a('string'); + + const response2 = await supertest.get('/emit_log_with_trace_id'); + expect(response1.body.traceId).to.be.a('string'); + + expect(response2.body.traceId).not.to.be(response1.body.traceId); + + await assertLogContains({ + description: 'traceId included in the Kibana logs', + predicate: (record) => + // we don't check trace.id value since trace.id in the test plugin and Kibana are different on CI. + // because different 'elastic-apm-node' instaces are imported + Boolean(record.http?.request?.id?.includes('myheader1') && record.trace?.id), + retry, + }); + }); + }); +} diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts index 1140f9c17a9f7..b208826ec7aa4 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts @@ -296,7 +296,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should show all rule types on click euiFormControlLayoutClearButton', async () => { await pageObjects.triggersActionsUI.clickCreateAlertButton(); await testSubjects.setValue('alertNameInput', 'alertName'); - const ruleTypeSearchBox = await find.byCssSelector('.alertSearchField'); + const ruleTypeSearchBox = await find.byCssSelector('[data-test-subj="alertSearchField"]'); await ruleTypeSearchBox.type('notexisting rule type'); await ruleTypeSearchBox.pressKeys(browser.keys.ENTER); diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts index eb4c0fbe425c4..04b9b1b45b633 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts @@ -479,6 +479,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(filterWithSlackOnlyResults[0].interval).to.equal('1 min'); expect(filterWithSlackOnlyResults[0].duration).to.match(/\d{2}:\d{2}:\d{2}.\d{3}/); }); + await testSubjects.click('alertTypeFilterButton'); + + // de-select action type filter + await testSubjects.click('actionTypeFilterButton'); + await testSubjects.click('actionType.slackFilterOption'); + + await testSubjects.missingOrFail('centerJustifiedSpinner'); }); }); }; diff --git a/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts index f08867b445f75..d657db443e4ec 100644 --- a/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts @@ -6,7 +6,7 @@ */ import expect from '@kbn/expect'; -import { delay } from 'bluebird'; +import { setTimeout as setTimeoutAsync } from 'timers/promises'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -99,7 +99,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { let alert: any; await retry.tryForTime(60 * 1000, async () => { // add a delay before next call to not overload the server - await delay(1500); + await setTimeoutAsync(1500); const apiResponse = await supertest.get('/api/alerts/_find?search=uptime-test'); const alertsFromThisTest = apiResponse.body.data.filter( ({ name }: { name: string }) => name === 'uptime-test' diff --git a/x-pack/test/load/runner.ts b/x-pack/test/load/runner.ts index c48a8e33d6eef..e9750bd19881a 100644 --- a/x-pack/test/load/runner.ts +++ b/x-pack/test/load/runner.ts @@ -10,7 +10,7 @@ import { resolve } from 'path'; import { REPO_ROOT } from '@kbn/utils'; import Fs from 'fs'; import { createFlagError } from '@kbn/dev-utils'; -import { delay } from 'bluebird'; +import { setTimeout as setTimeoutAsync } from 'timers/promises'; import { FtrProviderContext } from './../functional/ftr_provider_context'; const baseSimulationPath = 'src/test/scala/org/kibanaLoadTest/simulation'; @@ -82,7 +82,7 @@ export async function GatlingTestRunner({ getService }: FtrProviderContext) { }); // wait a minute between simulations, skip for the last one if (i < simulationClasses.length - 1) { - await delay(60 * 1000); + await setTimeoutAsync(60 * 1000); } } }); diff --git a/x-pack/test/observability_functional/apps/observability/alerts/alert_disclaimer.ts b/x-pack/test/observability_functional/apps/observability/alerts/alert_disclaimer.ts index c687210286304..d63739da47d5b 100644 --- a/x-pack/test/observability_functional/apps/observability/alerts/alert_disclaimer.ts +++ b/x-pack/test/observability_functional/apps/observability/alerts/alert_disclaimer.ts @@ -34,8 +34,8 @@ export default ({ getService, getPageObject }: FtrProviderContext) => { }); it('Dismiss experimental disclaimer', async () => { - await testSubjects.click('o11y-experimental-disclaimer-dismiss-btn'); - const o11yExperimentalDisclaimer = await testSubjects.exists('o11y-experimental-disclaimer'); + await testSubjects.click('o11yExperimentalDisclaimerDismissBtn'); + const o11yExperimentalDisclaimer = await testSubjects.exists('o11yExperimentalDisclaimer'); expect(o11yExperimentalDisclaimer).not.to.be(null); }); }); diff --git a/x-pack/test/observability_functional/apps/observability/alerts/index.ts b/x-pack/test/observability_functional/apps/observability/alerts/index.ts index 3190a151cb47b..2b760b65a1c46 100644 --- a/x-pack/test/observability_functional/apps/observability/alerts/index.ts +++ b/x-pack/test/observability_functional/apps/observability/alerts/index.ts @@ -20,8 +20,9 @@ const TOTAL_ALERTS_CELL_COUNT = 198; export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); + const find = getService('find'); - describe('Observability alerts', function () { + describe('Observability alerts 1', function () { this.tags('includeFirefox'); const testSubjects = getService('testSubjects'); @@ -178,6 +179,10 @@ export default ({ getService }: FtrProviderContext) => { it('Displays a View in App button', async () => { await observability.alerts.common.getAlertsFlyoutViewInAppButtonOrFail(); }); + + it('Displays a View rule details link', async () => { + await observability.alerts.common.getAlertsFlyoutViewRuleDetailsLinkOrFail(); + }); }); }); @@ -213,28 +218,23 @@ export default ({ getService }: FtrProviderContext) => { }); }); }); - }); - describe('Actions Button', () => { - before(async () => { - await observability.users.setTestUserRole( - observability.users.defineBasicObservabilityRole({ - observabilityCases: ['read'], - logs: ['read'], - }) - ); - await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); - await observability.alerts.common.navigateToTimeWithData(); - }); + describe('Actions Button', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); + await observability.alerts.common.navigateToTimeWithData(); + }); - after(async () => { - await observability.users.restoreDefaultTestUserRole(); - await esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'); - }); + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'); + }); - it('Is disabled when a user has only read privilages', async () => { - const actionsButton = await observability.alerts.common.getActionsButtonByIndex(0); - expect(await actionsButton.getAttribute('disabled')).to.be('true'); + it('Opens rule details page when click on "View Rule Details"', async () => { + const actionsButton = await observability.alerts.common.getActionsButtonByIndex(0); + await actionsButton.click(); + await observability.alerts.common.viewRuleDetailsButtonClick(); + expect(await find.existsByCssSelector('[title="Rules and Connectors"]')).to.eql(true); + }); }); }); }); diff --git a/x-pack/test/performance/config.ts b/x-pack/test/performance/config.ts index 89b7b52e28670..82586ee62ad80 100644 --- a/x-pack/test/performance/config.ts +++ b/x-pack/test/performance/config.ts @@ -32,6 +32,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.get('kbnTestServer'), env: { ELASTIC_APM_ACTIVE: 'true', + ELASTIC_APM_CONTEXT_PROPAGATION_ONLY: 'false', ELASTIC_APM_ENVIRONMENT: process.env.CI ? 'ci' : 'development', ELASTIC_APM_TRANSACTION_SAMPLE_RATE: '1.0', ELASTIC_APM_SERVER_URL: APM_SERVER_URL, diff --git a/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts b/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts index ed1a1f3c16fcc..84673c7b68f2e 100644 --- a/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts +++ b/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { parse as parseCookie, Cookie } from 'tough-cookie'; -import { delay } from 'bluebird'; +import { setTimeout as setTimeoutAsync } from 'timers/promises'; import { adminTestUser } from '@kbn/test'; import { FtrProviderContext } from '../../ftr_provider_context'; import { @@ -319,7 +319,7 @@ export default function ({ getService }: FtrProviderContext) { // Access token expiration is set to 15s for API integration tests. // Let's wait for 20s to make sure token expires. - await delay(20000); + await setTimeoutAsync(20000); // This api call should succeed and automatically refresh token. Returned cookie will contain // the new access and refresh token pair. @@ -350,7 +350,7 @@ export default function ({ getService }: FtrProviderContext) { // Access token expiration is set to 15s for API integration tests. // Let's wait for 20s to make sure token expires. - await delay(20000); + await setTimeoutAsync(20000); // This request should succeed and automatically refresh token. Returned cookie will contain // the new access and refresh token pair. diff --git a/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts b/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts index a7a8702894482..eb12d4240a372 100644 --- a/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts +++ b/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { parse as parseCookie, Cookie } from 'tough-cookie'; import url from 'url'; -import { delay } from 'bluebird'; +import { setTimeout as setTimeoutAsync } from 'timers/promises'; import { adminTestUser } from '@kbn/test'; import { getStateAndNonce } from '../../../fixtures/oidc/oidc_tools'; import { FtrProviderContext } from '../../../ftr_provider_context'; @@ -494,7 +494,7 @@ export default function ({ getService }: FtrProviderContext) { // Access token expiration is set to 15s for API integration tests. // Let's wait for 20s to make sure token expires. - await delay(20000); + await setTimeoutAsync(20000); // This api call should succeed and automatically refresh token. Returned cookie will contain // the new access and refresh token pair. diff --git a/x-pack/test/security_api_integration/tests/pki/pki_auth.ts b/x-pack/test/security_api_integration/tests/pki/pki_auth.ts index f857e5c149be4..76e3cb2248815 100644 --- a/x-pack/test/security_api_integration/tests/pki/pki_auth.ts +++ b/x-pack/test/security_api_integration/tests/pki/pki_auth.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { parse as parseCookie, Cookie } from 'tough-cookie'; -import { delay } from 'bluebird'; +import { setTimeout as setTimeoutAsync } from 'timers/promises'; import { readFileSync } from 'fs'; import { resolve } from 'path'; import { CA_CERT_PATH } from '@kbn/dev-utils'; @@ -358,7 +358,7 @@ export default function ({ getService }: FtrProviderContext) { // Access token expiration is set to 15s for API integration tests. // Let's wait for 20s to make sure token expires. - await delay(20000); + await setTimeoutAsync(20000); // This api call should succeed and automatically refresh token. Returned cookie will contain // the new access token. @@ -382,7 +382,7 @@ export default function ({ getService }: FtrProviderContext) { // Access token expiration is set to 15s for API integration tests. // Let's wait for 20s to make sure token expires. - await delay(20000); + await setTimeoutAsync(20000); // This request should succeed and automatically refresh token. Returned cookie will contain // the new access and refresh token pair. diff --git a/x-pack/test/security_api_integration/tests/saml/saml_login.ts b/x-pack/test/security_api_integration/tests/saml/saml_login.ts index 97fdcb77f4d66..c0ea296297fe6 100644 --- a/x-pack/test/security_api_integration/tests/saml/saml_login.ts +++ b/x-pack/test/security_api_integration/tests/saml/saml_login.ts @@ -7,7 +7,7 @@ import { stringify } from 'query-string'; import url from 'url'; -import { delay } from 'bluebird'; +import { setTimeout as setTimeoutAsync } from 'timers/promises'; import expect from '@kbn/expect'; import { parse as parseCookie, Cookie } from 'tough-cookie'; import { adminTestUser } from '@kbn/test'; @@ -468,7 +468,7 @@ export default function ({ getService }: FtrProviderContext) { // Access token expiration is set to 15s for API integration tests. // Let's wait for 20s to make sure token expires. - await delay(20000); + await setTimeoutAsync(20000); }); const expectNewSessionCookie = (cookie: Cookie) => { @@ -639,7 +639,7 @@ export default function ({ getService }: FtrProviderContext) { ['when access token is valid', async () => {}], // Scenario when active cookie has an expired access token. Access token expiration is set // to 15s for API integration tests so we need to wait for 20s to make sure token expires. - ['when access token is expired', async () => await delay(20000)], + ['when access token is expired', async () => await setTimeoutAsync(20000)], // Scenario when active cookie references to access/refresh token pair that were already // removed from Elasticsearch (to simulate 24h when expired tokens are removed). [ diff --git a/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts b/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts index beb7bdfbdfccc..39231df307a9e 100644 --- a/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts +++ b/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts @@ -6,7 +6,7 @@ */ import { parse as parseCookie, Cookie } from 'tough-cookie'; -import { delay } from 'bluebird'; +import { setTimeout as setTimeoutAsync } from 'timers/promises'; import expect from '@kbn/expect'; import { adminTestUser } from '@kbn/test'; import type { AuthenticationProvider } from '../../../../plugins/security/common/model'; @@ -101,7 +101,7 @@ export default function ({ getService }: FtrProviderContext) { // Cleanup routine runs every 10s, and idle timeout threshold is three times larger than 5s // idle timeout, let's wait for 40s to make sure cleanup routine runs when idle timeout // threshold is exceeded. - await delay(40000); + await setTimeoutAsync(40000); // Session info is removed from the index and cookie isn't valid anymore expect(await getNumberOfSessionDocuments()).to.be(0); @@ -143,7 +143,7 @@ export default function ({ getService }: FtrProviderContext) { // Cleanup routine runs every 10s, and idle timeout threshold is three times larger than 5s // idle timeout, let's wait for 40s to make sure cleanup routine runs when idle timeout // threshold is exceeded. - await delay(40000); + await setTimeoutAsync(40000); // Session for basic and SAML that used global session settings should not be valid anymore. expect(await getNumberOfSessionDocuments()).to.be(2); @@ -191,7 +191,7 @@ export default function ({ getService }: FtrProviderContext) { // least twice. for (const counter of [...Array(20).keys()]) { // Session idle timeout is 15s, let's wait 10s and make a new request that would extend the session. - await delay(1500); + await setTimeoutAsync(1500); sessionCookie = (await checkSessionCookie(sessionCookie, basicUsername, { type: 'basic', diff --git a/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts b/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts index 6b5308f623805..32222794ac23b 100644 --- a/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts +++ b/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts @@ -6,7 +6,7 @@ */ import { parse as parseCookie, Cookie } from 'tough-cookie'; -import { delay } from 'bluebird'; +import { setTimeout as setTimeoutAsync } from 'timers/promises'; import expect from '@kbn/expect'; import { adminTestUser } from '@kbn/test'; import type { AuthenticationProvider } from '../../../../plugins/security/common/model'; @@ -98,7 +98,7 @@ export default function ({ getService }: FtrProviderContext) { // Cleanup routine runs every 10s, let's wait for 40s to make sure it runs multiple times and // when lifespan is exceeded. - await delay(40000); + await setTimeoutAsync(40000); // Session info is removed from the index and cookie isn't valid anymore expect(await getNumberOfSessionDocuments()).to.be(0); @@ -138,7 +138,7 @@ export default function ({ getService }: FtrProviderContext) { // Cleanup routine runs every 10s, let's wait for 40s to make sure it runs multiple times and // when lifespan is exceeded. - await delay(40000); + await setTimeoutAsync(40000); // Session for basic and SAML that used global session settings should not be valid anymore. expect(await getNumberOfSessionDocuments()).to.be(2); diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts index d1cfddbca3a9c..70d60ba5c1b67 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts @@ -15,8 +15,7 @@ import { export default function (providerContext: FtrProviderContext) { const { loadTestFile, getService } = providerContext; - // FAILING: https://github.com/elastic/kibana/issues/72874 - describe.skip('endpoint', function () { + describe('endpoint', function () { const ingestManager = getService('ingestManager'); const log = getService('log'); const endpointTestResources = getService('endpointTestResources'); diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts index 49206ffb1070e..88462a2872fe4 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts @@ -309,8 +309,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('should show the supported Endpoint version', async () => { - const supportedVersion = await testSubjects.find('policySupportedVersions'); - expect(supportedVersion).to.be('Agent version ' + popupVersionsMap.get('malware')); + expect(await testSubjects.getVisibleText('policySupportedVersions')).to.equal( + 'Agent version ' + popupVersionsMap.get('malware') + ); }); it('should show the custom message text area when the Notify User checkbox is checked', async () => { @@ -401,6 +402,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(agentFullPolicy.inputs).to.eql([ getExpectedAgentPolicyEndpointInput({ id: policyInfo.packagePolicy.id, + name: policyInfo.packagePolicy.name, meta: { package: { version: policyInfo.packageInfo.version, @@ -447,6 +449,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(agentFullPolicy.inputs).to.eql([ getExpectedAgentPolicyEndpointInput({ id: policyInfo.packagePolicy.id, + name: policyInfo.packagePolicy.name, meta: { package: { version: policyInfo.packageInfo.version, @@ -485,6 +488,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(agentFullPolicyUpdated.inputs).to.eql([ getExpectedAgentPolicyEndpointInput({ id: policyInfo.packagePolicy.id, + name: policyInfo.packagePolicy.name, revision: 3, meta: { package: { diff --git a/x-pack/test/security_solution_endpoint/services/endpoint_policy.ts b/x-pack/test/security_solution_endpoint/services/endpoint_policy.ts index 26b6c5c0b5546..f718390dc291a 100644 --- a/x-pack/test/security_solution_endpoint/services/endpoint_policy.ts +++ b/x-pack/test/security_solution_endpoint/services/endpoint_policy.ts @@ -5,6 +5,7 @@ * 2.0. */ +import uuid from 'uuid'; import { FtrProviderContext } from '../ftr_provider_context'; import { CreateAgentPolicyRequest, @@ -161,7 +162,7 @@ export function EndpointPolicyTestResourcesProvider({ getService }: FtrProviderC let agentPolicy: CreateAgentPolicyResponse['item']; try { const newAgentPolicyData: CreateAgentPolicyRequest['body'] = { - name: 'East Coast', + name: `East Coast ${uuid.v4()}`, description: 'East Coast call center', namespace: 'default', }; @@ -182,7 +183,7 @@ export function EndpointPolicyTestResourcesProvider({ getService }: FtrProviderC let packagePolicy: CreatePackagePolicyResponse['item']; try { const newPackagePolicyData: CreatePackagePolicyRequest['body'] = { - name: 'Protect East Coast', + name: `Protect East Coast ${uuid.v4()}`, description: 'Protect the worlds data - but in the East Coast', policy_id: agentPolicy!.id, enabled: true, diff --git a/yarn.lock b/yarn.lock index 7c2f64a0983b9..a4739826997ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5905,11 +5905,6 @@ resolved "https://registry.yarnpkg.com/@types/base64-js/-/base64-js-1.2.5.tgz#582b2476169a6cba460a214d476c744441d873d5" integrity sha1-WCskdhaabLpGCiFNR2x0REHYc9U= -"@types/bluebird@^3.1.1": - version "3.5.30" - resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.30.tgz#ee034a0eeea8b84ed868b1aa60d690b08a6cfbc5" - integrity sha512-8LhzvcjIoqoi1TghEkRMkbbmM+jhHnBokPGkJWjclMK+Ks0MxEBow3/p2/iFTZ+OIbJHQDSfpgdZEb+af3gfVw== - "@types/braces@*": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/braces/-/braces-3.0.0.tgz#7da1c0d44ff1c7eb660a36ec078ea61ba7eb42cb" @@ -9422,11 +9417,6 @@ bluebird@3.5.3: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== -bluebird@3.5.5: - version "3.5.5" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f" - integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w== - bluebird@3.7.2, bluebird@^3.3.5, bluebird@^3.4.1, bluebird@^3.5.0, bluebird@^3.5.1, bluebird@^3.5.5, bluebird@^3.7.1, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" @@ -13225,10 +13215,10 @@ ejs@^3.1.2, ejs@^3.1.6: dependencies: jake "^10.6.1" -elastic-apm-http-client@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/elastic-apm-http-client/-/elastic-apm-http-client-10.1.0.tgz#8fbfa3f026f40d82b22b77bf4ed539cc20623edb" - integrity sha512-G+UsOQS8+kTyjbZ9PBXgbN8RGgeTe3FfbVljiwuN+eIf0UwpSR8k5Oh+Z2BELTTVwTcit7NCH4+B4MPayYx1mw== +elastic-apm-http-client@^10.3.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/elastic-apm-http-client/-/elastic-apm-http-client-10.3.0.tgz#12b95dc190a755cd1a8ce2c296cd28ef50f16aa4" + integrity sha512-BAqB7k5JA/x09L8BVj04WRoknRptmW2rLAoHQVrPvPhUm/IgNz63wPfiBuhWVE//Hl7xEpURO5pMV6az0UArkA== dependencies: breadth-filter "^2.0.0" container-info "^1.0.1" @@ -13239,10 +13229,10 @@ elastic-apm-http-client@^10.1.0: readable-stream "^3.4.0" stream-chopper "^3.0.1" -elastic-apm-node@^3.23.0: - version "3.23.0" - resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-3.23.0.tgz#e842aa505d576003579803e45fe91f572db74a72" - integrity sha512-yzdO/MwAcjT+TbcBQBKWbDb4beDVmmrIaFCu9VA+z6Ow9GKlQv7QaD9/cQjuN8/KI6ASiJfQI8cPgqy1SgSUuA== +elastic-apm-node@3.24.0: + version "3.24.0" + resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-3.24.0.tgz#d7acb3352f928a23c28ebabab2bd30098562814e" + integrity sha512-Fmj/W2chWQa2zb1FfMYK2ypLB4TcnKNX+1klaJFbytRYDLgeSfo0EC7egvI3a+bLPZSRL5053PXOp7slVTPO6Q== dependencies: "@elastic/ecs-pino-format" "^1.2.0" after-all-results "^2.0.0" @@ -13251,7 +13241,7 @@ elastic-apm-node@^3.23.0: basic-auth "^2.0.1" cookie "^0.4.0" core-util-is "^1.0.2" - elastic-apm-http-client "^10.1.0" + elastic-apm-http-client "^10.3.0" end-of-stream "^1.4.4" error-callsites "^2.0.4" error-stack-parser "^2.0.6"